Bug 1190680: Part 1 - [webext] Factor common extension context logic into a shared base class. r=billm

This commit is contained in:
Kris Maglione 2016-01-29 18:39:29 -08:00
parent 64d33abd44
commit 724b49e80c
3 changed files with 167 additions and 152 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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,