diff options
Diffstat (limited to 'devtools/shared/Loader.jsm')
-rw-r--r-- | devtools/shared/Loader.jsm | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/devtools/shared/Loader.jsm b/devtools/shared/Loader.jsm new file mode 100644 index 000000000..6b31075ee --- /dev/null +++ b/devtools/shared/Loader.jsm @@ -0,0 +1,244 @@ +/* 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 +}); |