summaryrefslogtreecommitdiffstats
path: root/toolkit/components/asyncshutdown/nsAsyncShutdown.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/asyncshutdown/nsAsyncShutdown.js')
-rw-r--r--toolkit/components/asyncshutdown/nsAsyncShutdown.js277
1 files changed, 277 insertions, 0 deletions
diff --git a/toolkit/components/asyncshutdown/nsAsyncShutdown.js b/toolkit/components/asyncshutdown/nsAsyncShutdown.js
new file mode 100644
index 000000000..bd2c9a2fd
--- /dev/null
+++ b/toolkit/components/asyncshutdown/nsAsyncShutdown.js
@@ -0,0 +1,277 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * An implementation of nsIAsyncShutdown* based on AsyncShutdown.jsm
+ */
+
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+var XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}).XPCOMUtils;
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+ "resource://gre/modules/AsyncShutdown.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+
+/**
+ * Conversion between nsIPropertyBag and JS object
+ */
+var PropertyBagConverter = {
+ // From nsIPropertyBag to JS
+ toObject: function(bag) {
+ if (!(bag instanceof Ci.nsIPropertyBag)) {
+ throw new TypeError("Not a property bag");
+ }
+ let result = {};
+ let enumerator = bag.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let {name, value: property} = enumerator.getNext().QueryInterface(Ci.nsIProperty);
+ let value = this.toValue(property);
+ result[name] = value;
+ }
+ return result;
+ },
+ toValue: function(property) {
+ if (typeof property != "object") {
+ return property;
+ }
+ if (Array.isArray(property)) {
+ return property.map(this.toValue, this);
+ }
+ if (property && property instanceof Ci.nsIPropertyBag) {
+ return this.toObject(property);
+ }
+ return property;
+ },
+
+ // From JS to nsIPropertyBag
+ fromObject: function(obj) {
+ if (obj == null || typeof obj != "object") {
+ throw new TypeError("Invalid object: " + obj);
+ }
+ let bag = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ for (let k of Object.keys(obj)) {
+ let value = this.fromValue(obj[k]);
+ bag.setProperty(k, value);
+ }
+ return bag;
+ },
+ fromValue: function(value) {
+ if (typeof value == "function") {
+ return null; // Emulating the behavior of JSON.stringify with functions
+ }
+ if (Array.isArray(value)) {
+ return value.map(this.fromValue, this);
+ }
+ if (value == null || typeof value != "object") {
+ // Auto-converted to nsIVariant
+ return value;
+ }
+ return this.fromObject(value);
+ },
+};
+
+
+
+/**
+ * Construct an instance of nsIAsyncShutdownClient from a
+ * AsyncShutdown.Barrier client.
+ *
+ * @param {object} moduleClient A client, as returned from the `client`
+ * property of an instance of `AsyncShutdown.Barrier`. This client will
+ * serve as back-end for methods `addBlocker` and `removeBlocker`.
+ * @constructor
+ */
+function nsAsyncShutdownClient(moduleClient) {
+ if (!moduleClient) {
+ throw new TypeError("nsAsyncShutdownClient expects one argument");
+ }
+ this._moduleClient = moduleClient;
+ this._byName = new Map();
+}
+nsAsyncShutdownClient.prototype = {
+ _getPromisified: function(xpcomBlocker) {
+ let candidate = this._byName.get(xpcomBlocker.name);
+ if (!candidate) {
+ return null;
+ }
+ if (candidate.xpcom === xpcomBlocker) {
+ return candidate.jsm;
+ }
+ return null;
+ },
+ _setPromisified: function(xpcomBlocker, moduleBlocker) {
+ let candidate = this._byName.get(xpcomBlocker.name);
+ if (!candidate) {
+ this._byName.set(xpcomBlocker.name, {xpcom: xpcomBlocker,
+ jsm: moduleBlocker});
+ return;
+ }
+ if (candidate.xpcom === xpcomBlocker) {
+ return;
+ }
+ throw new Error("We have already registered a distinct blocker with the same name: " + xpcomBlocker.name);
+ },
+ _deletePromisified: function(xpcomBlocker) {
+ let candidate = this._byName.get(xpcomBlocker.name);
+ if (!candidate || candidate.xpcom !== xpcomBlocker) {
+ return false;
+ }
+ this._byName.delete(xpcomBlocker.name);
+ return true;
+ },
+ get jsclient() {
+ return this._moduleClient;
+ },
+ get name() {
+ return this._moduleClient.name;
+ },
+ addBlocker: function(/* nsIAsyncShutdownBlocker*/ xpcomBlocker,
+ fileName, lineNumber, stack) {
+ // We need a Promise-based function with the same behavior as
+ // `xpcomBlocker`. Furthermore, to support `removeBlocker`, we
+ // need to ensure that we always get the same Promise-based
+ // function if we call several `addBlocker`/`removeBlocker` several
+ // times with the same `xpcomBlocker`.
+ //
+ // Ideally, this should be done with a WeakMap() with xpcomBlocker
+ // as a key, but XPConnect NativeWrapped objects cannot serve as
+ // WeakMap keys.
+ //
+ let moduleBlocker = this._getPromisified(xpcomBlocker);
+ if (!moduleBlocker) {
+ moduleBlocker = () => new Promise(
+ // This promise is never resolved. By opposition to AsyncShutdown
+ // blockers, `nsIAsyncShutdownBlocker`s are always lifted by calling
+ // `removeBlocker`.
+ () => xpcomBlocker.blockShutdown(this)
+ );
+
+ this._setPromisified(xpcomBlocker, moduleBlocker);
+ }
+
+ this._moduleClient.addBlocker(xpcomBlocker.name,
+ moduleBlocker,
+ {
+ fetchState: () => {
+ let state = xpcomBlocker.state;
+ if (state) {
+ return PropertyBagConverter.toValue(state);
+ }
+ return null;
+ },
+ filename: fileName,
+ lineNumber: lineNumber,
+ stack: stack,
+ });
+ },
+
+ removeBlocker: function(xpcomBlocker) {
+ let moduleBlocker = this._getPromisified(xpcomBlocker);
+ if (!moduleBlocker) {
+ return false;
+ }
+ this._deletePromisified(xpcomBlocker);
+ return this._moduleClient.removeBlocker(moduleBlocker);
+ },
+
+ /* ........ QueryInterface .............. */
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAsyncShutdownBarrier]),
+ classID: Components.ID("{314e9e96-cc37-4d5c-843b-54709ce11426}"),
+};
+
+/**
+ * Construct an instance of nsIAsyncShutdownBarrier from an instance
+ * of AsyncShutdown.Barrier.
+ *
+ * @param {object} moduleBarrier an instance if
+ * `AsyncShutdown.Barrier`. This instance will serve as back-end for
+ * all methods.
+ * @constructor
+ */
+function nsAsyncShutdownBarrier(moduleBarrier) {
+ this._client = new nsAsyncShutdownClient(moduleBarrier.client);
+ this._moduleBarrier = moduleBarrier;
+}
+nsAsyncShutdownBarrier.prototype = {
+ get state() {
+ return PropertyBagConverter.fromValue(this._moduleBarrier.state);
+ },
+ get client() {
+ return this._client;
+ },
+ wait: function(onReady) {
+ this._moduleBarrier.wait().then(() => {
+ onReady.done();
+ });
+ // By specification, _moduleBarrier.wait() cannot reject.
+ },
+
+ /* ........ QueryInterface .............. */
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAsyncShutdownBarrier]),
+ classID: Components.ID("{29a0e8b5-9111-4c09-a0eb-76cd02bf20fa}"),
+};
+
+function nsAsyncShutdownService() {
+ // Cache for the getters
+
+ for (let _k of
+ [// Parent process
+ "profileBeforeChange",
+ "profileChangeTeardown",
+ "quitApplicationGranted",
+ "sendTelemetry",
+
+ // Child processes
+ "contentChildShutdown",
+
+ // All processes
+ "webWorkersShutdown",
+ "xpcomWillShutdown",
+ ]) {
+ let k = _k;
+ Object.defineProperty(this, k, {
+ configurable: true,
+ get: function() {
+ delete this[k];
+ let wrapped = AsyncShutdown[k]; // May be undefined, if we're on the wrong process.
+ let result = wrapped ? new nsAsyncShutdownClient(wrapped) : undefined;
+ Object.defineProperty(this, k, {
+ value: result
+ });
+ return result;
+ }
+ });
+ }
+
+ // Hooks for testing purpose
+ this.wrappedJSObject = {
+ _propertyBagConverter: PropertyBagConverter
+ };
+}
+nsAsyncShutdownService.prototype = {
+ makeBarrier: function(name) {
+ return new nsAsyncShutdownBarrier(new AsyncShutdown.Barrier(name));
+ },
+
+ /* ........ QueryInterface .............. */
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAsyncShutdownService]),
+ classID: Components.ID("{35c496de-a115-475d-93b5-ffa3f3ae6fe3}"),
+};
+
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
+ nsAsyncShutdownService,
+ nsAsyncShutdownBarrier,
+ nsAsyncShutdownClient,
+]);