summaryrefslogtreecommitdiffstats
path: root/devtools/shared/Loader.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/Loader.jsm')
-rw-r--r--devtools/shared/Loader.jsm244
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
+});