fune/toolkit/components/extensions/test/xpcshell/head_service_worker.js
Luca Greco bc9802e70b Bug 1724026 - Add a new xpcshell-serviceworker.ini manifest and include all alarms API tests in it. r=mixedpuppy
This patch includes:

- a new xpcshell-serviceworker.ini manifest, all tests included in this manifest will also run in the
  "background service worker mode", initially including all `alarms` API xpcshell tests

- some small changes to test_ext_alarms.js to temporarily skip test case that can't yet pass
  while running in "background service worker mode" (until fixed in separate followups)

- some small tweaks to head_service_worker.js to avoid failure due to an existing ExtensionCommon
  const bindings being redefined while running a test for the xpcshell-remote.ini manifest

Differential Revision: https://phabricator.services.mozilla.com/D135125
2022-01-12 11:33:13 +00:00

161 lines
5.1 KiB
JavaScript

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* exported TestWorkerWatcher */
XPCOMUtils.defineLazyModuleGetters(this, {
ExtensionCommon: "resource://gre/modules/ExtensionCommon.jsm",
});
// 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;
const getModuleURI = fileName =>
Services.io.newFileURI(do_get_file(`${this.dataRelPath}/${fileName}`))
.spec;
ChromeUtils.registerProcessActor(JS_ACTOR_NAME, {
parent: {
moduleURI: getModuleURI(`${JS_ACTOR_NAME}Parent.jsm`),
},
child: {
moduleURI: getModuleURI(`${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();
}
}