Chrome nettleser, Nyheter

Chrome Extensions: Extending API to support Instant Navigation

TL;DR: The Extensions API has been updated to support back/forward cache,
preloading navigations. See below for details.

Chrome has been working hard at making navigation fast. Instant Navigation
technologies such as Back/Forward Cache
(shipped on desktop in
Chrome 96) and Speculation Rules
(shipped in Chrome 103) 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
user returns to the previous page, 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 part of the life cycle pages were
in because there was only one for a tab, the active/visible state.

Eviction of the active page
Eviction of the active page.

With Back/Forward Cache and prerendering, there is no longer a one to one
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.

Types of pagesTypes of pages.

Note that a tab can have a series of prerendered 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
of the outermost frame is 0 (a previous best practice) 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 in the same tab 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?”
section of this post.

Life cycle of frames versus documents

Another concept that is problematic with extensions is the life cycle 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
which is a unique identifier for each 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 life cycle state (between
prerender/active/cached) because it remains the same.

Web navigation events

Events in the can fire multiple times on the
same page depending on the life cycle it is in. See
“How do I tell what life cycle the page is in?”
and “How do I determine when a page transitions?” sections.

How do I tell what life cycle the page is in?

The DocumentLifecycle
type has been added to a number of extensions APIs where the frameId was
previously available. If the DocumentLifecycle type is present on an event
(such as onCommitted),
its value is the state in which the event was generated. You can always query
information from the WebNavigation getFrame()
and getAllFrames()
methods, but using the value from the event is always preferred. If you do use
either method be aware the state of the frame may change between when the event
was generated and when the promises return by both methods is resolved.

The 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": The document is being destroyed.

How do I determine if a frame is the outermost frame?

Previously extensions may have checked whether frameId == 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 prerendered 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 type called FrameType
so determining if the frame is indeed the outermost frame is now easy.

has the following values:

  • "outermost_frame": Typically referred to as the topmost frame. Note that
    there are multiples of these. For example, if you have a prerendered and cached
    pages, each has an outermost frame that could be called its topmost frame.
  • "fenced_frame": Reserved for future use.
  • "sub_frame": Typically an iframe.

We can combine DocumentLifecycle with FrameType and determine if a frame is
the active outermost frame. For example:

tab.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
of the frame it might be different than when the event occured, this is called
a time of use issue.

To address this, we introduced documentId
(and parentDocumentId).
The webNavigation.getFrame()
method 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?

There are explicit signals to determine when a page transitions between states.

Let’s look at the WebNavigation events.

For a very first navigation of any page you will see four events in the order
listed below. Note that these four events could occur with the
DocumentLifecycle state being either "prerender" or "active".

onBeforeNavigate
onCommitted
onDOMContentLoaded
onCompleted

This is illustrated in the diagram below which shows the documentId changing
to "xyz" when the prerendered page becomes the active page.

The documentId changes when the prerendered page becomes the active page
The `documentId` changes when the prerendered page becomes the
active page.

When a page transitions from either Back/Forward Cache or prerender to the
active state there will be three more events (but with DcumentLifecyle
being "active").

onBeforeNavigate
onCommitted
onCompleted

The documentId will remain the same as in the original events. This is
illustrated above when documentId == xyz activates. Note that the
same navigation events fire, except for the onDOMContentLoaded
event because the page has already been loaded.

If you have any comments or questions please feel free to ask on the
chromium-extensions
group.

This post is also available in: English

author-avatar

About Aksel Lian

En selvstendig full stack webutvikler med en bred variasjon av kunnskaper herunder SEO, CMS, Webfotografi, Webutvikling inkl. kodespråk..