diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm index 3985cb0359c0..a38d13c39331 100644 --- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -363,16 +363,12 @@ GlobalManager = { Schemas.inject(chromeObj, schemaWrapper); }; - let id = ExtensionManagement.getAddonIdForWindow(contentWindow); - - // We don't inject privileged APIs into sub-frames of a UI page. - const { FULL_PRIVILEGES } = ExtensionManagement.API_LEVELS; - if (ExtensionManagement.getAPILevelForWindow(contentWindow, id) !== FULL_PRIVILEGES) { - return; - } - - // We don't inject privileged APIs if the addonId is null - // or doesn't exist. + // Find the add-on associated with this document via the + // principal's originAttributes. This value is computed by + // extensionURIToAddonID, which ensures that we don't inject our + // API into webAccessibleResources or remote web pages. + let principal = contentWindow.document.nodePrincipal; + let id = principal.originAttributes.addonId; if (!this.extensionMap.has(id)) { return; } @@ -391,6 +387,10 @@ GlobalManager = { return; } + // We don't inject into sub-frames of a UI page. + if (contentWindow != contentWindow.top) { + return; + } let extension = this.extensionMap.get(id); let uri = contentWindow.document.documentURIObject; let incognito = PrivateBrowsingUtils.isContentWindowPrivate(contentWindow); diff --git a/toolkit/components/extensions/ExtensionContent.jsm b/toolkit/components/extensions/ExtensionContent.jsm index 78a28d515b11..bcc2c958e3a3 100644 --- a/toolkit/components/extensions/ExtensionContent.jsm +++ b/toolkit/components/extensions/ExtensionContent.jsm @@ -216,10 +216,7 @@ 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; - - this.isExtensionPage = isExtensionPage; +function ExtensionContext(extensionId, contentWindow) { this.extension = ExtensionManager.get(extensionId); this.extensionId = extensionId; this.contentWindow = contentWindow; @@ -246,27 +243,12 @@ function ExtensionContext(extensionId, contentWindow, contextOptions = {}) { 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"], - }); - } + this.sandbox = Cu.Sandbox(prin, { + sandboxPrototype: contentWindow, + wantXrays: true, + isWebExtensionContentScript: true, + wantGlobalProperties: ["XMLHttpRequest"], + }); let delegate = { getSender(context, target, sender) { @@ -283,19 +265,12 @@ function ExtensionContext(extensionId, contentWindow, contextOptions = {}) { let filter = {extensionId, frameId}; this.messenger = new Messenger(this, broker, sender, filter, delegate); - this.chromeObj = Cu.createObjectIn(this.sandbox, {defineAs: "browser"}); + let 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; - } + Cu.waiveXrays(this.sandbox).chrome = Cu.waiveXrays(this.sandbox).browser; + injectAPI(api(this), chromeObj); } ExtensionContext.prototype = { @@ -319,17 +294,7 @@ ExtensionContext.prototype = { for (let obj of this.onClose) { obj.close(); } - - // 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). - if (this.isExtensionPage && !Cu.isDeadWrapper(this.contentWindow) && - Cu.waiveXrays(this.contentWindow).browser === this.chromeObj) { - Cu.createObjectIn(this.contentWindow, { defineAs: "browser" }); - Cu.createObjectIn(this.contentWindow, { defineAs: "chrome" }); - } - Cu.nukeSandbox(this.sandbox); - this.sandbox = null; }, }; @@ -345,10 +310,7 @@ var DocumentManager = { extensionCount: 0, // Map[windowId -> Map[extensionId -> ExtensionContext]] - contentScriptWindows: new Map(), - - // Map[windowId -> ExtensionContext] - extensionPageWindows: new Map(), + windows: new Map(), init() { Services.obs.addObserver(this, "document-element-inserted", false); @@ -386,40 +348,23 @@ var DocumentManager = { return; } - // Enable the content script APIs should be available in subframes' window - // if it is recognized as a valid addon id (see Bug 1214658 for rationale). - const { CONTENTSCRIPT_PRIVILEGES } = ExtensionManagement.API_LEVELS; - let extensionId = ExtensionManagement.getAddonIdForWindow(window); - - if (ExtensionManagement.getAPILevelForWindow(window, extensionId) == CONTENTSCRIPT_PRIVILEGES && - ExtensionManager.get(extensionId)) { - DocumentManager.getExtensionPageContext(extensionId, window); - } - this.trigger("document_start", window); /* eslint-disable mozilla/balanced-listeners */ window.addEventListener("DOMContentLoaded", this, true); window.addEventListener("load", this, true); /* eslint-enable mozilla/balanced-listeners */ } else if (topic == "inner-window-destroyed") { - let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data; - - // Close any existent content-script context for the destroyed window. - if (this.contentScriptWindows.has(windowId)) { - let extensions = this.contentScriptWindows.get(windowId); - for (let [, context] of extensions) { - context.close(); - } - - this.contentScriptWindows.delete(windowId); + let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + if (!this.windows.has(id)) { + return; } - // Close any existent iframe extension page context for the destroyed window. - if (this.extensionPageWindows.has(windowId)) { - let context = this.extensionWindows.get(windowId); + let extensions = this.windows.get(id); + for (let [, context] of extensions) { context.close(); - this.extensionPageWindows.delete(windowId); } + + this.windows.delete(id); } }, @@ -444,7 +389,7 @@ var DocumentManager = { executeScript(global, extensionId, script) { let window = global.content; - let context = this.getContentScriptContext(extensionId, window); + let context = this.getContext(extensionId, window); if (!context) { return; } @@ -468,33 +413,19 @@ var DocumentManager = { } }, - getContentScriptContext(extensionId, window) { + getContext(extensionId, window) { let winId = windowId(window); - if (!this.contentScriptWindows.has(winId)) { - this.contentScriptWindows.set(winId, new Map()); + if (!this.windows.has(winId)) { + this.windows.set(winId, new Map()); } - - let extensions = this.contentScriptWindows.get(winId); + let extensions = this.windows.get(winId); if (!extensions.has(extensionId)) { let context = new ExtensionContext(extensionId, window); extensions.set(extensionId, context); } - return extensions.get(extensionId); }, - getExtensionPageContext(extensionId, window) { - let winId = windowId(window); - - let context = this.extensionPageWindows.get(winId); - if (!context) { - let context = new ExtensionContext(extensionId, window, { isExtensionPage: true }); - this.extensionPageWindows.set(winId, context); - } - - return context; - }, - startupExtension(extensionId) { if (this.extensionCount == 0) { this.init(); @@ -509,7 +440,7 @@ var DocumentManager = { for (let [window, state] of this.enumerateWindows(global.docShell)) { for (let script of extension.scripts) { if (script.matches(window)) { - let context = this.getContentScriptContext(extensionId, window); + let context = this.getContext(extensionId, window); context.execute(script, scheduled => isWhenBeforeOrSame(scheduled, state)); } } @@ -518,8 +449,7 @@ var DocumentManager = { }, shutdownExtension(extensionId) { - // Clean up content-script contexts on extension shutdown. - for (let [, extensions] of this.contentScriptWindows) { + for (let [, extensions] of this.windows) { let context = extensions.get(extensionId); if (context) { context.close(); @@ -527,14 +457,6 @@ var DocumentManager = { } } - // Clean up iframe extension page contexts on extension shutdown. - for (let [winId, context] of this.extensionPageWindows) { - if (context.extensionId == extensionId) { - context.close(); - this.extensionPageWindows.delete(winId); - } - } - this.extensionCount--; if (this.extensionCount == 0) { this.uninit(); @@ -546,7 +468,7 @@ var DocumentManager = { for (let [extensionId, extension] of ExtensionManager.extensions) { for (let script of extension.scripts) { if (script.matches(window)) { - let context = this.getContentScriptContext(extensionId, window); + let context = this.getContext(extensionId, window); context.execute(script, scheduled => scheduled == state); } } diff --git a/toolkit/components/extensions/ExtensionManagement.jsm b/toolkit/components/extensions/ExtensionManagement.jsm index 58f1b9758992..36c29a783050 100644 --- a/toolkit/components/extensions/ExtensionManagement.jsm +++ b/toolkit/components/extensions/ExtensionManagement.jsm @@ -202,58 +202,18 @@ var Service = { // This is used to set the addonId on the originAttributes for the // nsIPrincipal attached to the URI. extensionURIToAddonID(uri) { + if (this.extensionURILoadableByAnyone(uri)) { + // We don't want webAccessibleResources to be associated with + // the add-on. That way they don't get any special privileges. + return null; + } + let uuid = uri.host; let extension = this.uuidMap.get(uuid); return extension ? extension.id : undefined; }, }; -// API Levels Helpers - -// Find the add-on associated with this document via the -// principal's originAttributes. This value is computed by -// extensionURIToAddonID, which ensures that we don't inject our -// API into webAccessibleResources or remote web pages. -function getAddonIdForWindow(window) { - let principal = window.document.nodePrincipal; - return principal.originAttributes.addonId; -} - -const API_LEVELS = Object.freeze({ - NO_PRIVILEGES: 0, - CONTENTSCRIPT_PRIVILEGES: 1, - FULL_PRIVILEGES: 2, -}); - -// Finds the API Level ("FULL_PRIVILEGES", "CONTENTSCRIPT_PRIVILEGES", "NO_PRIVILEGES") -// with a given a window object. -function getAPILevelForWindow(window, addonId) { - const { NO_PRIVILEGES, CONTENTSCRIPT_PRIVILEGES, FULL_PRIVILEGES } = API_LEVELS; - - // Non WebExtension URLs and WebExtension URLs from a different extension - // has no access to APIs. - if (!addonId && getAddonIdForWindow(window) != addonId) { - return NO_PRIVILEGES; - } - - let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell); - - // WebExtension URLs loaded into sub-frame UI have "content script API level privileges". - // (see Bug 1214658 for rationale) - if (docShell.sameTypeParent) { - return CONTENTSCRIPT_PRIVILEGES; - } - - // Extension pages running in the content process defaults to "content script API level privileges". - if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { - return CONTENTSCRIPT_PRIVILEGES; - } - - // WebExtension URLs loaded into top frames UI could have full API level privileges. - return FULL_PRIVILEGES; -} - this.ExtensionManagement = { startupExtension: Service.startupExtension.bind(Service), shutdownExtension: Service.shutdownExtension.bind(Service), @@ -266,9 +226,4 @@ this.ExtensionManagement = { getFrameId: Frames.getId.bind(Frames), getParentFrameId: Frames.getParentId.bind(Frames), - - // exported API Level Helpers - getAddonIdForWindow, - getAPILevelForWindow, - API_LEVELS, }; diff --git a/toolkit/components/extensions/test/mochitest/file_ext_test_api_injection.js b/toolkit/components/extensions/test/mochitest/file_ext_background_api_injection.js similarity index 100% rename from toolkit/components/extensions/test/mochitest/file_ext_test_api_injection.js rename to toolkit/components/extensions/test/mochitest/file_ext_background_api_injection.js diff --git a/toolkit/components/extensions/test/mochitest/mochitest.ini b/toolkit/components/extensions/test/mochitest/mochitest.ini index aed909db6a60..f1364edefce2 100644 --- a/toolkit/components/extensions/test/mochitest/mochitest.ini +++ b/toolkit/components/extensions/test/mochitest/mochitest.ini @@ -20,15 +20,13 @@ support-files = file_sample.html redirection.sjs file_privilege_escalation.html - file_ext_test_api_injection.js + file_ext_background_api_injection.js file_permission_xhr.html [test_ext_simple.html] [test_ext_geturl.html] [test_ext_contentscript.html] skip-if = buildapp == 'b2g' # runat != document_idle is not supported. -[test_ext_contentscript_create_iframe.html] -[test_ext_contentscript_api_injection.html] [test_ext_i18n_css.html] [test_ext_generate.html] [test_ext_localStorage.html] diff --git a/toolkit/components/extensions/test/mochitest/test_ext_background_api_injection.html b/toolkit/components/extensions/test/mochitest/test_ext_background_api_injection.html index 45d2290cc94c..b5f76022f190 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_background_api_injection.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_background_api_injection.html @@ -25,7 +25,7 @@ add_task(function* testBackgroundWindow() { let awaitConsole = new Promise(resolve => { let chromeScript = SpecialPowers.loadChromeScript( - SimpleTest.getTestFileURL("file_ext_test_api_injection.js")); + SimpleTest.getTestFileURL("file_ext_background_api_injection.js")); chromeScript.addMessageListener("console-message", resolve); }); diff --git a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_api_injection.html b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_api_injection.html deleted file mode 100644 index 42ea42575ce8..000000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_api_injection.html +++ /dev/null @@ -1,88 +0,0 @@ - - -
-