toolkit/components/workerloader/require.js

branch
TOR_BUG_9701
changeset 14
925c144e1f1f
equal deleted inserted replaced
-1:000000000000 0:67b71eb4ac14
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
5
6 /**
7 * Implementation of a CommonJS module loader for workers.
8 *
9 * Use:
10 * // in the .js file loaded by the constructor of the worker
11 * importScripts("resource://gre/modules/workers/require.js");
12 * let module = require("resource://gre/modules/worker/myModule.js");
13 *
14 * // in myModule.js
15 * // Load dependencies
16 * let SimpleTest = require("resource://gre/modules/workers/SimpleTest.js");
17 * let Logger = require("resource://gre/modules/workers/Logger.js");
18 *
19 * // Define things that will not be exported
20 * let someValue = // ...
21 *
22 * // Export symbols
23 * exports.foo = // ...
24 * exports.bar = // ...
25 *
26 *
27 * Note #1:
28 * Properties |fileName| and |stack| of errors triggered from a module
29 * contain file names that do not correspond to human-readable module paths.
30 * Human readers should rather use properties |moduleName| and |moduleStack|.
31 *
32 * Note #2:
33 * The current version of |require()| only accepts absolute URIs.
34 *
35 * Note #3:
36 * By opposition to some other module loader implementations, this module
37 * loader does not enforce separation of global objects. Consequently, if
38 * a module modifies a global object (e.g. |String.prototype|), all other
39 * modules in the same worker may be affected.
40 */
41
42
43 (function(exports) {
44 "use strict";
45
46 if (exports.require) {
47 // Avoid double-imports
48 return;
49 }
50
51 // Simple implementation of |require|
52 let require = (function() {
53
54 /**
55 * Mapping from module paths to module exports.
56 *
57 * @keys {string} The absolute path to a module.
58 * @values {object} The |exports| objects for that module.
59 */
60 let modules = new Map();
61
62 /**
63 * Mapping from object urls to module paths.
64 */
65 let paths = {
66 /**
67 * @keys {string} The object url holding a module.
68 * @values {string} The absolute path to that module.
69 */
70 _map: new Map(),
71 /**
72 * A regexp that may be used to search for all mapped paths.
73 */
74 get regexp() {
75 if (this._regexp) {
76 return this._regexp;
77 }
78 let objectURLs = [];
79 for (let [objectURL, _] of this._map) {
80 objectURLs.push(objectURL);
81 }
82 return this._regexp = new RegExp(objectURLs.join("|"), "g");
83 },
84 _regexp: null,
85 /**
86 * Add a mapping from an object url to a path.
87 */
88 set: function(url, path) {
89 this._regexp = null; // invalidate regexp
90 this._map.set(url, path);
91 },
92 /**
93 * Get a mapping from an object url to a path.
94 */
95 get: function(url) {
96 return this._map.get(url);
97 },
98 /**
99 * Transform a string by replacing all the instances of objectURLs
100 * appearing in that string with the corresponding module path.
101 *
102 * This is used typically to translate exception stacks.
103 *
104 * @param {string} source A source string.
105 * @return {string} The same string as |source|, in which every occurrence
106 * of an objectURL registered in this object has been replaced with the
107 * corresponding module path.
108 */
109 substitute: function(source) {
110 let map = this._map;
111 return source.replace(this.regexp, function(url) {
112 return map.get(url) || url;
113 }, "g");
114 }
115 };
116
117 /**
118 * A human-readable version of |stack|.
119 *
120 * @type {string}
121 */
122 Object.defineProperty(Error.prototype, "moduleStack",
123 {
124 get: function() {
125 return paths.substitute(this.stack);
126 }
127 });
128 /**
129 * A human-readable version of |fileName|.
130 *
131 * @type {string}
132 */
133 Object.defineProperty(Error.prototype, "moduleName",
134 {
135 get: function() {
136 return paths.substitute(this.fileName);
137 }
138 });
139
140 /**
141 * Import a module
142 *
143 * @param {string} path The path to the module.
144 * @return {*} An object containing the properties exported by the module.
145 */
146 return function require(path) {
147 if (typeof path != "string" || path.indexOf("://") == -1) {
148 throw new TypeError("The argument to require() must be a string uri, got " + path);
149 }
150 // Automatically add ".js" if there is no extension
151 let uri;
152 if (path.lastIndexOf(".") <= path.lastIndexOf("/")) {
153 uri = path + ".js";
154 } else {
155 uri = path;
156 }
157
158 // Exports provided by the module
159 let exports = Object.create(null);
160
161 // Identification of the module
162 let module = {
163 id: path,
164 uri: uri,
165 exports: exports
166 };
167
168 // Make module available immediately
169 // (necessary in case of circular dependencies)
170 if (modules.has(path)) {
171 return modules.get(path).exports;
172 }
173 modules.set(path, module);
174
175 let name = ":" + path;
176 let objectURL;
177 try {
178 // Load source of module, synchronously
179 let xhr = new XMLHttpRequest();
180 xhr.open("GET", uri, false);
181 xhr.responseType = "text";
182 xhr.send();
183
184
185 let source = xhr.responseText;
186 if (source == "") {
187 // There doesn't seem to be a better way to detect that the file couldn't be found
188 throw new Error("Could not find module " + path);
189 }
190 // From the source, build a function and an object URL. We
191 // avoid any newline at the start of the file to ensure that
192 // we do not mess up with line numbers. However, using object URLs
193 // messes up with stack traces in instances of Error().
194 source = "require._tmpModules[\"" + name + "\"] = " +
195 "function(exports, require, module) {" +
196 source +
197 "\n}\n";
198 let blob = new Blob(
199 [
200 (new TextEncoder()).encode(source)
201 ], {
202 type: "application/javascript"
203 });
204 objectURL = URL.createObjectURL(blob);
205 paths.set(objectURL, path);
206 importScripts(objectURL);
207 require._tmpModules[name].call(null, exports, require, module);
208
209 } catch (ex) {
210 // Module loading has failed, exports should not be made available
211 // after all.
212 modules.delete(path);
213 throw ex;
214 } finally {
215 if (objectURL) {
216 // Clean up the object url as soon as possible. It will not be needed.
217 URL.revokeObjectURL(objectURL);
218 }
219 delete require._tmpModules[name];
220 }
221
222 Object.freeze(module.exports);
223 Object.freeze(module);
224 return module.exports;
225 };
226 })();
227
228 /**
229 * An object used to hold temporarily the module constructors
230 * while they are being loaded.
231 *
232 * @keys {string} The path to the module, prefixed with ":".
233 * @values {function} A function wrapping the module.
234 */
235 require._tmpModules = Object.create(null);
236 Object.freeze(require);
237
238 Object.defineProperty(exports, "require", {
239 value: require,
240 enumerable: true,
241 configurable: false
242 });
243 })(this);

mercurial