tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

service-worker-registration.js (8033B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const { Actor } = require("resource://devtools/shared/protocol.js");
      8 const {
      9  serviceWorkerRegistrationSpec,
     10 } = require("resource://devtools/shared/specs/worker/service-worker-registration.js");
     11 
     12 const { XPCOMUtils } = ChromeUtils.importESModule(
     13  "resource://gre/modules/XPCOMUtils.sys.mjs",
     14  { global: "contextual" }
     15 );
     16 const {
     17  PushSubscriptionActor,
     18 } = require("resource://devtools/server/actors/worker/push-subscription.js");
     19 const {
     20  ServiceWorkerActor,
     21 } = require("resource://devtools/server/actors/worker/service-worker.js");
     22 
     23 XPCOMUtils.defineLazyServiceGetter(
     24  this,
     25  "swm",
     26  "@mozilla.org/serviceworkers/manager;1",
     27  Ci.nsIServiceWorkerManager
     28 );
     29 
     30 XPCOMUtils.defineLazyServiceGetter(
     31  this,
     32  "PushService",
     33  "@mozilla.org/push/Service;1",
     34  Ci.nsIPushService
     35 );
     36 
     37 class ServiceWorkerRegistrationActor extends Actor {
     38  /**
     39   * Create the ServiceWorkerRegistrationActor
     40   *
     41   * @param DevToolsServerConnection conn
     42   *   The server connection.
     43   * @param ServiceWorkerRegistrationInfo registration
     44   *   The registration's information.
     45   */
     46  constructor(conn, registration) {
     47    super(conn, serviceWorkerRegistrationSpec);
     48    this._registration = registration;
     49    this._pushSubscriptionActor = null;
     50 
     51    // A flag to know if preventShutdown has been called and we should
     52    // try to allow the shutdown of the SW when the actor is destroyed
     53    this._preventedShutdown = false;
     54 
     55    this._registration.addListener(this);
     56 
     57    this._createServiceWorkerActors();
     58 
     59    Services.obs.addObserver(this, PushService.subscriptionModifiedTopic);
     60  }
     61 
     62  onChange() {
     63    this._destroyServiceWorkerActors();
     64    this._createServiceWorkerActors();
     65    this.emit("registration-changed");
     66  }
     67 
     68  form() {
     69    const registration = this._registration;
     70    const evaluatingWorker = this._evaluatingWorker.form();
     71    const installingWorker = this._installingWorker.form();
     72    const waitingWorker = this._waitingWorker.form();
     73    const activeWorker = this._activeWorker.form();
     74 
     75    const newestWorker =
     76      activeWorker || waitingWorker || installingWorker || evaluatingWorker;
     77 
     78    return {
     79      actor: this.actorID,
     80      scope: registration.scope,
     81      url: registration.scriptSpec,
     82      evaluatingWorker,
     83      installingWorker,
     84      waitingWorker,
     85      activeWorker,
     86      fetch: newestWorker?.fetch,
     87      // Check if we have an active worker
     88      active: !!activeWorker,
     89      lastUpdateTime: registration.lastUpdateTime,
     90      traits: {},
     91    };
     92  }
     93 
     94  destroy() {
     95    super.destroy();
     96 
     97    // Ensure resuming the service worker in case the connection drops
     98    if (this._registration.activeWorker && this._preventedShutdown) {
     99      this.allowShutdown();
    100    }
    101 
    102    Services.obs.removeObserver(this, PushService.subscriptionModifiedTopic);
    103    this._registration.removeListener(this);
    104    this._registration = null;
    105    if (this._pushSubscriptionActor) {
    106      this._pushSubscriptionActor.destroy();
    107    }
    108    this._pushSubscriptionActor = null;
    109 
    110    this._destroyServiceWorkerActors();
    111 
    112    this._evaluatingWorker = null;
    113    this._installingWorker = null;
    114    this._waitingWorker = null;
    115    this._activeWorker = null;
    116  }
    117 
    118  /**
    119   * Standard observer interface to listen to push messages and changes.
    120   */
    121  observe(subject, topic, data) {
    122    const scope = this._registration.scope;
    123    if (data !== scope) {
    124      // This event doesn't concern us, pretend nothing happened.
    125      return;
    126    }
    127    switch (topic) {
    128      case PushService.subscriptionModifiedTopic:
    129        if (this._pushSubscriptionActor) {
    130          this._pushSubscriptionActor.destroy();
    131          this._pushSubscriptionActor = null;
    132        }
    133        this.emit("push-subscription-modified");
    134        break;
    135    }
    136  }
    137 
    138  start() {
    139    const { activeWorker } = this._registration;
    140 
    141    // TODO: don't return "started" if there's no active worker.
    142    if (activeWorker) {
    143      // This starts up the Service Worker if it's not already running.
    144      // Note that the Service Workers exist in content processes but are
    145      // managed from the parent process. This is why we call `attachDebugger`
    146      // here (in the parent process) instead of in a process script.
    147      activeWorker.attachDebugger();
    148      activeWorker.detachDebugger();
    149    }
    150 
    151    return { type: "started" };
    152  }
    153 
    154  unregister() {
    155    const { principal, scope } = this._registration;
    156    const unregisterCallback = {
    157      unregisterSucceeded() {},
    158      unregisterFailed() {
    159        console.error("Failed to unregister the service worker for " + scope);
    160      },
    161      QueryInterface: ChromeUtils.generateQI([
    162        "nsIServiceWorkerUnregisterCallback",
    163      ]),
    164    };
    165    swm.propagateUnregister(principal, unregisterCallback, scope);
    166 
    167    return { type: "unregistered" };
    168  }
    169 
    170  push() {
    171    const { principal, scope } = this._registration;
    172    const originAttributes = ChromeUtils.originAttributesToSuffix(
    173      principal.originAttributes
    174    );
    175    swm.sendPushEvent(originAttributes, scope);
    176  }
    177 
    178  /**
    179   * Prevent the current active worker to shutdown after the idle timeout.
    180   */
    181  preventShutdown() {
    182    if (!this._registration.activeWorker) {
    183      throw new Error(
    184        "ServiceWorkerRegistrationActor.preventShutdown could not find " +
    185          "activeWorker in parent-intercept mode"
    186      );
    187    }
    188 
    189    // attachDebugger has to be called from the parent process in parent-intercept mode.
    190    this._registration.activeWorker.attachDebugger();
    191    this._preventedShutdown = true;
    192  }
    193 
    194  /**
    195   * Allow the current active worker to shut down again.
    196   */
    197  allowShutdown() {
    198    if (!this._registration.activeWorker) {
    199      throw new Error(
    200        "ServiceWorkerRegistrationActor.allowShutdown could not find " +
    201          "activeWorker in parent-intercept mode"
    202      );
    203    }
    204 
    205    this._registration.activeWorker.detachDebugger();
    206    this._preventedShutdown = false;
    207  }
    208 
    209  getPushSubscription() {
    210    const registration = this._registration;
    211    let pushSubscriptionActor = this._pushSubscriptionActor;
    212    if (pushSubscriptionActor) {
    213      return Promise.resolve(pushSubscriptionActor);
    214    }
    215    return new Promise(resolve => {
    216      PushService.getSubscription(
    217        registration.scope,
    218        registration.principal,
    219        (result, subscription) => {
    220          if (!subscription) {
    221            resolve(null);
    222            return;
    223          }
    224          pushSubscriptionActor = new PushSubscriptionActor(
    225            this.conn,
    226            subscription
    227          );
    228          this._pushSubscriptionActor = pushSubscriptionActor;
    229          resolve(pushSubscriptionActor);
    230        }
    231      );
    232    });
    233  }
    234 
    235  _destroyServiceWorkerActors() {
    236    this._evaluatingWorker.destroy();
    237    this._installingWorker.destroy();
    238    this._waitingWorker.destroy();
    239    this._activeWorker.destroy();
    240  }
    241 
    242  _createServiceWorkerActors() {
    243    const { evaluatingWorker, installingWorker, waitingWorker, activeWorker } =
    244      this._registration;
    245    const origin = this._registration.principal.origin;
    246 
    247    this._evaluatingWorker = new ServiceWorkerActor(
    248      this.conn,
    249      evaluatingWorker,
    250      origin
    251    );
    252    this._installingWorker = new ServiceWorkerActor(
    253      this.conn,
    254      installingWorker,
    255      origin
    256    );
    257    this._waitingWorker = new ServiceWorkerActor(
    258      this.conn,
    259      waitingWorker,
    260      origin
    261    );
    262    this._activeWorker = new ServiceWorkerActor(
    263      this.conn,
    264      activeWorker,
    265      origin
    266    );
    267 
    268    // Add the ServiceWorker actors as children of this ServiceWorkerRegistration actor,
    269    // assigning them valid actorIDs.
    270    this.manage(this._evaluatingWorker);
    271    this.manage(this._installingWorker);
    272    this.manage(this._waitingWorker);
    273    this.manage(this._activeWorker);
    274  }
    275 }
    276 
    277 exports.ServiceWorkerRegistrationActor = ServiceWorkerRegistrationActor;