From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../actors/worker/service-worker-registration.js | 264 +++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 devtools/server/actors/worker/service-worker-registration.js (limited to 'devtools/server/actors/worker/service-worker-registration.js') diff --git a/devtools/server/actors/worker/service-worker-registration.js b/devtools/server/actors/worker/service-worker-registration.js new file mode 100644 index 0000000000..1e5e80ae8b --- /dev/null +++ b/devtools/server/actors/worker/service-worker-registration.js @@ -0,0 +1,264 @@ +/* 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 { Actor } = require("resource://devtools/shared/protocol.js"); +const { + serviceWorkerRegistrationSpec, +} = require("resource://devtools/shared/specs/worker/service-worker-registration.js"); + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +const { + PushSubscriptionActor, +} = require("resource://devtools/server/actors/worker/push-subscription.js"); +const { + ServiceWorkerActor, +} = require("resource://devtools/server/actors/worker/service-worker.js"); + +XPCOMUtils.defineLazyServiceGetter( + this, + "swm", + "@mozilla.org/serviceworkers/manager;1", + "nsIServiceWorkerManager" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "PushService", + "@mozilla.org/push/Service;1", + "nsIPushService" +); + +class ServiceWorkerRegistrationActor extends Actor { + /** + * Create the ServiceWorkerRegistrationActor + * @param DevToolsServerConnection conn + * The server connection. + * @param ServiceWorkerRegistrationInfo registration + * The registration's information. + */ + constructor(conn, registration) { + super(conn, serviceWorkerRegistrationSpec); + this._registration = registration; + this._pushSubscriptionActor = null; + + // A flag to know if preventShutdown has been called and we should + // try to allow the shutdown of the SW when the actor is destroyed + this._preventedShutdown = false; + + this._registration.addListener(this); + + this._createServiceWorkerActors(); + + Services.obs.addObserver(this, PushService.subscriptionModifiedTopic); + } + + onChange() { + this._destroyServiceWorkerActors(); + this._createServiceWorkerActors(); + this.emit("registration-changed"); + } + + form() { + const registration = this._registration; + const evaluatingWorker = this._evaluatingWorker.form(); + const installingWorker = this._installingWorker.form(); + const waitingWorker = this._waitingWorker.form(); + const activeWorker = this._activeWorker.form(); + + const newestWorker = + activeWorker || waitingWorker || installingWorker || evaluatingWorker; + + return { + actor: this.actorID, + scope: registration.scope, + url: registration.scriptSpec, + evaluatingWorker, + installingWorker, + waitingWorker, + activeWorker, + fetch: newestWorker?.fetch, + // Check if we have an active worker + active: !!activeWorker, + lastUpdateTime: registration.lastUpdateTime, + traits: {}, + }; + } + + destroy() { + super.destroy(); + + // Ensure resuming the service worker in case the connection drops + if (this._registration.activeWorker && this._preventedShutdown) { + this.allowShutdown(); + } + + Services.obs.removeObserver(this, PushService.subscriptionModifiedTopic); + this._registration.removeListener(this); + this._registration = null; + if (this._pushSubscriptionActor) { + this._pushSubscriptionActor.destroy(); + } + this._pushSubscriptionActor = null; + + this._destroyServiceWorkerActors(); + + this._evaluatingWorker = null; + this._installingWorker = null; + this._waitingWorker = null; + this._activeWorker = null; + } + + /** + * Standard observer interface to listen to push messages and changes. + */ + observe(subject, topic, data) { + const scope = this._registration.scope; + if (data !== scope) { + // This event doesn't concern us, pretend nothing happened. + return; + } + switch (topic) { + case PushService.subscriptionModifiedTopic: + if (this._pushSubscriptionActor) { + this._pushSubscriptionActor.destroy(); + this._pushSubscriptionActor = null; + } + this.emit("push-subscription-modified"); + break; + } + } + + start() { + const { activeWorker } = this._registration; + + // TODO: don't return "started" if there's no active worker. + if (activeWorker) { + // This starts up the Service Worker if it's not already running. + // Note that the Service Workers exist in content processes but are + // managed from the parent process. This is why we call `attachDebugger` + // here (in the parent process) instead of in a process script. + activeWorker.attachDebugger(); + activeWorker.detachDebugger(); + } + + return { type: "started" }; + } + + unregister() { + const { principal, scope } = this._registration; + const unregisterCallback = { + unregisterSucceeded() {}, + unregisterFailed() { + console.error("Failed to unregister the service worker for " + scope); + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIServiceWorkerUnregisterCallback", + ]), + }; + swm.propagateUnregister(principal, unregisterCallback, scope); + + return { type: "unregistered" }; + } + + push() { + const { principal, scope } = this._registration; + const originAttributes = ChromeUtils.originAttributesToSuffix( + principal.originAttributes + ); + swm.sendPushEvent(originAttributes, scope); + } + + /** + * Prevent the current active worker to shutdown after the idle timeout. + */ + preventShutdown() { + if (!this._registration.activeWorker) { + throw new Error( + "ServiceWorkerRegistrationActor.preventShutdown could not find " + + "activeWorker in parent-intercept mode" + ); + } + + // attachDebugger has to be called from the parent process in parent-intercept mode. + this._registration.activeWorker.attachDebugger(); + this._preventedShutdown = true; + } + + /** + * Allow the current active worker to shut down again. + */ + allowShutdown() { + if (!this._registration.activeWorker) { + throw new Error( + "ServiceWorkerRegistrationActor.allowShutdown could not find " + + "activeWorker in parent-intercept mode" + ); + } + + this._registration.activeWorker.detachDebugger(); + this._preventedShutdown = false; + } + + getPushSubscription() { + const registration = this._registration; + let pushSubscriptionActor = this._pushSubscriptionActor; + if (pushSubscriptionActor) { + return Promise.resolve(pushSubscriptionActor); + } + return new Promise((resolve, reject) => { + PushService.getSubscription( + registration.scope, + registration.principal, + (result, subscription) => { + if (!subscription) { + resolve(null); + return; + } + pushSubscriptionActor = new PushSubscriptionActor( + this.conn, + subscription + ); + this._pushSubscriptionActor = pushSubscriptionActor; + resolve(pushSubscriptionActor); + } + ); + }); + } + + _destroyServiceWorkerActors() { + this._evaluatingWorker.destroy(); + this._installingWorker.destroy(); + this._waitingWorker.destroy(); + this._activeWorker.destroy(); + } + + _createServiceWorkerActors() { + const { evaluatingWorker, installingWorker, waitingWorker, activeWorker } = + this._registration; + + this._evaluatingWorker = new ServiceWorkerActor( + this.conn, + evaluatingWorker + ); + this._installingWorker = new ServiceWorkerActor( + this.conn, + installingWorker + ); + this._waitingWorker = new ServiceWorkerActor(this.conn, waitingWorker); + this._activeWorker = new ServiceWorkerActor(this.conn, activeWorker); + + // Add the ServiceWorker actors as children of this ServiceWorkerRegistration actor, + // assigning them valid actorIDs. + this.manage(this._evaluatingWorker); + this.manage(this._installingWorker); + this.manage(this._waitingWorker); + this.manage(this._activeWorker); + } +} + +exports.ServiceWorkerRegistrationActor = ServiceWorkerRegistrationActor; -- cgit v1.2.3