/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; /** * Manages the addon-sdk loader instance used to load the developer tools. */ var { utils: Cu } = Components; var { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); var { Loader, descriptor, resolveURI } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}); var { requireRawId } = Cu.import("resource://devtools/shared/loader-plugin-raw.jsm", {}); this.EXPORTED_SYMBOLS = ["DevToolsLoader", "devtools", "BuiltinProvider", "require", "loader"]; /** * Providers are different strategies for loading the devtools. */ var sharedGlobalBlocklist = ["sdk/indexed-db"]; /** * Used when the tools should be loaded from the Firefox package itself. * This is the default case. */ function BuiltinProvider() {} BuiltinProvider.prototype = { load: function () { const paths = { // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ "": "resource://gre/modules/commonjs/", // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ // Modules here are intended to have one implementation for // chrome, and a separate implementation for content. Here we // map the directory to the chrome subdirectory, but the content // loader will map to the content subdirectory. See the // README.md in devtools/shared/platform. "devtools/shared/platform": "resource://devtools/shared/platform/chrome", // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ "devtools": "resource://devtools", // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ "gcli": "resource://devtools/shared/gcli/source/lib/gcli", // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ "acorn": "resource://devtools/shared/acorn", // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ "acorn/util/walk": "resource://devtools/shared/acorn/walk.js", // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ "source-map": "resource://devtools/shared/sourcemap/source-map.js", // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ // Allow access to xpcshell test items from the loader. "xpcshell-test": "resource://test", // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠ // Allow access to locale data using paths closer to what is // used in the source tree. "devtools/client/locales": "chrome://devtools/locale", "devtools/shared/locales": "chrome://devtools-shared/locale", "toolkit/locales": "chrome://global/locale", }; // When creating a Loader invisible to the Debugger, we have to ensure // using only modules and not depend on any JSM. As everything that is // not loaded with Loader isn't going to respect `invisibleToDebugger`. // But we have to keep using Promise.jsm for other loader to prevent // breaking unhandled promise rejection in tests. if (this.invisibleToDebugger) { paths.promise = "resource://gre/modules/Promise-backend.js"; } this.loader = new Loader.Loader({ id: "fx-devtools", paths, invisibleToDebugger: this.invisibleToDebugger, sharedGlobal: true, sharedGlobalBlocklist, requireHook: (id, require) => { if (id.startsWith("raw!")) { return requireRawId(id, require); } return require(id); }, }); }, unload: function (reason) { Loader.unload(this.loader, reason); delete this.loader; }, }; var gNextLoaderID = 0; /** * The main devtools API. The standard instance of this loader is exported as * |devtools| below, but if a fresh copy of the loader is needed, then a new * one can also be created. */ this.DevToolsLoader = function DevToolsLoader() { this.require = this.require.bind(this); Services.obs.addObserver(this, "devtools-unload", false); }; DevToolsLoader.prototype = { destroy: function (reason = "shutdown") { Services.obs.removeObserver(this, "devtools-unload"); if (this._provider) { this._provider.unload(reason); delete this._provider; } }, get provider() { if (!this._provider) { this._loadProvider(); } return this._provider; }, _provider: null, get id() { if (this._id) { return this._id; } this._id = ++gNextLoaderID; return this._id; }, /** * A dummy version of require, in case a provider hasn't been chosen yet when * this is first called. This will then be replaced by the real version. * @see setProvider */ require: function () { if (!this._provider) { this._loadProvider(); } return this.require.apply(this, arguments); }, /** * Return true if |id| refers to something requiring help from a * loader plugin. */ isLoaderPluginId: function (id) { return id.startsWith("raw!"); }, /** * Override the provider used to load the tools. */ setProvider: function (provider) { if (provider === this._provider) { return; } if (this._provider) { delete this.require; this._provider.unload("newprovider"); } this._provider = provider; // Pass through internal loader settings specific to this loader instance this._provider.invisibleToDebugger = this.invisibleToDebugger; this._provider.load(); this.require = Loader.Require(this._provider.loader, { id: "devtools" }); // Fetch custom pseudo modules and globals let { modules, globals } = this.require("devtools/shared/builtin-modules"); // When creating a Loader for the browser toolbox, we have to use // Promise-backend.js, as a Loader module. Instead of Promise.jsm which // can't be flagged as invisible to debugger. if (this.invisibleToDebugger) { delete modules.promise; } // Register custom pseudo modules to the current loader instance let loader = this._provider.loader; for (let id in modules) { let exports = modules[id]; let uri = resolveURI(id, loader.mapping); loader.modules[uri] = { exports }; } // Register custom globals to the current loader instance globals.loader.id = this.id; Object.defineProperties(loader.globals, descriptor(globals)); // Expose lazy helpers on loader this.lazyGetter = globals.loader.lazyGetter; this.lazyImporter = globals.loader.lazyImporter; this.lazyServiceGetter = globals.loader.lazyServiceGetter; this.lazyRequireGetter = globals.loader.lazyRequireGetter; }, /** * Choose a default tools provider based on the preferences. */ _loadProvider: function () { this.setProvider(new BuiltinProvider()); }, /** * Handles "devtools-unload" event * * @param String data * reason passed to modules when unloaded */ observe: function (subject, topic, data) { if (topic != "devtools-unload") { return; } this.destroy(data); }, /** * Sets whether the compartments loaded by this instance should be invisible * to the debugger. Invisibility is needed for loaders that support debugging * of chrome code. This is true of remote target environments, like Fennec or * B2G. It is not the default case for desktop Firefox because we offer the * Browser Toolbox for chrome debugging there, which uses its own, separate * loader instance. * @see devtools/client/framework/ToolboxProcess.jsm */ invisibleToDebugger: Services.appinfo.name !== "Firefox" }; // Export the standard instance of DevToolsLoader used by the tools. this.devtools = this.loader = new DevToolsLoader(); this.require = this.devtools.require.bind(this.devtools); // For compatibility reasons, expose these symbols on "devtools": Object.defineProperty(this.devtools, "Toolbox", { get: () => this.require("devtools/client/framework/toolbox").Toolbox }); Object.defineProperty(this.devtools, "TargetFactory", { get: () => this.require("devtools/client/framework/target").TargetFactory });