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/starting a service worker or ensuring the service worker’s liveness. It also documents how ES modules can be imported in Manifest V3. Furthermore, it documents error reporting and handling for service worker based extensions.
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:///.
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.
ES modules in service workers details the current state of ES module support in service workers. In Manifest V3, type is added to the background key in the manifest file, where two types are supported: classic and module. Type is set to classic by default. For a module service worker, background.type should be set to module in the manifest file. The following shows an example background key in Manifest V3:
"background": {
"service_worker": "sw.js",
"type": "classic" (default) | "module"
}
For service worker-based extensions, when service worker registration fails, an error is displayed in chrome://extensions page. Runtime errors are also displayed in chrome://extensions page. When registering or starting a service worker fails, a detailed error code is propagated from the content layer to the //extensions layer, which enables the //extensions layer to display the error in chrome://extensions to give developers a hint about the issue.