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