toolkit/devtools/Loader.jsm

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

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

mercurial