"use strict"; const { Schemas } = ChromeUtils.importESModule( "resource://gre/modules/Schemas.sys.mjs" ); 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, }); }); });