Source code

Revision control

Other Tools

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/. */
4
5
"use strict";
6
7
/**
8
* Manages the base loader (base-loader.js) instance used to load the developer tools.
9
*/
10
11
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
12
var { Loader, Require, resolveURI, unload } = ChromeUtils.import(
14
);
15
var { requireRawId } = ChromeUtils.import(
17
);
18
19
this.EXPORTED_SYMBOLS = [
20
"DevToolsLoader",
21
"require",
22
"loader",
23
// Export StructuredCloneHolder for its use from builtin-modules
24
"StructuredCloneHolder",
25
];
26
27
var gNextLoaderID = 0;
28
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 ⚠
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",
62
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
};
71
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) {
79
}
80
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
});
93
94
this.require = Require(this.loader, { id: "devtools" });
95
96
// Fetch custom pseudo modules and globals
97
const { modules, globals } = this.require("devtools/shared/builtin-modules");
98
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
}
105
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
}
115
116
// Register custom globals to the current loader instance
117
Object.defineProperties(
118
this.loader.globals,
119
Object.getOwnPropertyDescriptors(globals)
120
);
121
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;
130
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;
139
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
}
150
};
151
152
DevToolsLoader.prototype = {
153
destroy: function(reason = "shutdown") {
154
unload(this.loader, reason);
155
delete this.loader;
156
},
157
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
},
165
};
166
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/browser-toolbox/Launcher.jsm
177
*/
178
invisibleToDebugger: Services.appinfo.name !== "Firefox",
179
});
180
181
this.require = this.loader.require;