1/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
2/* vim: set sts=2 sw=2 et tw=80: */
3/* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6"use strict";
7
8/**
9 * This module contains utilities and base classes for logic which is
10 * common between the parent and child process, and in particular
11 * between ExtensionParent.jsm and ExtensionChild.jsm.
12 */
13
14/* exported ExtensionCommon */
15
16var EXPORTED_SYMBOLS = ["ExtensionCommon"];
17
18const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
19const { XPCOMUtils } = ChromeUtils.import(
20  "resource://gre/modules/XPCOMUtils.jsm"
21);
22
23XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
24
25XPCOMUtils.defineLazyModuleGetters(this, {
26  AppConstants: "resource://gre/modules/AppConstants.jsm",
27  ConsoleAPI: "resource://gre/modules/Console.jsm",
28  MessageChannel: "resource://gre/modules/MessageChannel.jsm",
29  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
30  Schemas: "resource://gre/modules/Schemas.jsm",
31  SchemaRoot: "resource://gre/modules/Schemas.jsm",
32});
33
34XPCOMUtils.defineLazyServiceGetter(
35  this,
36  "styleSheetService",
37  "@mozilla.org/content/style-sheet-service;1",
38  "nsIStyleSheetService"
39);
40
41const { ExtensionUtils } = ChromeUtils.import(
42  "resource://gre/modules/ExtensionUtils.jsm"
43);
44
45var {
46  DefaultMap,
47  DefaultWeakMap,
48  ExtensionError,
49  filterStack,
50  getInnerWindowID,
51  getUniqueId,
52  getWinUtils,
53} = ExtensionUtils;
54
55function getConsole() {
56  return new ConsoleAPI({
57    maxLogLevelPref: "extensions.webextensions.log.level",
58    prefix: "WebExtensions",
59  });
60}
61
62XPCOMUtils.defineLazyGetter(this, "console", getConsole);
63
64XPCOMUtils.defineLazyPreferenceGetter(
65  this,
66  "DELAYED_BG_STARTUP",
67  "extensions.webextensions.background-delayed-startup"
68);
69
70var ExtensionCommon;
71
72// Run a function and report exceptions.
73function runSafeSyncWithoutClone(f, ...args) {
74  try {
75    return f(...args);
76  } catch (e) {
77    dump(
78      `Extension error: ${e} ${e.fileName} ${
79        e.lineNumber
80      }\n[[Exception stack\n${filterStack(e)}Current stack\n${filterStack(
81        Error()
82      )}]]\n`
83    );
84    Cu.reportError(e);
85  }
86}
87
88// Return true if the given value is an instance of the given
89// native type.
90function instanceOf(value, type) {
91  return (
92    value &&
93    typeof value === "object" &&
94    ChromeUtils.getClassName(value) === type
95  );
96}
97
98/**
99 * Convert any of several different representations of a date/time to a Date object.
100 * Accepts several formats:
101 * a Date object, an ISO8601 string, or a number of milliseconds since the epoch as
102 * either a number or a string.
103 *
104 * @param {Date|string|number} date
105 *      The date to convert.
106 * @returns {Date}
107 *      A Date object
108 */
109function normalizeTime(date) {
110  // Of all the formats we accept the "number of milliseconds since the epoch as a string"
111  // is an outlier, everything else can just be passed directly to the Date constructor.
112  return new Date(
113    typeof date == "string" && /^\d+$/.test(date) ? parseInt(date, 10) : date
114  );
115}
116
117function withHandlingUserInput(window, callable) {
118  let handle = getWinUtils(window).setHandlingUserInput(true);
119  try {
120    return callable();
121  } finally {
122    handle.destruct();
123  }
124}
125
126/**
127 * Defines a lazy getter for the given property on the given object. The
128 * first time the property is accessed, the return value of the getter
129 * is defined on the current `this` object with the given property name.
130 * Importantly, this means that a lazy getter defined on an object
131 * prototype will be invoked separately for each object instance that
132 * it's accessed on.
133 *
134 * @param {object} object
135 *        The prototype object on which to define the getter.
136 * @param {string|Symbol} prop
137 *        The property name for which to define the getter.
138 * @param {function} getter
139 *        The function to call in order to generate the final property
140 *        value.
141 */
142function defineLazyGetter(object, prop, getter) {
143  let redefine = (obj, value) => {
144    Object.defineProperty(obj, prop, {
145      enumerable: true,
146      configurable: true,
147      writable: true,
148      value,
149    });
150    return value;
151  };
152
153  Object.defineProperty(object, prop, {
154    enumerable: true,
155    configurable: true,
156
157    get() {
158      return redefine(this, getter.call(this));
159    },
160
161    set(value) {
162      redefine(this, value);
163    },
164  });
165}
166
167function checkLoadURL(url, principal, options) {
168  let ssm = Services.scriptSecurityManager;
169
170  let flags = ssm.STANDARD;
171  if (!options.allowScript) {
172    flags |= ssm.DISALLOW_SCRIPT;
173  }
174  if (!options.allowInheritsPrincipal) {
175    flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
176  }
177  if (options.dontReportErrors) {
178    flags |= ssm.DONT_REPORT_ERRORS;
179  }
180
181  try {
182    ssm.checkLoadURIWithPrincipal(principal, Services.io.newURI(url), flags);
183  } catch (e) {
184    return false;
185  }
186  return true;
187}
188
189function makeWidgetId(id) {
190  id = id.toLowerCase();
191  // FIXME: This allows for collisions.
192  return id.replace(/[^a-z0-9_-]/g, "_");
193}
194
195/**
196 * A sentinel class to indicate that an array of values should be
197 * treated as an array when used as a promise resolution value, but as a
198 * spread expression (...args) when passed to a callback.
199 */
200class SpreadArgs extends Array {
201  constructor(args) {
202    super();
203    this.push(...args);
204  }
205}
206
207/**
208 * Like SpreadArgs, but also indicates that the array values already
209 * belong to the target compartment, and should not be cloned before
210 * being passed.
211 *
212 * The `unwrappedValues` property contains an Array object which belongs
213 * to the target compartment, and contains the same unwrapped values
214 * passed the NoCloneSpreadArgs constructor.
215 */
216class NoCloneSpreadArgs {
217  constructor(args) {
218    this.unwrappedValues = args;
219  }
220
221  [Symbol.iterator]() {
222    return this.unwrappedValues[Symbol.iterator]();
223  }
224}
225
226const LISTENERS = Symbol("listeners");
227const ONCE_MAP = Symbol("onceMap");
228
229class EventEmitter {
230  constructor() {
231    this[LISTENERS] = new Map();
232    this[ONCE_MAP] = new WeakMap();
233  }
234
235  /**
236   * Checks whether there is some listener for the given event.
237   *
238   * @param {string} event
239   *       The name of the event to listen for.
240   * @returns {boolean}
241   */
242  has(event) {
243    return this[LISTENERS].has(event);
244  }
245
246  /**
247   * Adds the given function as a listener for the given event.
248   *
249   * The listener function may optionally return a Promise which
250   * resolves when it has completed all operations which event
251   * dispatchers may need to block on.
252   *
253   * @param {string} event
254   *       The name of the event to listen for.
255   * @param {function(string, ...any)} listener
256   *        The listener to call when events are emitted.
257   */
258  on(event, listener) {
259    let listeners = this[LISTENERS].get(event);
260    if (!listeners) {
261      listeners = new Set();
262      this[LISTENERS].set(event, listeners);
263    }
264
265    listeners.add(listener);
266  }
267
268  /**
269   * Removes the given function as a listener for the given event.
270   *
271   * @param {string} event
272   *       The name of the event to stop listening for.
273   * @param {function(string, ...any)} listener
274   *        The listener function to remove.
275   */
276  off(event, listener) {
277    let set = this[LISTENERS].get(event);
278    if (set) {
279      set.delete(listener);
280      set.delete(this[ONCE_MAP].get(listener));
281      if (!set.size) {
282        this[LISTENERS].delete(event);
283      }
284    }
285  }
286
287  /**
288   * Adds the given function as a listener for the given event once.
289   *
290   * @param {string} event
291   *       The name of the event to listen for.
292   * @param {function(string, ...any)} listener
293   *        The listener to call when events are emitted.
294   */
295  once(event, listener) {
296    let wrapper = (...args) => {
297      this.off(event, wrapper);
298      this[ONCE_MAP].delete(listener);
299
300      return listener(...args);
301    };
302    this[ONCE_MAP].set(listener, wrapper);
303
304    this.on(event, wrapper);
305  }
306
307  /**
308   * Triggers all listeners for the given event. If any listeners return
309   * a value, returns a promise which resolves when all returned
310   * promises have resolved. Otherwise, returns undefined.
311   *
312   * @param {string} event
313   *       The name of the event to emit.
314   * @param {any} args
315   *        Arbitrary arguments to pass to the listener functions, after
316   *        the event name.
317   * @returns {Promise?}
318   */
319  emit(event, ...args) {
320    let listeners = this[LISTENERS].get(event);
321
322    if (listeners) {
323      let promises = [];
324
325      for (let listener of listeners) {
326        try {
327          let result = listener(event, ...args);
328          if (result !== undefined) {
329            promises.push(result);
330          }
331        } catch (e) {
332          Cu.reportError(e);
333        }
334      }
335
336      if (promises.length) {
337        return Promise.all(promises);
338      }
339    }
340  }
341}
342
343/**
344 * Base class for WebExtension APIs.  Each API creates a new class
345 * that inherits from this class, the derived class is instantiated
346 * once for each extension that uses the API.
347 */
348class ExtensionAPI extends EventEmitter {
349  constructor(extension) {
350    super();
351
352    this.extension = extension;
353
354    extension.once("shutdown", (what, isAppShutdown) => {
355      if (this.onShutdown) {
356        this.onShutdown(isAppShutdown);
357      }
358      this.extension = null;
359    });
360  }
361
362  destroy() {}
363
364  onManifestEntry(entry) {}
365
366  getAPI(context) {
367    throw new Error("Not Implemented");
368  }
369}
370
371/**
372 * A wrapper around a window that returns the window iff the inner window
373 * matches the inner window at the construction of this wrapper.
374 *
375 * This wrapper should not be used after the inner window is destroyed.
376 **/
377class InnerWindowReference {
378  constructor(contentWindow, innerWindowID) {
379    this.contentWindow = contentWindow;
380    this.innerWindowID = innerWindowID;
381    this.needWindowIDCheck = false;
382
383    contentWindow.addEventListener(
384      "pagehide",
385      this,
386      { mozSystemGroup: true },
387      false
388    );
389    contentWindow.addEventListener(
390      "pageshow",
391      this,
392      { mozSystemGroup: true },
393      false
394    );
395  }
396
397  get() {
398    // If the pagehide event has fired, the inner window ID needs to be checked,
399    // in case the window ref is dereferenced in a pageshow listener (before our
400    // pageshow listener was dispatched) or during the unload event.
401    if (
402      !this.needWindowIDCheck ||
403      getInnerWindowID(this.contentWindow) === this.innerWindowID
404    ) {
405      return this.contentWindow;
406    }
407    return null;
408  }
409
410  invalidate() {
411    // If invalidate() is called while the inner window is in the bfcache, then
412    // we are unable to remove the event listener, and handleEvent will be
413    // called once more if the page is revived from the bfcache.
414    if (this.contentWindow && !Cu.isDeadWrapper(this.contentWindow)) {
415      this.contentWindow.removeEventListener("pagehide", this, {
416        mozSystemGroup: true,
417      });
418      this.contentWindow.removeEventListener("pageshow", this, {
419        mozSystemGroup: true,
420      });
421    }
422    this.contentWindow = null;
423    this.needWindowIDCheck = false;
424  }
425
426  handleEvent(event) {
427    if (this.contentWindow) {
428      this.needWindowIDCheck = event.type === "pagehide";
429    } else {
430      // Remove listener when restoring from the bfcache - see invalidate().
431      event.currentTarget.removeEventListener("pagehide", this, {
432        mozSystemGroup: true,
433      });
434      event.currentTarget.removeEventListener("pageshow", this, {
435        mozSystemGroup: true,
436      });
437    }
438  }
439}
440
441/**
442 * This class contains the information we have about an individual
443 * extension.  It is never instantiated directly, instead subclasses
444 * for each type of process extend this class and add members that are
445 * relevant for that process.
446 * @abstract
447 */
448class BaseContext {
449  constructor(envType, extension) {
450    this.envType = envType;
451    this.onClose = new Set();
452    this.checkedLastError = false;
453    this._lastError = null;
454    this.contextId = getUniqueId();
455    this.unloaded = false;
456    this.extension = extension;
457    this.jsonSandbox = null;
458    this.active = true;
459    this.incognito = null;
460    this.messageManager = null;
461    this.contentWindow = null;
462    this.innerWindowID = 0;
463
464    // These two properties are assigned in ContentScriptContextChild subclass
465    // to keep a copy of the content script sandbox Error and Promise globals
466    // (which are used by the WebExtensions internals) before any extension
467    // content script code had any chance to redefine them.
468    this.cloneScopeError = null;
469    this.cloneScopePromise = null;
470  }
471
472  get Error() {
473    // Return the copy stored in the context instance (when the context is an instance of
474    // ContentScriptContextChild or the global from extension page window otherwise).
475    return this.cloneScopeError || this.cloneScope.Error;
476  }
477
478  get Promise() {
479    // Return the copy stored in the context instance (when the context is an instance of
480    // ContentScriptContextChild or the global from extension page window otherwise).
481    return this.cloneScopePromise || this.cloneScope.Promise;
482  }
483
484  get privateBrowsingAllowed() {
485    return this.extension.privateBrowsingAllowed;
486  }
487
488  canAccessWindow(window) {
489    return this.extension.canAccessWindow(window);
490  }
491
492  /**
493   * Opens a conduit linked to this context, populating related address fields.
494   * Only available in child contexts with an associated contentWindow.
495   * @param {object} subject
496   * @param {ConduitAddress} address
497   * @returns {PointConduit}
498   */
499  openConduit(subject, address) {
500    let wgc = this.contentWindow.windowGlobalChild;
501    let conduit = wgc.getActor("Conduits").openConduit(subject, {
502      id: subject.id || getUniqueId(),
503      extensionId: this.extension.id,
504      envType: this.envType,
505      ...address,
506    });
507    this.callOnClose(conduit);
508    conduit.setCloseCallback(() => {
509      this.forgetOnClose(conduit);
510    });
511    return conduit;
512  }
513
514  setContentWindow(contentWindow) {
515    if (!this.canAccessWindow(contentWindow)) {
516      throw new Error(
517        "BaseContext attempted to load when extension is not allowed due to incognito settings."
518      );
519    }
520
521    this.innerWindowID = getInnerWindowID(contentWindow);
522    this.messageManager = contentWindow.docShell.messageManager;
523
524    if (this.incognito == null) {
525      this.incognito = PrivateBrowsingUtils.isContentWindowPrivate(
526        contentWindow
527      );
528    }
529
530    MessageChannel.setupMessageManagers([this.messageManager]);
531
532    let windowRef = new InnerWindowReference(contentWindow, this.innerWindowID);
533    Object.defineProperty(this, "active", {
534      configurable: true,
535      enumerable: true,
536      get: () => windowRef.get() !== null,
537    });
538    Object.defineProperty(this, "contentWindow", {
539      configurable: true,
540      enumerable: true,
541      get: () => windowRef.get(),
542    });
543    this.callOnClose({
544      close: () => {
545        // Allow other "close" handlers to use these properties, until the next tick.
546        Promise.resolve().then(() => {
547          windowRef.invalidate();
548          windowRef = null;
549          Object.defineProperty(this, "contentWindow", { value: null });
550          Object.defineProperty(this, "active", { value: false });
551        });
552      },
553    });
554  }
555
556  // All child contexts must implement logActivity.  This is handled if the child
557  // context subclasses ExtensionBaseContextChild.  ProxyContextParent overrides
558  // this with a noop for parent contexts.
559  logActivity(type, name, data) {
560    throw new Error(`Not implemented for ${this.envType}`);
561  }
562
563  get cloneScope() {
564    throw new Error("Not implemented");
565  }
566
567  get principal() {
568    throw new Error("Not implemented");
569  }
570
571  runSafe(callback, ...args) {
572    return this.applySafe(callback, args);
573  }
574
575  runSafeWithoutClone(callback, ...args) {
576    return this.applySafeWithoutClone(callback, args);
577  }
578
579  applySafe(callback, args, caller) {
580    if (this.unloaded) {
581      Cu.reportError("context.runSafe called after context unloaded", caller);
582    } else if (!this.active) {
583      Cu.reportError(
584        "context.runSafe called while context is inactive",
585        caller
586      );
587    } else {
588      try {
589        let { cloneScope } = this;
590        args = args.map(arg => Cu.cloneInto(arg, cloneScope));
591      } catch (e) {
592        Cu.reportError(e);
593        dump(
594          `runSafe failure: cloning into ${
595            this.cloneScope
596          }: ${e}\n\n${filterStack(Error())}`
597        );
598      }
599
600      return this.applySafeWithoutClone(callback, args, caller);
601    }
602  }
603
604  applySafeWithoutClone(callback, args, caller) {
605    if (this.unloaded) {
606      Cu.reportError(
607        "context.runSafeWithoutClone called after context unloaded",
608        caller
609      );
610    } else if (!this.active) {
611      Cu.reportError(
612        "context.runSafeWithoutClone called while context is inactive",
613        caller
614      );
615    } else {
616      try {
617        return Reflect.apply(callback, null, args);
618      } catch (e) {
619        dump(
620          `Extension error: ${e} ${e.fileName} ${
621            e.lineNumber
622          }\n[[Exception stack\n${filterStack(e)}Current stack\n${filterStack(
623            Error()
624          )}]]\n`
625        );
626        Cu.reportError(e);
627      }
628    }
629  }
630
631  checkLoadURL(url, options = {}) {
632    // As an optimization, f the URL starts with the extension's base URL,
633    // don't do any further checks. It's always allowed to load it.
634    if (url.startsWith(this.extension.baseURL)) {
635      return true;
636    }
637
638    return checkLoadURL(url, this.principal, options);
639  }
640
641  /**
642   * Safely call JSON.stringify() on an object that comes from an
643   * extension.
644   *
645   * @param {array<any>} args Arguments for JSON.stringify()
646   * @returns {string} The stringified representation of obj
647   */
648  jsonStringify(...args) {
649    if (!this.jsonSandbox) {
650      this.jsonSandbox = Cu.Sandbox(this.principal, {
651        sameZoneAs: this.cloneScope,
652        wantXrays: false,
653      });
654    }
655
656    return Cu.waiveXrays(this.jsonSandbox.JSON).stringify(...args);
657  }
658
659  callOnClose(obj) {
660    this.onClose.add(obj);
661  }
662
663  forgetOnClose(obj) {
664    this.onClose.delete(obj);
665  }
666
667  /**
668   * A wrapper around MessageChannel.sendMessage which adds the extension ID
669   * to the recipient object, and ensures replies are not processed after the
670   * context has been unloaded.
671   *
672   * @param {nsIMessageManager} target
673   * @param {string} messageName
674   * @param {object} data
675   * @param {object} [options]
676   * @param {object} [options.sender]
677   * @param {object} [options.recipient]
678   *
679   * @returns {Promise}
680   */
681  sendMessage(target, messageName, data, options = {}) {
682    options.recipient = Object.assign(
683      { extensionId: this.extension.id },
684      options.recipient
685    );
686    options.sender = options.sender || {};
687
688    options.sender.extensionId = this.extension.id;
689    options.sender.contextId = this.contextId;
690
691    return MessageChannel.sendMessage(target, messageName, data, options);
692  }
693
694  get lastError() {
695    this.checkedLastError = true;
696    return this._lastError;
697  }
698
699  set lastError(val) {
700    this.checkedLastError = false;
701    this._lastError = val;
702  }
703
704  /**
705   * Normalizes the given error object for use by the target scope. If
706   * the target is an error object which belongs to that scope, it is
707   * returned as-is. If it is an ordinary object with a `message`
708   * property, it is converted into an error belonging to the target
709   * scope. If it is an Error object which does *not* belong to the
710   * clone scope, it is reported, and converted to an unexpected
711   * exception error.
712   *
713   * @param {Error|object} error
714   * @param {SavedFrame?} [caller]
715   * @returns {Error}
716   */
717  normalizeError(error, caller) {
718    if (error instanceof this.Error) {
719      return error;
720    }
721    let message, fileName;
722    if (error && typeof error === "object") {
723      const isPlain = ChromeUtils.getClassName(error) === "Object";
724      if (isPlain && error.mozWebExtLocation) {
725        caller = error.mozWebExtLocation;
726      }
727      if (isPlain && caller && (error.mozWebExtLocation || !error.fileName)) {
728        caller = Cu.cloneInto(caller, this.cloneScope);
729        return ChromeUtils.createError(error.message, caller);
730      }
731
732      if (
733        isPlain ||
734        error instanceof ExtensionError ||
735        this.principal.subsumes(Cu.getObjectPrincipal(error))
736      ) {
737        message = error.message;
738        fileName = error.fileName;
739      }
740    }
741
742    if (!message) {
743      Cu.reportError(error);
744      message = "An unexpected error occurred";
745    }
746    return new this.Error(message, fileName);
747  }
748
749  /**
750   * Sets the value of `.lastError` to `error`, calls the given
751   * callback, and reports an error if the value has not been checked
752   * when the callback returns.
753   *
754   * @param {object} error An object with a `message` property. May
755   *     optionally be an `Error` object belonging to the target scope.
756   * @param {SavedFrame?} caller
757   *        The optional caller frame which triggered this callback, to be used
758   *        in error reporting.
759   * @param {function} callback The callback to call.
760   * @returns {*} The return value of callback.
761   */
762  withLastError(error, caller, callback) {
763    this.lastError = this.normalizeError(error);
764    try {
765      return callback();
766    } finally {
767      if (!this.checkedLastError) {
768        Cu.reportError(`Unchecked lastError value: ${this.lastError}`, caller);
769      }
770      this.lastError = null;
771    }
772  }
773
774  /**
775   * Captures the most recent stack frame which belongs to the extension.
776   *
777   * @returns {SavedFrame?}
778   */
779  getCaller() {
780    return ChromeUtils.getCallerLocation(this.principal);
781  }
782
783  /**
784   * Wraps the given promise so it can be safely returned to extension
785   * code in this context.
786   *
787   * If `callback` is provided, however, it is used as a completion
788   * function for the promise, and no promise is returned. In this case,
789   * the callback is called when the promise resolves or rejects. In the
790   * latter case, `lastError` is set to the rejection value, and the
791   * callback function must check `browser.runtime.lastError` or
792   * `extension.runtime.lastError` in order to prevent it being reported
793   * to the console.
794   *
795   * @param {Promise} promise The promise with which to wrap the
796   *     callback. May resolve to a `SpreadArgs` instance, in which case
797   *     each element will be used as a separate argument.
798   *
799   *     Unless the promise object belongs to the cloneScope global, its
800   *     resolution value is cloned into cloneScope prior to calling the
801   *     `callback` function or resolving the wrapped promise.
802   *
803   * @param {function} [callback] The callback function to wrap
804   *
805   * @returns {Promise|undefined} If callback is null, a promise object
806   *     belonging to the target scope. Otherwise, undefined.
807   */
808  wrapPromise(promise, callback = null) {
809    let caller = this.getCaller();
810    let applySafe = this.applySafe.bind(this);
811    if (Cu.getGlobalForObject(promise) === this.cloneScope) {
812      applySafe = this.applySafeWithoutClone.bind(this);
813    }
814
815    if (callback) {
816      promise.then(
817        args => {
818          if (this.unloaded) {
819            Cu.reportError(`Promise resolved after context unloaded\n`, caller);
820          } else if (!this.active) {
821            Cu.reportError(
822              `Promise resolved while context is inactive\n`,
823              caller
824            );
825          } else if (args instanceof NoCloneSpreadArgs) {
826            this.applySafeWithoutClone(callback, args.unwrappedValues, caller);
827          } else if (args instanceof SpreadArgs) {
828            applySafe(callback, args, caller);
829          } else {
830            applySafe(callback, [args], caller);
831          }
832        },
833        error => {
834          this.withLastError(error, caller, () => {
835            if (this.unloaded) {
836              Cu.reportError(
837                `Promise rejected after context unloaded\n`,
838                caller
839              );
840            } else if (!this.active) {
841              Cu.reportError(
842                `Promise rejected while context is inactive\n`,
843                caller
844              );
845            } else {
846              this.applySafeWithoutClone(callback, [], caller);
847            }
848          });
849        }
850      );
851    } else {
852      return new this.Promise((resolve, reject) => {
853        promise.then(
854          value => {
855            if (this.unloaded) {
856              Cu.reportError(
857                `Promise resolved after context unloaded\n`,
858                caller
859              );
860            } else if (!this.active) {
861              Cu.reportError(
862                `Promise resolved while context is inactive\n`,
863                caller
864              );
865            } else if (value instanceof NoCloneSpreadArgs) {
866              let values = value.unwrappedValues;
867              this.applySafeWithoutClone(
868                resolve,
869                values.length == 1 ? [values[0]] : [values],
870                caller
871              );
872            } else if (value instanceof SpreadArgs) {
873              applySafe(resolve, value.length == 1 ? value : [value], caller);
874            } else {
875              applySafe(resolve, [value], caller);
876            }
877          },
878          value => {
879            if (this.unloaded) {
880              Cu.reportError(
881                `Promise rejected after context unloaded: ${value &&
882                  value.message}\n`,
883                caller
884              );
885            } else if (!this.active) {
886              Cu.reportError(
887                `Promise rejected while context is inactive: ${value &&
888                  value.message}\n`,
889                caller
890              );
891            } else {
892              this.applySafeWithoutClone(
893                reject,
894                [this.normalizeError(value, caller)],
895                caller
896              );
897            }
898          }
899        );
900      });
901    }
902  }
903
904  unload() {
905    this.unloaded = true;
906
907    MessageChannel.abortResponses({
908      extensionId: this.extension.id,
909      contextId: this.contextId,
910    });
911
912    for (let obj of this.onClose) {
913      obj.close();
914    }
915    this.onClose.clear();
916  }
917
918  /**
919   * A simple proxy for unload(), for use with callOnClose().
920   */
921  close() {
922    this.unload();
923  }
924}
925
926/**
927 * An object that runs the implementation of a schema API. Instantiations of
928 * this interfaces are used by Schemas.jsm.
929 *
930 * @interface
931 */
932class SchemaAPIInterface {
933  /**
934   * Calls this as a function that returns its return value.
935   *
936   * @abstract
937   * @param {Array} args The parameters for the function.
938   * @returns {*} The return value of the invoked function.
939   */
940  callFunction(args) {
941    throw new Error("Not implemented");
942  }
943
944  /**
945   * Calls this as a function and ignores its return value.
946   *
947   * @abstract
948   * @param {Array} args The parameters for the function.
949   */
950  callFunctionNoReturn(args) {
951    throw new Error("Not implemented");
952  }
953
954  /**
955   * Calls this as a function that completes asynchronously.
956   *
957   * @abstract
958   * @param {Array} args The parameters for the function.
959   * @param {function(*)} [callback] The callback to be called when the function
960   *     completes.
961   * @param {boolean} [requireUserInput=false] If true, the function should
962   *                  fail if the browser is not currently handling user input.
963   * @returns {Promise|undefined} Must be void if `callback` is set, and a
964   *     promise otherwise. The promise is resolved when the function completes.
965   */
966  callAsyncFunction(args, callback, requireUserInput = false) {
967    throw new Error("Not implemented");
968  }
969
970  /**
971   * Retrieves the value of this as a property.
972   *
973   * @abstract
974   * @returns {*} The value of the property.
975   */
976  getProperty() {
977    throw new Error("Not implemented");
978  }
979
980  /**
981   * Assigns the value to this as property.
982   *
983   * @abstract
984   * @param {string} value The new value of the property.
985   */
986  setProperty(value) {
987    throw new Error("Not implemented");
988  }
989
990  /**
991   * Registers a `listener` to this as an event.
992   *
993   * @abstract
994   * @param {function} listener The callback to be called when the event fires.
995   * @param {Array} args Extra parameters for EventManager.addListener.
996   * @see EventManager.addListener
997   */
998  addListener(listener, args) {
999    throw new Error("Not implemented");
1000  }
1001
1002  /**
1003   * Checks whether `listener` is listening to this as an event.
1004   *
1005   * @abstract
1006   * @param {function} listener The event listener.
1007   * @returns {boolean} Whether `listener` is registered with this as an event.
1008   * @see EventManager.hasListener
1009   */
1010  hasListener(listener) {
1011    throw new Error("Not implemented");
1012  }
1013
1014  /**
1015   * Unregisters `listener` from this as an event.
1016   *
1017   * @abstract
1018   * @param {function} listener The event listener.
1019   * @see EventManager.removeListener
1020   */
1021  removeListener(listener) {
1022    throw new Error("Not implemented");
1023  }
1024
1025  /**
1026   * Revokes the implementation object, and prevents any further method
1027   * calls from having external effects.
1028   *
1029   * @abstract
1030   */
1031  revoke() {
1032    throw new Error("Not implemented");
1033  }
1034}
1035
1036/**
1037 * An object that runs a locally implemented API.
1038 */
1039class LocalAPIImplementation extends SchemaAPIInterface {
1040  /**
1041   * Constructs an implementation of the `name` method or property of `pathObj`.
1042   *
1043   * @param {object} pathObj The object containing the member with name `name`.
1044   * @param {string} name The name of the implemented member.
1045   * @param {BaseContext} context The context in which the schema is injected.
1046   */
1047  constructor(pathObj, name, context) {
1048    super();
1049    this.pathObj = pathObj;
1050    this.name = name;
1051    this.context = context;
1052  }
1053
1054  revoke() {
1055    if (this.pathObj[this.name][Schemas.REVOKE]) {
1056      this.pathObj[this.name][Schemas.REVOKE]();
1057    }
1058
1059    this.pathObj = null;
1060    this.name = null;
1061    this.context = null;
1062  }
1063
1064  callFunction(args) {
1065    try {
1066      return this.pathObj[this.name](...args);
1067    } catch (e) {
1068      throw this.context.normalizeError(e);
1069    }
1070  }
1071
1072  callFunctionNoReturn(args) {
1073    try {
1074      this.pathObj[this.name](...args);
1075    } catch (e) {
1076      throw this.context.normalizeError(e);
1077    }
1078  }
1079
1080  callAsyncFunction(args, callback, requireUserInput) {
1081    let promise;
1082    try {
1083      if (requireUserInput) {
1084        if (!getWinUtils(this.context.contentWindow).isHandlingUserInput) {
1085          throw new ExtensionError(
1086            `${this.name} may only be called from a user input handler`
1087          );
1088        }
1089      }
1090      promise = this.pathObj[this.name](...args) || Promise.resolve();
1091    } catch (e) {
1092      promise = Promise.reject(e);
1093    }
1094    return this.context.wrapPromise(promise, callback);
1095  }
1096
1097  getProperty() {
1098    return this.pathObj[this.name];
1099  }
1100
1101  setProperty(value) {
1102    this.pathObj[this.name] = value;
1103  }
1104
1105  addListener(listener, args) {
1106    try {
1107      this.pathObj[this.name].addListener.call(null, listener, ...args);
1108    } catch (e) {
1109      throw this.context.normalizeError(e);
1110    }
1111  }
1112
1113  hasListener(listener) {
1114    return this.pathObj[this.name].hasListener.call(null, listener);
1115  }
1116
1117  removeListener(listener) {
1118    this.pathObj[this.name].removeListener.call(null, listener);
1119  }
1120}
1121
1122// Recursively copy properties from source to dest.
1123function deepCopy(dest, source) {
1124  for (let prop in source) {
1125    let desc = Object.getOwnPropertyDescriptor(source, prop);
1126    if (typeof desc.value == "object") {
1127      if (!(prop in dest)) {
1128        dest[prop] = {};
1129      }
1130      deepCopy(dest[prop], source[prop]);
1131    } else {
1132      Object.defineProperty(dest, prop, desc);
1133    }
1134  }
1135}
1136
1137function getChild(map, key) {
1138  let child = map.children.get(key);
1139  if (!child) {
1140    child = {
1141      modules: new Set(),
1142      children: new Map(),
1143    };
1144
1145    map.children.set(key, child);
1146  }
1147  return child;
1148}
1149
1150function getPath(map, path) {
1151  for (let key of path) {
1152    map = getChild(map, key);
1153  }
1154  return map;
1155}
1156
1157function mergePaths(dest, source) {
1158  for (let name of source.modules) {
1159    dest.modules.add(name);
1160  }
1161
1162  for (let [name, child] of source.children.entries()) {
1163    mergePaths(getChild(dest, name), child);
1164  }
1165}
1166
1167/**
1168 * Manages loading and accessing a set of APIs for a specific extension
1169 * context.
1170 *
1171 * @param {BaseContext} context
1172 *        The context to manage APIs for.
1173 * @param {SchemaAPIManager} apiManager
1174 *        The API manager holding the APIs to manage.
1175 * @param {object} root
1176 *        The root object into which APIs will be injected.
1177 */
1178class CanOfAPIs {
1179  constructor(context, apiManager, root) {
1180    this.context = context;
1181    this.scopeName = context.envType;
1182    this.apiManager = apiManager;
1183    this.root = root;
1184
1185    this.apiPaths = new Map();
1186
1187    this.apis = new Map();
1188  }
1189
1190  /**
1191   * Synchronously loads and initializes an ExtensionAPI instance.
1192   *
1193   * @param {string} name
1194   *        The name of the API to load.
1195   */
1196  loadAPI(name) {
1197    if (this.apis.has(name)) {
1198      return;
1199    }
1200
1201    let { extension } = this.context;
1202
1203    let api = this.apiManager.getAPI(name, extension, this.scopeName);
1204    if (!api) {
1205      return;
1206    }
1207
1208    this.apis.set(name, api);
1209
1210    deepCopy(this.root, api.getAPI(this.context));
1211  }
1212
1213  /**
1214   * Asynchronously loads and initializes an ExtensionAPI instance.
1215   *
1216   * @param {string} name
1217   *        The name of the API to load.
1218   */
1219  async asyncLoadAPI(name) {
1220    if (this.apis.has(name)) {
1221      return;
1222    }
1223
1224    let { extension } = this.context;
1225    if (!Schemas.checkPermissions(name, extension)) {
1226      return;
1227    }
1228
1229    let api = await this.apiManager.asyncGetAPI(
1230      name,
1231      extension,
1232      this.scopeName
1233    );
1234    // Check again, because async;
1235    if (this.apis.has(name)) {
1236      return;
1237    }
1238
1239    this.apis.set(name, api);
1240
1241    deepCopy(this.root, api.getAPI(this.context));
1242  }
1243
1244  /**
1245   * Finds the API at the given path from the root object, and
1246   * synchronously loads the API that implements it if it has not
1247   * already been loaded.
1248   *
1249   * @param {string} path
1250   *        The "."-separated path to find.
1251   * @returns {*}
1252   */
1253  findAPIPath(path) {
1254    if (this.apiPaths.has(path)) {
1255      return this.apiPaths.get(path);
1256    }
1257
1258    let obj = this.root;
1259    let modules = this.apiManager.modulePaths;
1260
1261    let parts = path.split(".");
1262    for (let [i, key] of parts.entries()) {
1263      if (!obj) {
1264        return;
1265      }
1266      modules = getChild(modules, key);
1267
1268      for (let name of modules.modules) {
1269        if (!this.apis.has(name)) {
1270          this.loadAPI(name);
1271        }
1272      }
1273
1274      if (!(key in obj) && i < parts.length - 1) {
1275        obj[key] = {};
1276      }
1277      obj = obj[key];
1278    }
1279
1280    this.apiPaths.set(path, obj);
1281    return obj;
1282  }
1283
1284  /**
1285   * Finds the API at the given path from the root object, and
1286   * asynchronously loads the API that implements it if it has not
1287   * already been loaded.
1288   *
1289   * @param {string} path
1290   *        The "."-separated path to find.
1291   * @returns {Promise<*>}
1292   */
1293  async asyncFindAPIPath(path) {
1294    if (this.apiPaths.has(path)) {
1295      return this.apiPaths.get(path);
1296    }
1297
1298    let obj = this.root;
1299    let modules = this.apiManager.modulePaths;
1300
1301    let parts = path.split(".");
1302    for (let [i, key] of parts.entries()) {
1303      if (!obj) {
1304        return;
1305      }
1306      modules = getChild(modules, key);
1307
1308      for (let name of modules.modules) {
1309        if (!this.apis.has(name)) {
1310          await this.asyncLoadAPI(name);
1311        }
1312      }
1313
1314      if (!(key in obj) && i < parts.length - 1) {
1315        obj[key] = {};
1316      }
1317
1318      if (typeof obj[key] === "function") {
1319        obj = obj[key].bind(obj);
1320      } else {
1321        obj = obj[key];
1322      }
1323    }
1324
1325    this.apiPaths.set(path, obj);
1326    return obj;
1327  }
1328}
1329
1330/**
1331 * @class APIModule
1332 * @abstract
1333 *
1334 * @property {string} url
1335 *       The URL of the script which contains the module's
1336 *       implementation. This script must define a global property
1337 *       matching the modules name, which must be a class constructor
1338 *       which inherits from {@link ExtensionAPI}.
1339 *
1340 * @property {string} schema
1341 *       The URL of the JSON schema which describes the module's API.
1342 *
1343 * @property {Array<string>} scopes
1344 *       The list of scope names into which the API may be loaded.
1345 *
1346 * @property {Array<string>} manifest
1347 *       The list of top-level manifest properties which will trigger
1348 *       the module to be loaded, and its `onManifestEntry` method to be
1349 *       called.
1350 *
1351 * @property {Array<string>} events
1352 *       The list events which will trigger the module to be loaded, and
1353 *       its appropriate event handler method to be called. Currently
1354 *       only accepts "startup".
1355 *
1356 * @property {Array<string>} permissions
1357 *       An optional list of permissions, any of which must be present
1358 *       in order for the module to load.
1359 *
1360 * @property {Array<Array<string>>} paths
1361 *       A list of paths from the root API object which, when accessed,
1362 *       will cause the API module to be instantiated and injected.
1363 */
1364
1365/**
1366 * This object loads the ext-*.js scripts that define the extension API.
1367 *
1368 * This class instance is shared with the scripts that it loads, so that the
1369 * ext-*.js scripts and the instantiator can communicate with each other.
1370 */
1371class SchemaAPIManager extends EventEmitter {
1372  /**
1373   * @param {string} processType
1374   *     "main" - The main, one and only chrome browser process.
1375   *     "addon" - An addon process.
1376   *     "content" - A content process.
1377   *     "devtools" - A devtools process.
1378   * @param {SchemaRoot} schema
1379   */
1380  constructor(processType, schema) {
1381    super();
1382    this.processType = processType;
1383    this.global = null;
1384    if (schema) {
1385      this.schema = schema;
1386    }
1387
1388    this.modules = new Map();
1389    this.modulePaths = { children: new Map(), modules: new Set() };
1390    this.manifestKeys = new Map();
1391    this.eventModules = new DefaultMap(() => new Set());
1392    this.settingsModules = new Set();
1393
1394    this._modulesJSONLoaded = false;
1395
1396    this.schemaURLs = new Map();
1397
1398    this.apis = new DefaultWeakMap(() => new Map());
1399
1400    this._scriptScopes = [];
1401  }
1402
1403  onStartup(extension) {
1404    let promises = [];
1405    for (let apiName of this.eventModules.get("startup")) {
1406      promises.push(
1407        extension.apiManager.asyncGetAPI(apiName, extension).then(api => {
1408          if (api) {
1409            api.onStartup();
1410          }
1411        })
1412      );
1413    }
1414
1415    return Promise.all(promises);
1416  }
1417
1418  async loadModuleJSON(urls) {
1419    let promises = urls.map(url => fetch(url).then(resp => resp.json()));
1420
1421    return this.initModuleJSON(await Promise.all(promises));
1422  }
1423
1424  initModuleJSON(blobs) {
1425    for (let json of blobs) {
1426      this.registerModules(json);
1427    }
1428
1429    this._modulesJSONLoaded = true;
1430
1431    return new StructuredCloneHolder({
1432      modules: this.modules,
1433      modulePaths: this.modulePaths,
1434      manifestKeys: this.manifestKeys,
1435      eventModules: this.eventModules,
1436      settingsModules: this.settingsModules,
1437      schemaURLs: this.schemaURLs,
1438    });
1439  }
1440
1441  initModuleData(moduleData) {
1442    if (!this._modulesJSONLoaded) {
1443      let data = moduleData.deserialize({}, true);
1444
1445      this.modules = data.modules;
1446      this.modulePaths = data.modulePaths;
1447      this.manifestKeys = data.manifestKeys;
1448      this.eventModules = new DefaultMap(() => new Set(), data.eventModules);
1449      this.settingsModules = new Set(data.settingsModules);
1450      this.schemaURLs = data.schemaURLs;
1451    }
1452
1453    this._modulesJSONLoaded = true;
1454  }
1455
1456  /**
1457   * Registers a set of ExtensionAPI modules to be lazily loaded and
1458   * managed by this manager.
1459   *
1460   * @param {object} obj
1461   *        An object containing property for eacy API module to be
1462   *        registered. Each value should be an object implementing the
1463   *        APIModule interface.
1464   */
1465  registerModules(obj) {
1466    for (let [name, details] of Object.entries(obj)) {
1467      details.namespaceName = name;
1468
1469      if (this.modules.has(name)) {
1470        throw new Error(`Module '${name}' already registered`);
1471      }
1472      this.modules.set(name, details);
1473
1474      if (details.schema) {
1475        let content =
1476          details.scopes &&
1477          (details.scopes.includes("content_parent") ||
1478            details.scopes.includes("content_child"));
1479        this.schemaURLs.set(details.schema, { content });
1480      }
1481
1482      for (let event of details.events || []) {
1483        this.eventModules.get(event).add(name);
1484      }
1485
1486      if (details.settings) {
1487        this.settingsModules.add(name);
1488      }
1489
1490      for (let key of details.manifest || []) {
1491        if (this.manifestKeys.has(key)) {
1492          throw new Error(
1493            `Manifest key '${key}' already registered by '${this.manifestKeys.get(
1494              key
1495            )}'`
1496          );
1497        }
1498
1499        this.manifestKeys.set(key, name);
1500      }
1501
1502      for (let path of details.paths || []) {
1503        getPath(this.modulePaths, path).modules.add(name);
1504      }
1505    }
1506  }
1507
1508  /**
1509   * Emits an `onManifestEntry` event for the top-level manifest entry
1510   * on all relevant {@link ExtensionAPI} instances for the given
1511   * extension.
1512   *
1513   * The API modules will be synchronously loaded if they have not been
1514   * loaded already.
1515   *
1516   * @param {Extension} extension
1517   *        The extension for which to emit the events.
1518   * @param {string} entry
1519   *        The name of the top-level manifest entry.
1520   *
1521   * @returns {*}
1522   */
1523  emitManifestEntry(extension, entry) {
1524    let apiName = this.manifestKeys.get(entry);
1525    if (apiName) {
1526      let api = extension.apiManager.getAPI(apiName, extension);
1527      return api.onManifestEntry(entry);
1528    }
1529  }
1530  /**
1531   * Emits an `onManifestEntry` event for the top-level manifest entry
1532   * on all relevant {@link ExtensionAPI} instances for the given
1533   * extension.
1534   *
1535   * The API modules will be asynchronously loaded if they have not been
1536   * loaded already.
1537   *
1538   * @param {Extension} extension
1539   *        The extension for which to emit the events.
1540   * @param {string} entry
1541   *        The name of the top-level manifest entry.
1542   *
1543   * @returns {Promise<*>}
1544   */
1545  async asyncEmitManifestEntry(extension, entry) {
1546    let apiName = this.manifestKeys.get(entry);
1547    if (apiName) {
1548      let api = await extension.apiManager.asyncGetAPI(apiName, extension);
1549      return api.onManifestEntry(entry);
1550    }
1551  }
1552
1553  /**
1554   * Returns the {@link ExtensionAPI} instance for the given API module,
1555   * for the given extension, in the given scope, synchronously loading
1556   * and instantiating it if necessary.
1557   *
1558   * @param {string} name
1559   *        The name of the API module to load.
1560   * @param {Extension} extension
1561   *        The extension for which to load the API.
1562   * @param {string} [scope = null]
1563   *        The scope type for which to retrieve the API, or null if not
1564   *        being retrieved for a particular scope.
1565   *
1566   * @returns {ExtensionAPI?}
1567   */
1568  getAPI(name, extension, scope = null) {
1569    if (!this._checkGetAPI(name, extension, scope)) {
1570      return;
1571    }
1572
1573    let apis = this.apis.get(extension);
1574    if (apis.has(name)) {
1575      return apis.get(name);
1576    }
1577
1578    let module = this.loadModule(name);
1579
1580    let api = new module(extension);
1581    apis.set(name, api);
1582    return api;
1583  }
1584  /**
1585   * Returns the {@link ExtensionAPI} instance for the given API module,
1586   * for the given extension, in the given scope, asynchronously loading
1587   * and instantiating it if necessary.
1588   *
1589   * @param {string} name
1590   *        The name of the API module to load.
1591   * @param {Extension} extension
1592   *        The extension for which to load the API.
1593   * @param {string} [scope = null]
1594   *        The scope type for which to retrieve the API, or null if not
1595   *        being retrieved for a particular scope.
1596   *
1597   * @returns {Promise<ExtensionAPI>?}
1598   */
1599  async asyncGetAPI(name, extension, scope = null) {
1600    if (!this._checkGetAPI(name, extension, scope)) {
1601      return;
1602    }
1603
1604    let apis = this.apis.get(extension);
1605    if (apis.has(name)) {
1606      return apis.get(name);
1607    }
1608
1609    let module = await this.asyncLoadModule(name);
1610
1611    // Check again, because async.
1612    if (apis.has(name)) {
1613      return apis.get(name);
1614    }
1615
1616    let api = new module(extension);
1617    apis.set(name, api);
1618    return api;
1619  }
1620
1621  /**
1622   * Synchronously loads an API module, if not already loaded, and
1623   * returns its ExtensionAPI constructor.
1624   *
1625   * @param {string} name
1626   *        The name of the module to load.
1627   *
1628   * @returns {class}
1629   */
1630  loadModule(name) {
1631    let module = this.modules.get(name);
1632    if (module.loaded) {
1633      return this.global[name];
1634    }
1635
1636    this._checkLoadModule(module, name);
1637
1638    this.initGlobal();
1639
1640    Services.scriptloader.loadSubScript(module.url, this.global);
1641
1642    module.loaded = true;
1643
1644    return this.global[name];
1645  }
1646  /**
1647   * aSynchronously loads an API module, if not already loaded, and
1648   * returns its ExtensionAPI constructor.
1649   *
1650   * @param {string} name
1651   *        The name of the module to load.
1652   *
1653   * @returns {Promise<class>}
1654   */
1655  asyncLoadModule(name) {
1656    let module = this.modules.get(name);
1657    if (module.loaded) {
1658      return Promise.resolve(this.global[name]);
1659    }
1660    if (module.asyncLoaded) {
1661      return module.asyncLoaded;
1662    }
1663
1664    this._checkLoadModule(module, name);
1665
1666    module.asyncLoaded = ChromeUtils.compileScript(module.url).then(script => {
1667      this.initGlobal();
1668      script.executeInGlobal(this.global);
1669
1670      module.loaded = true;
1671
1672      return this.global[name];
1673    });
1674
1675    return module.asyncLoaded;
1676  }
1677
1678  asyncLoadSettingsModules() {
1679    return Promise.all(
1680      Array.from(this.settingsModules).map(apiName =>
1681        this.asyncLoadModule(apiName)
1682      )
1683    );
1684  }
1685
1686  getModule(name) {
1687    return this.modules.get(name);
1688  }
1689
1690  /**
1691   * Checks whether the given API module may be loaded for the given
1692   * extension, in the given scope.
1693   *
1694   * @param {string} name
1695   *        The name of the API module to check.
1696   * @param {Extension} extension
1697   *        The extension for which to check the API.
1698   * @param {string} [scope = null]
1699   *        The scope type for which to check the API, or null if not
1700   *        being checked for a particular scope.
1701   *
1702   * @returns {boolean}
1703   *        Whether the module may be loaded.
1704   */
1705  _checkGetAPI(name, extension, scope = null) {
1706    let module = this.getModule(name);
1707
1708    if (
1709      module.permissions &&
1710      !module.permissions.some(perm => extension.hasPermission(perm))
1711    ) {
1712      return false;
1713    }
1714
1715    if (!scope) {
1716      return true;
1717    }
1718
1719    if (!module.scopes.includes(scope)) {
1720      return false;
1721    }
1722
1723    if (!Schemas.checkPermissions(module.namespaceName, extension)) {
1724      return false;
1725    }
1726
1727    return true;
1728  }
1729
1730  _checkLoadModule(module, name) {
1731    if (!module) {
1732      throw new Error(`Module '${name}' does not exist`);
1733    }
1734    if (module.asyncLoaded) {
1735      throw new Error(`Module '${name}' currently being lazily loaded`);
1736    }
1737    if (this.global && this.global[name]) {
1738      throw new Error(
1739        `Module '${name}' conflicts with existing global property`
1740      );
1741    }
1742  }
1743
1744  /**
1745   * Create a global object that is used as the shared global for all ext-*.js
1746   * scripts that are loaded via `loadScript`.
1747   *
1748   * @returns {object} A sandbox that is used as the global by `loadScript`.
1749   */
1750  _createExtGlobal() {
1751    let global = Cu.Sandbox(
1752      Services.scriptSecurityManager.getSystemPrincipal(),
1753      {
1754        wantXrays: false,
1755        wantGlobalProperties: ["ChromeUtils"],
1756        sandboxName: `Namespace of ext-*.js scripts for ${this.processType} (from: resource://gre/modules/ExtensionCommon.jsm)`,
1757      }
1758    );
1759
1760    Object.assign(global, {
1761      Cc,
1762      ChromeWorker,
1763      Ci,
1764      Cr,
1765      Cu,
1766      ExtensionAPI,
1767      ExtensionCommon,
1768      MatchGlob,
1769      MatchPattern,
1770      MatchPatternSet,
1771      StructuredCloneHolder,
1772      XPCOMUtils,
1773      extensions: this,
1774      global,
1775    });
1776
1777    ChromeUtils.import("resource://gre/modules/AppConstants.jsm", global);
1778
1779    XPCOMUtils.defineLazyGetter(global, "console", getConsole);
1780
1781    XPCOMUtils.defineLazyModuleGetters(global, {
1782      ExtensionUtils: "resource://gre/modules/ExtensionUtils.jsm",
1783      XPCOMUtils: "resource://gre/modules/XPCOMUtils.jsm",
1784    });
1785
1786    return global;
1787  }
1788
1789  initGlobal() {
1790    if (!this.global) {
1791      this.global = this._createExtGlobal();
1792    }
1793  }
1794
1795  /**
1796   * Load an ext-*.js script. The script runs in its own scope, if it wishes to
1797   * share state with another script it can assign to the `global` variable. If
1798   * it wishes to communicate with this API manager, use `extensions`.
1799   *
1800   * @param {string} scriptUrl The URL of the ext-*.js script.
1801   */
1802  loadScript(scriptUrl) {
1803    // Create the object in the context of the sandbox so that the script runs
1804    // in the sandbox's context instead of here.
1805    let scope = Cu.createObjectIn(this.global);
1806
1807    Services.scriptloader.loadSubScript(scriptUrl, scope);
1808
1809    // Save the scope to avoid it being garbage collected.
1810    this._scriptScopes.push(scope);
1811  }
1812}
1813
1814class LazyAPIManager extends SchemaAPIManager {
1815  constructor(processType, moduleData, schemaURLs) {
1816    super(processType);
1817
1818    this.initialized = false;
1819
1820    this.initModuleData(moduleData);
1821
1822    this.schemaURLs = schemaURLs;
1823  }
1824}
1825
1826defineLazyGetter(LazyAPIManager.prototype, "schema", function() {
1827  let root = new SchemaRoot(Schemas.rootSchema, this.schemaURLs);
1828  root.parseSchemas();
1829  return root;
1830});
1831
1832class MultiAPIManager extends SchemaAPIManager {
1833  constructor(processType, children) {
1834    super(processType);
1835
1836    this.initialized = false;
1837
1838    this.children = children;
1839  }
1840
1841  async lazyInit() {
1842    if (!this.initialized) {
1843      this.initialized = true;
1844
1845      for (let child of this.children) {
1846        if (child.lazyInit) {
1847          let res = child.lazyInit();
1848          if (res && typeof res.then === "function") {
1849            await res;
1850          }
1851        }
1852
1853        mergePaths(this.modulePaths, child.modulePaths);
1854      }
1855    }
1856  }
1857
1858  onStartup(extension) {
1859    return Promise.all(this.children.map(child => child.onStartup(extension)));
1860  }
1861
1862  getModule(name) {
1863    for (let child of this.children) {
1864      if (child.modules.has(name)) {
1865        return child.modules.get(name);
1866      }
1867    }
1868  }
1869
1870  loadModule(name) {
1871    for (let child of this.children) {
1872      if (child.modules.has(name)) {
1873        return child.loadModule(name);
1874      }
1875    }
1876  }
1877
1878  asyncLoadModule(name) {
1879    for (let child of this.children) {
1880      if (child.modules.has(name)) {
1881        return child.asyncLoadModule(name);
1882      }
1883    }
1884  }
1885}
1886
1887defineLazyGetter(MultiAPIManager.prototype, "schema", function() {
1888  let bases = this.children.map(child => child.schema);
1889
1890  // All API manager schema roots should derive from the global schema root,
1891  // so it doesn't need its own entry.
1892  if (bases[bases.length - 1] === Schemas) {
1893    bases.pop();
1894  }
1895
1896  if (bases.length === 1) {
1897    bases = bases[0];
1898  }
1899  return new SchemaRoot(bases, new Map());
1900});
1901
1902function LocaleData(data) {
1903  this.defaultLocale = data.defaultLocale;
1904  this.selectedLocale = data.selectedLocale;
1905  this.locales = data.locales || new Map();
1906  this.warnedMissingKeys = new Set();
1907
1908  // Map(locale-name -> Map(message-key -> localized-string))
1909  //
1910  // Contains a key for each loaded locale, each of which is a
1911  // Map of message keys to their localized strings.
1912  this.messages = data.messages || new Map();
1913
1914  if (data.builtinMessages) {
1915    this.messages.set(this.BUILTIN, data.builtinMessages);
1916  }
1917}
1918
1919LocaleData.prototype = {
1920  // Representation of the object to send to content processes. This
1921  // should include anything the content process might need.
1922  serialize() {
1923    return {
1924      defaultLocale: this.defaultLocale,
1925      selectedLocale: this.selectedLocale,
1926      messages: this.messages,
1927      locales: this.locales,
1928    };
1929  },
1930
1931  BUILTIN: "@@BUILTIN_MESSAGES",
1932
1933  has(locale) {
1934    return this.messages.has(locale);
1935  },
1936
1937  // https://developer.chrome.com/extensions/i18n
1938  localizeMessage(message, substitutions = [], options = {}) {
1939    let defaultOptions = {
1940      defaultValue: "",
1941      cloneScope: null,
1942    };
1943
1944    let locales = this.availableLocales;
1945    if (options.locale) {
1946      locales = new Set(
1947        [this.BUILTIN, options.locale, this.defaultLocale].filter(locale =>
1948          this.messages.has(locale)
1949        )
1950      );
1951    }
1952
1953    options = Object.assign(defaultOptions, options);
1954
1955    // Message names are case-insensitive, so normalize them to lower-case.
1956    message = message.toLowerCase();
1957    for (let locale of locales) {
1958      let messages = this.messages.get(locale);
1959      if (messages.has(message)) {
1960        let str = messages.get(message);
1961
1962        if (!str.includes("$")) {
1963          return str;
1964        }
1965
1966        if (!Array.isArray(substitutions)) {
1967          substitutions = [substitutions];
1968        }
1969
1970        let replacer = (matched, index, dollarSigns) => {
1971          if (index) {
1972            // This is not quite Chrome-compatible. Chrome consumes any number
1973            // of digits following the $, but only accepts 9 substitutions. We
1974            // accept any number of substitutions.
1975            index = parseInt(index, 10) - 1;
1976            return index in substitutions ? substitutions[index] : "";
1977          }
1978          // For any series of contiguous `$`s, the first is dropped, and
1979          // the rest remain in the output string.
1980          return dollarSigns;
1981        };
1982        return str.replace(/\$(?:([1-9]\d*)|(\$+))/g, replacer);
1983      }
1984    }
1985
1986    // Check for certain pre-defined messages.
1987    if (message == "@@ui_locale") {
1988      return this.uiLocale;
1989    } else if (message.startsWith("@@bidi_")) {
1990      let rtl = Services.locale.isAppLocaleRTL;
1991
1992      if (message == "@@bidi_dir") {
1993        return rtl ? "rtl" : "ltr";
1994      } else if (message == "@@bidi_reversed_dir") {
1995        return rtl ? "ltr" : "rtl";
1996      } else if (message == "@@bidi_start_edge") {
1997        return rtl ? "right" : "left";
1998      } else if (message == "@@bidi_end_edge") {
1999        return rtl ? "left" : "right";
2000      }
2001    }
2002
2003    if (!this.warnedMissingKeys.has(message)) {
2004      let error = `Unknown localization message ${message}`;
2005      if (options.cloneScope) {
2006        error = new options.cloneScope.Error(error);
2007      }
2008      Cu.reportError(error);
2009      this.warnedMissingKeys.add(message);
2010    }
2011    return options.defaultValue;
2012  },
2013
2014  // Localize a string, replacing all |__MSG_(.*)__| tokens with the
2015  // matching string from the current locale, as determined by
2016  // |this.selectedLocale|.
2017  //
2018  // This may not be called before calling either |initLocale| or
2019  // |initAllLocales|.
2020  localize(str, locale = this.selectedLocale) {
2021    if (!str) {
2022      return str;
2023    }
2024
2025    return str.replace(/__MSG_([A-Za-z0-9@_]+?)__/g, (matched, message) => {
2026      return this.localizeMessage(message, [], {
2027        locale,
2028        defaultValue: matched,
2029      });
2030    });
2031  },
2032
2033  // Validates the contents of a locale JSON file, normalizes the
2034  // messages into a Map of message key -> localized string pairs.
2035  addLocale(locale, messages, extension) {
2036    let result = new Map();
2037
2038    let isPlainObject = obj =>
2039      obj &&
2040      typeof obj === "object" &&
2041      ChromeUtils.getClassName(obj) === "Object";
2042
2043    // Chrome does not document the semantics of its localization
2044    // system very well. It handles replacements by pre-processing
2045    // messages, replacing |$[a-zA-Z0-9@_]+$| tokens with the value of their
2046    // replacements. Later, it processes the resulting string for
2047    // |$[0-9]| replacements.
2048    //
2049    // Again, it does not document this, but it accepts any number
2050    // of sequential |$|s, and replaces them with that number minus
2051    // 1. It also accepts |$| followed by any number of sequential
2052    // digits, but refuses to process a localized string which
2053    // provides more than 9 substitutions.
2054    if (!isPlainObject(messages)) {
2055      extension.packagingError(`Invalid locale data for ${locale}`);
2056      return result;
2057    }
2058
2059    for (let key of Object.keys(messages)) {
2060      let msg = messages[key];
2061
2062      if (!isPlainObject(msg) || typeof msg.message != "string") {
2063        extension.packagingError(
2064          `Invalid locale message data for ${locale}, message ${JSON.stringify(
2065            key
2066          )}`
2067        );
2068        continue;
2069      }
2070
2071      // Substitutions are case-insensitive, so normalize all of their names
2072      // to lower-case.
2073      let placeholders = new Map();
2074      if ("placeholders" in msg && isPlainObject(msg.placeholders)) {
2075        for (let key of Object.keys(msg.placeholders)) {
2076          placeholders.set(key.toLowerCase(), msg.placeholders[key]);
2077        }
2078      }
2079
2080      let replacer = (match, name) => {
2081        let replacement = placeholders.get(name.toLowerCase());
2082        if (isPlainObject(replacement) && "content" in replacement) {
2083          return replacement.content;
2084        }
2085        return "";
2086      };
2087
2088      let value = msg.message.replace(/\$([A-Za-z0-9@_]+)\$/g, replacer);
2089
2090      // Message names are also case-insensitive, so normalize them to lower-case.
2091      result.set(key.toLowerCase(), value);
2092    }
2093
2094    this.messages.set(locale, result);
2095    return result;
2096  },
2097
2098  get acceptLanguages() {
2099    let result = Services.prefs.getComplexValue(
2100      "intl.accept_languages",
2101      Ci.nsIPrefLocalizedString
2102    ).data;
2103    return result.split(/\s*,\s*/g);
2104  },
2105
2106  get uiLocale() {
2107    return Services.locale.appLocaleAsBCP47;
2108  },
2109};
2110
2111defineLazyGetter(LocaleData.prototype, "availableLocales", function() {
2112  return new Set(
2113    [this.BUILTIN, this.selectedLocale, this.defaultLocale].filter(locale =>
2114      this.messages.has(locale)
2115    )
2116  );
2117});
2118
2119/**
2120 * This is a generic class for managing event listeners.
2121 *
2122 * @example
2123 * new EventManager({
2124 *   context,
2125 *   name: "api.subAPI",
2126 *   register:  fire => {
2127 *     let listener = (...) => {
2128 *       // Fire any listeners registered with addListener.
2129 *       fire.async(arg1, arg2);
2130 *     };
2131 *     // Register the listener.
2132 *     SomehowRegisterListener(listener);
2133 *     return () => {
2134 *       // Return a way to unregister the listener.
2135 *       SomehowUnregisterListener(listener);
2136 *     };
2137 *   }
2138 * }).api()
2139 *
2140 * The result is an object with addListener, removeListener, and
2141 * hasListener methods. `context` is an add-on scope (either an
2142 * ExtensionContext in the chrome process or ExtensionContext in a
2143 * content process).
2144 */
2145class EventManager {
2146  /*
2147   * @param {object} params
2148   *        Parameters that control this EventManager.
2149   * @param {BaseContext} params.context
2150   *        An object representing the extension instance using this event.
2151   * @param {string} params.name
2152   *        A name used only for debugging.
2153   * @param {functon} params.register
2154   *        A function called whenever a new listener is added.
2155   * @param {boolean} [params.inputHandling=false]
2156   *        If true, the "handling user input" flag is set while handlers
2157   *        for this event are executing.
2158   * @param {object} [params.persistent]
2159   *        Details for persistent event listeners
2160   * @param {string} params.persistent.module
2161   *        The name of the module in which this event is defined.
2162   * @param {string} params.persistent.event
2163   *        The name of this event.
2164   */
2165  constructor(params) {
2166    let {
2167      context,
2168      name,
2169      register,
2170      inputHandling = false,
2171      persistent = null,
2172    } = params;
2173    this.context = context;
2174    this.name = name;
2175    this.register = register;
2176    this.inputHandling = inputHandling;
2177    this.persistent = persistent;
2178
2179    // Don't bother with persistent event handling if delayed background
2180    // startup is not enabled.
2181    if (!DELAYED_BG_STARTUP) {
2182      this.persistent = null;
2183    }
2184
2185    this.unregister = new Map();
2186    this.remove = new Map();
2187
2188    if (this.persistent) {
2189      if (AppConstants.DEBUG) {
2190        if (this.context.envType !== "addon_parent") {
2191          throw new Error(
2192            "Persistent event managers can only be created for addon_parent"
2193          );
2194        }
2195        if (!this.persistent.module || !this.persistent.event) {
2196          throw new Error(
2197            "Persistent event manager must specify module and event"
2198          );
2199        }
2200      }
2201      if (this.context.viewType !== "background") {
2202        this.persistent = null;
2203      }
2204    }
2205  }
2206
2207  /*
2208   * Information about listeners to persistent events is associated with
2209   * the extension to which they belong.  Any extension thas has such
2210   * listeners has a property called `persistentListeners` that is a
2211   * 3-level Map.  The first 2 keys are the module name (e.g., webRequest)
2212   * and the name of the event within the module (e.g., onBeforeRequest).
2213   * The third level of the map is used to track multiple listeners for
2214   * the same event, these listeners are distinguished by the extra arguments
2215   * passed to addListener().  For quick lookups, the key to the third Map
2216   * is the result of calling uneval() on the array of extra arguments.
2217   *
2218   * The value stored in the Map is a plain object with a property called
2219   * `params` that is the original (ie, not uneval()ed) extra arguments to
2220   * addListener().  For a primed listener (i.e., the stub listener created
2221   * during browser startup before the extension background page is started,
2222   * the object also has a `primed` property that holds the things needed
2223   * to handle events during startup and eventually connect the listener
2224   * with a callback registered from the extension.
2225   *
2226   * @param {Extension} extension
2227   * @returns {boolean} True if the extension had any persistent listeners.
2228   */
2229  static _initPersistentListeners(extension) {
2230    if (extension.persistentListeners) {
2231      return false;
2232    }
2233
2234    let listeners = new DefaultMap(() => new DefaultMap(() => new Map()));
2235    extension.persistentListeners = listeners;
2236
2237    let { persistentListeners } = extension.startupData;
2238    if (!persistentListeners) {
2239      return false;
2240    }
2241
2242    let found = false;
2243    for (let [module, entry] of Object.entries(persistentListeners)) {
2244      for (let [event, paramlists] of Object.entries(entry)) {
2245        for (let paramlist of paramlists) {
2246          let key = uneval(paramlist);
2247          listeners
2248            .get(module)
2249            .get(event)
2250            .set(key, { params: paramlist });
2251          found = true;
2252        }
2253      }
2254    }
2255    return found;
2256  }
2257
2258  // Extract just the information needed at startup for all persistent
2259  // listeners, and arrange for it to be saved.  This should be called
2260  // whenever the set of persistent listeners for an extension changes.
2261  static _writePersistentListeners(extension) {
2262    let startupListeners = {};
2263    for (let [module, moduleEntry] of extension.persistentListeners) {
2264      startupListeners[module] = {};
2265      for (let [event, eventEntry] of moduleEntry) {
2266        startupListeners[module][event] = Array.from(
2267          eventEntry.values(),
2268          listener => listener.params
2269        );
2270      }
2271    }
2272
2273    extension.startupData.persistentListeners = startupListeners;
2274    extension.saveStartupData();
2275  }
2276
2277  // Set up "primed" event listeners for any saved event listeners
2278  // in an extension's startup data.
2279  // This function is only called during browser startup, it stores details
2280  // about all primed listeners in the extension's persistentListeners Map.
2281  static primeListeners(extension) {
2282    if (!EventManager._initPersistentListeners(extension)) {
2283      return;
2284    }
2285
2286    for (let [module, moduleEntry] of extension.persistentListeners) {
2287      let api = extension.apiManager.getAPI(module, extension, "addon_parent");
2288      if (!api.primeListener) {
2289        // The runtime module no longer implements primed listeners, drop them.
2290        extension.persistentListeners.delete(module);
2291        EventManager._writePersistentListeners(extension);
2292        continue;
2293      }
2294      for (let [event, eventEntry] of moduleEntry) {
2295        for (let listener of eventEntry.values()) {
2296          let primed = { pendingEvents: [] };
2297          listener.primed = primed;
2298
2299          let fireEvent = (...args) =>
2300            new Promise((resolve, reject) => {
2301              if (!listener.primed) {
2302                reject(new Error("primed listener not re-registered"));
2303                return;
2304              }
2305              primed.pendingEvents.push({ args, resolve, reject });
2306              extension.emit("background-page-event");
2307            });
2308
2309          let fire = {
2310            wakeup: () => extension.wakeupBackground(),
2311            sync: fireEvent,
2312            async: fireEvent,
2313          };
2314
2315          let { unregister, convert } = api.primeListener(
2316            extension,
2317            event,
2318            fire,
2319            listener.params
2320          );
2321          Object.assign(primed, { unregister, convert });
2322        }
2323      }
2324    }
2325  }
2326
2327  // Remove any primed listeners that were not re-registered.
2328  // This function is called after the background page has started.
2329  // The removed listeners are removed from the set of saved listeners, unless
2330  // `clearPersistent` is false. If false, the listeners are cleared from
2331  // memory, but not removed from the extension's startup data.
2332  static clearPrimedListeners(extension, clearPersistent = true) {
2333    for (let [module, moduleEntry] of extension.persistentListeners) {
2334      for (let [event, listeners] of moduleEntry) {
2335        for (let [key, listener] of listeners) {
2336          let { primed } = listener;
2337          if (!primed) {
2338            continue;
2339          }
2340          listener.primed = null;
2341
2342          for (let evt of primed.pendingEvents) {
2343            evt.reject(new Error("listener not re-registered"));
2344          }
2345
2346          if (clearPersistent) {
2347            EventManager.clearPersistentListener(extension, module, event, key);
2348          }
2349          primed.unregister();
2350        }
2351      }
2352    }
2353  }
2354
2355  // Record the fact that there is a listener for the given event in
2356  // the given extension.  `args` is an Array containing any extra
2357  // arguments that were passed to addListener().
2358  static savePersistentListener(extension, module, event, args = []) {
2359    EventManager._initPersistentListeners(extension);
2360    let key = uneval(args);
2361    extension.persistentListeners
2362      .get(module)
2363      .get(event)
2364      .set(key, { params: args });
2365    EventManager._writePersistentListeners(extension);
2366  }
2367
2368  // Remove the record for the given event listener from the extension's
2369  // startup data.  `key` must be a string, the result of calling uneval()
2370  // on the array of extra arguments originally passed to addListener().
2371  static clearPersistentListener(extension, module, event, key = uneval([])) {
2372    let listeners = extension.persistentListeners.get(module).get(event);
2373    listeners.delete(key);
2374
2375    if (listeners.size == 0) {
2376      let moduleEntry = extension.persistentListeners.get(module);
2377      moduleEntry.delete(event);
2378      if (moduleEntry.size == 0) {
2379        extension.persistentListeners.delete(module);
2380      }
2381    }
2382
2383    EventManager._writePersistentListeners(extension);
2384  }
2385
2386  addListener(callback, ...args) {
2387    if (this.unregister.has(callback)) {
2388      return;
2389    }
2390    this.context.logActivity("api_call", `${this.name}.addListener`, { args });
2391
2392    let shouldFire = () => {
2393      if (this.context.unloaded) {
2394        dump(`${this.name} event fired after context unloaded.\n`);
2395      } else if (!this.context.active) {
2396        dump(`${this.name} event fired while context is inactive.\n`);
2397      } else if (this.unregister.has(callback)) {
2398        return true;
2399      }
2400      return false;
2401    };
2402
2403    let fire = {
2404      sync: (...args) => {
2405        if (shouldFire()) {
2406          let result = this.context.applySafe(callback, args);
2407          this.context.logActivity("api_event", this.name, { args, result });
2408          return result;
2409        }
2410      },
2411      async: (...args) => {
2412        return Promise.resolve().then(() => {
2413          if (shouldFire()) {
2414            let result = this.context.applySafe(callback, args);
2415            this.context.logActivity("api_event", this.name, { args, result });
2416            return result;
2417          }
2418        });
2419      },
2420      raw: (...args) => {
2421        if (!shouldFire()) {
2422          throw new Error("Called raw() on unloaded/inactive context");
2423        }
2424        let result = Reflect.apply(callback, null, args);
2425        this.context.logActivity("api_event", this.name, { args, result });
2426        return result;
2427      },
2428      asyncWithoutClone: (...args) => {
2429        return Promise.resolve().then(() => {
2430          if (shouldFire()) {
2431            let result = this.context.applySafeWithoutClone(callback, args);
2432            this.context.logActivity("api_event", this.name, { args, result });
2433            return result;
2434          }
2435        });
2436      },
2437    };
2438
2439    let { extension } = this.context;
2440
2441    let unregister = null;
2442    let recordStartupData = false;
2443
2444    // If this is a persistent event, check for a listener that was already
2445    // created during startup.  If there is one, use it and don't create a
2446    // new one.
2447    if (this.persistent) {
2448      recordStartupData = true;
2449      let { module, event } = this.persistent;
2450
2451      let key = uneval(args);
2452      EventManager._initPersistentListeners(extension);
2453      let listener = extension.persistentListeners
2454        .get(module)
2455        .get(event)
2456        .get(key);
2457
2458      if (listener) {
2459        // If extensions.webextensions.background-delayed-startup is disabled,
2460        // we can have stored info here but no primed listener.  This check
2461        // can be removed if/when we make delayed background startup the only
2462        // supported setting.
2463        let { primed } = listener;
2464        if (primed) {
2465          listener.primed = null;
2466
2467          primed.convert(fire, this.context);
2468          unregister = primed.unregister;
2469
2470          for (let evt of primed.pendingEvents) {
2471            evt.resolve(fire.async(...evt.args));
2472          }
2473        }
2474
2475        recordStartupData = false;
2476        this.remove.set(callback, () => {
2477          EventManager.clearPersistentListener(
2478            extension,
2479            module,
2480            event,
2481            uneval(args)
2482          );
2483        });
2484      }
2485    }
2486
2487    if (!unregister) {
2488      unregister = this.register(fire, ...args);
2489    }
2490
2491    this.unregister.set(callback, unregister);
2492    this.context.callOnClose(this);
2493
2494    // If this is a new listener for a persistent event, record
2495    // the details for subsequent startups.
2496    if (recordStartupData) {
2497      let { module, event } = this.persistent;
2498      EventManager.savePersistentListener(extension, module, event, args);
2499      this.remove.set(callback, () => {
2500        EventManager.clearPersistentListener(
2501          extension,
2502          module,
2503          event,
2504          uneval(args)
2505        );
2506      });
2507    }
2508  }
2509
2510  removeListener(callback, clearPersistentListener = true) {
2511    if (!this.unregister.has(callback)) {
2512      return;
2513    }
2514    this.context.logActivity("api_call", `${this.name}.removeListener`, {
2515      args: [],
2516    });
2517
2518    let unregister = this.unregister.get(callback);
2519    this.unregister.delete(callback);
2520    try {
2521      unregister();
2522    } catch (e) {
2523      Cu.reportError(e);
2524    }
2525
2526    if (clearPersistentListener && this.remove.has(callback)) {
2527      let cleanup = this.remove.get(callback);
2528      this.remove.delete(callback);
2529      cleanup();
2530    }
2531
2532    if (this.unregister.size == 0) {
2533      this.context.forgetOnClose(this);
2534    }
2535  }
2536
2537  hasListener(callback) {
2538    return this.unregister.has(callback);
2539  }
2540
2541  revoke() {
2542    for (let callback of this.unregister.keys()) {
2543      this.removeListener(callback, false);
2544    }
2545  }
2546
2547  close() {
2548    this.revoke();
2549  }
2550
2551  api() {
2552    return {
2553      addListener: (...args) => this.addListener(...args),
2554      removeListener: (...args) => this.removeListener(...args),
2555      hasListener: (...args) => this.hasListener(...args),
2556      setUserInput: this.inputHandling,
2557      [Schemas.REVOKE]: () => this.revoke(),
2558    };
2559  }
2560}
2561
2562// Simple API for event listeners where events never fire.
2563function ignoreEvent(context, name) {
2564  return {
2565    addListener: function(callback) {
2566      let id = context.extension.id;
2567      let frame = Components.stack.caller;
2568      let msg = `In add-on ${id}, attempting to use listener "${name}", which is unimplemented.`;
2569      let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(
2570        Ci.nsIScriptError
2571      );
2572      scriptError.init(
2573        msg,
2574        frame.filename,
2575        null,
2576        frame.lineNumber,
2577        frame.columnNumber,
2578        Ci.nsIScriptError.warningFlag,
2579        "content javascript"
2580      );
2581      Services.console.logMessage(scriptError);
2582    },
2583    removeListener: function(callback) {},
2584    hasListener: function(callback) {},
2585  };
2586}
2587
2588const stylesheetMap = new DefaultMap(url => {
2589  let uri = Services.io.newURI(url);
2590  return styleSheetService.preloadSheet(uri, styleSheetService.AGENT_SHEET);
2591});
2592
2593ExtensionCommon = {
2594  BaseContext,
2595  CanOfAPIs,
2596  EventManager,
2597  ExtensionAPI,
2598  EventEmitter,
2599  LocalAPIImplementation,
2600  LocaleData,
2601  NoCloneSpreadArgs,
2602  SchemaAPIInterface,
2603  SchemaAPIManager,
2604  SpreadArgs,
2605  checkLoadURL,
2606  defineLazyGetter,
2607  getConsole,
2608  ignoreEvent,
2609  instanceOf,
2610  makeWidgetId,
2611  normalizeTime,
2612  runSafeSyncWithoutClone,
2613  stylesheetMap,
2614  withHandlingUserInput,
2615
2616  MultiAPIManager,
2617  LazyAPIManager,
2618};
2619