mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-16 04:07:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
1041
toolkit/components/asyncshutdown/AsyncShutdown.jsm
Normal file
1041
toolkit/components/asyncshutdown/AsyncShutdown.jsm
Normal file
File diff suppressed because it is too large
Load diff
25
toolkit/components/asyncshutdown/moz.build
Normal file
25
toolkit/components/asyncshutdown/moz.build
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
|
||||
|
||||
XPIDL_MODULE = 'toolkit_asyncshutdown'
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIAsyncShutdown.idl',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'AsyncShutdown.jsm',
|
||||
]
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'nsAsyncShutdown.js',
|
||||
'nsAsyncShutdown.manifest',
|
||||
]
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Toolkit', 'Async Tooling')
|
||||
277
toolkit/components/asyncshutdown/nsAsyncShutdown.js
Normal file
277
toolkit/components/asyncshutdown/nsAsyncShutdown.js
Normal file
|
|
@ -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,
|
||||
]);
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
component {35c496de-a115-475d-93b5-ffa3f3ae6fe3} nsAsyncShutdown.js
|
||||
contract @mozilla.org/async-shutdown-service;1 {35c496de-a115-475d-93b5-ffa3f3ae6fe3}
|
||||
220
toolkit/components/asyncshutdown/nsIAsyncShutdown.idl
Normal file
220
toolkit/components/asyncshutdown/nsIAsyncShutdown.idl
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* A mechanism for specifying shutdown dependencies between
|
||||
* asynchronous services.
|
||||
*
|
||||
* Note that this XPCOM component is designed primarily for C++
|
||||
* clients. JavaScript clients should rather use AsyncShutdown.jsm,
|
||||
* which provides a better API and better error reporting for them.
|
||||
*/
|
||||
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "nsIPropertyBag.idl"
|
||||
#include "nsIVariant.idl"
|
||||
|
||||
interface nsIAsyncShutdownClient;
|
||||
|
||||
/**
|
||||
* A blocker installed by a client to be informed during some stage of
|
||||
* shutdown and block shutdown asynchronously until some condition is
|
||||
* complete.
|
||||
*
|
||||
* If you wish to use AsyncShutdown, you will need to implement this
|
||||
* interface (and only this interface).
|
||||
*/
|
||||
[scriptable, uuid(4ef43f29-6715-4b57-a750-2ff83695ddce)]
|
||||
interface nsIAsyncShutdownBlocker: nsISupports {
|
||||
/**
|
||||
* The *unique* name of the blocker.
|
||||
*
|
||||
* By convention, it should respect the following format:
|
||||
* "MyModuleName: Doing something while it's time"
|
||||
* e.g.
|
||||
* "OS.File: Flushing before profile-before-change"
|
||||
*
|
||||
* This attribute is uploaded as part of crash reports.
|
||||
*/
|
||||
readonly attribute AString name;
|
||||
|
||||
/**
|
||||
* Inform the blocker that the stage of shutdown has started.
|
||||
* Shutdown will NOT proceed until `aBarrierClient.removeBlocker(this)`
|
||||
* has been called.
|
||||
*/
|
||||
void blockShutdown(in nsIAsyncShutdownClient aBarrierClient);
|
||||
|
||||
/**
|
||||
* The current state of the blocker.
|
||||
*
|
||||
* In case of crash, this is converted to JSON and attached to
|
||||
* the crash report.
|
||||
*
|
||||
* This field may be used to provide JSON-style data structures.
|
||||
* For this purpose, use
|
||||
* - nsIPropertyBag to represent objects;
|
||||
* - nsIVariant to represent field values (which may hold nsIPropertyBag
|
||||
* themselves).
|
||||
*/
|
||||
readonly attribute nsIPropertyBag state;
|
||||
};
|
||||
|
||||
/**
|
||||
* A client for a nsIAsyncShutdownBarrier.
|
||||
*/
|
||||
[scriptable, uuid(d2031049-b990-43a2-95be-59f8a3ca5954)]
|
||||
interface nsIAsyncShutdownClient: nsISupports {
|
||||
/**
|
||||
* The name of the barrier.
|
||||
*/
|
||||
readonly attribute AString name;
|
||||
|
||||
/**
|
||||
* Add a blocker.
|
||||
*
|
||||
* After a `blocker` has been added with `addBlocker`, if it is not
|
||||
* removed with `removeBlocker`, this will, by design, eventually
|
||||
* CAUSE A CRASH.
|
||||
*
|
||||
* Calling `addBlocker` once nsIAsyncShutdownBarrier::wait() has been
|
||||
* called on the owning barrier returns an error.
|
||||
*
|
||||
* @param aBlocker The blocker to add. Once
|
||||
* nsIAsyncShutdownBarrier::wait() has been called, it will not
|
||||
* call its `aOnReady` callback until all blockers have been
|
||||
* removed, each by a call to `removeBlocker`.
|
||||
* @param aFileName The filename of the callsite, as given by `__FILE__`.
|
||||
* @param aLineNumber The linenumber of the callsite, as given by `__LINE__`.
|
||||
* @param aStack Information on the stack that lead to this call. Generally
|
||||
* empty when called from C++.
|
||||
*/
|
||||
void addBlocker(in nsIAsyncShutdownBlocker aBlocker,
|
||||
in AString aFileName,
|
||||
in long aLineNumber,
|
||||
in AString aStack);
|
||||
|
||||
/**
|
||||
* Remove a blocker.
|
||||
*
|
||||
* @param aBlocker A blocker previously added to this client through
|
||||
* `addBlocker`. Noop if the blocker has never been added or has been
|
||||
* removed already.
|
||||
*/
|
||||
void removeBlocker(in nsIAsyncShutdownBlocker aBlocker);
|
||||
|
||||
/**
|
||||
* The JS implementation of the client.
|
||||
*
|
||||
* It is strongly recommended that JS clients of this API use
|
||||
* `jsclient` instead of the `nsIAsyncShutdownClient`. See
|
||||
* AsyncShutdown.jsm for more information on the JS version of
|
||||
* this API.
|
||||
*/
|
||||
readonly attribute jsval jsclient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback invoked once all blockers of a barrier have been removed.
|
||||
*/
|
||||
[scriptable, function, uuid(910c9309-1da0-4dd0-8bdb-a325a38c604e)]
|
||||
interface nsIAsyncShutdownCompletionCallback: nsISupports {
|
||||
/**
|
||||
* The operation has been completed.
|
||||
*/
|
||||
void done();
|
||||
};
|
||||
|
||||
/**
|
||||
* A stage of shutdown that supports blocker registration.
|
||||
*/
|
||||
[scriptable, uuid(50fa8a86-9c91-4256-8389-17d310adec90)]
|
||||
interface nsIAsyncShutdownBarrier: nsISupports {
|
||||
|
||||
/**
|
||||
* The blocker registration capability. Most services may wish to
|
||||
* publish this capability to let services that depend on it register
|
||||
* blockers.
|
||||
*/
|
||||
readonly attribute nsIAsyncShutdownClient client;
|
||||
|
||||
/**
|
||||
* The state of all the blockers of the barrier.
|
||||
*
|
||||
* See the documentation of `nsIAsyncShutdownBlocker` for the
|
||||
* format.
|
||||
*/
|
||||
readonly attribute nsIPropertyBag state;
|
||||
|
||||
/**
|
||||
* Wait for all blockers to complete.
|
||||
*
|
||||
* Method `aOnReady` will be called once all blockers have finished.
|
||||
* The callback always receives NS_OK.
|
||||
*/
|
||||
void wait(in nsIAsyncShutdownCompletionCallback aOnReady);
|
||||
};
|
||||
|
||||
/**
|
||||
* A service that allows registering shutdown-time dependencies.
|
||||
*/
|
||||
[scriptable, uuid(db365c78-c860-4e64-9a63-25b73f89a016)]
|
||||
interface nsIAsyncShutdownService: nsISupports {
|
||||
/**
|
||||
* Create a new barrier.
|
||||
*
|
||||
* By convention, the name should respect the following format:
|
||||
* "MyModuleName: Doing something while it's time"
|
||||
* e.g.
|
||||
* "OS.File: Waiting for clients to flush before shutting down"
|
||||
*
|
||||
* This attribute is uploaded as part of crash reports.
|
||||
*/
|
||||
nsIAsyncShutdownBarrier makeBarrier(in AString aName);
|
||||
|
||||
|
||||
// Barriers for global shutdown stages in the parent process.
|
||||
|
||||
/**
|
||||
* Barrier for notification profile-before-change.
|
||||
*/
|
||||
readonly attribute nsIAsyncShutdownClient profileBeforeChange;
|
||||
|
||||
/**
|
||||
* Barrier for notification profile-change-teardown.
|
||||
*/
|
||||
readonly attribute nsIAsyncShutdownClient profileChangeTeardown;
|
||||
|
||||
/**
|
||||
* Barrier for notification quit-application-granted.
|
||||
*/
|
||||
readonly attribute nsIAsyncShutdownClient quitApplicationGranted;
|
||||
|
||||
/**
|
||||
* Barrier for notification profile-before-change-telemetry.
|
||||
*/
|
||||
readonly attribute nsIAsyncShutdownClient sendTelemetry;
|
||||
|
||||
|
||||
// Barriers for global shutdown stages in all processes.
|
||||
|
||||
/**
|
||||
* Barrier for notification web-workers-shutdown.
|
||||
*/
|
||||
readonly attribute nsIAsyncShutdownClient webWorkersShutdown;
|
||||
|
||||
/**
|
||||
* Barrier for notification xpcom-will-shutdown.
|
||||
*/
|
||||
readonly attribute nsIAsyncShutdownClient xpcomWillShutdown;
|
||||
|
||||
// Don't add a barrier for content-child-shutdown because this
|
||||
// makes it easier to cause shutdown hangs.
|
||||
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_ASYNCSHUTDOWNSERVICE_CONTRACTID "@mozilla.org/async-shutdown-service;1"
|
||||
%}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/xpcshell/xpcshell.eslintrc.js"
|
||||
]
|
||||
};
|
||||
174
toolkit/components/asyncshutdown/tests/xpcshell/head.js
Normal file
174
toolkit/components/asyncshutdown/tests/xpcshell/head.js
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
var Cu = Components.utils;
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
|
||||
|
||||
var asyncShutdownService = Cc["@mozilla.org/async-shutdown-service;1"].
|
||||
getService(Ci.nsIAsyncShutdownService);
|
||||
|
||||
|
||||
Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
|
||||
|
||||
/**
|
||||
* Utility function used to provide the same API for various sources
|
||||
* of async shutdown barriers.
|
||||
*
|
||||
* @param {string} kind One of
|
||||
* - "phase" to test an AsyncShutdown phase;
|
||||
* - "barrier" to test an instance of AsyncShutdown.Barrier;
|
||||
* - "xpcom-barrier" to test an instance of nsIAsyncShutdownBarrier;
|
||||
* - "xpcom-barrier-unwrapped" to test the field `jsclient` of a nsIAsyncShutdownClient.
|
||||
*
|
||||
* @return An object with the following methods:
|
||||
* - addBlocker() - the same method as AsyncShutdown phases and barrier clients
|
||||
* - wait() - trigger the resolution of the lock
|
||||
*/
|
||||
function makeLock(kind) {
|
||||
if (kind == "phase") {
|
||||
let topic = "test-Phase-" + ++makeLock.counter;
|
||||
let phase = AsyncShutdown._getPhase(topic);
|
||||
return {
|
||||
addBlocker: function(...args) {
|
||||
return phase.addBlocker(...args);
|
||||
},
|
||||
removeBlocker: function(blocker) {
|
||||
return phase.removeBlocker(blocker);
|
||||
},
|
||||
wait: function() {
|
||||
Services.obs.notifyObservers(null, topic, null);
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
} else if (kind == "barrier") {
|
||||
let name = "test-Barrier-" + ++makeLock.counter;
|
||||
let barrier = new AsyncShutdown.Barrier(name);
|
||||
return {
|
||||
addBlocker: barrier.client.addBlocker,
|
||||
removeBlocker: barrier.client.removeBlocker,
|
||||
wait: function() {
|
||||
return barrier.wait();
|
||||
}
|
||||
};
|
||||
} else if (kind == "xpcom-barrier") {
|
||||
let name = "test-xpcom-Barrier-" + ++makeLock.counter;
|
||||
let barrier = asyncShutdownService.makeBarrier(name);
|
||||
return {
|
||||
addBlocker: function(blockerName, condition, state) {
|
||||
if (condition == null) {
|
||||
// Slight trick as `null` or `undefined` cannot be used as keys
|
||||
// for `xpcomMap`. Note that this has no incidence on the result
|
||||
// of the test as the XPCOM interface imposes that the condition
|
||||
// is a method, so it cannot be `null`/`undefined`.
|
||||
condition = "<this case can't happen with the xpcom interface>";
|
||||
}
|
||||
let blocker = makeLock.xpcomMap.get(condition);
|
||||
if (!blocker) {
|
||||
blocker = {
|
||||
name: blockerName,
|
||||
state: state,
|
||||
blockShutdown: function(aBarrierClient) {
|
||||
return Task.spawn(function*() {
|
||||
try {
|
||||
if (typeof condition == "function") {
|
||||
yield Promise.resolve(condition());
|
||||
} else {
|
||||
yield Promise.resolve(condition);
|
||||
}
|
||||
} finally {
|
||||
aBarrierClient.removeBlocker(blocker);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
makeLock.xpcomMap.set(condition, blocker);
|
||||
}
|
||||
let {fileName, lineNumber, stack} = (new Error());
|
||||
return barrier.client.addBlocker(blocker, fileName, lineNumber, stack);
|
||||
},
|
||||
removeBlocker: function(condition) {
|
||||
let blocker = makeLock.xpcomMap.get(condition);
|
||||
if (!blocker) {
|
||||
return;
|
||||
}
|
||||
barrier.client.removeBlocker(blocker);
|
||||
},
|
||||
wait: function() {
|
||||
return new Promise(resolve => {
|
||||
barrier.wait(resolve);
|
||||
});
|
||||
}
|
||||
};
|
||||
} else if ("unwrapped-xpcom-barrier") {
|
||||
let name = "unwrapped-xpcom-barrier-" + ++makeLock.counter;
|
||||
let barrier = asyncShutdownService.makeBarrier(name);
|
||||
let client = barrier.client.jsclient;
|
||||
return {
|
||||
addBlocker: client.addBlocker,
|
||||
removeBlocker: client.removeBlocker,
|
||||
wait: function() {
|
||||
return new Promise(resolve => {
|
||||
barrier.wait(resolve);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
throw new TypeError("Unknown kind " + kind);
|
||||
}
|
||||
makeLock.counter = 0;
|
||||
makeLock.xpcomMap = new Map(); // Note: Not a WeakMap as we wish to handle non-gc-able keys (e.g. strings)
|
||||
|
||||
/**
|
||||
* An asynchronous task that takes several ticks to complete.
|
||||
*
|
||||
* @param {*=} resolution The value with which the resulting promise will be
|
||||
* resolved once the task is complete. This may be a rejected promise,
|
||||
* in which case the resulting promise will itself be rejected.
|
||||
* @param {object=} outResult An object modified by side-effect during the task.
|
||||
* Initially, its field |isFinished| is set to |false|. Once the task is
|
||||
* complete, its field |isFinished| is set to |true|.
|
||||
*
|
||||
* @return {promise} A promise fulfilled once the task is complete
|
||||
*/
|
||||
function longRunningAsyncTask(resolution = undefined, outResult = {}) {
|
||||
outResult.isFinished = false;
|
||||
if (!("countFinished" in outResult)) {
|
||||
outResult.countFinished = 0;
|
||||
}
|
||||
let deferred = Promise.defer();
|
||||
do_timeout(100, function() {
|
||||
++outResult.countFinished;
|
||||
outResult.isFinished = true;
|
||||
deferred.resolve(resolution);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function get_exn(f) {
|
||||
try {
|
||||
f();
|
||||
return null;
|
||||
} catch (ex) {
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
function do_check_exn(exn, constructor) {
|
||||
do_check_neq(exn, null);
|
||||
if (exn.name == constructor) {
|
||||
do_check_eq(exn.constructor.name, constructor);
|
||||
return;
|
||||
}
|
||||
do_print("Wrong error constructor");
|
||||
do_print(exn.constructor.name);
|
||||
do_print(exn.stack);
|
||||
do_check_true(false);
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/PromiseUtils.jsm", this);
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_no_condition() {
|
||||
for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {
|
||||
do_print("Testing a barrier with no condition (" + kind + ")");
|
||||
let lock = makeLock(kind);
|
||||
yield lock.wait();
|
||||
do_print("Barrier with no condition didn't lock");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_phase_various_failures() {
|
||||
for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {
|
||||
do_print("Kind: " + kind);
|
||||
// Testing with wrong arguments
|
||||
let lock = makeLock(kind);
|
||||
|
||||
Assert.throws(() => lock.addBlocker(), /TypeError|NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS/);
|
||||
Assert.throws(() => lock.addBlocker(null, true), /TypeError|NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS/);
|
||||
|
||||
if (kind != "xpcom-barrier") {
|
||||
// xpcom-barrier actually expects a string in that position
|
||||
Assert.throws(() => lock.addBlocker("Test 2", () => true, "not a function"), /TypeError/);
|
||||
}
|
||||
|
||||
// Attempting to add a blocker after we are done waiting
|
||||
yield lock.wait();
|
||||
Assert.throws(() => lock.addBlocker("Test 3", () => true), /is finished/);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_reentrant() {
|
||||
do_print("Ensure that we can call addBlocker from within a blocker");
|
||||
|
||||
for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {
|
||||
do_print("Kind: " + kind);
|
||||
let lock = makeLock(kind);
|
||||
|
||||
let deferredOuter = PromiseUtils.defer();
|
||||
let deferredInner = PromiseUtils.defer();
|
||||
let deferredBlockInner = PromiseUtils.defer();
|
||||
|
||||
lock.addBlocker("Outer blocker", () => {
|
||||
do_print("Entering outer blocker");
|
||||
deferredOuter.resolve();
|
||||
lock.addBlocker("Inner blocker", () => {
|
||||
do_print("Entering inner blocker");
|
||||
deferredInner.resolve();
|
||||
return deferredBlockInner.promise;
|
||||
});
|
||||
});
|
||||
|
||||
// Note that phase-style locks spin the event loop and do not return from
|
||||
// `lock.wait()` until after all blockers have been resolved. Therefore,
|
||||
// to be able to test them, we need to dispatch the following steps to the
|
||||
// event loop before calling `lock.wait()`, which we do by forcing
|
||||
// a Promise.resolve().
|
||||
//
|
||||
let promiseSteps = Task.spawn(function* () {
|
||||
yield Promise.resolve();
|
||||
|
||||
do_print("Waiting until we have entered the outer blocker");
|
||||
yield deferredOuter.promise;
|
||||
|
||||
do_print("Waiting until we have entered the inner blocker");
|
||||
yield deferredInner.promise;
|
||||
|
||||
do_print("Allowing the lock to resolve")
|
||||
deferredBlockInner.resolve();
|
||||
});
|
||||
|
||||
do_print("Starting wait");
|
||||
yield lock.wait();
|
||||
|
||||
do_print("Waiting until all steps have been walked");
|
||||
yield promiseSteps;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
add_task(function* test_phase_removeBlocker() {
|
||||
do_print("Testing that we can call removeBlocker before, during and after the call to wait()");
|
||||
|
||||
for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {
|
||||
|
||||
do_print("Switching to kind " + kind);
|
||||
do_print("Attempt to add then remove a blocker before wait()");
|
||||
let lock = makeLock(kind);
|
||||
let blocker = () => {
|
||||
do_print("This promise will never be resolved");
|
||||
return Promise.defer().promise;
|
||||
};
|
||||
|
||||
lock.addBlocker("Wait forever", blocker);
|
||||
let do_remove_blocker = function(aLock, aBlocker, aShouldRemove) {
|
||||
do_print("Attempting to remove blocker " + aBlocker + ", expecting result " + aShouldRemove);
|
||||
if (kind == "xpcom-barrier") {
|
||||
// The xpcom variant always returns `undefined`, so we can't
|
||||
// check its result.
|
||||
aLock.removeBlocker(aBlocker);
|
||||
return;
|
||||
}
|
||||
do_check_eq(aLock.removeBlocker(aBlocker), aShouldRemove);
|
||||
};
|
||||
do_remove_blocker(lock, blocker, true);
|
||||
do_remove_blocker(lock, blocker, false);
|
||||
do_print("Attempt to remove non-registered blockers before wait()");
|
||||
do_remove_blocker(lock, "foo", false);
|
||||
do_remove_blocker(lock, null, false);
|
||||
do_print("Waiting (should lift immediately)");
|
||||
yield lock.wait();
|
||||
|
||||
do_print("Attempt to add a blocker then remove it during wait()");
|
||||
lock = makeLock(kind);
|
||||
let blockers = [
|
||||
() => {
|
||||
do_print("This blocker will self-destruct");
|
||||
do_remove_blocker(lock, blockers[0], true);
|
||||
return Promise.defer().promise;
|
||||
},
|
||||
() => {
|
||||
do_print("This blocker will self-destruct twice");
|
||||
do_remove_blocker(lock, blockers[1], true);
|
||||
do_remove_blocker(lock, blockers[1], false);
|
||||
return Promise.defer().promise;
|
||||
},
|
||||
() => {
|
||||
do_print("Attempt to remove non-registered blockers during wait()");
|
||||
do_remove_blocker(lock, "foo", false);
|
||||
do_remove_blocker(lock, null, false);
|
||||
}
|
||||
];
|
||||
for (let i in blockers) {
|
||||
lock.addBlocker("Wait forever again: " + i, blockers[i]);
|
||||
}
|
||||
do_print("Waiting (should lift very quickly)");
|
||||
yield lock.wait();
|
||||
do_remove_blocker(lock, blockers[0], false);
|
||||
|
||||
|
||||
do_print("Attempt to remove a blocker after wait");
|
||||
lock = makeLock(kind);
|
||||
blocker = Promise.resolve.bind(Promise);
|
||||
yield lock.wait();
|
||||
do_remove_blocker(lock, blocker, false);
|
||||
|
||||
do_print("Attempt to remove non-registered blocker after wait()");
|
||||
do_remove_blocker(lock, "foo", false);
|
||||
do_remove_blocker(lock, null, false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
add_task(function* test_state() {
|
||||
do_print("Testing information contained in `state`");
|
||||
|
||||
let BLOCKER_NAME = "test_state blocker " + Math.random();
|
||||
|
||||
// Set up the barrier. Note that we cannot test `barrier.state`
|
||||
// immediately, as it initially contains "Not started"
|
||||
let barrier = new AsyncShutdown.Barrier("test_filename");
|
||||
let deferred = Promise.defer();
|
||||
let {filename, lineNumber} = Components.stack;
|
||||
barrier.client.addBlocker(BLOCKER_NAME,
|
||||
function() {
|
||||
return deferred.promise;
|
||||
});
|
||||
|
||||
let promiseDone = barrier.wait();
|
||||
|
||||
// Now that we have called `wait()`, the state contains interesting things
|
||||
let state = barrier.state[0];
|
||||
do_print("State: " + JSON.stringify(barrier.state, null, "\t"));
|
||||
Assert.equal(state.filename, filename);
|
||||
Assert.equal(state.lineNumber, lineNumber + 1);
|
||||
Assert.equal(state.name, BLOCKER_NAME);
|
||||
Assert.ok(state.stack.some(x => x.includes("test_state")), "The stack contains the caller function's name");
|
||||
Assert.ok(state.stack.some(x => x.includes(filename)), "The stack contains the calling file's name");
|
||||
|
||||
deferred.resolve();
|
||||
yield promiseDone;
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
Services.prefs.clearUserPref("toolkit.asyncshutdown.testing");
|
||||
});
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
//
|
||||
// This file contains tests that need to leave uncaught asynchronous
|
||||
// errors. If your test catches all its asynchronous errors, please
|
||||
// put it in another file.
|
||||
//
|
||||
|
||||
Promise.Debugging.clearUncaughtErrorObservers();
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_phase_simple_async() {
|
||||
do_print("Testing various combinations of a phase with a single condition");
|
||||
for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {
|
||||
for (let arg of [undefined, null, "foo", 100, new Error("BOOM")]) {
|
||||
for (let resolution of [arg, Promise.reject(arg)]) {
|
||||
for (let success of [false, true]) {
|
||||
for (let state of [[null],
|
||||
[],
|
||||
[() => "some state"],
|
||||
[function() {
|
||||
throw new Error("State BOOM"); }],
|
||||
[function() {
|
||||
return {
|
||||
toJSON: function() {
|
||||
throw new Error("State.toJSON BOOM");
|
||||
}
|
||||
};
|
||||
}]]) {
|
||||
// Asynchronous phase
|
||||
do_print("Asynchronous test with " + arg + ", " + resolution + ", " + kind);
|
||||
let lock = makeLock(kind);
|
||||
let outParam = { isFinished: false };
|
||||
lock.addBlocker(
|
||||
"Async test",
|
||||
function() {
|
||||
if (success) {
|
||||
return longRunningAsyncTask(resolution, outParam);
|
||||
}
|
||||
throw resolution;
|
||||
},
|
||||
...state
|
||||
);
|
||||
do_check_false(outParam.isFinished);
|
||||
yield lock.wait();
|
||||
do_check_eq(outParam.isFinished, success);
|
||||
}
|
||||
}
|
||||
|
||||
// Synchronous phase - just test that we don't throw/freeze
|
||||
do_print("Synchronous test with " + arg + ", " + resolution + ", " + kind);
|
||||
let lock = makeLock(kind);
|
||||
lock.addBlocker(
|
||||
"Sync test",
|
||||
resolution
|
||||
);
|
||||
yield lock.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_phase_many() {
|
||||
do_print("Testing various combinations of a phase with many conditions");
|
||||
for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {
|
||||
let lock = makeLock(kind);
|
||||
let outParams = [];
|
||||
for (let arg of [undefined, null, "foo", 100, new Error("BOOM")]) {
|
||||
for (let resolve of [true, false]) {
|
||||
do_print("Testing with " + kind + ", " + arg + ", " + resolve);
|
||||
let resolution = resolve ? arg : Promise.reject(arg);
|
||||
let outParam = { isFinished: false };
|
||||
lock.addBlocker(
|
||||
"Test " + Math.random(),
|
||||
() => longRunningAsyncTask(resolution, outParam)
|
||||
);
|
||||
}
|
||||
}
|
||||
do_check_true(outParams.every((x) => !x.isFinished));
|
||||
yield lock.wait();
|
||||
do_check_true(outParams.every((x) => x.isFinished));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
add_task(function*() {
|
||||
Services.prefs.clearUserPref("toolkit.asyncshutdown.testing");
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test conversion between nsIPropertyBag and JS values.
|
||||
*/
|
||||
|
||||
var PropertyBagConverter = asyncShutdownService.wrappedJSObject._propertyBagConverter;
|
||||
|
||||
function run_test() {
|
||||
test_conversions();
|
||||
}
|
||||
|
||||
function normalize(obj) {
|
||||
if (obj == null || typeof obj != "object") {
|
||||
return obj;
|
||||
}
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(normalize);
|
||||
}
|
||||
let result = {};
|
||||
for (let k of Object.keys(obj).sort()) {
|
||||
result[k] = normalize(obj[k]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function test_conversions() {
|
||||
const SAMPLES = [
|
||||
// Simple values
|
||||
1,
|
||||
true,
|
||||
"string",
|
||||
null,
|
||||
|
||||
// Objects
|
||||
{
|
||||
a: 1,
|
||||
b: true,
|
||||
c: "string",
|
||||
d:.5,
|
||||
e: [2, false, "another string", .3],
|
||||
f: [],
|
||||
g: {
|
||||
a2: 1,
|
||||
b2: true,
|
||||
c2: "string",
|
||||
d2:.5,
|
||||
e2: [2, false, "another string", .3],
|
||||
f2: [],
|
||||
g2: [{
|
||||
a3: 1,
|
||||
b3: true,
|
||||
c3: "string",
|
||||
d3:.5,
|
||||
e3: [2, false, "another string", .3],
|
||||
f3: [],
|
||||
g3: {}
|
||||
}]
|
||||
}
|
||||
}];
|
||||
|
||||
for (let sample of SAMPLES) {
|
||||
let stringified = JSON.stringify(normalize(sample), null, "\t");
|
||||
do_print("Testing conversions of " + stringified);
|
||||
let rewrites = [sample];
|
||||
for (let i = 1; i < 3; ++i) {
|
||||
let source = rewrites[i - 1];
|
||||
let bag = PropertyBagConverter.fromValue(source);
|
||||
do_print(" => " + bag);
|
||||
if (source == null) {
|
||||
Assert.ok(bag == null, "The bag is null");
|
||||
} else if (typeof source == "object") {
|
||||
Assert.ok(bag instanceof Ci.nsIPropertyBag, "The bag is a property bag");
|
||||
} else {
|
||||
Assert.ok(typeof bag != "object", "The bag is not an object");
|
||||
}
|
||||
let dest = PropertyBagConverter.toValue(bag);
|
||||
let restringified = JSON.stringify(normalize(dest), null, "\t");
|
||||
do_print("Comparing");
|
||||
do_print(stringified);
|
||||
do_print(restringified);
|
||||
Assert.deepEqual(sample, dest, "Testing after " + i + " conversions");
|
||||
rewrites.push(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
[DEFAULT]
|
||||
head=head.js
|
||||
tail=
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_AsyncShutdown.js]
|
||||
[test_AsyncShutdown_leave_uncaught.js]
|
||||
[test_converters.js]
|
||||
Loading…
Add table
Add a link
Reference in a new issue