2014-09-11 13:01:00 +00:00
|
|
|
/* 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;
|
|
|
|
|
2015-09-15 18:19:45 +00:00
|
|
|
var XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}).XPCOMUtils;
|
2014-09-11 13:01:00 +00:00
|
|
|
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
|
|
|
|
*/
|
2015-09-15 18:19:45 +00:00
|
|
|
var PropertyBagConverter = {
|
2014-09-11 13:01:00 +00:00
|
|
|
// 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
|
|
|
|
["profileBeforeChange",
|
|
|
|
"profileChangeTeardown",
|
|
|
|
"sendTelemetry",
|
|
|
|
"webWorkersShutdown",
|
|
|
|
"xpcomThreadsShutdown"]) {
|
|
|
|
let k = _k;
|
|
|
|
Object.defineProperty(this, k, {
|
|
|
|
configurable: true,
|
|
|
|
get: function() {
|
|
|
|
delete this[k];
|
|
|
|
let result = new nsAsyncShutdownClient(AsyncShutdown[k]);
|
|
|
|
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,
|
|
|
|
]);
|
|
|
|
|