diff options
Diffstat (limited to 'toolkit/components/webextensions/ExtensionManagement.jsm')
-rw-r--r-- | toolkit/components/webextensions/ExtensionManagement.jsm | 321 |
1 files changed, 0 insertions, 321 deletions
diff --git a/toolkit/components/webextensions/ExtensionManagement.jsm b/toolkit/components/webextensions/ExtensionManagement.jsm deleted file mode 100644 index 324c5b71b..000000000 --- a/toolkit/components/webextensions/ExtensionManagement.jsm +++ /dev/null @@ -1,321 +0,0 @@ -/* 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/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = ["ExtensionManagement"]; - -const Ci = Components.interfaces; -const Cc = Components.classes; -const Cu = Components.utils; -const Cr = Components.results; - -Cu.import("resource://gre/modules/AppConstants.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils", - "resource://gre/modules/ExtensionUtils.jsm"); - -XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionUtils.getConsole()); - -XPCOMUtils.defineLazyGetter(this, "UUIDMap", () => { - let {UUIDMap} = Cu.import("resource://gre/modules/Extension.jsm", {}); - return UUIDMap; -}); - -/* - * This file should be kept short and simple since it's loaded even - * when no extensions are running. - */ - -// Keep track of frame IDs for content windows. Mostly we can just use -// the outer window ID as the frame ID. However, the API specifies -// that top-level windows have a frame ID of 0. So we need to keep -// track of which windows are top-level. This code listens to messages -// from ExtensionContent to do that. -var Frames = { - // Window IDs of top-level content windows. - topWindowIds: new Set(), - - init() { - if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { - return; - } - - Services.mm.addMessageListener("Extension:TopWindowID", this); - Services.mm.addMessageListener("Extension:RemoveTopWindowID", this, true); - }, - - isTopWindowId(windowId) { - return this.topWindowIds.has(windowId); - }, - - // Convert an outer window ID to a frame ID. An outer window ID of 0 - // is invalid. - getId(windowId) { - if (this.isTopWindowId(windowId)) { - return 0; - } - if (windowId == 0) { - return -1; - } - return windowId; - }, - - // Convert an outer window ID for a parent window to a frame - // ID. Outer window IDs follow the same convention that - // |window.top.parent === window.top|. The API works differently, - // giving a frame ID of -1 for the the parent of a top-level - // window. This function handles the conversion. - getParentId(parentWindowId, windowId) { - if (parentWindowId == windowId) { - // We have a top-level window. - return -1; - } - - // Not a top-level window. Just return the ID as normal. - return this.getId(parentWindowId); - }, - - receiveMessage({name, data}) { - switch (name) { - case "Extension:TopWindowID": - // FIXME: Need to handle the case where the content process - // crashes. Right now we leak its top window IDs. - this.topWindowIds.add(data.windowId); - break; - - case "Extension:RemoveTopWindowID": - this.topWindowIds.delete(data.windowId); - break; - } - }, -}; -Frames.init(); - -var APIs = { - apis: new Map(), - - register(namespace, schema, script) { - if (this.apis.has(namespace)) { - throw new Error(`API namespace already exists: ${namespace}`); - } - - this.apis.set(namespace, {schema, script}); - }, - - unregister(namespace) { - if (!this.apis.has(namespace)) { - throw new Error(`API namespace does not exist: ${namespace}`); - } - - this.apis.delete(namespace); - }, -}; - -function getURLForExtension(id, path = "") { - let uuid = UUIDMap.get(id, false); - if (!uuid) { - Cu.reportError(`Called getURLForExtension on unmapped extension ${id}`); - return null; - } - return `moz-extension://${uuid}/${path}`; -} - -// This object manages various platform-level issues related to -// moz-extension:// URIs. It lives here so that it can be used in both -// the parent and child processes. -// -// moz-extension URIs have the form moz-extension://uuid/path. Each -// extension has its own UUID, unique to the machine it's installed -// on. This is easier and more secure than using the extension ID, -// since it makes it slightly harder to fingerprint for extensions if -// each user uses different URIs for the extension. -var Service = { - initialized: false, - - // Map[uuid -> extension]. - // extension can be an Extension (parent process) or BrowserExtensionContent (child process). - uuidMap: new Map(), - - init() { - let aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService); - aps = aps.wrappedJSObject; - this.aps = aps; - aps.setExtensionURILoadCallback(this.extensionURILoadableByAnyone.bind(this)); - aps.setExtensionURIToAddonIdCallback(this.extensionURIToAddonID.bind(this)); - }, - - // Called when a new extension is loaded. - startupExtension(uuid, uri, extension) { - if (!this.initialized) { - this.initialized = true; - this.init(); - } - - // Create the moz-extension://uuid mapping. - let handler = Services.io.getProtocolHandler("moz-extension"); - handler.QueryInterface(Ci.nsISubstitutingProtocolHandler); - handler.setSubstitution(uuid, uri); - - this.uuidMap.set(uuid, extension); - this.aps.setAddonHasPermissionCallback(extension.id, extension.hasPermission.bind(extension)); - this.aps.setAddonLoadURICallback(extension.id, this.checkAddonMayLoad.bind(this, extension)); - this.aps.setAddonLocalizeCallback(extension.id, extension.localize.bind(extension)); - this.aps.setAddonCSP(extension.id, extension.manifest.content_security_policy); - this.aps.setBackgroundPageUrlCallback(uuid, this.generateBackgroundPageUrl.bind(this, extension)); - }, - - // Called when an extension is unloaded. - shutdownExtension(uuid) { - let extension = this.uuidMap.get(uuid); - this.uuidMap.delete(uuid); - this.aps.setAddonHasPermissionCallback(extension.id, null); - this.aps.setAddonLoadURICallback(extension.id, null); - this.aps.setAddonLocalizeCallback(extension.id, null); - this.aps.setAddonCSP(extension.id, null); - this.aps.setBackgroundPageUrlCallback(uuid, null); - - let handler = Services.io.getProtocolHandler("moz-extension"); - handler.QueryInterface(Ci.nsISubstitutingProtocolHandler); - handler.setSubstitution(uuid, null); - }, - - // Return true if the given URI can be loaded from arbitrary web - // content. The manifest.json |web_accessible_resources| directive - // determines this. - extensionURILoadableByAnyone(uri) { - let uuid = uri.host; - let extension = this.uuidMap.get(uuid); - if (!extension || !extension.webAccessibleResources) { - return false; - } - - let path = uri.QueryInterface(Ci.nsIURL).filePath; - if (path.length > 0 && path[0] == "/") { - path = path.substr(1); - } - return extension.webAccessibleResources.matches(path); - }, - - // Checks whether a given extension can load this URI (typically via - // an XML HTTP request). The manifest.json |permissions| directive - // determines this. - checkAddonMayLoad(extension, uri) { - return extension.whiteListedHosts.matchesIgnoringPath(uri); - }, - - generateBackgroundPageUrl(extension) { - let background_scripts = extension.manifest.background && - extension.manifest.background.scripts; - if (!background_scripts) { - return; - } - let html = "<!DOCTYPE html>\n<body>\n"; - for (let script of background_scripts) { - script = script.replace(/"/g, """); - html += `<script src="${script}"></script>\n`; - } - html += "</body>\n</html>\n"; - return "data:text/html;charset=utf-8," + encodeURIComponent(html); - }, - - // Finds the add-on ID associated with a given moz-extension:// URI. - // This is used to set the addonId on the originAttributes for the - // nsIPrincipal attached to the URI. - extensionURIToAddonID(uri) { - 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) { - return Cu.getObjectPrincipal(window).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; - } - - // Extension pages running in the content process always defaults to - // "content script API level privileges". - if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { - return CONTENTSCRIPT_PRIVILEGES; - } - - let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell); - - // Handling of ExtensionPages running inside sub-frames. - if (docShell.sameTypeParent) { - let parentWindow = docShell.sameTypeParent.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - - // The option page iframe embedded in the about:addons tab should have - // full API level privileges. (see Bug 1256282 for rationale) - let parentDocument = parentWindow.document; - let parentIsSystemPrincipal = Services.scriptSecurityManager - .isSystemPrincipal(parentDocument.nodePrincipal); - if (parentDocument.location.href == "about:addons" && parentIsSystemPrincipal) { - return FULL_PRIVILEGES; - } - - // The addon iframes embedded in a addon page from with the same addonId - // should have the same privileges of the sameTypeParent. - // (see Bug 1258347 for rationale) - let parentSameAddonPrivileges = getAPILevelForWindow(parentWindow, addonId); - if (parentSameAddonPrivileges > NO_PRIVILEGES) { - return parentSameAddonPrivileges; - } - - // In all the other cases, WebExtension URLs loaded into sub-frame UI - // will have "content script API level privileges". - // (see Bug 1214658 for rationale) - 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), - - registerAPI: APIs.register.bind(APIs), - unregisterAPI: APIs.unregister.bind(APIs), - - getFrameId: Frames.getId.bind(Frames), - getParentFrameId: Frames.getParentId.bind(Frames), - - getURLForExtension, - - // exported API Level Helpers - getAddonIdForWindow, - getAPILevelForWindow, - API_LEVELS, - - APIs, -}; |