Bug 1437864 - Save a copy of the Error and Promise globals from extension context before they can be redefined.
MozReview-Commit-ID: ALNJDiBwZL7
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -371,16 +371,35 @@ class BaseContext {
this.extension = extension;
this.jsonSandbox = null;
this.active = true;
this.incognito = null;
this.messageManager = null;
this.docShell = null;
this.contentWindow = null;
this.innerWindowID = 0;
+
+ // This two properties are assigned in ContentScriptContextChild subclass
+ // to keep a copy of the content script sandbox Error and Promise globals
+ // (which are used by the WebExtensions internals) before any extension
+ // content script code had any chance to redefine them.
+ this.cloneScopeError = null;
+ this.cloneScopePromise = null;
+ }
+
+ get Error() {
+ // Return the copy stored in the context instance (when the context is an instance of
+ // ContentScriptContextChild or the global from extension page window otherwise).
+ return this.cloneScopeError || this.cloneScope.Error;
+ }
+
+ get Promise() {
+ // Return the copy stored in the context instance (when the context is an instance of
+ // ContentScriptContextChild or the global from extension page window otherwise).
+ return this.cloneScopePromise || this.cloneScope.Promise;
}
setContentWindow(contentWindow) {
let {document, docShell} = contentWindow;
this.innerWindowID = getInnerWindowID(contentWindow);
this.messageManager = docShell.messageManager;
@@ -554,17 +573,17 @@ class BaseContext {
* clone scope, it is reported, and converted to an unexpected
* exception error.
*
* @param {Error|object} error
* @param {SavedFrame?} [caller]
* @returns {Error}
*/
normalizeError(error, caller) {
- if (error instanceof this.cloneScope.Error) {
+ if (error instanceof this.Error) {
return error;
}
let message, fileName;
if (error && typeof error === "object") {
const isPlain = ChromeUtils.getClassName(error) === "Object";
if (isPlain && error.mozWebExtLocation) {
caller = error.mozWebExtLocation;
}
@@ -580,17 +599,17 @@ class BaseContext {
fileName = error.fileName;
}
}
if (!message) {
Cu.reportError(error);
message = "An unexpected error occurred";
}
- return new this.cloneScope.Error(message, fileName);
+ return new this.Error(message, fileName);
}
/**
* Sets the value of `.lastError` to `error`, calls the given
* callback, and reports an error if the value has not been checked
* when the callback returns.
*
* @param {object} error An object with a `message` property. May
@@ -680,17 +699,17 @@ class BaseContext {
Cu.reportError(`Promise rejected while context is inactive\n`,
caller);
} else {
this.applySafeWithoutClone(callback, [], caller);
}
});
});
} else {
- return new this.cloneScope.Promise((resolve, reject) => {
+ return new this.Promise((resolve, reject) => {
promise.then(
value => {
if (this.unloaded) {
Cu.reportError(`Promise resolved after context unloaded\n`,
caller);
} else if (!this.active) {
Cu.reportError(`Promise resolved while context is inactive\n`,
caller);
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -667,16 +667,22 @@ class ContentScriptContextChild extends
sameZoneAs: contentWindow,
wantXrays: true,
isWebExtensionContentScript: true,
wantExportHelpers: true,
wantGlobalProperties: ["XMLHttpRequest", "fetch"],
originAttributes: attrs,
});
+ // Preserve a copy of the original Error and Promise globals from the sandbox object,
+ // which are used in the WebExtensions internals (before any content script code had
+ // any chance to redefine them).
+ this.cloneScopePromise = this.sandbox.Promise;
+ this.cloneScopeError = this.sandbox.Error;
+
// Preserve a copy of the original window's XMLHttpRequest and fetch
// in a content object (fetch is manually binded to the window
// to prevent it from raising a TypeError because content object is not
// a real window).
Cu.evalInSandbox(`
this.content = {
XMLHttpRequest: window.XMLHttpRequest,
fetch: window.fetch.bind(window),
--- a/toolkit/components/extensions/child/ext-test.js
+++ b/toolkit/components/extensions/child/ext-test.js
@@ -70,17 +70,17 @@ const toSource = value => {
}
};
this.test = class extends ExtensionAPI {
getAPI(context) {
const {extension} = context;
function getStack() {
- return new context.cloneScope.Error().stack.replace(/^/gm, " ");
+ return new context.Error().stack.replace(/^/gm, " ");
}
function assertTrue(value, msg) {
extension.emit("test-result", Boolean(value), String(msg), getStack());
}
class TestEventManager extends EventManager {
addListener(callback, ...args) {