This repository has been archived by the owner on Mar 14, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Extensions API blog post for documentId and friends.
- Instant Navigation adds additional fields documentId, parentDocumentId, documentLifecycle, frameType to help improve the experience for back/forward cache and prerendering.
- Loading branch information
Showing
2 changed files
with
183 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
--- | ||
layout: 'layouts/blog-post.njk' | ||
title: "Chrome Extensions: Extending API to support Instant Navigation" | ||
description: > | ||
The Extensions API has been updated to support back/forward cache, preloading navigations. | ||
authors: | ||
- dtapuska | ||
hero: 'image/zKrSUSkPboWMTTSEkowJbqw5Egi2/r5IdmaTdE5wBS2d6CCQY.png' | ||
alt: > | ||
Instant navigation in a tab. | ||
date: 2022-10-04 | ||
--- | ||
|
||
TL;DR: You may need to update your extension if it assumes `frameId == 0` | ||
is a page’s main frame. See below for details. | ||
|
||
Chrome has been working hard at making navigation fast. Instant Navigation | ||
technologies such as [Back/Forward Cache](https://web.dev/bfcache/) | ||
([shipped](https://chromestatus.com/feature/6279906713403392) on desktop in M96) | ||
and [Speculation Rules](https://chromestatus.com/feature/5740655424831488) | ||
(shipped in M103) improve both the going back and going forward experience. | ||
In this post we will explore the updates we’ve made to browser extensions APIs | ||
to accommodate these new workflows. | ||
|
||
## Understanding the Types of Pages | ||
|
||
Prior to the introduction of Back/Forward Cache and Prerendering, an individual | ||
tab only had one active page. This was always the one that was visible. If a | ||
tab was navigated backwards, the active page would be destroyed (Page B) and | ||
the previous page in history would be completely reconstructed (Page A). | ||
Extensions did not need to worry about what life cycle pages were in because | ||
there was only one for a tab, the Active/Visible state. | ||
|
||
{% Img src="image/zKrSUSkPboWMTTSEkowJbqw5Egi2/VR5hJLKud7lNIou8baHx.png", alt="Eviction of active page", width="350", height="209" %} | ||
|
||
With Back Forward Cache and Prerendering, there is no longer a 1:1 relationship | ||
between tabs and pages. Now, each tab actually stores multiple pages and pages | ||
transition between states rather than being destroyed and reconstructed. | ||
|
||
For example, a page could begin its life as a prerendered (not visible) page, | ||
transition into an active (visible) page when the user clicks a link, and then | ||
be stored in the Back Forward Cache (not visible) when the user navigates to | ||
another page, all without the page ever being destroyed. Later in this article | ||
we will look at the new properties exposed to help extensions understand what | ||
state pages are in. | ||
|
||
{% Img src="image/zKrSUSkPboWMTTSEkowJbqw5Egi2/0DT7Tzx0vQhNZgegsoEj.png", alt="Types of Pages", width="531", height="209" %} | ||
|
||
Note that a tab can have a series of prerender pages (not just one), a single | ||
*active* (visible) page, and a series of Back/Forward cached pages. | ||
|
||
## What’s changing for extension developers? | ||
### FrameId == 0 | ||
In Chromium, we refer to the topmost/main frame as the outermost frame. | ||
|
||
Extension authors that assume the [*frameId*](/docs/extensions/reference/webNavigation/#a-note-about-frame-ids:~:text=onErrorOccurred%20event%20fired.-,frameId,-number) | ||
of the outermost [frame is 0](/docs/extensions/reference/webNavigation/#a-note-about-frame-ids) | ||
may have problems. Since a tab can now have multiple outermost frames | ||
(prerendered and cached pages), the assumption that there is a single outermost | ||
frame for a Tab is incorrect. frameId == 0 will still continue to represent the | ||
outermost frame of the **active** page, but the outermost frames of **other** | ||
pages will be non-zero. A new field frameType has been added to fix this | ||
problem. See the [“How do I determine if a frame is the outermost frame?”](#outermost-frame) | ||
section. | ||
|
||
### Lifecycle of Frame vs Document | ||
|
||
Another concept that is problematic with extensions is the lifecycle of the | ||
frame. A frame hosts a document (which is associated with a committed URL). | ||
The document can change (say by navigating) but the *frameId* won’t, and so it | ||
is difficult to associate that something happened in a specific document with | ||
just *frameIds*. We are introducing a concept of a [documentId](/docs/extensions/reference/webNavigation/#method-getFrame:~:text=retrieve%20information%20about.-,documentId,-string%C2%A0optional) | ||
which is a unique identifier per document. If a frame is navigated and opens a | ||
new document the identifier will change. This field is useful for determining | ||
when pages change their lifecycle state (between prerender/active/cached) | ||
because it remains the same. | ||
|
||
### Web Navigation Events | ||
|
||
WebNavigation events can fire multiple times on the same page depending on the | ||
lifecycle it is in. See [“How do I tell what life cycle the page is in?”](#lifecyle) | ||
and [“How do I determine when a page transitions?”](#transitions) sections. | ||
|
||
## How do I tell what life cycle the page is in? {: #lifecyle} | ||
|
||
The [documentLifecycle](/docs/extensions/reference/extensionTypes/#type-DocumentLifecycle) | ||
attribute has been added to a number of extensions APIs where the *frameId* was | ||
previously available. If the value is present on an event (such as | ||
[onCommitted](/docs/extensions/reference/webNavigation/#event-onCommitted)), | ||
the value is the state at which the event was generated. You can always query | ||
information from the [WebNavigation GetFrame/GetAllFrames](/docs/extensions/reference/webNavigation/#method-getFrame) | ||
APIs, but using the value from the event is always preferred. If you do use the | ||
[GetFrame](/docs/extensions/reference/webNavigation/#method-getFrame) API be | ||
aware the state of the frame may have changed from when the event was generated | ||
to when the GetFrame promise is resolved. | ||
|
||
The [documentLifecycle](/docs/extensions/reference/extensionTypes/#type-DocumentLifecycle) | ||
has the following values: | ||
|
||
- **prerender** : Not currently presented to the user but preparing to possibly be displayed to the user. | ||
- **active**: Presently displayed to the user. | ||
- **cached**: Stored in the back/forward cache. | ||
- **pending_deletion**: A special case where the document is actively being destroyed. | ||
|
||
## How do I determine if a frame is the outermost frame? {: #outermost-frame} | ||
|
||
Previously extensions may have used checking [*frameId*](/docs/extensions/reference/webNavigation/#a-note-about-frame-ids) | ||
== 0 to determine if the event occurring is for the outermost frame or not. | ||
With multiple pages in a tab we now have multiple outermost frames, so the | ||
definition of *frameId* is problematic. You will never receive events about a | ||
Back/Forward cached frame. However, for prerender frames the *frameId* will be | ||
non-zero for the outermost frame. So using *frameId* == 0 as a signal for | ||
determining if it is the outermost frame is incorrect. | ||
|
||
To help with this, we introduced a new field called [*frameType*](/docs/extensions/reference/extensionTypes/#type-FrameType) | ||
so determining if the frame is indeed the outermost frame is now easy. | ||
|
||
The [*frameType*](/docs/extensions/reference/extensionTypes/#type-FrameType) | ||
has the following values: | ||
|
||
- **outermost_frame**: Typically referred to the topmost frame. Note that there are multiple of | ||
these: there could be many for the various documentLifecycles (such as a | ||
prerendered outermost_frame and a cached outermost_frame). | ||
- **fenced_frame**: A special type of frame that is embedded inside other frames | ||
(see [fenced-frames](https://github.com/WICG/fenced-frame)) | ||
- **sub_frame**: Typically an iframe | ||
|
||
|
||
We can combine *documentLifecycle* with *frameType* and determine if a frame | ||
is the active outermost frame. ie. | ||
(documentLifecycle == “active” && frameType == “outermost_frame”) | ||
|
||
## How do I solve Time of Use problems with frames? | ||
|
||
As we said above a frame hosts a document and the frame may navigate to a new | ||
document, but the *frameId* will not change. This creates problems when you | ||
receive an event with just a *frameId*. If you [look up the URL](/docs/extensions/reference/webNavigation/#method-getFrame) | ||
of the frame it might be different than when the event occured, this is called | ||
a Time of Use issue. We introduced [*documentId*](/docs/extensions/reference/webNavigation/#method-getFrame:~:text=retrieve%20information%20about.-,documentId,-string%C2%A0optional) | ||
(and [*parentDocumentId*](/docs/extensions/reference/webNavigation/#method-getFrame:~:text=parentDocumentId)) | ||
to address this issue. The [GetFrame](/docs/extensions/reference/webNavigation/#method-getFrame) | ||
API now makes the *frameId* optional if a *documentId* is provided. The | ||
*documentId* will change whenever a frame is navigated. | ||
|
||
## How do I determine when a page transitions? {: #transitions} | ||
|
||
There are explicit signals to determine when a page transitions these states. | ||
|
||
Let’s look at the [WebNavigation events](/docs/extensions/reference/webNavigation/#event). | ||
|
||
For a very first navigation of any page you will see 4 events. Note that these | ||
4 events could occur with the *documentLifecycle* state being either *prerender* | ||
or *active*. | ||
|
||
```js | ||
onBeforeNavigate -> onCommitted -> onDOMContentLoaded -> onCompleted | ||
``` | ||
|
||
This is illustrated in the picture below loading the prerender page with | ||
*documentId* == xyz. | ||
|
||
{% Img src="image/zKrSUSkPboWMTTSEkowJbqw5Egi2/p0ulwify9TGLuLh6RJpW.png", alt="ALT_TEXT_HERE", width="350", height="463" %} | ||
|
||
When a page transitions from either Back/Forward Cache or prerender to the | ||
active state there will be 3 more events (but with *documentLifecyle* | ||
being *active*). | ||
|
||
```js | ||
onBeforeNavigate -> onCommitted -> onCompleted | ||
``` | ||
|
||
The *documentId* will remain the same as the original events. This is | ||
illustrated above when *documentId* == xyz activates. Note that the | ||
same navigation events fire, except from the ```onDOMContentLoaded``` | ||
event because the page already has been loaded. | ||
|
||
If you have any comments or questions please feel free to ask on the | ||
[chromium-extensions](https://groups.google.com/u/1/a/chromium.org/g/chromium-extensions) | ||
group. |