Bug 1525200 - Add an xpcshell test which loads all the WebExtensions API modules. r=zombie
authorLuca Greco <lgreco@mozilla.com>
Mon, 11 Feb 2019 20:08:59 +0000
changeset 458601 9959d6380a99843b68b98c3d6b06419b76e8b2bf
parent 458600 87194c6797c2a69c9e71dc33af86e7c264a589c8
child 458602 0ace82a1db85a61001ed428415b175ae899cef76
push id35538
push userbtara@mozilla.com
push dateTue, 12 Feb 2019 05:25:24 +0000
treeherdermozilla-central@09beaf742eae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerszombie
bugs1525200
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1525200 - Add an xpcshell test which loads all the WebExtensions API modules. r=zombie This patch defines a new xpcshell test which loads all the WebExtensions API modules twice, first in alphabetic order and then again in reversed order. The goal of this new test is to make it easier to detect issues with API module loading, because all these API modules are loaded lazily in a shared global and it can fail if multiple API modules defines the same global in incompatible ways (e.g. something which is defined as a `const` in one module, and then as a `var` in a different one). Depends on D18683 Differential Revision: https://phabricator.services.mozilla.com/D19160
toolkit/components/extensions/test/xpcshell/test_load_all_api_modules.js
toolkit/components/extensions/test/xpcshell/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_load_all_api_modules.js
@@ -0,0 +1,156 @@
+"use strict";
+
+const {ExtensionCommon} = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
+const {Schemas} = ChromeUtils.import("resource://gre/modules/Schemas.jsm");
+
+const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
+
+const CATEGORY_EXTENSION_MODULES = "webextension-modules";
+const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas";
+const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts";
+
+const CATEGORY_EXTENSION_SCRIPTS_ADDON = "webextension-scripts-addon";
+const CATEGORY_EXTENSION_SCRIPTS_CONTENT = "webextension-scripts-content";
+const CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS = "webextension-scripts-devtools";
+
+let schemaURLs = new Set();
+schemaURLs.add("chrome://extensions/content/schemas/experiments.json");
+
+// Helper class used to load the API modules similarly to the apiManager
+// defined in ExtensionParent.jsm.
+class FakeAPIManager extends ExtensionCommon.SchemaAPIManager {
+  constructor(processType = "main") {
+    super(processType, Schemas);
+    this.initialized = false;
+  }
+
+  getModuleJSONURLs() {
+    return Array.from(Services.catMan.enumerateCategory(CATEGORY_EXTENSION_MODULES),
+                      ({value}) => value);
+  }
+
+  async lazyInit() {
+    if (this.initialized) {
+      return;
+    }
+
+    this.initialized = true;
+
+    let modulesPromise = this.loadModuleJSON(this.getModuleJSONURLs());
+
+    let scriptURLs = [];
+    for (let {value} of Services.catMan.enumerateCategory(CATEGORY_EXTENSION_SCRIPTS)) {
+      scriptURLs.push(value);
+    }
+
+    let scripts = await Promise.all(scriptURLs.map(url => ChromeUtils.compileScript(url)));
+
+    this.initModuleData(await modulesPromise);
+
+    this.initGlobal();
+    for (let script of scripts) {
+      script.executeInGlobal(this.global);
+    }
+
+    // Load order matters here. The base manifest defines types which are
+    // extended by other schemas, so needs to be loaded first.
+    await Schemas.load(BASE_SCHEMA).then(() => {
+      let promises = [];
+      for (let {value} of Services.catMan.enumerateCategory(CATEGORY_EXTENSION_SCHEMAS)) {
+        promises.push(Schemas.load(value));
+      }
+      for (let [url, {content}] of this.schemaURLs) {
+        promises.push(Schemas.load(url, content));
+      }
+      for (let url of schemaURLs) {
+        promises.push(Schemas.load(url));
+      }
+      return Promise.all(promises).then(() => {
+        Schemas.updateSharedSchemas();
+      });
+    });
+  }
+
+  async loadAllModules(reverseOrder = false) {
+    await this.lazyInit();
+
+    let apiModuleNames = Array.from(this.modules.keys()).filter(moduleName => {
+      let moduleDesc = this.modules.get(moduleName);
+      return moduleDesc && !!moduleDesc.url;
+    }).sort();
+
+    apiModuleNames = reverseOrder ? apiModuleNames.reverse() : apiModuleNames;
+
+    for (let apiModule of apiModuleNames) {
+      info(`Loading apiModule ${apiModule}: ${this.modules.get(apiModule).url}`);
+      await this.asyncLoadModule(apiModule);
+    }
+  }
+}
+
+// Specialized helper class used to test loading "child process" modules (similarly to the
+// SchemaAPIManagers sub-classes defined in ExtensionPageChild.jsm and ExtensionContent.jsm).
+class FakeChildProcessAPIManager extends FakeAPIManager {
+  constructor({processType, categoryScripts}) {
+    super(processType, Schemas);
+
+    this.categoryScripts = categoryScripts;
+  }
+
+  async lazyInit() {
+    if (!this.initialized) {
+      this.initialized = true;
+      this.initGlobal();
+      for (let {value} of Services.catMan.enumerateCategory(this.categoryScripts)) {
+        await this.loadScript(value);
+      }
+    }
+  }
+}
+
+async function test_loading_api_modules(createAPIManager) {
+  let fakeAPIManager;
+
+  info("Load API modules in alphabetic order");
+
+  fakeAPIManager = createAPIManager();
+  await fakeAPIManager.loadAllModules();
+
+  info("Load API modules in reverse order");
+
+  fakeAPIManager = createAPIManager();
+  await fakeAPIManager.loadAllModules(true);
+}
+
+add_task(function test_loading_main_process_api_modules() {
+  return test_loading_api_modules(() => {
+    return new FakeAPIManager();
+  });
+});
+
+add_task(function test_loading_extension_process_modules() {
+  return test_loading_api_modules(() => {
+    return new FakeChildProcessAPIManager({
+      processType: "addon",
+      categoryScripts: CATEGORY_EXTENSION_SCRIPTS_ADDON,
+    });
+  });
+});
+
+add_task(function test_loading_devtools_modules() {
+  return test_loading_api_modules(() => {
+    return new FakeChildProcessAPIManager({
+      processType: "devtools",
+      categoryScripts: CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS,
+    });
+  });
+});
+
+add_task(async function test_loading_content_process_modules() {
+  return test_loading_api_modules(() => {
+    return new FakeChildProcessAPIManager({
+      processType: "content",
+      categoryScripts: CATEGORY_EXTENSION_SCRIPTS_CONTENT,
+    });
+  });
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -45,16 +45,17 @@ skip-if = toolkit == 'android' # browser
 [test_ext_schemas_roots.js]
 [test_ext_schemas_async.js]
 [test_ext_schemas_allowed_contexts.js]
 [test_ext_schemas_interactive.js]
 [test_ext_schemas_manifest_permissions.js]
 [test_ext_schemas_privileged.js]
 [test_ext_schemas_revoke.js]
 [test_ext_unknown_permissions.js]
+[test_load_all_api_modules.js]
 [test_locale_converter.js]
 [test_locale_data.js]
 [test_ext_ipcBlob.js]
 
 [test_ext_runtime_sendMessage_args.js]
 
 [include:xpcshell-common.ini]
 skip-if = os != 'android' # only android is left without e10s