зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1214658 - Enable content script APIs into sub-frames pointed to a valid add-on url. r=kmag
This commit is contained in:
Родитель
4763acc777
Коммит
d1682407d2
|
@ -363,12 +363,16 @@ GlobalManager = {
|
|||
Schemas.inject(chromeObj, schemaWrapper);
|
||||
};
|
||||
|
||||
// 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;
|
||||
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.
|
||||
if (!this.extensionMap.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
@ -387,10 +391,6 @@ 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);
|
||||
|
|
|
@ -216,7 +216,10 @@ 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) {
|
||||
function ExtensionContext(extensionId, contentWindow, contextOptions = {}) {
|
||||
let { isExtensionPage } = contextOptions;
|
||||
|
||||
this.isExtensionPage = isExtensionPage;
|
||||
this.extension = ExtensionManager.get(extensionId);
|
||||
this.extensionId = extensionId;
|
||||
this.contentWindow = contentWindow;
|
||||
|
@ -243,12 +246,27 @@ function ExtensionContext(extensionId, contentWindow) {
|
|||
prin = [contentPrincipal, extensionPrincipal];
|
||||
}
|
||||
|
||||
this.sandbox = Cu.Sandbox(prin, {
|
||||
sandboxPrototype: contentWindow,
|
||||
wantXrays: true,
|
||||
isWebExtensionContentScript: true,
|
||||
wantGlobalProperties: ["XMLHttpRequest"],
|
||||
});
|
||||
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) {
|
||||
|
@ -265,12 +283,19 @@ function ExtensionContext(extensionId, contentWindow) {
|
|||
let filter = {extensionId, frameId};
|
||||
this.messenger = new Messenger(this, broker, sender, filter, delegate);
|
||||
|
||||
let chromeObj = Cu.createObjectIn(this.sandbox, {defineAs: "browser"});
|
||||
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 = Cu.waiveXrays(this.sandbox).browser;
|
||||
injectAPI(api(this), chromeObj);
|
||||
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 = {
|
||||
|
@ -294,7 +319,17 @@ 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;
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -310,7 +345,10 @@ var DocumentManager = {
|
|||
extensionCount: 0,
|
||||
|
||||
// Map[windowId -> Map[extensionId -> ExtensionContext]]
|
||||
windows: new Map(),
|
||||
contentScriptWindows: new Map(),
|
||||
|
||||
// Map[windowId -> ExtensionContext]
|
||||
extensionPageWindows: new Map(),
|
||||
|
||||
init() {
|
||||
Services.obs.addObserver(this, "document-element-inserted", false);
|
||||
|
@ -348,23 +386,40 @@ 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 id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
if (!this.windows.has(id)) {
|
||||
return;
|
||||
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 extensions = this.windows.get(id);
|
||||
for (let [, context] of extensions) {
|
||||
// Close any existent iframe extension page context for the destroyed window.
|
||||
if (this.extensionPageWindows.has(windowId)) {
|
||||
let context = this.extensionWindows.get(windowId);
|
||||
context.close();
|
||||
this.extensionPageWindows.delete(windowId);
|
||||
}
|
||||
|
||||
this.windows.delete(id);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -389,7 +444,7 @@ var DocumentManager = {
|
|||
|
||||
executeScript(global, extensionId, script) {
|
||||
let window = global.content;
|
||||
let context = this.getContext(extensionId, window);
|
||||
let context = this.getContentScriptContext(extensionId, window);
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
@ -413,19 +468,33 @@ var DocumentManager = {
|
|||
}
|
||||
},
|
||||
|
||||
getContext(extensionId, window) {
|
||||
getContentScriptContext(extensionId, window) {
|
||||
let winId = windowId(window);
|
||||
if (!this.windows.has(winId)) {
|
||||
this.windows.set(winId, new Map());
|
||||
if (!this.contentScriptWindows.has(winId)) {
|
||||
this.contentScriptWindows.set(winId, new Map());
|
||||
}
|
||||
let extensions = this.windows.get(winId);
|
||||
|
||||
let extensions = this.contentScriptWindows.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();
|
||||
|
@ -440,7 +509,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.getContext(extensionId, window);
|
||||
let context = this.getContentScriptContext(extensionId, window);
|
||||
context.execute(script, scheduled => isWhenBeforeOrSame(scheduled, state));
|
||||
}
|
||||
}
|
||||
|
@ -449,7 +518,8 @@ var DocumentManager = {
|
|||
},
|
||||
|
||||
shutdownExtension(extensionId) {
|
||||
for (let [, extensions] of this.windows) {
|
||||
// Clean up content-script contexts on extension shutdown.
|
||||
for (let [, extensions] of this.contentScriptWindows) {
|
||||
let context = extensions.get(extensionId);
|
||||
if (context) {
|
||||
context.close();
|
||||
|
@ -457,6 +527,14 @@ 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();
|
||||
|
@ -468,7 +546,7 @@ var DocumentManager = {
|
|||
for (let [extensionId, extension] of ExtensionManager.extensions) {
|
||||
for (let script of extension.scripts) {
|
||||
if (script.matches(window)) {
|
||||
let context = this.getContext(extensionId, window);
|
||||
let context = this.getContentScriptContext(extensionId, window);
|
||||
context.execute(script, scheduled => scheduled == state);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -202,18 +202,58 @@ 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),
|
||||
|
@ -226,4 +266,9 @@ this.ExtensionManagement = {
|
|||
|
||||
getFrameId: Frames.getId.bind(Frames),
|
||||
getParentFrameId: Frames.getParentId.bind(Frames),
|
||||
|
||||
// exported API Level Helpers
|
||||
getAddonIdForWindow,
|
||||
getAPILevelForWindow,
|
||||
API_LEVELS,
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче