276 lines
7.8 KiB
JavaScript
276 lines
7.8 KiB
JavaScript
/* 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",
|
|
{ global: "contextual" }
|
|
);
|
|
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 => {
|
|
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;
|
|
const origin = this._registration.principal.origin;
|
|
|
|
this._evaluatingWorker = new ServiceWorkerActor(
|
|
this.conn,
|
|
evaluatingWorker,
|
|
origin
|
|
);
|
|
this._installingWorker = new ServiceWorkerActor(
|
|
this.conn,
|
|
installingWorker,
|
|
origin
|
|
);
|
|
this._waitingWorker = new ServiceWorkerActor(
|
|
this.conn,
|
|
waitingWorker,
|
|
origin
|
|
);
|
|
this._activeWorker = new ServiceWorkerActor(
|
|
this.conn,
|
|
activeWorker,
|
|
origin
|
|
);
|
|
|
|
// 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;
|