diff options
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/head_service_worker.js')
-rw-r--r-- | toolkit/components/extensions/test/xpcshell/head_service_worker.js | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/head_service_worker.js b/toolkit/components/extensions/test/xpcshell/head_service_worker.js new file mode 100644 index 0000000000..f83250f84c --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/head_service_worker.js @@ -0,0 +1,158 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +/* exported TestWorkerWatcher */ + +ChromeUtils.defineESModuleGetters(this, { + ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs", +}); + +// Ensure that the profile-after-change message has been notified, +// so that ServiceWokerRegistrar is going to be initialized, +// otherwise tests using a background service worker will fail. +// in debug builds because of an assertion failure triggered +// by ServiceWorkerRegistrar.cpp (due to not being initialized +// automatically on startup as in a real Firefox instance). +Services.obs.notifyObservers( + null, + "profile-after-change", + "force-serviceworkerrestart-init" +); + +// A test utility class used in the test case to watch for a given extension +// service worker being spawned and terminated (using the same kind of Firefox DevTools +// internals that about:debugging is using to watch the workers activity). +// +// NOTE: this helper class does also depends from the two jsm files where the +// Parent and Child TestWorkerWatcher actor is defined: +// +// - data/TestWorkerWatcherParent.jsm +// - data/TestWorkerWatcherChild.jsm +class TestWorkerWatcher extends ExtensionCommon.EventEmitter { + JS_ACTOR_NAME = "TestWorkerWatcher"; + + constructor(dataRelPath = "./data") { + super(); + this.dataRelPath = dataRelPath; + this.extensionProcess = null; + this.extensionProcessActor = null; + this.registerProcessActor(); + this.getAndWatchExtensionProcess(); + // Observer child process creation and shutdown if the extension + // are meant to run in a child process. + Services.obs.addObserver(this, "ipc:content-created"); + Services.obs.addObserver(this, "ipc:content-shutdown"); + } + + async destroy() { + await this.stopWatchingWorkers(); + ChromeUtils.unregisterProcessActor(this.JS_ACTOR_NAME); + } + + get swm() { + return Cc["@mozilla.org/serviceworkers/manager;1"].getService( + Ci.nsIServiceWorkerManager + ); + } + + getRegistration(extension) { + return this.swm.getRegistrationByPrincipal( + extension.extension.principal, + extension.extension.principal.spec + ); + } + + watchExtensionServiceWorker(extension) { + // These events are emitted by TestWatchExtensionWorkersParent. + const promiseWorkerSpawned = this.waitForEvent("worker-spawned", extension); + const promiseWorkerTerminated = this.waitForEvent( + "worker-terminated", + extension + ); + + // Terminate the worker sooner by settng the idle_timeout to 0, + // then clear the pref as soon as the worker has been terminated. + const terminate = () => { + promiseWorkerTerminated.then(() => { + Services.prefs.clearUserPref("dom.serviceWorkers.idle_timeout"); + }); + Services.prefs.setIntPref("dom.serviceWorkers.idle_timeout", 0); + const swReg = this.getRegistration(extension); + // If the active worker is already active, we have to make sure the new value + // set on the idle_timeout pref is picked up by ServiceWorkerPrivate::ResetIdleTimeout. + swReg.activeWorker?.attachDebugger(); + swReg.activeWorker?.detachDebugger(); + return promiseWorkerTerminated; + }; + + return { + promiseWorkerSpawned, + promiseWorkerTerminated, + terminate, + }; + } + + // Methods only used internally. + + waitForEvent(event, extension) { + return new Promise(resolve => { + const listener = (_eventName, data) => { + if (!data.workerUrl.startsWith(extension.extension?.principal.spec)) { + return; + } + this.off(event, listener); + resolve(data); + }; + + this.on(event, listener); + }); + } + + registerProcessActor() { + const { JS_ACTOR_NAME } = this; + ChromeUtils.registerProcessActor(JS_ACTOR_NAME, { + parent: { + moduleURI: `resource://testing-common/${JS_ACTOR_NAME}Parent.jsm`, + }, + child: { + moduleURI: `resource://testing-common/${JS_ACTOR_NAME}Child.jsm`, + }, + }); + } + + startWatchingWorkers() { + if (!this.extensionProcessActor) { + return; + } + this.extensionProcessActor.eventEmitter = this; + return this.extensionProcessActor.sendQuery("Test:StartWatchingWorkers"); + } + + stopWatchingWorkers() { + if (!this.extensionProcessActor) { + return; + } + this.extensionProcessActor.eventEmitter = null; + return this.extensionProcessActor.sendQuery("Test:StopWatchingWorkers"); + } + + getAndWatchExtensionProcess() { + const extensionProcess = ChromeUtils.getAllDOMProcesses().find(p => { + return p.remoteType === "extension"; + }); + if (extensionProcess !== this.extensionProcess) { + this.extensionProcess = extensionProcess; + this.extensionProcessActor = extensionProcess + ? extensionProcess.getActor(this.JS_ACTOR_NAME) + : null; + this.startWatchingWorkers(); + } + } + + observe(subject, topic, childIDString) { + // Keep the watched process and related test child process actor updated + // when a process is created or destroyed. + this.getAndWatchExtensionProcess(); + } +} |