123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612 |
- /* 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 { Ci } = require("chrome");
- const { DebuggerServer } = require("devtools/server/main");
- const Services = require("Services");
- const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
- const protocol = require("devtools/shared/protocol");
- const { Arg, method, RetVal } = protocol;
- const {
- workerSpec,
- pushSubscriptionSpec,
- serviceWorkerRegistrationSpec,
- serviceWorkerSpec,
- } = require("devtools/shared/specs/worker");
- loader.lazyRequireGetter(this, "ChromeUtils");
- loader.lazyRequireGetter(this, "events", "sdk/event/core");
- XPCOMUtils.defineLazyServiceGetter(
- this, "wdm",
- "@mozilla.org/dom/workers/workerdebuggermanager;1",
- "nsIWorkerDebuggerManager"
- );
- XPCOMUtils.defineLazyServiceGetter(
- this, "swm",
- "@mozilla.org/serviceworkers/manager;1",
- "nsIServiceWorkerManager"
- );
- XPCOMUtils.defineLazyServiceGetter(
- this, "PushService",
- "@mozilla.org/push/Service;1",
- "nsIPushService"
- );
- function matchWorkerDebugger(dbg, options) {
- if ("type" in options && dbg.type !== options.type) {
- return false;
- }
- if ("window" in options) {
- let window = dbg.window;
- while (window !== null && window.parent !== window) {
- window = window.parent;
- }
- if (window !== options.window) {
- return false;
- }
- }
- return true;
- }
- let WorkerActor = protocol.ActorClassWithSpec(workerSpec, {
- initialize(conn, dbg) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this._dbg = dbg;
- this._attached = false;
- this._threadActor = null;
- this._transport = null;
- },
- form(detail) {
- if (detail === "actorid") {
- return this.actorID;
- }
- let form = {
- actor: this.actorID,
- consoleActor: this._consoleActor,
- url: this._dbg.url,
- type: this._dbg.type
- };
- if (this._dbg.type === Ci.nsIWorkerDebugger.TYPE_SERVICE) {
- let registration = this._getServiceWorkerRegistrationInfo();
- form.scope = registration.scope;
- }
- return form;
- },
- attach() {
- if (this._dbg.isClosed) {
- return { error: "closed" };
- }
- if (!this._attached) {
- // Automatically disable their internal timeout that shut them down
- // Should be refactored by having actors specific to service workers
- if (this._dbg.type == Ci.nsIWorkerDebugger.TYPE_SERVICE) {
- let worker = this._getServiceWorkerInfo();
- if (worker) {
- worker.attachDebugger();
- }
- }
- this._dbg.addListener(this);
- this._attached = true;
- }
- return {
- type: "attached",
- url: this._dbg.url
- };
- },
- detach() {
- if (!this._attached) {
- return { error: "wrongState" };
- }
- this._detach();
- return { type: "detached" };
- },
- destroy() {
- protocol.Actor.prototype.destroy.call(this);
- if (this._attached) {
- this._detach();
- }
- },
- disconnect() {
- this.destroy();
- },
- connect(options) {
- if (!this._attached) {
- return { error: "wrongState" };
- }
- if (this._threadActor !== null) {
- return {
- type: "connected",
- threadActor: this._threadActor
- };
- }
- return DebuggerServer.connectToWorker(
- this.conn, this._dbg, this.actorID, options
- ).then(({ threadActor, transport, consoleActor }) => {
- this._threadActor = threadActor;
- this._transport = transport;
- this._consoleActor = consoleActor;
- return {
- type: "connected",
- threadActor: this._threadActor,
- consoleActor: this._consoleActor
- };
- }, (error) => {
- return { error: error.toString() };
- });
- },
- push() {
- if (this._dbg.type !== Ci.nsIWorkerDebugger.TYPE_SERVICE) {
- return { error: "wrongType" };
- }
- let registration = this._getServiceWorkerRegistrationInfo();
- let originAttributes = ChromeUtils.originAttributesToSuffix(
- this._dbg.principal.originAttributes);
- swm.sendPushEvent(originAttributes, registration.scope);
- return { type: "pushed" };
- },
- onClose() {
- if (this._attached) {
- this._detach();
- }
- this.conn.sendActorEvent(this.actorID, "close");
- },
- onError(filename, lineno, message) {
- reportError("ERROR:" + filename + ":" + lineno + ":" + message + "\n");
- },
- _getServiceWorkerRegistrationInfo() {
- return swm.getRegistrationByPrincipal(this._dbg.principal, this._dbg.url);
- },
- _getServiceWorkerInfo() {
- let registration = this._getServiceWorkerRegistrationInfo();
- return registration.getWorkerByID(this._dbg.serviceWorkerID);
- },
- _detach() {
- if (this._threadActor !== null) {
- this._transport.close();
- this._transport = null;
- this._threadActor = null;
- }
- // If the worker is already destroyed, nsIWorkerDebugger.type throws
- // (_dbg.closed appears to be false when it throws)
- let type;
- try {
- type = this._dbg.type;
- } catch (e) {}
- if (type == Ci.nsIWorkerDebugger.TYPE_SERVICE) {
- let worker = this._getServiceWorkerInfo();
- if (worker) {
- worker.detachDebugger();
- }
- }
- this._dbg.removeListener(this);
- this._attached = false;
- }
- });
- exports.WorkerActor = WorkerActor;
- function WorkerActorList(conn, options) {
- this._conn = conn;
- this._options = options;
- this._actors = new Map();
- this._onListChanged = null;
- this._mustNotify = false;
- this.onRegister = this.onRegister.bind(this);
- this.onUnregister = this.onUnregister.bind(this);
- }
- WorkerActorList.prototype = {
- getList() {
- // Create a set of debuggers.
- let dbgs = new Set();
- let e = wdm.getWorkerDebuggerEnumerator();
- while (e.hasMoreElements()) {
- let dbg = e.getNext().QueryInterface(Ci.nsIWorkerDebugger);
- if (matchWorkerDebugger(dbg, this._options)) {
- dbgs.add(dbg);
- }
- }
- // Delete each actor for which we don't have a debugger.
- for (let [dbg, ] of this._actors) {
- if (!dbgs.has(dbg)) {
- this._actors.delete(dbg);
- }
- }
- // Create an actor for each debugger for which we don't have one.
- for (let dbg of dbgs) {
- if (!this._actors.has(dbg)) {
- this._actors.set(dbg, new WorkerActor(this._conn, dbg));
- }
- }
- let actors = [];
- for (let [, actor] of this._actors) {
- actors.push(actor);
- }
- if (!this._mustNotify) {
- if (this._onListChanged !== null) {
- wdm.addListener(this);
- }
- this._mustNotify = true;
- }
- return Promise.resolve(actors);
- },
- get onListChanged() {
- return this._onListChanged;
- },
- set onListChanged(onListChanged) {
- if (typeof onListChanged !== "function" && onListChanged !== null) {
- throw new Error("onListChanged must be either a function or null.");
- }
- if (onListChanged === this._onListChanged) {
- return;
- }
- if (this._mustNotify) {
- if (this._onListChanged === null && onListChanged !== null) {
- wdm.addListener(this);
- }
- if (this._onListChanged !== null && onListChanged === null) {
- wdm.removeListener(this);
- }
- }
- this._onListChanged = onListChanged;
- },
- _notifyListChanged() {
- this._onListChanged();
- if (this._onListChanged !== null) {
- wdm.removeListener(this);
- }
- this._mustNotify = false;
- },
- onRegister(dbg) {
- if (matchWorkerDebugger(dbg, this._options)) {
- this._notifyListChanged();
- }
- },
- onUnregister(dbg) {
- if (matchWorkerDebugger(dbg, this._options)) {
- this._notifyListChanged();
- }
- }
- };
- exports.WorkerActorList = WorkerActorList;
- let PushSubscriptionActor = protocol.ActorClassWithSpec(pushSubscriptionSpec, {
- initialize(conn, subscription) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this._subscription = subscription;
- },
- form(detail) {
- if (detail === "actorid") {
- return this.actorID;
- }
- let subscription = this._subscription;
- return {
- actor: this.actorID,
- endpoint: subscription.endpoint,
- pushCount: subscription.pushCount,
- lastPush: subscription.lastPush,
- quota: subscription.quota
- };
- },
- destroy() {
- protocol.Actor.prototype.destroy.call(this);
- this._subscription = null;
- },
- });
- let ServiceWorkerActor = protocol.ActorClassWithSpec(serviceWorkerSpec, {
- initialize(conn, worker) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this._worker = worker;
- },
- form() {
- if (!this._worker) {
- return null;
- }
- return {
- url: this._worker.scriptSpec,
- state: this._worker.state,
- };
- },
- destroy() {
- protocol.Actor.prototype.destroy.call(this);
- this._worker = null;
- },
- });
- // Lazily load the service-worker-child.js process script only once.
- let _serviceWorkerProcessScriptLoaded = false;
- let ServiceWorkerRegistrationActor =
- protocol.ActorClassWithSpec(serviceWorkerRegistrationSpec, {
- /**
- * Create the ServiceWorkerRegistrationActor
- * @param DebuggerServerConnection conn
- * The server connection.
- * @param ServiceWorkerRegistrationInfo registration
- * The registration's information.
- */
- initialize(conn, registration) {
- protocol.Actor.prototype.initialize.call(this, conn);
- this._conn = conn;
- this._registration = registration;
- this._pushSubscriptionActor = null;
- this._registration.addListener(this);
- let {installingWorker, waitingWorker, activeWorker} = registration;
- this._installingWorker = new ServiceWorkerActor(conn, installingWorker);
- this._waitingWorker = new ServiceWorkerActor(conn, waitingWorker);
- this._activeWorker = new ServiceWorkerActor(conn, activeWorker);
- Services.obs.addObserver(this, PushService.subscriptionModifiedTopic, false);
- },
- onChange() {
- this._installingWorker.destroy();
- this._waitingWorker.destroy();
- this._activeWorker.destroy();
- let {installingWorker, waitingWorker, activeWorker} = this._registration;
- this._installingWorker = new ServiceWorkerActor(this._conn, installingWorker);
- this._waitingWorker = new ServiceWorkerActor(this._conn, waitingWorker);
- this._activeWorker = new ServiceWorkerActor(this._conn, activeWorker);
- events.emit(this, "registration-changed");
- },
- form(detail) {
- if (detail === "actorid") {
- return this.actorID;
- }
- let registration = this._registration;
- let installingWorker = this._installingWorker.form();
- let waitingWorker = this._waitingWorker.form();
- let activeWorker = this._activeWorker.form();
- let isE10s = Services.appinfo.browserTabsRemoteAutostart;
- return {
- actor: this.actorID,
- scope: registration.scope,
- url: registration.scriptSpec,
- installingWorker,
- waitingWorker,
- activeWorker,
- // - In e10s: only active registrations are available.
- // - In non-e10s: registrations always have at least one worker, if the worker is
- // active, the registration is active.
- active: isE10s ? true : !!activeWorker
- };
- },
- destroy() {
- protocol.Actor.prototype.destroy.call(this);
- Services.obs.removeObserver(this, PushService.subscriptionModifiedTopic, false);
- this._registration.removeListener(this);
- this._registration = null;
- if (this._pushSubscriptionActor) {
- this._pushSubscriptionActor.destroy();
- }
- this._pushSubscriptionActor = null;
- this._installingWorker.destroy();
- this._waitingWorker.destroy();
- this._activeWorker.destroy();
- this._installingWorker = null;
- this._waitingWorker = null;
- this._activeWorker = null;
- },
- disconnect() {
- this.destroy();
- },
- /**
- * Standard observer interface to listen to push messages and changes.
- */
- observe(subject, topic, data) {
- let 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;
- }
- events.emit(this, "push-subscription-modified");
- break;
- }
- },
- start() {
- if (!_serviceWorkerProcessScriptLoaded) {
- Services.ppmm.loadProcessScript(
- "resource://devtools/server/service-worker-child.js", true);
- _serviceWorkerProcessScriptLoaded = true;
- }
- Services.ppmm.broadcastAsyncMessage("serviceWorkerRegistration:start", {
- scope: this._registration.scope
- });
- return { type: "started" };
- },
- unregister() {
- let { principal, scope } = this._registration;
- let unregisterCallback = {
- unregisterSucceeded: function () {},
- unregisterFailed: function () {
- console.error("Failed to unregister the service worker for " + scope);
- },
- QueryInterface: XPCOMUtils.generateQI(
- [Ci.nsIServiceWorkerUnregisterCallback])
- };
- swm.propagateUnregister(principal, unregisterCallback, scope);
- return { type: "unregistered" };
- },
- getPushSubscription() {
- let 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);
- }
- );
- });
- },
- });
- function ServiceWorkerRegistrationActorList(conn) {
- this._conn = conn;
- this._actors = new Map();
- this._onListChanged = null;
- this._mustNotify = false;
- this.onRegister = this.onRegister.bind(this);
- this.onUnregister = this.onUnregister.bind(this);
- }
- ServiceWorkerRegistrationActorList.prototype = {
- getList() {
- // Create a set of registrations.
- let registrations = new Set();
- let array = swm.getAllRegistrations();
- for (let index = 0; index < array.length; ++index) {
- registrations.add(
- array.queryElementAt(index, Ci.nsIServiceWorkerRegistrationInfo));
- }
- // Delete each actor for which we don't have a registration.
- for (let [registration, ] of this._actors) {
- if (!registrations.has(registration)) {
- this._actors.delete(registration);
- }
- }
- // Create an actor for each registration for which we don't have one.
- for (let registration of registrations) {
- if (!this._actors.has(registration)) {
- this._actors.set(registration,
- new ServiceWorkerRegistrationActor(this._conn, registration));
- }
- }
- if (!this._mustNotify) {
- if (this._onListChanged !== null) {
- swm.addListener(this);
- }
- this._mustNotify = true;
- }
- let actors = [];
- for (let [, actor] of this._actors) {
- actors.push(actor);
- }
- return Promise.resolve(actors);
- },
- get onListchanged() {
- return this._onListchanged;
- },
- set onListChanged(onListChanged) {
- if (typeof onListChanged !== "function" && onListChanged !== null) {
- throw new Error("onListChanged must be either a function or null.");
- }
- if (this._mustNotify) {
- if (this._onListChanged === null && onListChanged !== null) {
- swm.addListener(this);
- }
- if (this._onListChanged !== null && onListChanged === null) {
- swm.removeListener(this);
- }
- }
- this._onListChanged = onListChanged;
- },
- _notifyListChanged() {
- this._onListChanged();
- if (this._onListChanged !== null) {
- swm.removeListener(this);
- }
- this._mustNotify = false;
- },
- onRegister(registration) {
- this._notifyListChanged();
- },
- onUnregister(registration) {
- this._notifyListChanged();
- }
- };
- exports.ServiceWorkerRegistrationActorList = ServiceWorkerRegistrationActorList;
|