Commit e306d88c authored by Ghazale Hosseinabadi's avatar Ghazale Hosseinabadi Committed by Chromium LUCI CQ

[Extensions] Document Extension Layer/Service Worker Interactions

This CL documents extension layer's interactions with service worker
layer.

Bug: 1152530
Change-Id: If66b51440d79400f619ca8b03ae72897976aa813
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2559213
Commit-Queue: Ghazale Hosseinabadi <ghazale@chromium.org>
Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#841707}
parent 1d8dd686
# Extension layer/service worker interactions
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](https://chromium.googlesource.com/chromium/src/+/HEAD/content/browser/service_worker/README.md).
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.
## Registration
When adding/loading an extension, `ExtensionRegistrar::ActivateExtension` is
called which results in calling `ServiceWorkerTaskQueue::ActivateExtension`
which calls `ServiceWorkerContext::RegisterServiceWorker`. During registration,
[`script_url`](https://source.chromium.org/chromium/chromium/src/+/77dcc35a2a0b98d3913148149496b8dd0d3464cc:content/public/browser/service_worker_context.h;l=125) 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.
## Unregistration
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`.
## Registration/Unregistration failure
`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.
## Starting the service worker
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:
- Service worker registration has completed.
- Call to `ServiceWorkerContext::StartWorkerForScope` has returned.
- Worker thread (in the renderer) has seen
`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.
## Notifications
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](https://bugs.chromium.org/p/chromium/issues/detail?id=1162193) tracks this fix.
## Service worker’s liveness
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.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment