Bug 1539764 - Add a targetFront attribute to the Front class to retrieve the target...
[gecko.git] / devtools / shared / Loader.jsm
blob6563c44d7441d9e5ce2ca5a34df6f44e3ea6dc19
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 /**
8  * Manages the base loader (base-loader.js) instance used to load the developer tools.
9  */
11 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
12 var { Loader, Require, resolveURI, unload } = ChromeUtils.import(
13   "resource://devtools/shared/base-loader.js"
15 var { requireRawId } = ChromeUtils.import(
16   "resource://devtools/shared/loader-plugin-raw.jsm"
19 this.EXPORTED_SYMBOLS = [
20   "DevToolsLoader",
21   "require",
22   "loader",
23   // Export StructuredCloneHolder for its use from builtin-modules
24   "StructuredCloneHolder",
27 var gNextLoaderID = 0;
29 /**
30  * The main devtools API. The standard instance of this loader is exported as
31  * |loader| below, but if a fresh copy of the loader is needed, then a new
32  * one can also be created.
33  *
34  * The two following boolean flags are used to control the sandboxes into
35  * which the modules are loaded.
36  * @param invisibleToDebugger boolean
37  *        If true, the modules won't be visible by the Debugger API.
38  *        This typically allows to hide server modules from the debugger panel.
39  * @param freshCompartment boolean
40  *        If true, the modules will be forced to be loaded in a distinct
41  *        compartment. It is typically used to load the modules in a distinct
42  *        system compartment, different from the main one, which is shared by
43  *        all JSMs, XPCOMs and modules loaded with this flag set to true.
44  *        We use this in order to debug modules loaded in this shared system
45  *        compartment. The debugger actor has to be running in a distinct
46  *        compartment than the context it is debugging.
47  */
48 this.DevToolsLoader = function DevToolsLoader({
49   invisibleToDebugger = false,
50   freshCompartment = false,
51 } = {}) {
52   const paths = {
53     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
54     devtools: "resource://devtools",
55     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
56     acorn: "resource://devtools/shared/acorn",
57     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
58     "acorn/util/walk": "resource://devtools/shared/acorn/walk.js",
59     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
60     // Allow access to xpcshell test items from the loader.
61     "xpcshell-test": "resource://test",
63     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
64     // Allow access to locale data using paths closer to what is
65     // used in the source tree.
66     "devtools/client/locales": "chrome://devtools/locale",
67     "devtools/shared/locales": "chrome://devtools-shared/locale",
68     "devtools/startup/locales": "chrome://devtools-startup/locale",
69     "toolkit/locales": "chrome://global/locale",
70   };
72   // When creating a Loader invisible to the Debugger, we have to ensure
73   // using only modules and not depend on any JSM. As everything that is
74   // not loaded with Loader isn't going to respect `invisibleToDebugger`.
75   // But we have to keep using Promise.jsm for other loader to prevent
76   // breaking unhandled promise rejection in tests.
77   if (invisibleToDebugger) {
78     paths.promise = "resource://gre/modules/Promise-backend.js";
79   }
81   this.loader = new Loader({
82     paths,
83     invisibleToDebugger,
84     freshCompartment,
85     sandboxName: "DevTools (Module loader)",
86     requireHook: (id, require) => {
87       if (id.startsWith("raw!") || id.startsWith("theme-loader!")) {
88         return requireRawId(id, require);
89       }
90       return require(id);
91     },
92   });
94   this.require = Require(this.loader, { id: "devtools" });
96   // Fetch custom pseudo modules and globals
97   const { modules, globals } = this.require("devtools/shared/builtin-modules");
99   // When creating a Loader for the browser toolbox, we have to use
100   // Promise-backend.js, as a Loader module. Instead of Promise.jsm which
101   // can't be flagged as invisible to debugger.
102   if (invisibleToDebugger) {
103     delete modules.promise;
104   }
106   // Register custom pseudo modules to the current loader instance
107   for (const id in modules) {
108     const uri = resolveURI(id, this.loader.mapping);
109     this.loader.modules[uri] = {
110       get exports() {
111         return modules[id];
112       },
113     };
114   }
116   // Register custom globals to the current loader instance
117   Object.defineProperties(
118     this.loader.globals,
119     Object.getOwnPropertyDescriptors(globals)
120   );
122   // Define the loader id for these two usecases:
123   // * access via the JSM (this.id)
124   // let { loader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
125   // loader.id
126   this.id = gNextLoaderID++;
127   // * access via module's `loader` global
128   // loader.id
129   globals.loader.id = this.id;
131   // Expose lazy helpers on `loader`
132   // ie. when you use it like that from a JSM:
133   // let { loader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
134   // loader.lazyGetter(...);
135   this.lazyGetter = globals.loader.lazyGetter;
136   this.lazyImporter = globals.loader.lazyImporter;
137   this.lazyServiceGetter = globals.loader.lazyServiceGetter;
138   this.lazyRequireGetter = globals.loader.lazyRequireGetter;
140   // When replaying, modify the require hook to allow the ReplayInspector to
141   // replace chrome interfaces with alternatives that understand the proxies
142   // created for objects in the recording/replaying process.
143   if (globals.isReplaying) {
144     const oldHook = this.loader.requireHook;
145     const ReplayInspector = this.require(
146       "devtools/server/actors/replay/inspector"
147     );
148     this.loader.requireHook = ReplayInspector.wrapRequireHook(oldHook);
149   }
152 DevToolsLoader.prototype = {
153   destroy: function(reason = "shutdown") {
154     unload(this.loader, reason);
155     delete this.loader;
156   },
158   /**
159    * Return true if |id| refers to something requiring help from a
160    * loader plugin.
161    */
162   isLoaderPluginId: function(id) {
163     return id.startsWith("raw!");
164   },
167 // Export the standard instance of DevToolsLoader used by the tools.
168 this.loader = new DevToolsLoader({
169   /**
170    * Sets whether the compartments loaded by this instance should be invisible
171    * to the debugger.  Invisibility is needed for loaders that support debugging
172    * of chrome code.  This is true of remote target environments, like Fennec or
173    * B2G.  It is not the default case for desktop Firefox because we offer the
174    * Browser Toolbox for chrome debugging there, which uses its own, separate
175    * loader instance.
176    * @see devtools/client/framework/ToolboxProcess.jsm
177    */
178   invisibleToDebugger: Services.appinfo.name !== "Firefox",
181 this.require = this.loader.require;