Bug 1464743: Restore extension child shutdown timeout. r?aswan
MozReview-Commit-ID: 8O6CgKsOwom
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -92,22 +92,26 @@ var {
ParentAPIManager,
StartupCache,
apiManager: Management,
} = ExtensionParent;
const {
EventEmitter,
getUniqueId,
+ promiseTimeout,
} = ExtensionUtils;
XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
XPCOMUtils.defineLazyGetter(this, "LocaleData", () => ExtensionCommon.LocaleData);
+// The maximum time to wait for extension child shutdown blockers to complete.
+const CHILD_SHUTDOWN_TIMEOUT_MS = 8000;
+
/**
* Classify an individual permission from a webextension manifest
* as a host/origin permission, an api permission, or a regular permission.
*
* @param {string} perm The permission string to classify
*
* @returns {object}
* An object with exactly one of the following properties:
@@ -1795,17 +1799,25 @@ class Extension extends ExtensionData {
obj.close();
}
ParentAPIManager.shutdownExtension(this.id);
Management.emit("shutdown", this);
this.emit("shutdown");
- await this.broadcast("Extension:Shutdown", {id: this.id});
+ const TIMED_OUT = Symbol();
+
+ let result = await Promise.race([
+ this.broadcast("Extension:Shutdown", {id: this.id}),
+ promiseTimeout(CHILD_SHUTDOWN_TIMEOUT_MS).then(() => TIMED_OUT),
+ ]);
+ if (result === TIMED_OUT) {
+ Cu.reportError(`Timeout while waiting for extension child to shutdown: ${this.policy.debugName}`);
+ }
MessageChannel.abortResponses({extensionId: this.id});
this.policy.active = false;
return this.cleanupGeneratedFile();
}
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -7,16 +7,18 @@
var EXPORTED_SYMBOLS = ["ExtensionUtils"];
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "ConsoleAPI",
"resource://gre/modules/Console.jsm");
+ChromeUtils.defineModuleGetter(this, "setTimeout",
+ "resource://gre/modules/Timer.jsm");
function getConsole() {
return new ConsoleAPI({
maxLogLevelPref: "extensions.webextensions.log.level",
prefix: "WebExtensions",
});
}
@@ -41,16 +43,19 @@ const uniqueProcessID = appinfo.uniquePr
const processIDMask = (uniqueProcessID & 0xffff) * (2 ** 37);
function getUniqueId() {
// Note: We can't use bitwise ops here, since they truncate to a 32 bit
// integer and we need all 53 mantissa bits.
return processIDMask + nextId++;
}
+function promiseTimeout(delay) {
+ return new Promise(resolve => setTimeout(resolve, delay));
+}
/**
* An Error subclass for which complete error messages are always passed
* to extensions, rather than being interpreted as an unknown error.
*/
class ExtensionError extends Error {}
function filterStack(error) {
@@ -684,16 +689,17 @@ var ExtensionUtils = {
instanceOf,
makeWidgetId,
normalizeTime,
promiseDocumentIdle,
promiseDocumentLoaded,
promiseDocumentReady,
promiseEvent,
promiseObserved,
+ promiseTimeout,
runSafeSyncWithoutClone,
withHandlingUserInput,
DefaultMap,
DefaultWeakMap,
EventEmitter,
ExtensionError,
LimitedSet,
MessageManagerProxy,