diff options
Diffstat (limited to 'components/asyncshutdown/nsAsyncShutdown.js')
-rw-r--r-- | components/asyncshutdown/nsAsyncShutdown.js | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/components/asyncshutdown/nsAsyncShutdown.js b/components/asyncshutdown/nsAsyncShutdown.js new file mode 100644 index 000000000..70fea9076 --- /dev/null +++ b/components/asyncshutdown/nsAsyncShutdown.js @@ -0,0 +1,276 @@ +/* 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", + + // 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, +]); |