Bug 1190680: Part 1 - Factor common extension context logic into a shared base class. r?billm
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -74,16 +74,17 @@ ExtensionManagement.registerSchema("chro
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/i18n.json");
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/idle.json");
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/runtime.json");
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_navigation.json");
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_request.json");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
+ BaseContext,
LocaleData,
MessageBroker,
Messenger,
injectAPI,
instanceOf,
extend,
flushJarCache,
} = ExtensionUtils;
@@ -209,97 +210,69 @@ var globalBroker = new MessageBroker([Se
// content loaded into browser tabs (type="tab").
//
// |params| is an object with the following properties:
// |type| is one of "background", "popup", or "tab".
// |contentWindow| is the DOM window the content runs in.
// |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();
+ExtensionPage = class extends BaseContext {
+ constructor(extension, params) {
+ super();
+
+ 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 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;
+ // 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;
- },
-
- 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.
unload() {
Management.emit("page-unload", this);
this.extension.views.delete(this);
- for (let obj of this.onClose) {
- obj.close();
- }
- },
+ super.unload();
+ }
};
// Responsible for loading extension APIs into the right globals.
GlobalManager = {
// Number of extensions currently enabled.
count: 0,
// Map[docShell -> {extension, context}] where context is an ExtensionPage.
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -28,16 +28,17 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
"resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
runSafeSyncWithoutClone,
+ BaseContext,
LocaleData,
MessageBroker,
Messenger,
injectAPI,
flushJarCache,
} = ExtensionUtils;
function isWhenBeforeOrSame(when1, when2) {
@@ -211,97 +212,90 @@ function getWindowMessageManager(content
}
}
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) {
- this.extension = ExtensionManager.get(extensionId);
- this.extensionId = extensionId;
- this.contentWindow = contentWindow;
+class ExtensionContext extends BaseContext {
+ constructor(extensionId, contentWindow) {
+ super();
+
+ this.extension = ExtensionManager.get(extensionId);
+ this.extensionId = extensionId;
+ this.contentWindow = contentWindow;
- this.onClose = new Set();
+ 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 utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- let outerWindowId = utils.outerWindowID;
- let frameId = contentWindow == contentWindow.top ? 0 : outerWindowId;
- this.frameId = frameId;
+ 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];
- let mm = getWindowMessageManager(contentWindow);
- this.messageManager = mm;
+ Object.defineProperty(this, "principal",
+ {value: extensionPrincipal, enumerable: true, configurable: true});
+ }
+
+ this.sandbox = Cu.Sandbox(prin, {
+ sandboxPrototype: contentWindow,
+ wantXrays: true,
+ isWebExtensionContentScript: true,
+ wantGlobalProperties: ["XMLHttpRequest"],
+ });
- 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];
+ 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);
+
+ 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 = Cu.waiveXrays(this.sandbox).browser;
+ injectAPI(api(this), chromeObj);
}
- 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);
-
- 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 = Cu.waiveXrays(this.sandbox).browser;
- injectAPI(api(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();
Cu.nukeSandbox(this.sandbox);
- },
-};
+ }
+}
function windowId(window) {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.currentInnerWindowID;
}
// Responsible for creating ExtensionContexts and injecting content
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -107,16 +107,63 @@ DefaultWeakMap.prototype = {
if (key) {
this.weakmap.set(key, value);
} else {
this.defaultValue = value;
}
},
};
+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;
this.locales = data.locales || new Map();
// Map(locale-name -> Map(message-key -> localized-string))
//
// Contains a key for each loaded locale, each of which is a
@@ -781,16 +828,17 @@ function flushJarCache(jarFile) {
Services.obs.notifyObservers(jarFile, "flush-cache-entry", null);
}
this.ExtensionUtils = {
runSafeWithoutClone,
runSafeSyncWithoutClone,
runSafe,
runSafeSync,
+ BaseContext,
DefaultWeakMap,
EventManager,
LocaleData,
SingletonEventManager,
ignoreEvent,
injectAPI,
MessageBroker,
Messenger,