// Copyright 2017 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef EXTENSIONS_BROWSER_SERVICE_WORKER_SERVICE_WORKER_TASK_QUEUE_H_ #define EXTENSIONS_BROWSER_SERVICE_WORKER_SERVICE_WORKER_TASK_QUEUE_H_ #include <map> #include <optional> #include <vector> #include "base/containers/flat_map.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/strings/string_util.h" #include "base/unguessable_token.h" #include "base/version.h" #include "components/keyed_service/core/keyed_service.h" #include "content/public/browser/service_worker_context.h" #include "content/public/browser/service_worker_context_observer.h" #include "extensions/browser/lazy_context_id.h" #include "extensions/browser/lazy_context_task_queue.h" #include "extensions/browser/service_worker/worker_id.h" #include "extensions/common/extension_id.h" #include "third_party/blink/public/common/service_worker/service_worker_status_code.h" #include "third_party/blink/public/common/tokens/tokens.h" #include "url/gurl.h" namespace content { class BrowserContext; struct ServiceWorkerRunningInfo; } namespace extensions { class Extension; class ProcessManager; // A service worker implementation of `LazyContextTaskQueue`. For an overview of // service workers on the web see https://web.dev/learn/pwa/service-workers. // Extension workers do not follow the typical web worker lifecycle. At a high // level: // * only one worker instance should run at any given time for an extension // (e.g. there should not be a active and waiting version) // * only one worker version (version of its code) should run for each browser // session // * events can be dispatched to the worker before it is activated // // This class, despite being a task queue, does much more than just queue tasks // for the worker. It handles worker registration, starting/stopping, and task // readiness monitoring. The highlights to understand this class are: // // Worker Registration: // // Worker registration must occur in order to start a worker for the extension. // Otherwise requests to start a worker will fail. Service worker registration // is persisted to disk in the //content layer to avoid unnecessary registration // requests. This prevents a registration request for every restart of the // browser. If there’s a registration record the registration is still verified // with the //content layer). // // Worker Started/Stopped: // // Starting: // // A worker must be started before it can become ready to process the event // tasks. Every task added outside of when the worker is starting will cause // this class to request the worker to start. This is done this way because it // is difficult to know if a worker is currently running and ready to process // tasks. // // `DidStartServiceWorkerContext()` is called asynchronously from the extension // renderer process (potentially before or after `DidStartWorkerForScope()`) and // it records that the worker has started in the renderer (process). // // Stopping: // // TODO(crbug.com/40936639): update the below once `OnStopped()` is called to // track browser starting. // // `DidStopServiceWorkerContext()` is called when the worker is stopped to track // renderer stopping. `DidStopServiceWorkerContext()` is not always guaranteed // to be called. // // Task Processing Readiness: // // Three worker started signals are together used to determine when a worker is // ready to process tasks. Due to this it makes the process more complicated // than just checking if the worker is “running” (e.g by calling the //content // layer for this). // // A worker is checked for readiness by its worker state. Readiness checks three // signals: `BrowserState`, `RendererState`, and `WorkerId` that are each set by // certain methods: // * `BrowserState`: `DidStartWorkerForScope()` signal sets the value to // ready. This signal means that the worker was *requested* to start and it // verified that a worker registration exists at the //content layer. It is // considered the “browser-side” signal that the worker is ready. // * `RendererState`: `DidStartServiceWorkerContext()` signal sets the value // to ready. This is start requests are sent to the worker. This signal // means: // * that there is a worker renderer process thread running the service // worker code // * the worker has done one pass and executed it’s entire JS global scope // * as part of executing that scope: the worker has registered all its // (top-level/global) event listeners with the //extensions layer (all // event listener mojom calls have been received and processed). This // ordering is guaranteed because the mojom message that calls this // signal is after the event listener mojom messages on an associated // mojom pipe. // * `worker_id_.has_value()`: this signal confirms that // the class is populated with the running service worker’s information // (render process and thread id, and worker version id) . This confirms // that when the task is dispatched to the worker it is sent to the running // worker (and not a previously stopped one). // // Ordering of Registration and Start Worker Completion: // // Note that while worker registration in //content `DidRegisterServiceWorker()` // will finish before requesting the worker to start, there is no guarantee on // how the signals for their completion will be received. // // For example `DidRegisterServiceWorker()`, `DidStartWorkerForScope()` and // `DidStartServiceWorkerContext()` signals are not guaranteed to finish in any // order. // // Activation Token: // // TODO(jlulejian): Explain how the activation token tracks // activation/deactivation and how the class uses it. // // TODO(lazyboy): Clean up queue when extension is unloaded/uninstalled. class ServiceWorkerTaskQueue : public KeyedService, public LazyContextTaskQueue, public content::ServiceWorkerContextObserver, public content::ServiceWorkerContextObserverSynchronous { … }; } // namespace extensions #endif // EXTENSIONS_BROWSER_SERVICE_WORKER_SERVICE_WORKER_TASK_QUEUE_H_