[JSC] Specifying same module entry point multiple times cause TypeError
[WebKit-https.git] / Source / JavaScriptCore / builtins / ModuleLoaderPrototype.js
1 /*
2  * Copyright (C) 2015, 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 // https://whatwg.github.io/loader/#loader-object
27 // Module Loader has several hooks that can be customized by the platform.
28 // For example, the [[Fetch]] hook can be provided by the JavaScriptCore shell
29 // as fetching the payload from the local file system.
30 // Currently, there are 4 hooks.
31 //    1. Loader.resolve
32 //    2. Loader.fetch
33 //    3. Loader.translate
34 //    4. Loader.instantiate
35
36 function setStateToMax(entry, newState)
37 {
38     // https://whatwg.github.io/loader/#set-state-to-max
39
40     "use strict";
41
42     if (entry.state < newState)
43         entry.state = newState;
44 }
45
46 function newRegistryEntry(key)
47 {
48     // https://whatwg.github.io/loader/#registry
49     //
50     // Each registry entry becomes one of the 5 states.
51     // 1. Fetch
52     //     Ready to fetch (or now fetching) the resource of this module.
53     //     Typically, we fetch the source code over the network or from the file system.
54     //     a. If the status is Fetch and there is no entry.fetch promise, the entry is ready to fetch.
55     //     b. If the status is Fetch and there is the entry.fetch promise, the entry is just fetching the resource.
56     //
57     // 2. Translate
58     //     Ready to translate (or now translating) the raw fetched resource to the ECMAScript source code.
59     //     We can insert the hook that translates the resources e.g. transpilers.
60     //     a. If the status is Translate and there is no entry.translate promise, the entry is ready to translate.
61     //     b. If the status is Translate and there is the entry.translate promise, the entry is just translating
62     //        the payload to the source code.
63     //
64     // 3. Instantiate (AnalyzeModule)
65     //     Ready to instantiate (or now instantiating) the module record from the fetched (and translated)
66     //     source code.
67     //     Typically, we parse the module code, extract the dependencies and binding information.
68     //     a. If the status is Instantiate and there is no entry.instantiate promise, the entry is ready to instantiate.
69     //     b. If the status is Instantiate and there is the entry.translate promise, the entry is just instantiating
70     //        the module record.
71     //
72     // 4. Satisfy
73     //     Ready to request the dependent modules (or now requesting & resolving).
74     //     Without this state, the current draft causes infinite recursion when there is circular dependency.
75     //     a. If the status is Satisfy and there is no entry.satisfy promise, the entry is ready to resolve the dependencies.
76     //     b. If the status is Satisfy and there is the entry.satisfy promise, the entry is just resolving
77     //        the dependencies.
78     //
79     // 5. Link
80     //     Ready to link the module with the other modules.
81     //     Linking means that the module imports and exports the bindings from/to the other modules.
82     //
83     // 6. Ready
84     //     The module is linked, so the module is ready to be executed.
85     //
86     // Each registry entry has the 4 promises; "fetch", "translate", "instantiate" and "satisfy".
87     // They are assigned when starting the each phase. And they are fulfilled when the each phase is completed.
88     //
89     // In the current module draft, linking will be performed after the whole modules are instantiated and the dependencies are resolved.
90     // And execution is also done after the all modules are linked.
91     //
92     // TODO: We need to exploit the way to execute the module while fetching non-related modules.
93     // One solution; introducing the ready promise chain to execute the modules concurrently while keeping
94     // the execution order.
95
96     "use strict";
97
98     return {
99         key: key,
100         state: @ModuleFetch,
101         metadata: @undefined,
102         fetch: @undefined,
103         translate: @undefined,
104         instantiate: @undefined,
105         satisfy: @undefined,
106         dependencies: [], // To keep the module order, we store the module keys in the array.
107         dependenciesMap: @undefined,
108         module: @undefined, // JSModuleRecord
109         error: @undefined,
110     };
111 }
112
113 function ensureRegistered(key)
114 {
115     // https://whatwg.github.io/loader/#ensure-registered
116
117     "use strict";
118
119     var entry = this.registry.@get(key);
120     if (entry)
121         return entry;
122
123     entry = this.newRegistryEntry(key);
124     this.registry.@set(key, entry);
125
126     return entry;
127 }
128
129 function forceFulfillPromise(promise, value)
130 {
131     "use strict";
132
133     if (promise.@promiseState === @promiseStatePending)
134         @fulfillPromise(promise, value);
135 }
136
137 function fulfillFetch(entry, payload)
138 {
139     // https://whatwg.github.io/loader/#fulfill-fetch
140
141     "use strict";
142
143     if (!entry.fetch)
144         entry.fetch = @newPromiseCapability(@InternalPromise).@promise;
145     this.forceFulfillPromise(entry.fetch, payload);
146     this.setStateToMax(entry, @ModuleTranslate);
147 }
148
149 function fulfillTranslate(entry, source)
150 {
151     // https://whatwg.github.io/loader/#fulfill-translate
152
153     "use strict";
154
155     if (!entry.translate)
156         entry.translate = @newPromiseCapability(@InternalPromise).@promise;
157     this.forceFulfillPromise(entry.translate, source);
158     this.setStateToMax(entry, @ModuleInstantiate);
159 }
160
161 function fulfillInstantiate(entry, optionalInstance, source)
162 {
163     // https://whatwg.github.io/loader/#fulfill-instantiate
164
165     "use strict";
166
167     if (!entry.instantiate)
168         entry.instantiate = @newPromiseCapability(@InternalPromise).@promise;
169     this.commitInstantiated(entry, optionalInstance, source);
170
171     // FIXME: The draft fulfills the promise in the CommitInstantiated operation.
172     // But it CommitInstantiated is also used in the requestInstantiate and
173     // we should not "force fulfill" there.
174     // So we separate "force fulfill" operation from the CommitInstantiated operation.
175     // https://github.com/whatwg/loader/pull/67
176     this.forceFulfillPromise(entry.instantiate, entry);
177 }
178
179 function commitInstantiated(entry, optionalInstance, source)
180 {
181     // https://whatwg.github.io/loader/#commit-instantiated
182
183     "use strict";
184
185     var moduleRecord = this.instantiation(optionalInstance, source, entry);
186
187     // FIXME: Described in the draft,
188     //   4. Fulfill entry.[[Instantiate]] with instance.
189     // But, instantiate promise should be fulfilled with the entry.
190     // We remove this statement because instantiate promise will be
191     // fulfilled without this "force fulfill" operation.
192     // https://github.com/whatwg/loader/pull/67
193
194     var dependencies = [];
195     var dependenciesMap = moduleRecord.dependenciesMap;
196     moduleRecord.registryEntry = entry;
197     var requestedModules = this.requestedModules(moduleRecord);
198     for (var i = 0, length = requestedModules.length; i < length; ++i) {
199         var depKey = requestedModules[i];
200         var pair = {
201             key: depKey,
202             value: @undefined
203         };
204         dependencies.@push(pair);
205         dependenciesMap.@set(depKey, pair);
206     }
207     entry.dependencies = dependencies;
208     entry.dependenciesMap = dependenciesMap;
209     entry.module = moduleRecord;
210     this.setStateToMax(entry, @ModuleSatisfy);
211 }
212
213 function instantiation(result, source, entry)
214 {
215     // https://whatwg.github.io/loader/#instantiation
216     // FIXME: Current implementation does not support optionalInstance.
217     // https://bugs.webkit.org/show_bug.cgi?id=148171
218
219     "use strict";
220
221     return this.parseModule(entry.key, source);
222 }
223
224 // Loader.
225
226 function requestFetch(key, initiator)
227 {
228     // https://whatwg.github.io/loader/#request-fetch
229
230     "use strict";
231
232     var entry = this.ensureRegistered(key);
233     if (entry.fetch)
234         return entry.fetch;
235
236     // Hook point.
237     // 2. Loader.fetch
238     //     https://whatwg.github.io/loader/#browser-fetch
239     //     Take the key and fetch the resource actually.
240     //     For example, JavaScriptCore shell can provide the hook fetching the resource
241     //     from the local file system.
242     var fetchPromise = this.fetch(key, initiator).then((payload) => {
243         this.setStateToMax(entry, @ModuleTranslate);
244         return payload;
245     });
246     entry.fetch = fetchPromise;
247     return fetchPromise;
248 }
249
250 function requestTranslate(key, initiator)
251 {
252     // https://whatwg.github.io/loader/#request-translate
253
254     "use strict";
255
256     var entry = this.ensureRegistered(key);
257     if (entry.translate)
258         return entry.translate;
259
260     var translatePromise = this.requestFetch(key, initiator).then((payload) => {
261         // Hook point.
262         // 3. Loader.translate
263         //     https://whatwg.github.io/loader/#browser-translate
264         //     Take the key and the fetched source code and translate it to the ES6 source code.
265         //     Typically it is used by the transpilers.
266         return this.translate(key, payload, initiator).then((source) => {
267             this.setStateToMax(entry, @ModuleInstantiate);
268             return source;
269         });
270     });
271     entry.translate = translatePromise;
272     return translatePromise;
273 }
274
275 function requestInstantiate(key, initiator)
276 {
277     // https://whatwg.github.io/loader/#request-instantiate
278
279     "use strict";
280
281     var entry = this.ensureRegistered(key);
282     if (entry.instantiate)
283         return entry.instantiate;
284
285     var instantiatePromise = this.requestTranslate(key, initiator).then((source) => {
286         // Hook point.
287         // 4. Loader.instantiate
288         //     https://whatwg.github.io/loader/#browser-instantiate
289         //     Take the key and the translated source code, and instantiate the module record
290         //     by parsing the module source code.
291         //     It has the chance to provide the optional module instance that is different from
292         //     the ordinary one.
293         return this.instantiate(key, source, initiator).then((optionalInstance) => {
294             this.commitInstantiated(entry, optionalInstance, source);
295             return entry;
296         });
297     });
298     entry.instantiate = instantiatePromise;
299     return instantiatePromise;
300 }
301
302 function requestSatisfy(key, initiator)
303 {
304     // https://whatwg.github.io/loader/#satisfy-instance
305
306     "use strict";
307
308     var entry = this.ensureRegistered(key);
309     if (entry.satisfy)
310         return entry.satisfy;
311
312     var satisfyPromise = this.requestInstantiate(key, initiator).then((entry) => {
313         var depLoads = [];
314         for (var i = 0, length = entry.dependencies.length; i < length; ++i) {
315             let pair = entry.dependencies[i];
316
317             // Hook point.
318             // 1. Loader.resolve.
319             //     https://whatwg.github.io/loader/#browser-resolve
320             //     Take the name and resolve it to the unique identifier for the resource location.
321             //     For example, take the "jquery" and return the URL for the resource.
322             var promise = this.resolve(pair.key, key, initiator).then((depKey) => {
323                 var depEntry = this.ensureRegistered(depKey);
324
325                 // Recursive resolving. The dependencies of this entry is being resolved or already resolved.
326                 // Stop tracing the circular dependencies.
327                 // But to retrieve the instantiated module record correctly,
328                 // we need to wait for the instantiation for the dependent module.
329                 // For example, reaching here, the module is starting resolving the dependencies.
330                 // But the module may or may not reach the instantiation phase in the loader's pipeline.
331                 // If we wait for the Satisfy for this module, it construct the circular promise chain and
332                 // rejected by the Promises runtime. Since only we need is the instantiated module, instead of waiting
333                 // the Satisfy for this module, we just wait Instantiate for this.
334                 if (depEntry.satisfy) {
335                     return depEntry.instantiate.then((entry) => {
336                         pair.value = entry.module;
337                         return entry;
338                     });
339                 }
340
341                 return this.requestSatisfy(depKey, initiator).then((entry) => {
342                     pair.value = entry.module;
343                     return entry;
344                 });
345             });
346             depLoads.@push(promise);
347         }
348
349         return @InternalPromise.internalAll(depLoads).then((modules) => {
350             this.setStateToMax(entry, @ModuleLink);
351             return entry;
352         });
353     });
354
355     entry.satisfy = satisfyPromise;
356     return satisfyPromise;
357 }
358
359 function requestInstantiateAll(key, initiator)
360 {
361     // https://whatwg.github.io/loader/#request-instantiate-all
362
363     "use strict";
364
365     return this.requestSatisfy(key, initiator);
366 }
367
368 function requestLink(key, initiator)
369 {
370     // https://whatwg.github.io/loader/#request-link
371
372     "use strict";
373
374     var entry = this.ensureRegistered(key);
375     if (entry.state > @ModuleLink) {
376         var deferred = @newPromiseCapability(@InternalPromise);
377         deferred.@resolve.@call(@undefined, entry);
378         return deferred.@promise;
379     }
380
381     return this.requestInstantiateAll(key, initiator).then((entry) => {
382         this.link(entry, initiator);
383         return entry;
384     });
385 }
386
387 function requestReady(key, initiator)
388 {
389     // https://whatwg.github.io/loader/#request-ready
390
391     "use strict";
392
393     return this.requestLink(key, initiator).then((entry) => {
394         this.moduleEvaluation(entry.module, initiator);
395     });
396 }
397
398 // Linking semantics.
399
400 function link(entry, initiator)
401 {
402     // https://whatwg.github.io/loader/#link
403
404     "use strict";
405
406     // FIXME: Current implementation does not support optionalInstance.
407     // So Link's step 3 is skipped.
408     // https://bugs.webkit.org/show_bug.cgi?id=148171
409
410     if (entry.state === @ModuleReady)
411         return;
412     this.setStateToMax(entry, @ModuleReady);
413
414     // Since we already have the "dependencies" field,
415     // we can call moduleDeclarationInstantiation with the correct order
416     // without constructing the dependency graph by calling dependencyGraph.
417     var dependencies = entry.dependencies;
418     for (var i = 0, length = dependencies.length; i < length; ++i) {
419         var pair = dependencies[i];
420         this.link(pair.value.registryEntry, initiator);
421     }
422
423     this.moduleDeclarationInstantiation(entry.module, initiator);
424 }
425
426 // Module semantics.
427
428 function moduleEvaluation(moduleRecord, initiator)
429 {
430     // http://www.ecma-international.org/ecma-262/6.0/#sec-moduleevaluation
431
432     "use strict";
433
434     if (moduleRecord.evaluated)
435         return;
436     moduleRecord.evaluated = true;
437
438     var entry = moduleRecord.registryEntry;
439
440     // The contents of the [[RequestedModules]] is cloned into entry.dependencies.
441     var dependencies = entry.dependencies;
442     for (var i = 0, length = dependencies.length; i < length; ++i) {
443         var pair = dependencies[i];
444         var requiredModuleRecord = pair.value;
445         this.moduleEvaluation(requiredModuleRecord, initiator);
446     }
447     this.evaluate(entry.key, moduleRecord, initiator);
448 }
449
450 // APIs to control the module loader.
451
452 function provide(key, stage, value)
453 {
454     "use strict";
455
456     var entry = this.ensureRegistered(key);
457
458     if (stage === @ModuleFetch) {
459         if (entry.state > @ModuleFetch)
460             @throwTypeError("Requested module is already fetched.");
461         this.fulfillFetch(entry, value);
462         return;
463     }
464
465     if (stage === @ModuleTranslate) {
466         if (entry.state > @ModuleTranslate)
467             @throwTypeError("Requested module is already translated.");
468         this.fulfillFetch(entry, @undefined);
469         this.fulfillTranslate(entry, value);
470         return;
471     }
472
473     if (stage === @ModuleInstantiate) {
474         if (entry.state > @ModuleInstantiate)
475             @throwTypeError("Requested module is already instantiated.");
476         this.fulfillFetch(entry, @undefined);
477         this.fulfillTranslate(entry, value);
478         entry.translate.then((source) => {
479             this.fulfillInstantiate(entry, value, source);
480         });
481         return;
482     }
483
484     @throwTypeError("Requested module is already ready to be executed.");
485 }
486
487 function loadAndEvaluateModule(moduleName, referrer, initiator)
488 {
489     "use strict";
490
491     // Loader.resolve hook point.
492     // resolve: moduleName => Promise(moduleKey)
493     // Take the name and resolve it to the unique identifier for the resource location.
494     // For example, take the "jquery" and return the URL for the resource.
495     return this.resolve(moduleName, referrer, initiator).then((key) => {
496         return this.requestReady(key, initiator);
497     });
498 }
499
500 function loadModule(moduleName, referrer, initiator)
501 {
502     "use strict";
503
504     // Loader.resolve hook point.
505     // resolve: moduleName => Promise(moduleKey)
506     // Take the name and resolve it to the unique identifier for the resource location.
507     // For example, take the "jquery" and return the URL for the resource.
508     return this.resolve(moduleName, referrer, initiator).then((key) => {
509         return this.requestInstantiateAll(key, initiator);
510     }).then((entry) => {
511         return entry.key;
512     });
513 }
514
515 function linkAndEvaluateModule(key, initiator)
516 {
517     "use strict";
518
519     var entry = this.ensureRegistered(key);
520     if (entry.state < @ModuleLink)
521         @throwTypeError("Requested module is not instantiated yet.");
522
523     this.link(entry, initiator);
524     return this.moduleEvaluation(entry.module, initiator);
525 }