Bug 1391158: Optimize checkLoadURL for the common case of extension URLs. r?mixedpuppy
MozReview-Commit-ID: KGFFcHxQSvZ
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -816,17 +816,18 @@ this.Extension = class extends Extension
throw new Error("Out-of-process WebExtensions are not supported with multiple child processes");
}
// This is filled in the first time an extension child is created.
this.parentMessageManager = null;
this.id = addonData.id;
this.version = addonData.version;
- this.baseURI = Services.io.newURI(this.getURL("")).QueryInterface(Ci.nsIURL);
+ this.baseURL = this.getURL("");
+ this.baseURI = Services.io.newURI(this.baseURL).QueryInterface(Ci.nsIURL);
this.principal = this.createPrincipal();
this.views = new Set();
this._backgroundPageFrameLoader = null;
this.onStartup = null;
this.hasShutdown = false;
this.onShutdown = new Set();
@@ -938,17 +939,27 @@ this.Extension = class extends Extension
return Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
}
// Checks that the given URL is a child of our baseURI.
isExtensionURL(url) {
let uri = Services.io.newURI(url);
let common = this.baseURI.getCommonBaseSpec(uri);
- return common == this.baseURI.spec;
+ return common == this.baseURL;
+ }
+
+ checkLoadURL(url, options = {}) {
+ // As an optimization, f the URL starts with the extension's base URL,
+ // don't do any further checks. It's always allowed to load it.
+ if (url.startsWith(this.baseURL)) {
+ return true;
+ }
+
+ return ExtensionUtils.checkLoadURL(url, this.principal, options);
}
async promiseLocales(locale) {
let locales = await StartupCache.locales
.get([this.id, "@@all_locales"], () => this._promiseLocaleMap());
return this._setupLocaleData(locales);
}
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -506,16 +506,17 @@ class BrowserExtensionContent extends Ev
this.whiteListedHosts = new MatchPatternSet(data.whiteListedHosts, {ignorePath: true});
this.permissions = data.permissions;
this.optionalPermissions = data.optionalPermissions;
this.principal = data.principal;
this.localeData = new LocaleData(data.localeData);
this.manifest = data.manifest;
+ this.baseURL = data.baseURL;
this.baseURI = Services.io.newURI(data.baseURL);
// Only used in addon processes.
this.views = new Set();
// Only used for devtools views.
this.devtoolsViews = new Set();
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -266,37 +266,23 @@ class BaseContext {
} else if (!this.active) {
Cu.reportError("context.runSafeWithoutClone called while context is inactive");
} else {
return runSafeSyncWithoutClone(...args);
}
}
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;
- }
- if (options.dontReportErrors) {
- flags |= ssm.DONT_REPORT_ERRORS;
+ // As an optimization, f the URL starts with the extension's base URL,
+ // don't do any further checks. It's always allowed to load it.
+ if (url.startsWith(this.extension.baseURL)) {
+ return true;
}
- try {
- ssm.checkLoadURIWithPrincipal(this.principal,
- Services.io.newURI(url),
- flags);
- } catch (e) {
- return false;
- }
- return true;
+ return ExtensionUtils.checkLoadURL(url, this.principal, options);
}
/**
* Safely call JSON.stringify() on an object that comes from an
* extension.
*
* @param {array<any>} args Arguments for JSON.stringify()
* @returns {string} The stringified representation of obj
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -1287,29 +1287,29 @@ let IconDetails = {
}
let url = baseURI.resolve(path[size]);
// The Chrome documentation specifies these parameters as
// relative paths. We currently accept absolute URLs as well,
// which means we need to check that the extension is allowed
// to load them. This will throw an error if it's not allowed.
- this._checkURL(url, extension.principal);
+ this._checkURL(url, extension);
result[size] = url;
}
}
if (themeIcons) {
themeIcons.forEach(({size, light, dark}) => {
let lightURL = baseURI.resolve(light);
let darkURL = baseURI.resolve(dark);
- this._checkURL(lightURL, extension.principal);
- this._checkURL(darkURL, extension.principal);
+ this._checkURL(lightURL, extension);
+ this._checkURL(darkURL, extension);
let defaultURL = result[size];
result[size] = {
"default": defaultURL || lightURL, // Fallback to the light url if no default is specified.
"light": lightURL,
"dark": darkURL,
};
});
@@ -1325,22 +1325,18 @@ let IconDetails = {
extension.manifestError(`Invalid icon data: ${e}`);
}
return result;
},
// Checks if the extension is allowed to load the given URL with the specified principal.
// This will throw an error if the URL is not allowed.
- _checkURL(url, principal) {
- try {
- Services.scriptSecurityManager.checkLoadURIWithPrincipal(
- principal, Services.io.newURI(url),
- Services.scriptSecurityManager.DISALLOW_SCRIPT);
- } catch (e) {
+ _checkURL(url, extension) {
+ if (!extension.checkLoadURL(url, {allowInheritsPrincipal: true})) {
throw new ExtensionError(`Illegal URL ${url}`);
}
},
// Returns the appropriate icon URL for the given icons object and the
// screen resolution of the given window.
getPreferredIcon(icons, extension = null, size = 16) {
const DEFAULT = "chrome://browser/content/extension.svg";
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -647,17 +647,42 @@ class MessageManagerProxy {
handleEvent(event) {
if (event.type == "SwapDocShells") {
this.removeListeners(this.eventTarget);
this.addListeners(event.detail);
}
}
}
+function checkLoadURL(url, principal, options) {
+ let ssm = Services.scriptSecurityManager;
+
+ let flags = ssm.STANDARD;
+ if (!options.allowScript) {
+ flags |= ssm.DISALLOW_SCRIPT;
+ }
+ if (!options.allowInheritsPrincipal) {
+ flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
+ }
+ if (options.dontReportErrors) {
+ flags |= ssm.DONT_REPORT_ERRORS;
+ }
+
+ try {
+ ssm.checkLoadURIWithPrincipal(principal,
+ Services.io.newURI(url),
+ flags);
+ } catch (e) {
+ return false;
+ }
+ return true;
+}
+
this.ExtensionUtils = {
+ checkLoadURL,
defineLazyGetter,
flushJarCache,
getConsole,
getInnerWindowID,
getMessageManager,
getUniqueId,
filterStack,
getWinUtils,