An extension background is the context that an extension runs on. It allows extensions to react to events or messages with specified instructions. Up until Manifest V2, there were two types of extension background pages, persistent background pages and non-persistent background pages. As part of Manifest V3, we are migrating extensions from the persistent/non-persistent background pages to service workers. Service worker is a web platform feature that forms the basis of app-like capabilities such as offline support, push notifications, and background sync. A service worker is an event-driven JavaScript program that runs in a worker thread. For a more detailed explanation, see the Service Workers documentation.
This document describes the assumptions the //extensions layer makes when relying on the service worker layer for registering/unregistering/startinga service worker or ensuring the service worker’s liveness.
When adding/loading an extension, ExtensionRegistrar::ActivateExtension
is called which results in calling ServiceWorkerTaskQueue::ActivateExtension
which calls ServiceWorkerContext::RegisterServiceWorker
. During registration, script_url
is set to the URL corresponding to the relative path from manifest.json's “background.service_worker” and scope is set to the extension root, i.e., chrome-extension://<extension_id>/.
When registering the service worker, the //extensions layer relies on the content layer’s guarantee that the registration is completed. OnRegistrationStored
is the first observer function that can guarantee StartWorkerForScope
can find the registration. After ServiceWorkerContextObserver::OnRegistrationStored
, ServiceWorkerContext::StartWorkerForScope
should be able to find the registration.
When an extension is removed/disabled/terminated, ExtensionRegistrar::DeactivateExtension
is called which will call ServiceWorkerTaskQueue::DeactivateExtension
. This will result in unregistering the service worker, by calling ServiceWorkerContext::UnregisterServiceWorker
.
DidRegisterServiceWorker
might fail, due to a few reasons: bad disk state, invalid service worker script. The recovery steps would depend on the use case.
DidUnregisterServiceWorker
failure is rare because it does not involve any user-provided JS code.
When DidRegisterServiceWorker/DidUnregisterServiceWorker
fails due to a disk error, the SW layer will try to wipe the whole SW database as the current implementation considers it a critical error.
A service worker is started when a pending task (e.g. an event dispatch) is run. A pending task is run only when all of the following conditions are met:
ServiceWorkerContext::StartWorkerForScope
has returned.DidStartServiceWorkerContextOnWorkerThread
.ServiceWorkerTaskQueue starts a service worker via StartWorkerForScope
. We note that, in the current code, StartWorkerForScope
should be called every time before asking the worker to do something instead of relying on ServiceWorkerContextObserver::OnVersionStoppedRunning
. The reason is that OnVersionStoppedRunning
is called after the worker thread is actually terminated. As a result, we can not rely on OnVersionStoppedRunning
to determine worker liveness. There are more fine-grained running status in the content layer: RUNNING, STOPPING and STOPPED. The listener is called when the worker’s state becomes STOPPED. When an event is dispatched to the worker, it should not be done when the worker is in STOPPPING state.
The flow of dispatching an event to a service worker is
1- Calling StartWorkerForScope
regardless of its running status,
2- Dispatching an event to a service worker inside of the callback triggered from StartWorkerForScope
synchronously.
In this way, we do not have to track whether the worker is running or not.
There are several possible reasons for StartWorkerForScope
failure, such as process allocation failure, timeout of the script evaluation, and disk corruption.
When starting a service worker, the //extensions layer wait for readiness notification from both the browser process and the renderer process. In the current code, after receiving both notifications and before OnVersionStoppedRunning
, the //extensions layer assume that the SW is alive and can dispatch events to an extension service worker. As explained above, we should call StartWorkerForScope
every time before asking the worker to do something instead of relying on OnVersionStoppedRunning
. We plan to fix this in our code. Bug 1162193 tracks this fix.
The //extensions layer rely on the service worker layer to ensure the service worker’s liveness. We use EventAck IPC to ensure that the service worker is alive while an event is dispatched. This is performed in two steps:
1- An event is dispatched from the browser process to the renderer.
2- Renderer responds with EventAck to the browser process.
We ensure that between step 1 and step 2, we do not consider the service worker as “inactive”. We achieve this with workers, i.e., we call ServiceWorkerContext::StartingExternalRequest
on step 1, and then we call ServiceWorkerContext::FinishedExternalRequest
after step 2.
It is guaranteed that the worker will not be stopped between step 1 and step 2, as long as we use ServiceWorkerContext::StartingExternalRequest
and ServiceWorkerContext::FinishedExternalRequest
. The external request is a mechanism to keep the worker alive.