Loader.jsm 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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. "use strict";
  5. /**
  6. * Manages the addon-sdk loader instance used to load the developer tools.
  7. */
  8. var { utils: Cu } = Components;
  9. var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
  10. var { Loader, descriptor, resolveURI } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
  11. var { requireRawId } = Cu.import("resource://devtools/shared/loader-plugin-raw.jsm", {});
  12. this.EXPORTED_SYMBOLS = ["DevToolsLoader", "devtools", "BuiltinProvider",
  13. "require", "loader"];
  14. /**
  15. * Providers are different strategies for loading the devtools.
  16. */
  17. var sharedGlobalBlocklist = ["sdk/indexed-db"];
  18. /**
  19. * Used when the tools should be loaded from the Firefox package itself.
  20. * This is the default case.
  21. */
  22. function BuiltinProvider() {}
  23. BuiltinProvider.prototype = {
  24. load: function () {
  25. const paths = {
  26. // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
  27. "": "resource://gre/modules/commonjs/",
  28. // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
  29. // Modules here are intended to have one implementation for
  30. // chrome, and a separate implementation for content. Here we
  31. // map the directory to the chrome subdirectory, but the content
  32. // loader will map to the content subdirectory. See the
  33. // README.md in devtools/shared/platform.
  34. "devtools/shared/platform": "resource://devtools/shared/platform/chrome",
  35. // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
  36. "devtools": "resource://devtools",
  37. // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
  38. "gcli": "resource://devtools/shared/gcli/source/lib/gcli",
  39. // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
  40. "acorn": "resource://devtools/shared/acorn",
  41. // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
  42. "acorn/util/walk": "resource://devtools/shared/acorn/walk.js",
  43. // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
  44. "source-map": "resource://devtools/shared/sourcemap/source-map.js",
  45. // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
  46. // Allow access to xpcshell test items from the loader.
  47. "xpcshell-test": "resource://test",
  48. // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
  49. // Allow access to locale data using paths closer to what is
  50. // used in the source tree.
  51. "devtools/client/locales": "chrome://devtools/locale",
  52. "devtools/shared/locales": "chrome://devtools-shared/locale",
  53. "toolkit/locales": "chrome://global/locale",
  54. };
  55. // When creating a Loader invisible to the Debugger, we have to ensure
  56. // using only modules and not depend on any JSM. As everything that is
  57. // not loaded with Loader isn't going to respect `invisibleToDebugger`.
  58. // But we have to keep using Promise.jsm for other loader to prevent
  59. // breaking unhandled promise rejection in tests.
  60. if (this.invisibleToDebugger) {
  61. paths.promise = "resource://gre/modules/Promise-backend.js";
  62. }
  63. this.loader = new Loader.Loader({
  64. id: "fx-devtools",
  65. paths,
  66. invisibleToDebugger: this.invisibleToDebugger,
  67. sharedGlobal: true,
  68. sharedGlobalBlocklist,
  69. requireHook: (id, require) => {
  70. if (id.startsWith("raw!")) {
  71. return requireRawId(id, require);
  72. }
  73. return require(id);
  74. },
  75. });
  76. },
  77. unload: function (reason) {
  78. Loader.unload(this.loader, reason);
  79. delete this.loader;
  80. },
  81. };
  82. var gNextLoaderID = 0;
  83. /**
  84. * The main devtools API. The standard instance of this loader is exported as
  85. * |devtools| below, but if a fresh copy of the loader is needed, then a new
  86. * one can also be created.
  87. */
  88. this.DevToolsLoader = function DevToolsLoader() {
  89. this.require = this.require.bind(this);
  90. Services.obs.addObserver(this, "devtools-unload", false);
  91. };
  92. DevToolsLoader.prototype = {
  93. destroy: function (reason = "shutdown") {
  94. Services.obs.removeObserver(this, "devtools-unload");
  95. if (this._provider) {
  96. this._provider.unload(reason);
  97. delete this._provider;
  98. }
  99. },
  100. get provider() {
  101. if (!this._provider) {
  102. this._loadProvider();
  103. }
  104. return this._provider;
  105. },
  106. _provider: null,
  107. get id() {
  108. if (this._id) {
  109. return this._id;
  110. }
  111. this._id = ++gNextLoaderID;
  112. return this._id;
  113. },
  114. /**
  115. * A dummy version of require, in case a provider hasn't been chosen yet when
  116. * this is first called. This will then be replaced by the real version.
  117. * @see setProvider
  118. */
  119. require: function () {
  120. if (!this._provider) {
  121. this._loadProvider();
  122. }
  123. return this.require.apply(this, arguments);
  124. },
  125. /**
  126. * Return true if |id| refers to something requiring help from a
  127. * loader plugin.
  128. */
  129. isLoaderPluginId: function (id) {
  130. return id.startsWith("raw!");
  131. },
  132. /**
  133. * Override the provider used to load the tools.
  134. */
  135. setProvider: function (provider) {
  136. if (provider === this._provider) {
  137. return;
  138. }
  139. if (this._provider) {
  140. delete this.require;
  141. this._provider.unload("newprovider");
  142. }
  143. this._provider = provider;
  144. // Pass through internal loader settings specific to this loader instance
  145. this._provider.invisibleToDebugger = this.invisibleToDebugger;
  146. this._provider.load();
  147. this.require = Loader.Require(this._provider.loader, { id: "devtools" });
  148. // Fetch custom pseudo modules and globals
  149. let { modules, globals } = this.require("devtools/shared/builtin-modules");
  150. // When creating a Loader for the browser toolbox, we have to use
  151. // Promise-backend.js, as a Loader module. Instead of Promise.jsm which
  152. // can't be flagged as invisible to debugger.
  153. if (this.invisibleToDebugger) {
  154. delete modules.promise;
  155. }
  156. // Register custom pseudo modules to the current loader instance
  157. let loader = this._provider.loader;
  158. for (let id in modules) {
  159. let exports = modules[id];
  160. let uri = resolveURI(id, loader.mapping);
  161. loader.modules[uri] = { exports };
  162. }
  163. // Register custom globals to the current loader instance
  164. globals.loader.id = this.id;
  165. Object.defineProperties(loader.globals, descriptor(globals));
  166. // Expose lazy helpers on loader
  167. this.lazyGetter = globals.loader.lazyGetter;
  168. this.lazyImporter = globals.loader.lazyImporter;
  169. this.lazyServiceGetter = globals.loader.lazyServiceGetter;
  170. this.lazyRequireGetter = globals.loader.lazyRequireGetter;
  171. },
  172. /**
  173. * Choose a default tools provider based on the preferences.
  174. */
  175. _loadProvider: function () {
  176. this.setProvider(new BuiltinProvider());
  177. },
  178. /**
  179. * Handles "devtools-unload" event
  180. *
  181. * @param String data
  182. * reason passed to modules when unloaded
  183. */
  184. observe: function (subject, topic, data) {
  185. if (topic != "devtools-unload") {
  186. return;
  187. }
  188. this.destroy(data);
  189. },
  190. /**
  191. * Sets whether the compartments loaded by this instance should be invisible
  192. * to the debugger. Invisibility is needed for loaders that support debugging
  193. * of chrome code. This is true of remote target environments, like Fennec or
  194. * B2G. It is not the default case for desktop Firefox because we offer the
  195. * Browser Toolbox for chrome debugging there, which uses its own, separate
  196. * loader instance.
  197. * @see devtools/client/framework/ToolboxProcess.jsm
  198. */
  199. invisibleToDebugger: Services.appinfo.name !== "Firefox"
  200. };
  201. // Export the standard instance of DevToolsLoader used by the tools.
  202. this.devtools = this.loader = new DevToolsLoader();
  203. this.require = this.devtools.require.bind(this.devtools);
  204. // For compatibility reasons, expose these symbols on "devtools":
  205. Object.defineProperty(this.devtools, "Toolbox", {
  206. get: () => this.require("devtools/client/framework/toolbox").Toolbox
  207. });
  208. Object.defineProperty(this.devtools, "TargetFactory", {
  209. get: () => this.require("devtools/client/framework/target").TargetFactory
  210. });