Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Service Worker Fetch Routing Pipeline

Concept

Vocabulary that names a phenomenon.

Chromium’s browser-owned path for deciding whether a controlled navigation or subresource request goes to a static router source, navigation preload, the HTTP cache, the network, or a service worker FetchEvent.

Service workers are often described as JavaScript that intercepts requests. That hides the important part for Chromium review: a controlled request doesn’t automatically run service-worker JavaScript. Chromium first asks which registration controls the request, which storage partition it belongs to, whether a static route can answer it, whether navigation preload has started a network request in parallel, and only then whether an active worker should receive a FetchEvent.

What It Is

The service worker fetch routing pipeline is the browser-side decision path for a request that may be controlled by a service worker. Chromium checks the relevant registration, evaluates the request against scope and storage key, and then decides which path supplies the response.

The registration is the first gate. The W3C Service Workers specification defines a service worker registration as a tuple of a scope URL, a storage key, and its installing, waiting, and active workers. That storage key matters because partitioning can make two same-origin embeds use different registrations. A same-origin service worker controls the requests that match its partition and scope, not every same-origin request on the web.

The active worker is the second gate. An installing or waiting worker is not yet the fetch authority for controlled clients. The active worker may receive a FetchEvent, but static routing can bypass it for configured routes. Starting in Chrome 123, the Service Worker Static Routing API lets a service worker declare routes during install with event.addRoutes(). Sources include network, cache, fetch-event, and race-network-and-fetch-handler, with conditions such as URL pattern, request method, request mode, request destination, and worker running status. If a static route matches, the browser can answer from the declared source without starting JavaScript to reach the same result.

Navigation preload is the third decision. When enabled, the browser starts the network request while the service worker boots, and the worker later reads event.preloadResponse inside the fetch handler. Preload doesn’t bypass the service worker; it hides part of the startup cost by letting network work run concurrently with boot. The two optimizations differ in where they leave the worker: static routing removes it from the path for a matched route, while navigation preload keeps it on the path but stops the network from waiting on it.

The outcome is visible in timing and diagnostics. Resource Timing and Navigation Timing carry workerMatchedRouterSource and workerFinalRouterSource, so a developer can distinguish the route that matched from the source that finally answered. A slow navigation may be a worker boot, a cache route, a static-router fallback, or a fetch handler waiting on application code.

Why It Matters

The concept closes a gap between URLLoaderFactory Trust Boundary and ordinary web debugging. The factory boundary explains who gives a renderer authority to ask for bytes. It doesn’t explain why a request with valid authority might be answered by a service worker rather than by a direct network load. Service worker routing is the middle: browser-owned request authority enters the Network Service, and the service worker subsystem decides whether script, cache, preload, or static routing supplies the response.

The Storage Partition Boundary shows up here as a concrete consequence. A service worker registration is origin, scope URL, and storage key, not origin plus path. A third-party iframe that registered a service worker under one top-level site may not control the same apparent origin under another top-level site. Bugs in this area often arrive as offline-cache failures or “the service worker didn’t run” reports, and the first question is whether the partition and scope match.

Timing is the other practical consequence. Service worker routing can sit on the main-resource critical path before a document commits, which is why the Navigation Commit Pipeline cares about it: static routing and navigation preload exist because starting a worker can cost enough time to matter to navigation latency. The Speculative Navigation Pipeline carries the same rule from a different angle — pre-commit or pre-activation work must not be mistaken for ordinary page execution.

Downstream Chromium-based products inherit all of this behavior. An enterprise browser, Electron shell, or WebView2 host that changes cache policy, request interception, custom schemes, or offline behavior can accidentally perturb service-worker routing. A product that treats every controlled request as “network first, unless JavaScript intercepts it” will miss static routes, navigation preload, and partitioned registrations.

How to Recognize It

The source tree names each stage plainly. content/browser/service_worker/service_worker_registration.h carries the registration object and its worker state. service_worker_main_resource_loader.cc handles the main-resource path where a navigation load may be mediated by a service worker. service_worker_fetch_dispatcher.h names the dispatch path for fetch events. Blink’s service_worker_router_type_converter.cc converts the JavaScript static-routing shape into Chromium’s internal route representation.

The web-facing names are just as concrete. In service-worker install code, event.addRoutes() signals static routing. In a fetch handler, event.preloadResponse signals navigation preload. In performance entries, workerMatchedRouterSource and workerFinalRouterSource report which router source matched and which source actually produced the response. A debugging session that ignores those terms is probably treating the request as simpler than Chromium treats it.

DevTools and internal pages expose the same split. Service-worker inspection surfaces show registrations, versions, running state, and controlled clients. The Network panel and timing APIs show whether the response path went through service-worker machinery. The useful question is which registration controlled the request, which source matched, whether the worker ran, and whether preload or cache answered first.

How It Plays Out

A page registers a service worker for /app/ and later loads /app/dashboard. The navigation matches the registration’s scope and storage key, so the active worker can control it. If no static route matches, Chromium may dispatch a FetchEvent. If navigation preload is enabled, the network request may already be in flight when the worker starts, and the handler can use event.preloadResponse. A trace that sees network activity before the handler runs hasn’t found a bypass by itself. It may be seeing preload.

A team adds a static route for image assets with source cache. On a matching request, the browser can use the declared source without starting the service worker just to run a fetch handler that would do the same lookup. The latency improvement is real, especially on devices where worker boot is costly. The cost is that diagnostics have to follow the router source rather than the JavaScript handler alone. If a cache miss falls back differently than expected, workerMatchedRouterSource and workerFinalRouterSource are more useful than guessing from the handler’s logs.

A downstream embedder reports that a same-origin iframe’s service worker controls requests on one customer site but not another. The origin is the same, and the scope URL looks right. The missing fact is the storage key: each top-level site may create a different partition and therefore a different registration map. The partition has to be checked before the worker lifecycle.

A developer working on navigation performance sees a long gap before first byte for a controlled route. The tempting fix is to bypass the service worker entirely. The safer analysis distinguishes three cases: use static routing when a route can be declared without application code, enable navigation preload when the worker must still decide but network can run concurrently, and keep the fetch handler when the response depends on service-worker JavaScript.

Consequences

The benefit is a clearer mental model for controlled requests. Service workers are not a single interception hook. They are a registration match, a storage partition decision, an active-worker lifecycle check, optional static routing, optional navigation preload, and possible fetch-event dispatch.

The pipeline also gives performance work a more precise toolset. Static routes avoid worker startup where the route is declarative. Navigation preload overlaps worker startup with network. Fetch handlers remain the flexible path for application logic. These mechanisms are not interchangeable, and treating them as interchangeable tends to create either stale cache behavior or unnecessary startup latency.

Complexity and observability are the cost. A request may be controlled even when no JavaScript runs. A network request may start before the fetch handler receives the event. A cache response may come from a static route rather than application code. A same-origin request may miss the expected worker because partitioning changed the registration map.

The model also creates downstream risk. Custom request interception, scheme handling, cache overrides, and embedded-runtime policy code can perturb the pipeline while appearing to touch only networking. Any downstream fork that changes request dispatch has to preserve storage-key matching, scope checks, static-router semantics, navigation-preload behavior, and timing diagnostics.

Notes for Agent Context

When modifying Chromium service-worker request code, resolve the controlling registration by storage key and scope URL before assuming an active worker can handle the request. Do not treat origin alone as the registration key, and do not dispatch a FetchEvent to an installing or waiting worker.

When diagnosing a controlled request, check static-router rules, navigation preload, and router-source timing before changing fetch-handler code. A request can be correctly answered by network, cache, fetch-event, or race-network-and-fetch-handler, and workerMatchedRouterSource / workerFinalRouterSource are the diagnostic fields that say which path won.

When adding or changing request interception in an embedder, preserve the browser-owned service-worker pipeline. Do not bypass URLLoaderFactory authority, storage-key matching, static routing, navigation preload, or the service-worker main-resource loader to make a custom network path simpler.

Sources

The W3C Service Workers specification is the primary source for scope URL, storage key, worker states, Handle Fetch, navigation preload, and router-source timing fields. Rachel Andrew’s Chrome for Developers article records the Chrome 123 Static Routing API launch, the Chrome 116 origin trial, event.addRoutes(), route conditions, and source strings. The WICG explainer gives the design reason: service-worker startup can be a performance cost, so declarative routes let the browser avoid starting JavaScript when a route can be answered without it. The blink-dev timing-field Intent records workerMatchedRouterSource and workerFinalRouterSource. Workbox documents the concurrent preload path and the preloadResponse handoff.

Technical Drill-Down