summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/watcher/target-helpers/service-worker-helper.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/watcher/target-helpers/service-worker-helper.js')
-rw-r--r--devtools/server/actors/watcher/target-helpers/service-worker-helper.js220
1 files changed, 220 insertions, 0 deletions
diff --git a/devtools/server/actors/watcher/target-helpers/service-worker-helper.js b/devtools/server/actors/watcher/target-helpers/service-worker-helper.js
new file mode 100644
index 0000000000..53fceead17
--- /dev/null
+++ b/devtools/server/actors/watcher/target-helpers/service-worker-helper.js
@@ -0,0 +1,220 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { waitForTick } = require("resource://devtools/shared/DevToolsUtils.js");
+
+const PROCESS_SCRIPT_URL =
+ "resource://devtools/server/actors/watcher/target-helpers/service-worker-jsprocessactor-startup.js";
+
+const PROCESS_ACTOR_NAME = "DevToolsServiceWorker";
+const PROCESS_ACTOR_OPTIONS = {
+ // Ignore the parent process.
+ includeParent: false,
+
+ parent: {
+ esModuleURI:
+ "resource://devtools/server/connectors/process-actor/DevToolsServiceWorkerParent.sys.mjs",
+ },
+
+ child: {
+ esModuleURI:
+ "resource://devtools/server/connectors/process-actor/DevToolsServiceWorkerChild.sys.mjs",
+
+ observers: [
+ // Tried various notification to ensure starting the actor
+ // from webServiceWorker processes... but none of them worked.
+ /*
+ "chrome-event-target-created",
+ "webnavigation-create",
+ "chrome-webnavigation-create",
+ "webnavigation-destroy",
+ "chrome-webnavigation-destroy",
+ "browsing-context-did-set-embedder",
+ "browsing-context-discarded",
+ "ipc:content-initializing",
+ "ipc:content-created",
+ */
+
+ // Fallback on firing a very custom notification from a "process script" (loadProcessScript)
+ "init-devtools-service-worker-actor",
+ ],
+ },
+};
+
+// List of all active watchers
+const gWatchers = new Set();
+
+/**
+ * Register the DevToolsServiceWorker JS Process Actor,
+ * if we are registering the first watcher actor.
+ *
+ * @param {Watcher Actor} watcher
+ */
+function maybeRegisterProcessActor(watcher) {
+ const sizeBefore = gWatchers.size;
+ gWatchers.add(watcher);
+
+ if (sizeBefore == 0 && gWatchers.size == 1) {
+ ChromeUtils.registerProcessActor(PROCESS_ACTOR_NAME, PROCESS_ACTOR_OPTIONS);
+
+ // For some reason JSProcessActor doesn't work out of the box for `webServiceWorker` content processes.
+ // So manually spawn our JSProcessActor from a process script emitting an observer service notification...
+ // The Process script are correctly executed on all process types during their early startup.
+ Services.ppmm.loadProcessScript(PROCESS_SCRIPT_URL, true);
+ }
+}
+
+/**
+ * Unregister the DevToolsServiceWorker JS Process Actor,
+ * if we are unregistering the last watcher actor.
+ *
+ * @param {Watcher Actor} watcher
+ */
+function maybeUnregisterProcessActor(watcher) {
+ const sizeBefore = gWatchers.size;
+ gWatchers.delete(watcher);
+
+ if (sizeBefore == 1 && gWatchers.size == 0) {
+ ChromeUtils.unregisterProcessActor(
+ PROCESS_ACTOR_NAME,
+ PROCESS_ACTOR_OPTIONS
+ );
+
+ Services.ppmm.removeDelayedProcessScript(PROCESS_SCRIPT_URL);
+ }
+}
+
+/**
+ * Return the list of all DOM Processes except the one for the parent process
+ *
+ * @return Array<nsIDOMProcessParent>
+ */
+function getAllContentProcesses() {
+ return ChromeUtils.getAllDOMProcesses().filter(
+ process => process.childID !== 0
+ );
+}
+
+/**
+ * Force creating targets for all existing service workers for a given Watcher Actor.
+ *
+ * @param WatcherActor watcher
+ * The Watcher Actor requesting to watch for new targets.
+ */
+async function createTargets(watcher) {
+ maybeRegisterProcessActor(watcher);
+ // Go over all existing content process in order to:
+ // - Force the instantiation of a DevToolsServiceWorkerChild
+ // - Have the DevToolsServiceWorkerChild to spawn the WorkerTargetActors
+
+ const promises = [];
+ for (const process of getAllContentProcesses()) {
+ const promise = process
+ .getActor(PROCESS_ACTOR_NAME)
+ .instantiateServiceWorkerTargets({
+ watcherActorID: watcher.actorID,
+ connectionPrefix: watcher.conn.prefix,
+ sessionContext: watcher.sessionContext,
+ sessionData: watcher.sessionData,
+ });
+ promises.push(promise);
+ }
+
+ // Await for the different queries in order to try to resolve only *after* we received
+ // the already available worker targets.
+ return Promise.all(promises);
+}
+
+/**
+ * Force destroying all worker targets which were related to a given watcher.
+ *
+ * @param WatcherActor watcher
+ * The Watcher Actor requesting to stop watching for new targets.
+ */
+async function destroyTargets(watcher) {
+ // Go over all existing content processes in order to destroy all targets
+ for (const process of getAllContentProcesses()) {
+ let processActor;
+ try {
+ processActor = process.getActor(PROCESS_ACTOR_NAME);
+ } catch (e) {
+ // Ignore any exception during destroy as we may be closing firefox/devtools/tab
+ // and that can easily lead to many exceptions.
+ continue;
+ }
+
+ processActor.destroyServiceWorkerTargets({
+ watcherActorID: watcher.actorID,
+ sessionContext: watcher.sessionContext,
+ });
+ }
+
+ // browser_dbg-breakpoints-columns.js crashes if we unregister the Process Actor
+ // in the same event loop as we call destroyServiceWorkerTargets.
+ await waitForTick();
+
+ maybeUnregisterProcessActor(watcher);
+}
+
+/**
+ * Go over all existing JSProcessActor in order to communicate about new data entries
+ *
+ * @param WatcherActor watcher
+ * The Watcher Actor requesting to update data entries.
+ * @param string type
+ * The type of data to be added
+ * @param Array<Object> entries
+ * The values to be added to this type of data
+ * @param String updateType
+ * "add" will only add the new entries in the existing data set.
+ * "set" will update the data set with the new entries.
+ */
+async function addOrSetSessionDataEntry({
+ watcher,
+ type,
+ entries,
+ updateType,
+}) {
+ maybeRegisterProcessActor(watcher);
+ const promises = [];
+ for (const process of getAllContentProcesses()) {
+ const promise = process
+ .getActor(PROCESS_ACTOR_NAME)
+ .addOrSetSessionDataEntry({
+ watcherActorID: watcher.actorID,
+ sessionContext: watcher.sessionContext,
+ type,
+ entries,
+ updateType,
+ });
+ promises.push(promise);
+ }
+ // Await for the queries in order to try to resolve only *after* the remote code processed the new data
+ return Promise.all(promises);
+}
+
+/**
+ * Notify all existing frame targets that some data entries have been removed
+ *
+ * See addOrSetSessionDataEntry for argument documentation.
+ */
+function removeSessionDataEntry({ watcher, type, entries }) {
+ for (const process of getAllContentProcesses()) {
+ process.getActor(PROCESS_ACTOR_NAME).removeSessionDataEntry({
+ watcherActorID: watcher.actorID,
+ sessionContext: watcher.sessionContext,
+ type,
+ entries,
+ });
+ }
+}
+
+module.exports = {
+ createTargets,
+ destroyTargets,
+ addOrSetSessionDataEntry,
+ removeSessionDataEntry,
+};