Bug 1287010 - Move Management logic to SchemaAPIManager
- Moved Management logic to ExtensionUtils (as SchemaAPIManager) so that
the logic can be used by addon and content processes.
- Remove the `context.extension.hasPermission(api.permission)` check in
`generateAPIs` because the only user (`registeredPrivilegedAPI`) was
removed before in
bug 1295082.
- Add new category "webextension-scripts-content", intended for
registering the few scripts that must be loaded in a content process.
MozReview-Commit-ID: 81nhblV8YE6
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -54,19 +54,16 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
"resource://gre/modules/Schemas.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "loadExtScriptInScope",
- "resource://gre/modules/ExtensionGlobalScope.jsm");
-
XPCOMUtils.defineLazyGetter(this, "require", () => {
let obj = {};
Cu.import("resource://devtools/shared/Loader.jsm", obj);
return obj.require;
});
Cu.import("resource://gre/modules/ExtensionContent.jsm");
Cu.import("resource://gre/modules/ExtensionManagement.jsm");
@@ -84,16 +81,17 @@ let schemaURLs = new Set();
if (!AppConstants.RELEASE_BUILD) {
schemaURLs.add("chrome://extensions/content/schemas/experiments.json");
}
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
BaseContext,
EventEmitter,
+ SchemaAPIManager,
LocaleData,
Messenger,
instanceOf,
flushJarCache,
} = ExtensionUtils;
const LOGGER_ID_BASE = "addons.webextension.";
const UUID_MAP_PREF = "extensions.webextensions.uuids";
@@ -110,26 +108,21 @@ const COMMENT_REGEXP = new RegExp(String
)
//.*
`.replace(/\s+/g, ""), "gm");
var ExtensionContext, GlobalManager;
// This object loads the ext-*.js scripts that define the extension API.
-var Management = {
- initialized: null,
- scopes: [],
- schemaApis: {
- addon_parent: [],
- addon_child: [],
- content_parent: [],
- content_child: [],
- },
- emitter: new EventEmitter(),
+var Management = new class extends SchemaAPIManager {
+ constructor() {
+ super("main");
+ this.initialized = null;
+ }
// Loads all the ext-*.js scripts currently registered.
lazyInit() {
if (this.initialized) {
return this.initialized;
}
// Load order matters here. The base manifest defines types which are
@@ -141,99 +134,35 @@ var Management = {
}
for (let url of schemaURLs) {
promises.push(Schemas.load(url));
}
return Promise.all(promises);
});
for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS)) {
- loadExtScriptInScope(value, this);
+ this.loadScript(value);
}
this.initialized = promise;
return this.initialized;
- },
+ }
- /**
- * Called by an ext-*.js script to register an API.
- *
- * @param {string} namespace The API namespace.
- * Intended to match the namespace of the generated API, but not used at
- * the moment - see bugzil.la/1295774.
- * @param {string} envType Restricts the API to contexts that run in the
- * given environment. Must be one of the following:
- * - "addon_parent" - addon APIs that runs in the main process.
- * - "addon_child" - addon APIs that runs in an addon process.
- * - "content_parent" - content script APIs that runs in the main process.
- * - "content_child" - content script APIs that runs in a content process.
- * @param {function(BaseContext)} getAPI A function that returns an object
- * that will be merged with |chrome| and |browser|. The next example adds
- * the create, update and remove methods to the tabs API.
- *
- * registerSchemaAPI("tabs", "addon_parent", (context) => ({
- * tabs: { create, update },
- * }));
- * registerSchemaAPI("tabs", "addon_parent", (context) => ({
- * tabs: { remove },
- * }));
- */
registerSchemaAPI(namespace, envType, getAPI) {
- this.schemaApis[envType].push({namespace, getAPI});
+ if (envType == "addon_parent" || envType == "content_parent") {
+ super.registerSchemaAPI(namespace, envType, getAPI);
+ }
if (envType === "addon_child") {
// TODO(robwu): Remove this. It is a temporary hack to ease the transition
// from ext-*.js running in the parent to APIs running in a child process.
// This can be removed once there is a dedicated ExtensionContext with type
// "addon_child".
- this.schemaApis.addon_parent.push({namespace, getAPI});
- }
- },
-
- // Mash together all the APIs from apis into obj.
- generateAPIs(context, apis, obj) {
- // Recursively copy properties from source to dest.
- function copy(dest, source) {
- for (let prop in source) {
- let desc = Object.getOwnPropertyDescriptor(source, prop);
- if (typeof(desc.value) == "object") {
- if (!(prop in dest)) {
- dest[prop] = {};
- }
- copy(dest[prop], source[prop]);
- } else {
- Object.defineProperty(dest, prop, desc);
- }
- }
+ super.registerSchemaAPI(namespace, "addon_parent", getAPI);
}
-
- for (let api of apis) {
- if (api.permission) {
- if (!context.extension.hasPermission(api.permission)) {
- continue;
- }
- }
-
- api = api.getAPI(context);
- copy(obj, api);
- }
- },
-
- // The ext-*.js scripts can ask to be notified for certain hooks.
- on(hook, callback) {
- this.emitter.on(hook, callback);
- },
-
- // Ask to run all the callbacks that are registered for a given hook.
- emit(hook, ...args) {
- return this.emitter.emit(hook, ...args);
- },
-
- off(hook, callback) {
- this.emitter.off(hook, callback);
- },
+ }
};
// An extension page is an execution context for any extension content
// that runs in the chrome process. It's used for background pages
// (type="background"), popups (type="popup"), and any extension
// content loaded into browser tabs (type="tab").
//
// |params| is an object with the following properties:
@@ -628,18 +557,18 @@ GlobalManager = {
getExtension(extensionId) {
return this.extensionMap.get(extensionId);
},
injectInObject(context, defaultCallback, dest) {
let apis = {
extensionTypes: {},
};
- Management.generateAPIs(context, Management.schemaApis[context.envType], apis);
- Management.generateAPIs(context, context.extension.apis, apis);
+ Management.generateAPIs(context, apis);
+ SchemaAPIManager.generateAPIs(context, context.extension.apis, apis);
let schemaWrapper = {
get principal() {
return context.principal;
},
get cloneScope() {
return context.cloneScope;
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -49,27 +49,53 @@ var {
LocaleData,
Messenger,
injectAPI,
flushJarCache,
detectLanguage,
getInnerWindowID,
promiseDocumentReady,
ChildAPIManager,
+ SchemaAPIManager,
} = ExtensionUtils;
XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
+const CATEGORY_EXTENSION_SCRIPTS_CONTENT = "webextension-scripts-content";
+
function isWhenBeforeOrSame(when1, when2) {
let table = {"document_start": 0,
"document_end": 1,
"document_idle": 2};
return table[when1] <= table[when2];
}
+var apiManager = new class extends SchemaAPIManager {
+ constructor() {
+ super("content");
+ this.initialized = false;
+ }
+
+ generateAPIs(...args) {
+ if (!this.initialized) {
+ this.initialized = true;
+ for (let [/* name */, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_EXTENSION_SCRIPTS_CONTENT)) {
+ this.loadScript(value);
+ }
+ }
+ return super.generateAPIs(...args);
+ }
+
+ registerSchemaAPI(namespace, envType, getAPI) {
+ if (envType == "content_child") {
+ super.registerSchemaAPI(namespace, envType, getAPI);
+ }
+ }
+};
+
// This is the fairly simple API that we inject into content
// scripts.
var api = context => {
return {
runtime: {
connect: function(extensionId, connectInfo) {
if (!connectInfo) {
connectInfo = extensionId;
@@ -414,16 +440,17 @@ class ExtensionContext extends BaseConte
type: "content_script",
url,
incognito,
});
Schemas.inject(this.chromeObj, this.childManager);
injectAPI(api(this), this.chromeObj);
+ injectAPI(apiManager.generateAPIs(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;
}
}
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -28,16 +28,19 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/Locale.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
"resource://gre/modules/MessageChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
"resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "loadExtScriptInScope",
+ "resource://gre/modules/ExtensionGlobalScope.jsm");
+
function getConsole() {
return new ConsoleAPI({
maxLogLevelPref: "extensions.webextensions.log.level",
prefix: "WebExtensions",
});
}
XPCOMUtils.defineLazyGetter(this, "console", getConsole);
@@ -1560,16 +1563,118 @@ class ChildAPIManager {
hasListener(pathObj, path, name, listener) {
let ref = path.concat(name).join(".");
let set = this.listeners.get(ref) || new Set();
return set.has(listener);
}
}
/**
+ * This object loads the ext-*.js scripts that define the extension API.
+ *
+ * This class instance is shared with the scripts that it loads, so that the
+ * ext-*.js scripts and the instantiator can communicate with each other.
+ */
+class SchemaAPIManager extends EventEmitter {
+ /**
+ * @param {string} processType
+ * "main" - The main, one and only chrome browser process.
+ * "addon" - An addon process.
+ * "content" - A content process.
+ */
+ constructor(processType) {
+ super();
+ this.processType = processType;
+ this._schemaApis = {
+ addon_parent: [],
+ addon_child: [],
+ content_parent: [],
+ content_child: [],
+ };
+ }
+
+ loadScript(scriptUrl) {
+ loadExtScriptInScope(scriptUrl, this);
+ }
+
+ /**
+ * Called by an ext-*.js script to register an API.
+ *
+ * @param {string} namespace The API namespace.
+ * Intended to match the namespace of the generated API, but not used at
+ * the moment - see bugzil.la/1295774.
+ * @param {string} envType Restricts the API to contexts that run in the
+ * given environment. Must be one of the following:
+ * - "addon_parent" - addon APIs that runs in the main process.
+ * - "addon_child" - addon APIs that runs in an addon process.
+ * - "content_parent" - content script APIs that runs in the main process.
+ * - "content_child" - content script APIs that runs in a content process.
+ * @param {function(BaseContext)} getAPI A function that returns an object
+ * that will be merged with |chrome| and |browser|. The next example adds
+ * the create, update and remove methods to the tabs API.
+ *
+ * registerSchemaAPI("tabs", "addon_parent", (context) => ({
+ * tabs: { create, update },
+ * }));
+ * registerSchemaAPI("tabs", "addon_parent", (context) => ({
+ * tabs: { remove },
+ * }));
+ */
+ registerSchemaAPI(namespace, envType, getAPI) {
+ this._schemaApis[envType].push({namespace, getAPI});
+ }
+
+ /**
+ * Exports all registered scripts to `obj`.
+ *
+ * @param {BaseContext} context The context for which the API bindings are
+ * generated.
+ * @param {object} obj The destination of the API.
+ */
+ generateAPIs(context, obj) {
+ let apis = this._schemaApis[context.envType];
+ if (!apis) {
+ Cu.reportError(`No APIs have been registered for ${context.envType}`);
+ return;
+ }
+ SchemaAPIManager.generateAPIs(context, apis, obj);
+ }
+
+ /**
+ * Mash together all the APIs from `apis` into `obj`.
+ *
+ * @param {BaseContext} context The context for which the API bindings are
+ * generated.
+ * @param {Array} apis A list of objects, see `registerSchemaAPI`.
+ * @param {object} obj The destination of the API.
+ */
+ static generateAPIs(context, apis, obj) {
+ // Recursively copy properties from source to dest.
+ function copy(dest, source) {
+ for (let prop in source) {
+ let desc = Object.getOwnPropertyDescriptor(source, prop);
+ if (typeof(desc.value) == "object") {
+ if (!(prop in dest)) {
+ dest[prop] = {};
+ }
+ copy(dest[prop], source[prop]);
+ } else {
+ Object.defineProperty(dest, prop, desc);
+ }
+ }
+ }
+
+ for (let api of apis) {
+ api = api.getAPI(context);
+ copy(obj, api);
+ }
+ }
+}
+
+/**
* Convert any of several different representations of a date/time to a Date object.
* Accepts several formats:
* a Date object, an ISO8601 string, or a number of milliseconds since the epoch as
* either a number or a string.
*
* @param {Date|string|number} date
* The date to convert.
* @returns {Date}
@@ -1605,9 +1710,10 @@ this.ExtensionUtils = {
EventManager,
IconDetails,
LocaleData,
Messenger,
PlatformInfo,
SingletonEventManager,
SpreadArgs,
ChildAPIManager,
+ SchemaAPIManager,
};