Bug 1389840: Part 1 - Store more computed manifest data in startup cache. r?aswan
MozReview-Commit-ID: 3qprhdfvTVs
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -524,77 +524,100 @@ this.ExtensionData = class {
if (this.localeData) {
context.preprocessors.localize = (value, context) => this.localize(value);
}
let normalized = Schemas.normalize(this.manifest, "manifest.WebExtensionManifest", context);
if (normalized.error) {
this.manifestError(normalized.error);
- } else {
- return normalized.value;
+ return null;
+ }
+
+ let manifest = normalized.value;
+
+ let id;
+ try {
+ if (manifest.applications.gecko.id) {
+ id = manifest.applications.gecko.id;
+ }
+ } catch (e) {
+ // Errors are handled by the type checks above.
}
+
+ let apiNames = new Set();
+ let dependencies = new Set();
+ let hostPermissions = new Set();
+ let permissions = new Set();
+
+ for (let perm of manifest.permissions) {
+ if (perm === "geckoProfiler") {
+ const acceptedExtensions = Services.prefs.getStringPref("extensions.geckoProfiler.acceptedExtensionIds", "");
+ if (!acceptedExtensions.split(",").includes(this.id)) {
+ this.manifestError("Only whitelisted extensions are allowed to access the geckoProfiler.");
+ continue;
+ }
+ }
+
+ let type = classifyPermission(perm);
+ if (type.origin) {
+ let matcher = new MatchPattern(perm, {ignorePath: true});
+
+ perm = matcher.pattern;
+ hostPermissions.add(perm);
+ } else if (type.api) {
+ apiNames.add(type.api);
+ }
+
+ permissions.add(perm);
+ }
+
+ // An extension always gets permission to its own url.
+ if (this.id) {
+ let matcher = new MatchPattern(this.getURL(), {ignorePath: true});
+ hostPermissions.add(matcher.pattern);
+ }
+
+ for (let api of apiNames) {
+ dependencies.add(`${api}@experiments.addons.mozilla.org`);
+ }
+
+ // Normalize all patterns to contain a single leading /
+ let webAccessibleResources = (manifest.web_accessible_resources || [])
+ .map(path => path.replace(/^\/*/, "/"));
+
+ return {apiNames, dependencies, hostPermissions, id, manifest, permissions,
+ webAccessibleResources};
});
}
// Reads the extension's |manifest.json| file, and stores its
// parsed contents in |this.manifest|.
async loadManifest() {
- [this.manifest] = await Promise.all([
+ let [manifestData] = await Promise.all([
this.parseManifest(),
Management.lazyInit(),
]);
- if (!this.manifest) {
+ if (!manifestData) {
return;
}
- try {
- // Do not override the add-on id that has been already assigned.
- if (!this.id && this.manifest.applications.gecko.id) {
- this.id = this.manifest.applications.gecko.id;
- }
- } catch (e) {
- // Errors are handled by the type checks above.
+ // Do not override the add-on id that has been already assigned.
+ if (!this.id) {
+ this.id = manifestData.id;
}
- let whitelist = [];
- for (let perm of this.manifest.permissions) {
- if (perm === "geckoProfiler") {
- const acceptedExtensions = Services.prefs.getStringPref("extensions.geckoProfiler.acceptedExtensionIds", "");
- if (!acceptedExtensions.split(",").includes(this.id)) {
- this.manifestError("Only whitelisted extensions are allowed to access the geckoProfiler.");
- continue;
- }
- }
-
- let type = classifyPermission(perm);
- if (type.origin) {
- let matcher = new MatchPattern(perm, {ignorePath: true});
+ this.manifest = manifestData.manifest;
+ this.apiNames = manifestData.apiNames;
+ this.dependencies = manifestData.dependencies;
+ this.permissions = manifestData.permissions;
- whitelist.push(matcher);
- perm = matcher.pattern;
- } else if (type.api) {
- this.apiNames.add(type.api);
- }
-
- this.permissions.add(perm);
- }
-
- // An extension always gets permission to its own url.
- if (this.id) {
- let matcher = new MatchPattern(this.getURL(), {ignorePath: true});
- whitelist.push(matcher);
- }
-
- this.whiteListedHosts = new MatchPatternSet(whitelist);
-
- for (let api of this.apiNames) {
- this.dependencies.add(`${api}@experiments.addons.mozilla.org`);
- }
+ this.webAccessibleResources = manifestData.webAccessibleResources.map(res => new MatchGlob(res));
+ this.whiteListedHosts = new MatchPatternSet(manifestData.hostPermissions);
return this.manifest;
}
localizeMessage(...args) {
return this.localeData.localizeMessage(...args);
}
@@ -942,33 +965,34 @@ this.Extension = class extends Extension
});
}
parseManifest() {
return StartupCache.manifests.get([this.id, this.version, Services.locale.getAppLocaleAsLangTag()],
() => super.parseManifest());
}
- loadManifest() {
- return super.loadManifest().then(manifest => {
- if (this.errors.length) {
- return Promise.reject({errors: this.errors});
- }
+ async loadManifest() {
+ let manifest = await super.loadManifest();
+ if (this.errors.length) {
+ return Promise.reject({errors: this.errors});
+ }
+
+ if (this.apiNames.size) {
// Load Experiments APIs that this extension depends on.
- return Promise.all(
- Array.from(this.apiNames, api => ExtensionCommon.ExtensionAPIs.load(api))
- ).then(apis => {
- for (let API of apis) {
- this.apis.push(new API(this));
- }
+ let apis = await Promise.all(
+ Array.from(this.apiNames, api => ExtensionCommon.ExtensionAPIs.load(api)));
- return manifest;
- });
- });
+ for (let API of apis) {
+ this.apis.push(new API(this));
+ }
+ }
+
+ return manifest;
}
// Representation of the extension to send to content
// processes. This should include anything the content process might
// need.
serialize() {
return {
id: this.id,
@@ -1155,23 +1179,16 @@ this.Extension = class extends Extension
}
if (perms.origins.length > 0) {
let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
this.whiteListedHosts = new MatchPatternSet([...patterns, ...perms.origins],
{ignorePath: true});
}
- // Normalize all patterns to contain a single leading /
- let resources = (this.manifest.web_accessible_resources || [])
- .map(path => path.replace(/^\/*/, "/"));
-
- this.webAccessibleResources = resources.map(res => new MatchGlob(res));
-
-
this.policy.active = false;
this.policy = processScript.initExtension(this.serialize(), this);
this.initUnlimitedStoragePermission();
// The "startup" Management event sent on the extension instance itself
// is emitted just before the Management "startup" event,
// and it is used to run code that needs to be executed before