Skip to content
This repository has been archived by the owner on Mar 14, 2024. It is now read-only.

Commit

Permalink
Add Extensions API blog post for documentId and friends.
Browse files Browse the repository at this point in the history
- Instant Navigation adds additional fields documentId,
parentDocumentId, documentLifecycle, frameType to help improve the
experience for back/forward cache and prerendering.
  • Loading branch information
dtapuska committed Oct 4, 2022
1 parent f874810 commit 6ab5612
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 1 deletion.
5 changes: 4 additions & 1 deletion site/_data/authorsData.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@
"github": "schuay"
},
"dtapuska": {
"twitter": "dtapuska"
"twitter": "dtapuska",
"github": "dtapuska",
"image": "image/zKrSUSkPboWMTTSEkowJbqw5Egi2/PMKyfQoKPntnI4amz8LD.jpeg",
"country": "CA"
},
"bokan": {
"twitter": "david_bokan"
Expand Down
179 changes: 179 additions & 0 deletions site/en/blog/extension-instantnav/index.md
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.

0 comments on commit 6ab5612

Please sign in to comment.