Bug 1368102: Part 4 - Use WebExtensionContentScript to match content scripts. r?mixedpuppy,zombie
MozReview-Commit-ID: 1Ga0259WjC
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -191,52 +191,52 @@ defineLazyGetter(BrowserExtensionContent
});
defineLazyGetter(BrowserExtensionContent.prototype, "authorCSS", () => {
return new CSSCache(Ci.nsIStyleSheetService.AUTHOR_SHEET);
});
// Represents a content script.
class Script {
- constructor(extension, options) {
+ constructor(extension, matcher) {
this.extension = extension;
- this.options = options;
+ this.matcher = matcher;
- this.runAt = this.options.run_at;
- this.js = this.options.js || [];
- this.css = this.options.css || [];
- this.remove_css = this.options.remove_css;
- this.css_origin = this.options.css_origin;
+ this.runAt = this.matcher.runAt;
+ this.js = this.matcher.jsPaths;
+ this.css = this.matcher.cssPaths;
+ this.removeCSS = this.matcher.removeCSS;
+ this.cssOrigin = this.matcher.cssOrigin;
- this.cssCache = extension[this.css_origin === "user" ? "userCSS"
- : "authorCSS"];
- this.scriptCache = extension[options.wantReturnValue ? "dynamicScripts"
+ this.cssCache = extension[this.cssOrigin === "user" ? "userCSS"
+ : "authorCSS"];
+ this.scriptCache = extension[matcher.wantReturnValue ? "dynamicScripts"
: "staticScripts"];
- if (options.wantReturnValue) {
+ if (matcher.wantReturnValue) {
this.compileScripts();
this.loadCSS();
}
- this.requiresCleanup = !this.remove_css && (this.css.length > 0 || options.cssCode);
+ this.requiresCleanup = !this.removeCss && (this.css.length > 0 || matcher.cssCode);
}
compileScripts() {
return this.js.map(url => this.scriptCache.get(url));
}
loadCSS() {
return this.cssURLs.map(url => this.cssCache.get(url));
}
cleanup(window) {
- if (!this.remove_css && this.cssURLs.length) {
+ if (!this.removeCss && this.cssURLs.length) {
let winUtils = getWinUtils(window);
- let type = this.css_origin === "user" ? winUtils.USER_SHEET : winUtils.AUTHOR_SHEET;
+ let type = this.cssOrigin === "user" ? winUtils.USER_SHEET : winUtils.AUTHOR_SHEET;
for (let url of this.cssURLs) {
this.cssCache.deleteDocument(url, window.document);
runSafeSyncWithoutClone(winUtils.removeSheetUsingURIString, url, type);
}
// Clear any sheets that were kept alive past their timeout as
// a result of living in this document.
this.cssCache.clear(CSS_EXPIRY_TIMEOUT_MS);
@@ -271,19 +271,19 @@ class Script {
context.addScript(this);
}
let cssPromise;
if (this.cssURLs.length) {
let window = context.contentWindow;
let winUtils = getWinUtils(window);
- let type = this.css_origin === "user" ? winUtils.USER_SHEET : winUtils.AUTHOR_SHEET;
+ let type = this.cssOrigin === "user" ? winUtils.USER_SHEET : winUtils.AUTHOR_SHEET;
- if (this.remove_css) {
+ if (this.removeCSS) {
for (let url of this.cssURLs) {
this.cssCache.deleteDocument(url, window.document);
runSafeSyncWithoutClone(winUtils.removeSheetUsingURIString, url, type);
}
} else {
cssPromise = Promise.all(this.loadCSS()).then(sheets => {
let window = context.contentWindow;
@@ -320,31 +320,31 @@ class Script {
}
// The evaluations below may throw, in which case the promise will be
// automatically rejected.
for (let script of scripts) {
result = script.executeInGlobal(context.cloneScope);
}
- if (this.options.jsCode) {
- result = Cu.evalInSandbox(this.options.jsCode, context.cloneScope, "latest");
+ if (this.matcher.jsCode) {
+ result = Cu.evalInSandbox(this.matcher.jsCode, context.cloneScope, "latest");
}
await cssPromise;
return result;
}
}
defineLazyGetter(Script.prototype, "cssURLs", function() {
// We can handle CSS urls (css) and CSS code (cssCode).
let urls = this.css.slice();
- if (this.options.cssCode) {
- urls.push("data:text/css;charset=utf-8," + encodeURIComponent(this.options.cssCode));
+ if (this.matcher.cssCode) {
+ urls.push("data:text/css;charset=utf-8," + encodeURIComponent(this.matcher.cssCode));
}
return urls;
});
/**
* An execution context for semi-privileged extension content scripts.
*
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -25,16 +25,33 @@ XPCOMUtils.defineLazyGetter(this, "UUIDM
const {appinfo} = Services;
const isParentProcess = appinfo.processType === appinfo.PROCESS_TYPE_DEFAULT;
/*
* This file should be kept short and simple since it's loaded even
* when no extensions are running.
*/
+function parseScriptOptions(options) {
+ return {
+ allFrames: options.all_frames,
+ matchAboutBlank: options.match_about_blank,
+ frameID: options.frame_id,
+ runAt: options.run_at,
+
+ matches: new MatchPatternSet(options.matches),
+ excludeMatches: new MatchPatternSet(options.exclude_matches || []),
+ includeGlobs: options.include_globs && options.include_globs.map(glob => new MatchGlob(glob)),
+ excludeGlobs: options.include_globs && options.exclude_globs.map(glob => new MatchGlob(glob)),
+
+ jsPaths: options.js || [],
+ cssPaths: options.css || [],
+ };
+}
+
var APIs = {
apis: new Map(),
register(namespace, schema, script) {
if (this.apis.has(namespace)) {
throw new Error(`API namespace already exists: ${namespace}`);
}
@@ -89,16 +106,18 @@ var ExtensionManagement = {
webAccessibleResources: extension.webAccessibleResources || [],
contentSecurityPolicy: extension.manifest.content_security_policy,
localizeCallback: extension.localize.bind(extension),
backgroundScripts: (extension.manifest.background &&
extension.manifest.background.scripts),
+
+ contentScripts: (extension.manifest.content_scripts || []).map(parseScriptOptions),
});
extension.policy = policy;
policy.active = true;
},
// Called when an extension is unloaded.
shutdownExtension(extension) {
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -14,18 +14,16 @@ const {classes: Cc, interfaces: Ci, util
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
"resource://gre/modules/ExtensionManagement.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
"resource://gre/modules/MessageChannel.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
- "resource://gre/modules/WebNavigationFrames.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChild",
"resource://gre/modules/ExtensionChild.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionContent",
"resource://gre/modules/ExtensionContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPageChild",
"resource://gre/modules/ExtensionPageChild.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
@@ -34,131 +32,66 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionUtils.getConsole());
XPCOMUtils.defineLazyGetter(this, "getInnerWindowID", () => ExtensionUtils.getInnerWindowID);
// We need to avoid touching Services.appinfo here in order to prevent
// the wrong version from being cached during xpcshell test startup.
const appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
const isContentProcess = appinfo.processType == appinfo.PROCESS_TYPE_CONTENT;
+function parseScriptOptions(options) {
+ return {
+ allFrames: options.all_frames,
+ matchAboutBlank: options.match_about_blank,
+ frameID: options.frame_id,
+ runAt: options.run_at,
+
+ matches: new MatchPatternSet(options.matches),
+ excludeMatches: new MatchPatternSet(options.exclude_matches || []),
+ includeGlobs: options.include_globs && options.include_globs.map(glob => new MatchGlob(glob)),
+ excludeGlobs: options.include_globs && options.exclude_globs.map(glob => new MatchGlob(glob)),
+
+ jsPaths: options.js || [],
+ cssPaths: options.css || [],
+ };
+}
class ScriptMatcher {
- constructor(extension, options) {
+ constructor(extension, matcher) {
this.extension = extension;
- this.options = options;
+ this.matcher = matcher;
this._script = null;
-
- this.allFrames = options.all_frames;
- this.matchAboutBlank = options.match_about_blank;
- this.frameId = options.frame_id;
- this.runAt = options.run_at;
-
- this.matches = new MatchPatternSet(options.matches);
- this.excludeMatches = new MatchPatternSet(options.exclude_matches || []);
- this.includeGlobs = options.include_globs && options.include_globs.map(glob => new MatchGlob(glob));
- this.excludeGlobs = options.include_globs && options.exclude_globs.map(glob => new MatchGlob(glob));
}
- toString() {
- return `[Script {js: [${this.options.js}], matchAboutBlank: ${this.matchAboutBlank}, runAt: ${this.runAt}, matches: ${this.options.matches}}]`;
+ get matchAboutBlank() {
+ return this.matcher.matchAboutBlank;
}
get script() {
if (!this._script) {
this._script = new ExtensionContent.Script(this.extension.realExtension,
- this.options);
+ this.matcher);
}
return this._script;
}
preload() {
let {script} = this;
script.loadCSS();
script.compileScripts();
}
matchesLoadInfo(uri, loadInfo) {
- if (!this.matchesURI(uri)) {
- return false;
- }
-
- if (!this.allFrames && !loadInfo.isTopLevelLoad) {
- return false;
- }
-
- return true;
- }
-
- matchesURI(uri) {
- if (!(this.matches.matches(uri))) {
- return false;
- }
-
- if (this.excludeMatches.matches(uri)) {
- return false;
- }
-
- if (this.includeGlobs && !this.includeGlobs.some(glob => glob.matches(uri.spec))) {
- return false;
- }
-
- if (this.excludeGlobs && this.excludeGlobs.some(glob => glob.matches(uri.spec))) {
- return false;
- }
-
- return true;
+ return this.matcher.matchesLoadInfo(uri, loadInfo);
}
matchesWindow(window) {
- if (!this.allFrames && this.frameId == null && window.parent !== window) {
- return false;
- }
-
- let uri = window.document.documentURIObject;
- let principal = window.document.nodePrincipal;
-
- if (this.matchAboutBlank) {
- // When matching top-level about:blank documents,
- // allow loading into any with a NullPrincipal.
- if (uri.spec === "about:blank" && window === window.parent && principal.isNullPrincipal) {
- return true;
- }
-
- // When matching about:blank/srcdoc iframes, the checks below
- // need to be performed against the "owner" document's URI.
- if (["about:blank", "about:srcdoc"].includes(uri.spec)) {
- uri = principal.URI;
- }
- }
-
- // Documents from data: URIs also inherit the principal.
- if (Services.netUtils.URIChainHasFlags(uri, Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT)) {
- if (!this.matchAboutBlank) {
- return false;
- }
- uri = principal.URI;
- }
-
- if (!this.matchesURI(uri)) {
- return false;
- }
-
- if (this.frameId != null && WebNavigationFrames.getFrameId(window) !== this.frameId) {
- return false;
- }
-
- // If mozAddonManager is present on this page, don't allow
- // content scripts.
- if (window.navigator.mozAddonManager !== undefined) {
- return false;
- }
-
- return true;
+ return this.matcher.matchesWindow(window);
}
injectInto(window) {
return this.script.injectInto(window);
}
}
function getMessageManager(window) {
@@ -197,17 +130,28 @@ class ExtensionGlobal {
receiveMessage({target, messageName, recipient, data}) {
switch (messageName) {
case "Extension:Capture":
return ExtensionContent.handleExtensionCapture(this.global, data.width, data.height, data.options);
case "Extension:DetectLanguage":
return ExtensionContent.handleDetectLanguage(this.global, target);
case "Extension:Execute":
let extension = ExtensionManager.get(recipient.extensionId);
- let script = new ScriptMatcher(extension, data.options);
+
+ let matcher = new WebExtensionContentScript(extension.policy, parseScriptOptions(data.options));
+
+ let options = Object.assign(matcher, {
+ wantReturnValue: data.options.wantReturnValue,
+ removeCSS: data.options.remove_css,
+ cssOrigin: data.options.cssOrigin,
+ cssCode: data.options.cssCode,
+ jsCode: data.options.jsCode,
+ });
+
+ let script = new ScriptMatcher(extension, options);
return ExtensionContent.handleExtensionExecute(this.global, target, data.options, script);
case "WebNavigation:GetFrame":
return ExtensionContent.handleWebNavigationGetFrame(this.global, data.options);
case "WebNavigation:GetAllFrames":
return ExtensionContent.handleWebNavigationGetAllFrames(this.global);
}
}
@@ -531,28 +475,30 @@ class StubExtension {
this.id = data.id;
this.uuid = data.uuid;
this.instanceId = data.instanceId;
this.manifest = data.manifest;
this.permissions = data.permissions;
this.whiteListedHosts = new MatchPatternSet(data.whiteListedHosts);
this.webAccessibleResources = data.webAccessibleResources.map(path => new MatchGlob(path));
- this.scripts = data.content_scripts.map(scriptData => new ScriptMatcher(this, scriptData));
-
this._realExtension = null;
this.startup();
+
+ this.scripts = this.policy.contentScripts.map(matcher => new ScriptMatcher(this, matcher));
}
startup() {
// Extension.jsm takes care of this in the parent.
if (isContentProcess) {
let uri = Services.io.newURI(this.data.resourceURL);
ExtensionManagement.startupExtension(this.uuid, uri, this);
+ } else {
+ this.policy = WebExtensionPolicy.getByID(this.id);
}
}
shutdown() {
if (isContentProcess) {
ExtensionManagement.shutdownExtension(this);
}
if (this._realExtension) {