toolkit/devtools/Loader.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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();

mercurial