1 of 27

ServiceWorker &

Loading Performance

bit.ly/sw-breakout-tpac2023

#serviceworkers irc.w3c.org

Shunya Shishido (sisidovski@chromium.org)

Breakout in TPAC 2023 Seville, Spain

2 of 27

Agenda

  • Background & Motivations
  • Proposed Solutions
    • Skip Empty Fetch Handler
    • BypassFetchHandlerForMainResources
    • Static Routing API
  • Q&A, discussion

3 of 27

Background & Motivations

4 of 27

How ServiceWorker works

ServiceWorker is on the critical path of the navigation and subresource requests!

example.com/article/page.html

ServiceWorker

Network

Cache

The bootstrap is required if it’s not started yet.

5 of 27

Cost of ServiceWorker

  • Bootstrapping cost
  • On Android
    • 8ms in p50
    • 80ms in p75
    • 500ms in p95
  • On Windows
    • 8ms p50
    • 25ms in p75
    • 400ms in p95

6 of 27

Cost of ServiceWorker

  • When ServiceWorker is stopped, it goes worse.
  • On Android
    • 170ms in p50
    • 330ms in p78
    • 940ms in p95
  • On Windows
    • 40ms p50
    • 150ms in p75
    • 980ms in p95

7 of 27

Cost of ServiceWorker

  • What percentage of navigation needs to bootstrap service worker?
    • About 30% of ServiceWorker are not running
    • For cross origin navigation, it’s about 40~50%
  • What percentage of fetch handlers result in fallback*?
    • On Windows, the fallback rate is 13%.
    • On Android, the fallback rate is 57%.

*fallback: fetch handler is executed, but the fetch handler never respond the content. The browser ends up sending the network request with the regular network stack.

8 of 27

Navigation Preload

addEventListener('activate', event => {

event.waitUntil(async function() {

await self.registration.navigationPreload.enable();

}());

});

Navigation preload improve service worker startup time by making requests in parallel.

without navigation preload

with navigation preload

Navigation request

SW boot

Navigation request

SW boot

9 of 27

Navigation Preload

  • SW boot is still on the critical path for the navigation.
  • SW boot may take longer than the navigation request.
  • Only works for navigation requests.

addEventListener('fetch', event => {

event.respondWith(async function() {

const response = await event.preloadResponse;

if (response) {

return response;

}

return fetch(event.request);

}());

});

Navigation request

SW boot

With navigation preload, but the SW boot may take longer time than the navigation request…

10 of 27

How to minimize the cost

11 of 27

Strategy: don’t start ServiceWorker if not needed!

example.com/article/page.html

ServiceWorker

You don’t need me because…

12 of 27

Skip no-op fetch handler

Explainer: https://github.com/yoshisatoyanagisawa/service-worker-skip-no-op-fetch-handler

Spec Update: https://github.com/w3c/ServiceWorker/pull/1674

An optimization to skip unnecessary fetch hander executions.

If the fetch handler is empty, navigation doesn’t involve ServiceWorker.

Still not 100% shipped yet in Chrome, but so far we see about 50-200ms LCP improvements on the sites having no-op fetch handlers.

addEventListener(‘fetch’, () => {});

13 of 27

BypassFetchHandlerForMainResources

https://github.com/sisidovski/service-worker-bypass-fetch-handler-for-main-resource

  • An experimental feature to skip ServiceWorker only when the request is for the main resource (navigation).
  • Chrome had an Origin Trial, and got positive feedback from partners, including loading performance improvements.
  • We’d like to provide the same behavior via the Static Routing API

14 of 27

Static Routing API

// Go straight to the network and bypass invoking fetch handlers for all URLs that start with '/images/'.

addEventListener('install', (event) => {

event.registerRouter({

condition: {

urlPattern: {pathname: "/images/*"}

},

source: "network"

});

});

Explainer: https://github.com/WICG/service-worker-static-routing-api

Spec update: https://github.com/w3c/ServiceWorker/pull/1686

Static Routing API allows developers to register routing rules, it can offload ServiceWorker related tasks from the loading critical path.

15 of 27

Static Routing API

16 of 27

URLPattern in Static Routing API

const pattern = new URLPattern({

hostname: `{*.}?example.com`

});

// Prints `true`

console.log(pattern.test("https://example.com/foo/bar"));

// Prints `true`

console.log(pattern.test({ hostname: "cdn.example.com" }));

// Prints `false` because the hostname component does not match

console.log(pattern.test("https://cdn-example.com/foo/bar"));

The URLPattern API provides a web platform primitive for matching URLs.

https://wicg.github.io/urlpattern/

This brings an unified and flexible way to write matching rules.

17 of 27

Static Routing API

addEventListener('install', (event) => {

event.registerRouter({

condition: {

urlPattern: {pathname: "/article/*"}

},

source: "network"

});

});

How it works:

In the install event handler, register routing info to the data structure associated with service worker.

example.com

18 of 27

Static Routing API

addEventListener('install', (event) => {

event.registerRouter({

condition: {

urlPattern: {pathname: "/article/*"}

},

source: "network"

});

});

Check if the request is matched with the registered routing rules.

example.com

GET example.com/article/page.html

If matched, the fetch event is not dispatched. The request goes directly to the network.

How it works:

19 of 27

Static Routing API

addEventListener('install', (event) => {

event.registerRouter({

condition: {

urlPattern: {pathname: "/article/*"}

},

source: "network"

});

});

Check if the request is matched with the registered routing rules.

example.com

GET example.com/article/page.html

How it works:

Network

Cache

If not matched, just go the through regular ServiceWorker fetch handler path.

20 of 27

Static Routing API common use case

addEventListener('install', (event) => {

event.registerRouter({

condition: {

requestMode: "navigate"

},

source: "network"

});

});

Skip SW for navigation requests

Main resource (navigation request) goes directly to the network.

Subresources (JS, CSS, images etc) goes to the ServiceWorker

21 of 27

Static Routing API common use case

addEventListener('install', (event) => {

event.registerRouter({

condition: {

and: [

{urlPattern: {pathname: "/form/*"}},

{requestMethod: "post"}

],

},

source: "network"

});

});

Skip POST requests in some pages

If the request url starts from “form” and method is POST, goes directly to the network.

Otherwise goes to the ServiceWorker.

22 of 27

Static Routing API common use case

addEventListener('install', (event) => {

event.registerRouter([

{

condition: {requestMode: "navigate"},

source: "network"

},

{

condition: {

urlPattern: {pathname: "/images/*"}

},

source: "cache"

}

]);

});

Skip SW for navigation requests, go directly to the cache for subresources.

Main resource (navigation request) goes directly to the network.

Images goes directly to the Cache storage.

23 of 27

Static Routing API common use case

// Cache first for JavaScript files.

addEventListener('install', (event) => {

event.registerRouter({

condition: {

urlPattern: {pathname: "/**/*.js"}

},

source: [

{

cacheName: "javascript"

},

"network"

]

});

});

Cache First

If there is no cache hit, fallback to the the network.

Goes directly to the Cache storage.

24 of 27

Static Routing API common use case

// Race for all URLs.

addEventListener('install', (event) => {

event.registerRouter({

condition: {

urlPattern: {}

},

source: "race-network-and-fetch-handler"

});

});

“race-network-and-fetch-handler” is an experimental option starting the race between the network request and the ServiceWorker fetch handler.

The browser uses the result of whichever was faster.

Race between the network and fetch handlers

25 of 27

Implementation Status in Chrome

Origin Trial is available! from Chrome 116 to 121. Early feedback is appreciated.

Chrome implements the feature step by step.

  • In 116, MVP features are implemented.
    • URLPattern based router condition
    • Source: “network”
  • In 117, added more features.
    • Request based condition
    • Source: “fetch-event”, “race-network-and-fetch-handler”
  • In 118
    • Source: “cache”

Full set of the API shape: https://github.com/WICG/service-worker-static-routing-api/blob/main/final-form.md

26 of 27

Related Links

Skip service worker no-op fetch handler

ServiceWorkerBypassFetchHandlerForMainResources

Static Routing API

27 of 27

Ideas Open for Discussion

  • Exposing the API in the install event
  • Do you update/delete router rules?
  • URLPattern to write a matching rule
  • Intergration to popular libraries e.g. Workbox
  • Is it useful on the edge?