diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm index 2a4abd92af6..2733f07b4b3 100644 --- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -81,6 +81,7 @@ ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_requ Cu.import("resource://gre/modules/ExtensionUtils.jsm"); var { + BaseContext, LocaleData, MessageBroker, Messenger, @@ -218,63 +219,45 @@ var gContextId = 0; // |uri| is the URI of the content (optional). // |docShell| is the docshell the content runs in (optional). // |incognito| is the content running in a private context (default: false). -ExtensionPage = function(extension, params) { - let {type, contentWindow, uri} = params; - this.extension = extension; - this.type = type; - this.contentWindow = contentWindow || null; - this.uri = uri || extension.baseURI; - this.incognito = params.incognito || false; - this.onClose = new Set(); - this.contextId = gContextId++; - this.unloaded = false; +ExtensionPage = class extends BaseContext { + constructor(extension, params) { + super(); - // This is the MessageSender property passed to extension. - // It can be augmented by the "page-open" hook. - let sender = {id: extension.uuid}; - if (uri) { - sender.url = uri.spec; + let {type, contentWindow, uri} = params; + this.extension = extension; + this.type = type; + this.contentWindow = contentWindow || null; + this.uri = uri || extension.baseURI; + this.incognito = params.incognito || false; + this.contextId = gContextId++; + this.unloaded = false; + + // This is the MessageSender property passed to extension. + // It can be augmented by the "page-open" hook. + let sender = {id: extension.uuid}; + if (uri) { + sender.url = uri.spec; + } + let delegate = { + getSender() {}, + }; + Management.emit("page-load", this, params, sender, delegate); + + // Properties in |filter| must match those in the |recipient| + // parameter of sendMessage. + let filter = {extensionId: extension.id}; + this.messenger = new Messenger(this, globalBroker, sender, filter, delegate); + + this.extension.views.add(this); } - let delegate = { - getSender() {}, - }; - Management.emit("page-load", this, params, sender, delegate); - // Properties in |filter| must match those in the |recipient| - // parameter of sendMessage. - let filter = {extensionId: extension.id}; - this.messenger = new Messenger(this, globalBroker, sender, filter, delegate); - - this.extension.views.add(this); -}; - -ExtensionPage.prototype = { get cloneScope() { return this.contentWindow; - }, + } get principal() { return this.contentWindow.document.nodePrincipal; - }, - - checkLoadURL(url, options = {}) { - let ssm = Services.scriptSecurityManager; - - let flags = ssm.STANDARD; - if (!options.allowScript) { - flags |= ssm.DISALLOW_SCRIPT; - } - if (!options.allowInheritsPrincipal) { - flags |= ssm.DISALLOW_INHERIT_PRINCIPAL; - } - - try { - ssm.checkLoadURIStrWithPrincipal(this.principal, url, flags); - } catch (e) { - return false; - } - return true; - }, + } // A wrapper around MessageChannel.sendMessage which adds the extension ID // to the recipient object, and ensures replies are not processed after the @@ -285,21 +268,13 @@ ExtensionPage.prototype = { sender.contextId = this.contextId; return MessageChannel.sendMessage(target, messageName, data, recipient, sender); - }, - - callOnClose(obj) { - this.onClose.add(obj); - }, - - forgetOnClose(obj) { - this.onClose.delete(obj); - }, + } // Called when the extension shuts down. shutdown() { Management.emit("page-shutdown", this); this.unload(); - }, + } // This method is called when an extension page navigates away or // its tab is closed. @@ -322,10 +297,8 @@ ExtensionPage.prototype = { this.extension.views.delete(this); - for (let obj of this.onClose) { - obj.close(); - } - }, + super.unload(); + } }; // Responsible for loading extension APIs into the right globals. diff --git a/toolkit/components/extensions/ExtensionContent.jsm b/toolkit/components/extensions/ExtensionContent.jsm index 96f9fb51258..13e64e08f23 100644 --- a/toolkit/components/extensions/ExtensionContent.jsm +++ b/toolkit/components/extensions/ExtensionContent.jsm @@ -37,6 +37,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel", Cu.import("resource://gre/modules/ExtensionUtils.jsm"); var { runSafeSyncWithoutClone, + BaseContext, LocaleData, MessageBroker, Messenger, @@ -227,109 +228,103 @@ var ExtensionManager; // Scope in which extension content script code can run. It uses // Cu.Sandbox to run the code. There is a separate scope for each // frame. -function ExtensionContext(extensionId, contentWindow, contextOptions = {}) { - let { isExtensionPage } = contextOptions; +class ExtensionContext extends BaseContext { + constructor(extensionId, contentWindow, contextOptions = {}) { + super(); - this.isExtensionPage = isExtensionPage; - this.extension = ExtensionManager.get(extensionId); - this.extensionId = extensionId; - this.contentWindow = contentWindow; + let { isExtensionPage } = contextOptions; - this.onClose = new Set(); + this.isExtensionPage = isExtensionPage; + this.extension = ExtensionManager.get(extensionId); + this.extensionId = extensionId; + this.contentWindow = contentWindow; - let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let outerWindowId = utils.outerWindowID; - let frameId = contentWindow == contentWindow.top ? 0 : outerWindowId; - this.frameId = frameId; + let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + let outerWindowId = utils.outerWindowID; + let frameId = contentWindow == contentWindow.top ? 0 : outerWindowId; + this.frameId = frameId; - let mm = getWindowMessageManager(contentWindow); - this.messageManager = mm; + let mm = getWindowMessageManager(contentWindow); + this.messageManager = mm; + + let prin; + let contentPrincipal = contentWindow.document.nodePrincipal; + let ssm = Services.scriptSecurityManager; - let prin; - let contentPrincipal = contentWindow.document.nodePrincipal; - let ssm = Services.scriptSecurityManager; - if (ssm.isSystemPrincipal(contentPrincipal)) { - // Make sure we don't hand out the system principal by accident. - prin = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal); - } else { let extensionPrincipal = ssm.createCodebasePrincipal(this.extension.baseURI, {addonId: extensionId}); - prin = [contentPrincipal, extensionPrincipal]; - } + Object.defineProperty(this, "principal", + {value: extensionPrincipal, enumerable: true, configurable: true}); - if (isExtensionPage) { - if (ExtensionManagement.getAddonIdForWindow(this.contentWindow) != extensionId) { - throw new Error("Invalid target window for this extension context"); + if (ssm.isSystemPrincipal(contentPrincipal)) { + // Make sure we don't hand out the system principal by accident. + prin = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal); + } else { + prin = [contentPrincipal, extensionPrincipal]; + } + + if (isExtensionPage) { + if (ExtensionManagement.getAddonIdForWindow(this.contentWindow) != extensionId) { + throw new Error("Invalid target window for this extension context"); + } + // This is an iframe with content script API enabled and its principal should be the + // contentWindow itself. (we create a sandbox with the contentWindow as principal and with X-rays disabled + // because it enables us to create the APIs object in this sandbox object and then copying it + // into the iframe's window, see Bug 1214658 for rationale) + this.sandbox = Cu.Sandbox(contentWindow, { + sandboxPrototype: contentWindow, + wantXrays: false, + isWebExtensionContentScript: true, + }); + } else { + this.sandbox = Cu.Sandbox(prin, { + sandboxPrototype: contentWindow, + wantXrays: true, + isWebExtensionContentScript: true, + wantGlobalProperties: ["XMLHttpRequest"], + }); + } + + let delegate = { + getSender(context, target, sender) { + // Nothing to do here. + }, + }; + + let url = contentWindow.location.href; + let broker = ExtensionContent.getBroker(mm); + // The |sender| parameter is passed directly to the extension. + let sender = {id: this.extension.uuid, frameId, url}; + // Properties in |filter| must match those in the |recipient| + // parameter of sendMessage. + let filter = {extensionId, frameId}; + this.messenger = new Messenger(this, broker, sender, filter, delegate); + + this.chromeObj = Cu.createObjectIn(this.sandbox, {defineAs: "browser"}); + + // Sandboxes don't get Xrays for some weird compatibility + // reason. However, we waive here anyway in case that changes. + Cu.waiveXrays(this.sandbox).chrome = this.chromeObj; + + injectAPI(api(this), this.chromeObj); + + // This is an iframe with content script API enabled. (See Bug 1214658 for rationale) + if (isExtensionPage) { + Cu.waiveXrays(this.contentWindow).chrome = this.chromeObj; + Cu.waiveXrays(this.contentWindow).browser = this.chromeObj; } - // This is an iframe with content script API enabled and its principal should be the - // contentWindow itself. (we create a sandbox with the contentWindow as principal and with X-rays disabled - // because it enables us to create the APIs object in this sandbox object and then copying it - // into the iframe's window, see Bug 1214658 for rationale) - this.sandbox = Cu.Sandbox(contentWindow, { - sandboxPrototype: contentWindow, - wantXrays: false, - isWebExtensionContentScript: true, - }); - } else { - this.sandbox = Cu.Sandbox(prin, { - sandboxPrototype: contentWindow, - wantXrays: true, - isWebExtensionContentScript: true, - wantGlobalProperties: ["XMLHttpRequest"], - }); } - let delegate = { - getSender(context, target, sender) { - // Nothing to do here. - }, - }; - - let url = contentWindow.location.href; - let broker = ExtensionContent.getBroker(mm); - // The |sender| parameter is passed directly to the extension. - let sender = {id: this.extension.uuid, frameId, url}; - // Properties in |filter| must match those in the |recipient| - // parameter of sendMessage. - let filter = {extensionId, frameId}; - this.messenger = new Messenger(this, broker, sender, filter, delegate); - - this.chromeObj = Cu.createObjectIn(this.sandbox, {defineAs: "browser"}); - - // Sandboxes don't get Xrays for some weird compatibility - // reason. However, we waive here anyway in case that changes. - Cu.waiveXrays(this.sandbox).chrome = this.chromeObj; - - injectAPI(api(this), this.chromeObj); - - // This is an iframe with content script API enabled. (See Bug 1214658 for rationale) - if (isExtensionPage) { - Cu.waiveXrays(this.contentWindow).chrome = this.chromeObj; - Cu.waiveXrays(this.contentWindow).browser = this.chromeObj; - } -} - -ExtensionContext.prototype = { get cloneScope() { return this.sandbox; - }, + } execute(script, shouldRun) { script.tryInject(this.extension, this.contentWindow, this.sandbox, shouldRun); - }, - - callOnClose(obj) { - this.onClose.add(obj); - }, - - forgetOnClose(obj) { - this.onClose.delete(obj); - }, + } close() { - for (let obj of this.onClose) { - obj.close(); - } + super.unload(); // Overwrite the content script APIs with an empty object if the APIs objects are still // defined in the content window (See Bug 1214658 for rationale). @@ -338,11 +333,10 @@ ExtensionContext.prototype = { Cu.createObjectIn(this.contentWindow, { defineAs: "browser" }); Cu.createObjectIn(this.contentWindow, { defineAs: "chrome" }); } - Cu.nukeSandbox(this.sandbox); this.sandbox = null; - }, -}; + } +} function windowId(window) { return window.QueryInterface(Ci.nsIInterfaceRequestor) diff --git a/toolkit/components/extensions/ExtensionUtils.jsm b/toolkit/components/extensions/ExtensionUtils.jsm index 0a6269987ac..cf2d4d88bdc 100644 --- a/toolkit/components/extensions/ExtensionUtils.jsm +++ b/toolkit/components/extensions/ExtensionUtils.jsm @@ -112,6 +112,53 @@ DefaultWeakMap.prototype = { }, }; +class BaseContext { + constructor() { + this.onClose = new Set(); + } + + get cloneScope() { + throw new Error("Not implemented"); + } + + get principal() { + throw new Error("Not implemented"); + } + + checkLoadURL(url, options = {}) { + let ssm = Services.scriptSecurityManager; + + let flags = ssm.STANDARD; + if (!options.allowScript) { + flags |= ssm.DISALLOW_SCRIPT; + } + if (!options.allowInheritsPrincipal) { + flags |= ssm.DISALLOW_INHERIT_PRINCIPAL; + } + + try { + ssm.checkLoadURIStrWithPrincipal(this.principal, url, flags); + } catch (e) { + return false; + } + return true; + } + + callOnClose(obj) { + this.onClose.add(obj); + } + + forgetOnClose(obj) { + this.onClose.delete(obj); + } + + unload() { + for (let obj of this.onClose) { + obj.close(); + } + } +} + function LocaleData(data) { this.defaultLocale = data.defaultLocale; this.selectedLocale = data.selectedLocale; @@ -786,6 +833,7 @@ this.ExtensionUtils = { runSafeSyncWithoutClone, runSafe, runSafeSync, + BaseContext, DefaultWeakMap, EventManager, LocaleData,