Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 addon-sdk loader instance used to load the developer tools.
9 */
11 let { Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu } = Components;
13 // addDebuggerToGlobal only allows adding the Debugger object to a global. The
14 // this object is not guaranteed to be a global (in particular on B2G, due to
15 // compartment sharing), so add the Debugger object to a sandbox instead.
16 let sandbox = Cu.Sandbox(CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')());
17 Cu.evalInSandbox(
18 "Components.utils.import('resource://gre/modules/jsdebugger.jsm');" +
19 "addDebuggerToGlobal(this);",
20 sandbox
21 );
22 let Debugger = sandbox.Debugger;
24 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
25 Cu.import("resource://gre/modules/Services.jsm");
26 let Timer = Cu.import("resource://gre/modules/Timer.jsm", {});
28 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
29 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
30 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
31 XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm");
33 let SourceMap = {};
34 Cu.import("resource://gre/modules/devtools/SourceMap.jsm", SourceMap);
36 let loader = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}).Loader;
37 let promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
39 this.EXPORTED_SYMBOLS = ["DevToolsLoader", "devtools", "BuiltinProvider",
40 "SrcdirProvider"];
42 /**
43 * Providers are different strategies for loading the devtools.
44 */
46 let loaderGlobals = {
47 btoa: btoa,
48 console: console,
49 promise: promise,
50 _Iterator: Iterator,
51 ChromeWorker: ChromeWorker,
52 loader: {
53 lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils),
54 lazyImporter: XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils),
55 lazyServiceGetter: XPCOMUtils.defineLazyServiceGetter.bind(XPCOMUtils)
56 }
57 };
59 // Used when the tools should be loaded from the Firefox package itself (the default)
60 function BuiltinProvider() {}
61 BuiltinProvider.prototype = {
62 load: function() {
63 this.loader = new loader.Loader({
64 modules: {
65 "Debugger": Debugger,
66 "Services": Object.create(Services),
67 "Timer": Object.create(Timer),
68 "toolkit/loader": loader,
69 "source-map": SourceMap,
70 },
71 paths: {
72 // When you add a line to this mapping, don't forget to make a
73 // corresponding addition to the SrcdirProvider mapping below as well.
74 "": "resource://gre/modules/commonjs/",
75 "main": "resource:///modules/devtools/main.js",
76 "devtools": "resource:///modules/devtools",
77 "devtools/toolkit": "resource://gre/modules/devtools",
78 "devtools/server": "resource://gre/modules/devtools/server",
79 "devtools/toolkit/webconsole": "resource://gre/modules/devtools/toolkit/webconsole",
80 "devtools/app-actor-front": "resource://gre/modules/devtools/app-actor-front.js",
81 "devtools/styleinspector/css-logic": "resource://gre/modules/devtools/styleinspector/css-logic",
82 "devtools/css-color": "resource://gre/modules/devtools/css-color",
83 "devtools/output-parser": "resource://gre/modules/devtools/output-parser",
84 "devtools/touch-events": "resource://gre/modules/devtools/touch-events",
85 "devtools/client": "resource://gre/modules/devtools/client",
86 "devtools/pretty-fast": "resource://gre/modules/devtools/pretty-fast.js",
87 "devtools/async-utils": "resource://gre/modules/devtools/async-utils",
88 "devtools/content-observer": "resource://gre/modules/devtools/content-observer",
89 "gcli": "resource://gre/modules/devtools/gcli",
90 "acorn": "resource://gre/modules/devtools/acorn",
91 "acorn/util/walk": "resource://gre/modules/devtools/acorn/walk.js",
93 // Allow access to xpcshell test items from the loader.
94 "xpcshell-test": "resource://test"
95 },
96 globals: loaderGlobals,
97 invisibleToDebugger: this.invisibleToDebugger
98 });
100 return promise.resolve(undefined);
101 },
103 unload: function(reason) {
104 loader.unload(this.loader, reason);
105 delete this.loader;
106 },
107 };
109 // Used when the tools should be loaded from a mozilla-central checkout. In addition
110 // to different paths, it needs to write chrome.manifest files to override chrome urls
111 // from the builtin tools.
112 function SrcdirProvider() {}
113 SrcdirProvider.prototype = {
114 fileURI: function(path) {
115 let file = new FileUtils.File(path);
116 return Services.io.newFileURI(file).spec;
117 },
119 load: function() {
120 let srcdir = Services.prefs.getComplexValue("devtools.loader.srcdir",
121 Ci.nsISupportsString);
122 srcdir = OS.Path.normalize(srcdir.data.trim());
123 let devtoolsDir = OS.Path.join(srcdir, "browser", "devtools");
124 let toolkitDir = OS.Path.join(srcdir, "toolkit", "devtools");
125 let mainURI = this.fileURI(OS.Path.join(devtoolsDir, "main.js"));
126 let devtoolsURI = this.fileURI(devtoolsDir);
127 let toolkitURI = this.fileURI(toolkitDir);
128 let serverURI = this.fileURI(OS.Path.join(toolkitDir, "server"));
129 let webconsoleURI = this.fileURI(OS.Path.join(toolkitDir, "webconsole"));
130 let appActorURI = this.fileURI(OS.Path.join(toolkitDir, "apps", "app-actor-front.js"));
131 let cssLogicURI = this.fileURI(OS.Path.join(toolkitDir, "styleinspector", "css-logic"));
132 let cssColorURI = this.fileURI(OS.Path.join(toolkitDir, "css-color"));
133 let outputParserURI = this.fileURI(OS.Path.join(toolkitDir, "output-parser"));
134 let touchEventsURI = this.fileURI(OS.Path.join(toolkitDir, "touch-events"));
135 let clientURI = this.fileURI(OS.Path.join(toolkitDir, "client"));
136 let prettyFastURI = this.fileURI(OS.Path.join(toolkitDir), "pretty-fast.js");
137 let asyncUtilsURI = this.fileURI(OS.Path.join(toolkitDir), "async-utils.js");
138 let contentObserverURI = this.fileURI(OS.Path.join(toolkitDir), "content-observer.js");
139 let gcliURI = this.fileURI(OS.Path.join(toolkitDir, "gcli", "source", "lib", "gcli"));
140 let acornURI = this.fileURI(OS.Path.join(toolkitDir, "acorn"));
141 let acornWalkURI = OS.Path.join(acornURI, "walk.js");
142 this.loader = new loader.Loader({
143 modules: {
144 "Debugger": Debugger,
145 "Services": Object.create(Services),
146 "Timer": Object.create(Timer),
147 "toolkit/loader": loader,
148 "source-map": SourceMap,
149 },
150 paths: {
151 "": "resource://gre/modules/commonjs/",
152 "main": mainURI,
153 "devtools": devtoolsURI,
154 "devtools/toolkit": toolkitURI,
155 "devtools/server": serverURI,
156 "devtools/toolkit/webconsole": webconsoleURI,
157 "devtools/app-actor-front": appActorURI,
158 "devtools/styleinspector/css-logic": cssLogicURI,
159 "devtools/css-color": cssColorURI,
160 "devtools/output-parser": outputParserURI,
161 "devtools/touch-events": touchEventsURI,
162 "devtools/client": clientURI,
163 "devtools/pretty-fast": prettyFastURI,
164 "devtools/async-utils": asyncUtilsURI,
165 "devtools/content-observer": contentObserverURI,
166 "gcli": gcliURI,
167 "acorn": acornURI,
168 "acorn/util/walk": acornWalkURI
169 },
170 globals: loaderGlobals,
171 invisibleToDebugger: this.invisibleToDebugger
172 });
174 return this._writeManifest(devtoolsDir).then(null, Cu.reportError);
175 },
177 unload: function(reason) {
178 loader.unload(this.loader, reason);
179 delete this.loader;
180 },
182 _readFile: function(filename) {
183 let deferred = promise.defer();
184 let file = new FileUtils.File(filename);
185 NetUtil.asyncFetch(file, (inputStream, status) => {
186 if (!Components.isSuccessCode(status)) {
187 deferred.reject(new Error("Couldn't load manifest: " + filename + "\n"));
188 return;
189 }
190 var data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
191 deferred.resolve(data);
192 });
193 return deferred.promise;
194 },
196 _writeFile: function(filename, data) {
197 let deferred = promise.defer();
198 let file = new FileUtils.File(filename);
200 var ostream = FileUtils.openSafeFileOutputStream(file)
202 var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
203 createInstance(Ci.nsIScriptableUnicodeConverter);
204 converter.charset = "UTF-8";
205 var istream = converter.convertToInputStream(data);
206 NetUtil.asyncCopy(istream, ostream, (status) => {
207 if (!Components.isSuccessCode(status)) {
208 deferred.reject(new Error("Couldn't write manifest: " + filename + "\n"));
209 return;
210 }
212 deferred.resolve(null);
213 });
214 return deferred.promise;
215 },
217 _writeManifest: function(dir) {
218 return this._readFile(OS.Path.join(dir, "jar.mn")).then((data) => {
219 // The file data is contained within inputStream.
220 // You can read it into a string with
221 let entries = [];
222 let lines = data.split(/\n/);
223 let preprocessed = /^\s*\*/;
224 let contentEntry = new RegExp("^\\s+content/(\\w+)/(\\S+)\\s+\\((\\S+)\\)");
225 for (let line of lines) {
226 if (preprocessed.test(line)) {
227 dump("Unable to override preprocessed file: " + line + "\n");
228 continue;
229 }
230 let match = contentEntry.exec(line);
231 if (match) {
232 let pathComponents = match[3].split("/");
233 pathComponents.unshift(dir);
234 let path = OS.Path.join.apply(OS.Path, pathComponents);
235 let uri = this.fileURI(path);
236 let entry = "override chrome://" + match[1] + "/content/" + match[2] + "\t" + uri;
237 entries.push(entry);
238 }
239 }
240 return this._writeFile(OS.Path.join(dir, "chrome.manifest"), entries.join("\n"));
241 }).then(() => {
242 Components.manager.addBootstrappedManifestLocation(new FileUtils.File(dir));
243 });
244 }
245 };
247 /**
248 * The main devtools API.
249 * In addition to a few loader-related details, this object will also include all
250 * exports from the main module. The standard instance of this loader is
251 * exported as |devtools| below, but if a fresh copy of the loader is needed,
252 * then a new one can also be created.
253 */
254 this.DevToolsLoader = function DevToolsLoader() {
255 this.require = this.require.bind(this);
256 };
258 DevToolsLoader.prototype = {
259 get provider() {
260 if (!this._provider) {
261 this._chooseProvider();
262 }
263 return this._provider;
264 },
266 _provider: null,
268 /**
269 * A dummy version of require, in case a provider hasn't been chosen yet when
270 * this is first called. This will then be replaced by the real version.
271 * @see setProvider
272 */
273 require: function() {
274 this._chooseProvider();
275 return this.require.apply(this, arguments);
276 },
278 /**
279 * Define a getter property on the given object that requires the given
280 * module. This enables delaying importing modules until the module is
281 * actually used.
282 *
283 * @param Object obj
284 * The object to define the property on.
285 * @param String property
286 * The property name.
287 * @param String module
288 * The module path.
289 */
290 lazyRequireGetter: function (obj, property, module) {
291 Object.defineProperty(obj, property, {
292 get: () => this.require(module)
293 });
294 },
296 /**
297 * Add a URI to the loader.
298 * @param string id
299 * The module id that can be used within the loader to refer to this module.
300 * @param string uri
301 * The URI to load as a module.
302 * @returns The module's exports.
303 */
304 loadURI: function(id, uri) {
305 let module = loader.Module(id, uri);
306 return loader.load(this.provider.loader, module).exports;
307 },
309 /**
310 * Let the loader know the ID of the main module to load.
311 *
312 * The loader doesn't need a main module, but it's nice to have. This
313 * will be called by the browser devtools to load the devtools/main module.
314 *
315 * When only using the server, there's no main module, and this method
316 * can be ignored.
317 */
318 main: function(id) {
319 // Ensure the main module isn't loaded twice, because it may have observable
320 // side-effects.
321 if (this._mainid) {
322 return;
323 }
324 this._mainid = id;
325 this._main = loader.main(this.provider.loader, id);
327 // Mirror the main module's exports on this object.
328 Object.getOwnPropertyNames(this._main).forEach(key => {
329 XPCOMUtils.defineLazyGetter(this, key, () => this._main[key]);
330 });
331 },
333 /**
334 * Override the provider used to load the tools.
335 */
336 setProvider: function(provider) {
337 if (provider === this._provider) {
338 return;
339 }
341 if (this._provider) {
342 var events = this.require("sdk/system/events");
343 events.emit("devtools-unloaded", {});
344 delete this.require;
345 this._provider.unload("newprovider");
346 }
347 this._provider = provider;
348 this._provider.invisibleToDebugger = this.invisibleToDebugger;
349 this._provider.load();
350 this.require = loader.Require(this._provider.loader, { id: "devtools" });
352 if (this._mainid) {
353 this.main(this._mainid);
354 }
355 },
357 /**
358 * Choose a default tools provider based on the preferences.
359 */
360 _chooseProvider: function() {
361 if (Services.prefs.prefHasUserValue("devtools.loader.srcdir")) {
362 this.setProvider(new SrcdirProvider());
363 } else {
364 this.setProvider(new BuiltinProvider());
365 }
366 },
368 /**
369 * Reload the current provider.
370 */
371 reload: function() {
372 var events = this.require("sdk/system/events");
373 events.emit("startupcache-invalidate", {});
374 events.emit("devtools-unloaded", {});
376 this._provider.unload("reload");
377 delete this._provider;
378 this._chooseProvider();
379 },
381 /**
382 * Sets whether the compartments loaded by this instance should be invisible
383 * to the debugger. Invisibility is needed for loaders that support debugging
384 * of chrome code. This is true of remote target environments, like Fennec or
385 * B2G. It is not the default case for desktop Firefox because we offer the
386 * Browser Toolbox for chrome debugging there, which uses its own, separate
387 * loader instance.
388 * @see browser/devtools/framework/ToolboxProcess.jsm
389 */
390 invisibleToDebugger: Services.appinfo.name !== "Firefox"
391 };
393 // Export the standard instance of DevToolsLoader used by the tools.
394 this.devtools = new DevToolsLoader();