bootstrap.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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. // @see http://mxr.mozilla.org/mozilla-central/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp
  5. 'use strict';
  6. // IMPORTANT: Avoid adding any initialization tasks here, if you need to do
  7. // something before add-on is loaded consider addon/runner module instead!
  8. const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
  9. results: Cr, manager: Cm } = Components;
  10. const ioService = Cc['@mozilla.org/network/io-service;1'].
  11. getService(Ci.nsIIOService);
  12. const resourceHandler = ioService.getProtocolHandler('resource').
  13. QueryInterface(Ci.nsIResProtocolHandler);
  14. const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
  15. const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
  16. getService(Ci.mozIJSSubScriptLoader);
  17. const prefService = Cc['@mozilla.org/preferences-service;1'].
  18. getService(Ci.nsIPrefService).
  19. QueryInterface(Ci.nsIPrefBranch);
  20. const appInfo = Cc["@mozilla.org/xre/app-info;1"].
  21. getService(Ci.nsIXULAppInfo);
  22. const vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
  23. getService(Ci.nsIVersionComparator);
  24. const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable',
  25. 'install', 'uninstall', 'upgrade', 'downgrade' ];
  26. const bind = Function.call.bind(Function.bind);
  27. let loader = null;
  28. let unload = null;
  29. let cuddlefishSandbox = null;
  30. let nukeTimer = null;
  31. // Utility function that synchronously reads local resource from the given
  32. // `uri` and returns content string.
  33. function readURI(uri) {
  34. let ioservice = Cc['@mozilla.org/network/io-service;1'].
  35. getService(Ci.nsIIOService);
  36. let channel = ioservice.newChannel(uri, 'UTF-8', null);
  37. let stream = channel.open();
  38. let cstream = Cc['@mozilla.org/intl/converter-input-stream;1'].
  39. createInstance(Ci.nsIConverterInputStream);
  40. cstream.init(stream, 'UTF-8', 0, 0);
  41. let str = {};
  42. let data = '';
  43. let read = 0;
  44. do {
  45. read = cstream.readString(0xffffffff, str);
  46. data += str.value;
  47. } while (read != 0);
  48. cstream.close();
  49. return data;
  50. }
  51. // We don't do anything on install & uninstall yet, but in a future
  52. // we should allow add-ons to cleanup after uninstall.
  53. function install(data, reason) {}
  54. function uninstall(data, reason) {}
  55. function startup(data, reasonCode) {
  56. try {
  57. let reason = REASON[reasonCode];
  58. // URI for the root of the XPI file.
  59. // 'jar:' URI if the addon is packed, 'file:' URI otherwise.
  60. // (Used by l10n module in order to fetch `locale` folder)
  61. let rootURI = data.resourceURI.spec;
  62. // TODO: Maybe we should perform read harness-options.json asynchronously,
  63. // since we can't do anything until 'sessionstore-windows-restored' anyway.
  64. let options = JSON.parse(readURI(rootURI + './harness-options.json'));
  65. let id = options.jetpackID;
  66. let name = options.name;
  67. // Clean the metadata
  68. options.metadata[name]['permissions'] = options.metadata[name]['permissions'] || {};
  69. // freeze the permissionss
  70. Object.freeze(options.metadata[name]['permissions']);
  71. // freeze the metadata
  72. Object.freeze(options.metadata[name]);
  73. // Register a new resource 'domain' for this addon which is mapping to
  74. // XPI's `resources` folder.
  75. // Generate the domain name by using jetpack ID, which is the extension ID
  76. // by stripping common characters that doesn't work as a domain name:
  77. let uuidRe =
  78. /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
  79. let domain = id.
  80. toLowerCase().
  81. replace(/@/g, '-at-').
  82. replace(/\./g, '-dot-').
  83. replace(uuidRe, '$1');
  84. let prefixURI = 'resource://' + domain + '/';
  85. let resourcesURI = ioService.newURI(rootURI + '/resources/', null, null);
  86. resourceHandler.setSubstitution(domain, resourcesURI);
  87. // Create path to URLs mapping supported by loader.
  88. let paths = {
  89. // Relative modules resolve to add-on package lib
  90. './': prefixURI + name + '/lib/',
  91. './tests/': prefixURI + name + '/tests/',
  92. '': 'resource://gre/modules/commonjs/'
  93. };
  94. // Maps addon lib and tests ressource folders for each package
  95. paths = Object.keys(options.metadata).reduce(function(result, name) {
  96. result[name + '/'] = prefixURI + name + '/lib/'
  97. result[name + '/tests/'] = prefixURI + name + '/tests/'
  98. return result;
  99. }, paths);
  100. // We need to map tests folder when we run sdk tests whose package name
  101. // is stripped
  102. if (name == 'addon-sdk')
  103. paths['tests/'] = prefixURI + name + '/tests/';
  104. let useBundledSDK = options['force-use-bundled-sdk'];
  105. if (!useBundledSDK) {
  106. try {
  107. useBundledSDK = prefService.getBoolPref("extensions.addon-sdk.useBundledSDK");
  108. }
  109. catch (e) {
  110. // Pref doesn't exist, allow using Firefox shipped SDK
  111. }
  112. }
  113. // Starting with Firefox 21.0a1, we start using modules shipped into firefox
  114. // Still allow using modules from the xpi if the manifest tell us to do so.
  115. // And only try to look for sdk modules in xpi if the xpi actually ship them
  116. if (options['is-sdk-bundled'] &&
  117. (vc.compare(appInfo.version, '21.0a1') < 0 || useBundledSDK)) {
  118. // Maps sdk module folders to their resource folder
  119. paths[''] = prefixURI + 'addon-sdk/lib/';
  120. // test.js is usually found in root commonjs or SDK_ROOT/lib/ folder,
  121. // so that it isn't shipped in the xpi. Keep a copy of it in sdk/ folder
  122. // until we no longer support SDK modules in XPI:
  123. paths['test'] = prefixURI + 'addon-sdk/lib/sdk/test.js';
  124. }
  125. // Retrieve list of module folder overloads based on preferences in order to
  126. // eventually used a local modules instead of files shipped into Firefox.
  127. let branch = prefService.getBranch('extensions.modules.' + id + '.path');
  128. paths = branch.getChildList('', {}).reduce(function (result, name) {
  129. // Allows overloading of any sub folder by replacing . by / in pref name
  130. let path = name.substr(1).split('.').join('/');
  131. // Only accept overloading folder by ensuring always ending with `/`
  132. if (path) path += '/';
  133. let fileURI = branch.getCharPref(name);
  134. // On mobile, file URI has to end with a `/` otherwise, setSubstitution
  135. // takes the parent folder instead.
  136. if (fileURI[fileURI.length-1] !== '/')
  137. fileURI += '/';
  138. // Maps the given file:// URI to a resource:// in order to avoid various
  139. // failure that happens with file:// URI and be close to production env
  140. let resourcesURI = ioService.newURI(fileURI, null, null);
  141. let resName = 'extensions.modules.' + domain + '.commonjs.path' + name;
  142. resourceHandler.setSubstitution(resName, resourcesURI);
  143. result[path] = 'resource://' + resName + '/';
  144. return result;
  145. }, paths);
  146. // Make version 2 of the manifest
  147. let manifest = options.manifest;
  148. // Import `cuddlefish.js` module using a Sandbox and bootstrap loader.
  149. let cuddlefishPath = 'loader/cuddlefish.js';
  150. let cuddlefishURI = 'resource://gre/modules/commonjs/sdk/' + cuddlefishPath;
  151. if (paths['sdk/']) { // sdk folder has been overloaded
  152. // (from pref, or cuddlefish is still in the xpi)
  153. cuddlefishURI = paths['sdk/'] + cuddlefishPath;
  154. }
  155. else if (paths['']) { // root modules folder has been overloaded
  156. cuddlefishURI = paths[''] + 'sdk/' + cuddlefishPath;
  157. }
  158. cuddlefishSandbox = loadSandbox(cuddlefishURI);
  159. let cuddlefish = cuddlefishSandbox.exports;
  160. // Normalize `options.mainPath` so that it looks like one that will come
  161. // in a new version of linker.
  162. let main = options.mainPath;
  163. unload = cuddlefish.unload;
  164. loader = cuddlefish.Loader({
  165. paths: paths,
  166. // modules manifest.
  167. manifest: manifest,
  168. // Add-on ID used by different APIs as a unique identifier.
  169. id: id,
  170. // Add-on name.
  171. name: name,
  172. // Add-on version.
  173. version: options.metadata[name].version,
  174. // Add-on package descriptor.
  175. metadata: options.metadata[name],
  176. // Add-on load reason.
  177. loadReason: reason,
  178. prefixURI: prefixURI,
  179. // Add-on URI.
  180. rootURI: rootURI,
  181. // options used by system module.
  182. // File to write 'OK' or 'FAIL' (exit code emulation).
  183. resultFile: options.resultFile,
  184. // Arguments passed as --static-args
  185. staticArgs: options.staticArgs,
  186. // Add-on preferences branch name
  187. preferencesBranch: options.preferencesBranch,
  188. // Arguments related to test runner.
  189. modules: {
  190. '@test/options': {
  191. allTestModules: options.allTestModules,
  192. iterations: options.iterations,
  193. filter: options.filter,
  194. profileMemory: options.profileMemory,
  195. stopOnError: options.stopOnError,
  196. verbose: options.verbose,
  197. parseable: options.parseable,
  198. checkMemory: options.check_memory,
  199. }
  200. }
  201. });
  202. let module = cuddlefish.Module('sdk/loader/cuddlefish', cuddlefishURI);
  203. let require = cuddlefish.Require(loader, module);
  204. require('sdk/addon/runner').startup(reason, {
  205. loader: loader,
  206. main: main,
  207. prefsURI: rootURI + 'defaults/preferences/prefs.js'
  208. });
  209. } catch (error) {
  210. dump('Bootstrap error: ' +
  211. (error.message ? error.message : String(error)) + '\n' +
  212. (error.stack || error.fileName + ': ' + error.lineNumber) + '\n');
  213. throw error;
  214. }
  215. };
  216. function loadSandbox(uri) {
  217. let proto = {
  218. sandboxPrototype: {
  219. loadSandbox: loadSandbox,
  220. ChromeWorker: ChromeWorker
  221. }
  222. };
  223. let sandbox = Cu.Sandbox(systemPrincipal, proto);
  224. // Create a fake commonjs environnement just to enable loading loader.js
  225. // correctly
  226. sandbox.exports = {};
  227. sandbox.module = { uri: uri, exports: sandbox.exports };
  228. sandbox.require = function (id) {
  229. if (id !== "chrome")
  230. throw new Error("Bootstrap sandbox `require` method isn't implemented.");
  231. return Object.freeze({ Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
  232. CC: bind(CC, Components), components: Components,
  233. ChromeWorker: ChromeWorker });
  234. };
  235. scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
  236. return sandbox;
  237. }
  238. function unloadSandbox(sandbox) {
  239. if ("nukeSandbox" in Cu)
  240. Cu.nukeSandbox(sandbox);
  241. }
  242. function setTimeout(callback, delay) {
  243. let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  244. timer.initWithCallback({ notify: callback }, delay,
  245. Ci.nsITimer.TYPE_ONE_SHOT);
  246. return timer;
  247. }
  248. function shutdown(data, reasonCode) {
  249. let reason = REASON[reasonCode];
  250. if (loader) {
  251. unload(loader, reason);
  252. unload = null;
  253. // Don't waste time cleaning up if the application is shutting down
  254. if (reason != "shutdown") {
  255. // Avoid leaking all modules when something goes wrong with one particular
  256. // module. Do not clean it up immediatly in order to allow executing some
  257. // actions on addon disabling.
  258. // We need to keep a reference to the timer, otherwise it is collected
  259. // and won't ever fire.
  260. nukeTimer = setTimeout(nukeModules, 1000);
  261. }
  262. }
  263. };
  264. function nukeModules() {
  265. nukeTimer = null;
  266. // module objects store `exports` which comes from sandboxes
  267. // We should avoid keeping link to these object to avoid leaking sandboxes
  268. for (let key in loader.modules) {
  269. delete loader.modules[key];
  270. }
  271. // Direct links to sandboxes should be removed too
  272. for (let key in loader.sandboxes) {
  273. let sandbox = loader.sandboxes[key];
  274. delete loader.sandboxes[key];
  275. // Bug 775067: From FF17 we can kill all CCW from a given sandbox
  276. unloadSandbox(sandbox);
  277. }
  278. loader = null;
  279. // both `toolkit/loader` and `system/xul-app` are loaded as JSM's via
  280. // `cuddlefish.js`, and needs to be unloaded to avoid memory leaks, when
  281. // the addon is unload.
  282. unloadSandbox(cuddlefishSandbox.loaderSandbox);
  283. unloadSandbox(cuddlefishSandbox.xulappSandbox);
  284. // Bug 764840: We need to unload cuddlefish otherwise it will stay alive
  285. // and keep a reference to this compartment.
  286. unloadSandbox(cuddlefishSandbox);
  287. cuddlefishSandbox = null;
  288. }