d4a777f6663fbd1481e7df21696b2e58bf8ce56a
[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.instantiate
34
35 @globalPrivate
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 @globalPrivate
47 function newRegistryEntry(key)
48 {
49     // https://whatwg.github.io/loader/#registry
50     //
51     // Each registry entry becomes one of the 5 states.
52     // 1. Fetch
53     //     Ready to fetch (or now fetching) the resource of this module.
54     //     Typically, we fetch the source code over the network or from the file system.
55     //     a. If the status is Fetch and there is no entry.fetch promise, the entry is ready to fetch.
56     //     b. If the status is Fetch and there is the entry.fetch promise, the entry is just fetching the resource.
57     //
58     // 2. Instantiate (AnalyzeModule)
59     //     Ready to instantiate (or now instantiating) the module record from the fetched
60     //     source code.
61     //     Typically, we parse the module code, extract the dependencies and binding information.
62     //     a. If the status is Instantiate and there is no entry.instantiate promise, the entry is ready to instantiate.
63     //     b. If the status is Instantiate and there is the entry.fetch promise, the entry is just instantiating
64     //        the module record.
65     //
66     // 3. Satisfy
67     //     Ready to request the dependent modules (or now requesting & resolving).
68     //     Without this state, the current draft causes infinite recursion when there is circular dependency.
69     //     a. If the status is Satisfy and there is no entry.satisfy promise, the entry is ready to resolve the dependencies.
70     //     b. If the status is Satisfy and there is the entry.satisfy promise, the entry is just resolving
71     //        the dependencies.
72     //
73     // 4. Link
74     //     Ready to link the module with the other modules.
75     //     Linking means that the module imports and exports the bindings from/to the other modules.
76     //
77     // 5. Ready
78     //     The module is linked, so the module is ready to be executed.
79     //
80     // Each registry entry has the 4 promises; "fetch", "instantiate" and "satisfy".
81     // They are assigned when starting the each phase. And they are fulfilled when the each phase is completed.
82     //
83     // In the current module draft, linking will be performed after the whole modules are instantiated and the dependencies are resolved.
84     // And execution is also done after the all modules are linked.
85     //
86     // TODO: We need to exploit the way to execute the module while fetching non-related modules.
87     // One solution; introducing the ready promise chain to execute the modules concurrently while keeping
88     // the execution order.
89
90     "use strict";
91
92     return {
93         key: key,
94         state: @ModuleFetch,
95         metadata: @undefined,
96         fetch: @undefined,
97         instantiate: @undefined,
98         satisfy: @undefined,
99         dependencies: [], // To keep the module order, we store the module keys in the array.
100         dependenciesMap: @undefined,
101         module: @undefined, // JSModuleRecord
102         error: @undefined,
103     };
104 }
105
106 function ensureRegistered(key)
107 {
108     // https://whatwg.github.io/loader/#ensure-registered
109
110     "use strict";
111
112     var entry = this.registry.@get(key);
113     if (entry)
114         return entry;
115
116     entry = @newRegistryEntry(key);
117     this.registry.@set(key, entry);
118
119     return entry;
120 }
121
122 function forceFulfillPromise(promise, value)
123 {
124     "use strict";
125
126     if (promise.@promiseState === @promiseStatePending)
127         @fulfillPromise(promise, value);
128 }
129
130 function fulfillFetch(entry, source)
131 {
132     // https://whatwg.github.io/loader/#fulfill-fetch
133
134     "use strict";
135
136     if (!entry.fetch)
137         entry.fetch = @newPromiseCapability(@InternalPromise).@promise;
138     this.forceFulfillPromise(entry.fetch, source);
139     @setStateToMax(entry, @ModuleInstantiate);
140 }
141
142 function fulfillInstantiate(entry, optionalInstance, source)
143 {
144     // https://whatwg.github.io/loader/#fulfill-instantiate
145
146     "use strict";
147
148     if (!entry.instantiate)
149         entry.instantiate = @newPromiseCapability(@InternalPromise).@promise;
150     this.commitInstantiated(entry, optionalInstance, source);
151
152     // FIXME: The draft fulfills the promise in the CommitInstantiated operation.
153     // But it CommitInstantiated is also used in the requestInstantiate and
154     // we should not "force fulfill" there.
155     // So we separate "force fulfill" operation from the CommitInstantiated operation.
156     // https://github.com/whatwg/loader/pull/67
157     this.forceFulfillPromise(entry.instantiate, entry);
158 }
159
160 function commitInstantiated(entry, optionalInstance, source)
161 {
162     // https://whatwg.github.io/loader/#commit-instantiated
163
164     "use strict";
165
166     var moduleRecord = this.instantiation(optionalInstance, source, entry);
167
168     // FIXME: Described in the draft,
169     //   4. Fulfill entry.[[Instantiate]] with instance.
170     // But, instantiate promise should be fulfilled with the entry.
171     // We remove this statement because instantiate promise will be
172     // fulfilled without this "force fulfill" operation.
173     // https://github.com/whatwg/loader/pull/67
174
175     var dependencies = [];
176     var dependenciesMap = moduleRecord.dependenciesMap;
177     moduleRecord.registryEntry = entry;
178     var requestedModules = this.requestedModules(moduleRecord);
179     for (var i = 0, length = requestedModules.length; i < length; ++i) {
180         var depKey = requestedModules[i];
181         var pair = {
182             key: depKey,
183             value: @undefined
184         };
185         @putByValDirect(dependencies, dependencies.length, pair);
186         dependenciesMap.@set(depKey, pair);
187     }
188     entry.dependencies = dependencies;
189     entry.dependenciesMap = dependenciesMap;
190     entry.module = moduleRecord;
191     @setStateToMax(entry, @ModuleSatisfy);
192 }
193
194 function instantiation(result, source, entry)
195 {
196     // https://whatwg.github.io/loader/#instantiation
197     // FIXME: Current implementation does not support optionalInstance.
198     // https://bugs.webkit.org/show_bug.cgi?id=148171
199
200     "use strict";
201
202     return this.parseModule(entry.key, source);
203 }
204
205 // Loader.
206
207 function requestFetch(key, initiator)
208 {
209     // https://whatwg.github.io/loader/#request-fetch
210
211     "use strict";
212
213     var entry = this.ensureRegistered(key);
214     if (entry.fetch)
215         return entry.fetch;
216
217     // Hook point.
218     // 2. Loader.fetch
219     //     https://whatwg.github.io/loader/#browser-fetch
220     //     Take the key and fetch the resource actually.
221     //     For example, JavaScriptCore shell can provide the hook fetching the resource
222     //     from the local file system.
223     var fetchPromise = this.fetch(key, initiator).then((source) => {
224         @setStateToMax(entry, @ModuleInstantiate);
225         return source;
226     });
227     entry.fetch = fetchPromise;
228     return fetchPromise;
229 }
230
231 function requestInstantiate(key, initiator)
232 {
233     // https://whatwg.github.io/loader/#request-instantiate
234
235     "use strict";
236
237     var entry = this.ensureRegistered(key);
238     if (entry.instantiate)
239         return entry.instantiate;
240
241     var instantiatePromise = this.requestFetch(key, initiator).then((source) => {
242         // Hook point.
243         // 3. Loader.instantiate
244         //     https://whatwg.github.io/loader/#browser-instantiate
245         //     Take the key and the fetched source code, and instantiate the module record
246         //     by parsing the module source code.
247         //     It has the chance to provide the optional module instance that is different from
248         //     the ordinary one.
249         return this.instantiate(key, source, initiator).then((optionalInstance) => {
250             this.commitInstantiated(entry, optionalInstance, source);
251             return entry;
252         });
253     });
254     entry.instantiate = instantiatePromise;
255     return instantiatePromise;
256 }
257
258 function requestSatisfy(key, initiator)
259 {
260     // https://whatwg.github.io/loader/#satisfy-instance
261
262     "use strict";
263
264     var entry = this.ensureRegistered(key);
265     if (entry.satisfy)
266         return entry.satisfy;
267
268     var satisfyPromise = this.requestInstantiate(key, initiator).then((entry) => {
269         var depLoads = [];
270         for (var i = 0, length = entry.dependencies.length; i < length; ++i) {
271             let pair = entry.dependencies[i];
272
273             // Hook point.
274             // 1. Loader.resolve.
275             //     https://whatwg.github.io/loader/#browser-resolve
276             //     Take the name and resolve it to the unique identifier for the resource location.
277             //     For example, take the "jquery" and return the URL for the resource.
278             var promise = this.resolve(pair.key, key, initiator).then((depKey) => {
279                 var depEntry = this.ensureRegistered(depKey);
280
281                 // Recursive resolving. The dependencies of this entry is being resolved or already resolved.
282                 // Stop tracing the circular dependencies.
283                 // But to retrieve the instantiated module record correctly,
284                 // we need to wait for the instantiation for the dependent module.
285                 // For example, reaching here, the module is starting resolving the dependencies.
286                 // But the module may or may not reach the instantiation phase in the loader's pipeline.
287                 // If we wait for the Satisfy for this module, it construct the circular promise chain and
288                 // rejected by the Promises runtime. Since only we need is the instantiated module, instead of waiting
289                 // the Satisfy for this module, we just wait Instantiate for this.
290                 if (depEntry.satisfy) {
291                     return depEntry.instantiate.then((entry) => {
292                         pair.value = entry.module;
293                         return entry;
294                     });
295                 }
296
297                 return this.requestSatisfy(depKey, initiator).then((entry) => {
298                     pair.value = entry.module;
299                     return entry;
300                 });
301             });
302             @putByValDirect(depLoads, depLoads.length, promise);
303         }
304
305         return @InternalPromise.internalAll(depLoads).then((modules) => {
306             @setStateToMax(entry, @ModuleLink);
307             return entry;
308         });
309     });
310
311     entry.satisfy = satisfyPromise;
312     return satisfyPromise;
313 }
314
315 function requestInstantiateAll(key, initiator)
316 {
317     // https://whatwg.github.io/loader/#request-instantiate-all
318
319     "use strict";
320
321     return this.requestSatisfy(key, initiator);
322 }
323
324 function requestLink(key, initiator)
325 {
326     // https://whatwg.github.io/loader/#request-link
327
328     "use strict";
329
330     var entry = this.ensureRegistered(key);
331     if (entry.state > @ModuleLink) {
332         var deferred = @newPromiseCapability(@InternalPromise);
333         deferred.@resolve.@call(@undefined, entry);
334         return deferred.@promise;
335     }
336
337     return this.requestInstantiateAll(key, initiator).then((entry) => {
338         this.link(entry, initiator);
339         return entry;
340     });
341 }
342
343 function requestReady(key, initiator)
344 {
345     // https://whatwg.github.io/loader/#request-ready
346
347     "use strict";
348
349     return this.requestLink(key, initiator).then((entry) => {
350         this.moduleEvaluation(entry.module, initiator);
351     });
352 }
353
354 // Linking semantics.
355
356 function link(entry, initiator)
357 {
358     // https://whatwg.github.io/loader/#link
359
360     "use strict";
361
362     // FIXME: Current implementation does not support optionalInstance.
363     // So Link's step 3 is skipped.
364     // https://bugs.webkit.org/show_bug.cgi?id=148171
365
366     if (entry.state === @ModuleReady)
367         return;
368     @setStateToMax(entry, @ModuleReady);
369
370     // Since we already have the "dependencies" field,
371     // we can call moduleDeclarationInstantiation with the correct order
372     // without constructing the dependency graph by calling dependencyGraph.
373     var dependencies = entry.dependencies;
374     for (var i = 0, length = dependencies.length; i < length; ++i) {
375         var pair = dependencies[i];
376         this.link(pair.value.registryEntry, initiator);
377     }
378
379     this.moduleDeclarationInstantiation(entry.module, initiator);
380 }
381
382 // Module semantics.
383
384 function moduleEvaluation(moduleRecord, initiator)
385 {
386     // http://www.ecma-international.org/ecma-262/6.0/#sec-moduleevaluation
387
388     "use strict";
389
390     if (moduleRecord.evaluated)
391         return;
392     moduleRecord.evaluated = true;
393
394     var entry = moduleRecord.registryEntry;
395
396     // The contents of the [[RequestedModules]] is cloned into entry.dependencies.
397     var dependencies = entry.dependencies;
398     for (var i = 0, length = dependencies.length; i < length; ++i) {
399         var pair = dependencies[i];
400         var requiredModuleRecord = pair.value;
401         this.moduleEvaluation(requiredModuleRecord, initiator);
402     }
403     this.evaluate(entry.key, moduleRecord, initiator);
404 }
405
406 // APIs to control the module loader.
407
408 function provide(key, stage, value)
409 {
410     "use strict";
411
412     var entry = this.ensureRegistered(key);
413
414     if (stage === @ModuleFetch) {
415         if (entry.state > @ModuleFetch)
416             @throwTypeError("Requested module is already fetched.");
417         this.fulfillFetch(entry, value);
418         return;
419     }
420
421     if (stage === @ModuleInstantiate) {
422         if (entry.state > @ModuleInstantiate)
423             @throwTypeError("Requested module is already instantiated.");
424         this.fulfillFetch(entry, @undefined);
425         entry.fetch.then((source) => {
426             this.fulfillInstantiate(entry, value, source);
427         });
428         return;
429     }
430
431     @throwTypeError("Requested module is already ready to be executed.");
432 }
433
434 function loadAndEvaluateModule(moduleName, referrer, initiator)
435 {
436     "use strict";
437
438     // Loader.resolve hook point.
439     // resolve: moduleName => Promise(moduleKey)
440     // Take the name and resolve it to the unique identifier for the resource location.
441     // For example, take the "jquery" and return the URL for the resource.
442     return this.resolve(moduleName, referrer, initiator).then((key) => {
443         return this.requestReady(key, initiator);
444     });
445 }
446
447 function loadModule(moduleName, referrer, initiator)
448 {
449     "use strict";
450
451     // Loader.resolve hook point.
452     // resolve: moduleName => Promise(moduleKey)
453     // Take the name and resolve it to the unique identifier for the resource location.
454     // For example, take the "jquery" and return the URL for the resource.
455     return this.resolve(moduleName, referrer, initiator).then((key) => {
456         return this.requestInstantiateAll(key, initiator);
457     }).then((entry) => {
458         return entry.key;
459     });
460 }
461
462 function linkAndEvaluateModule(key, initiator)
463 {
464     "use strict";
465
466     var entry = this.ensureRegistered(key);
467     if (entry.state < @ModuleLink)
468         @throwTypeError("Requested module is not instantiated yet.");
469
470     this.link(entry, initiator);
471     return this.moduleEvaluation(entry.module, initiator);
472 }
473
474 function importModule(moduleName, referrer, initiator)
475 {
476     "use strict";
477
478     // Loader.resolve hook point.
479     // resolve: moduleName => Promise(moduleKey)
480     // Take the name and resolve it to the unique identifier for the resource location.
481     // For example, take the "jquery" and return the URL for the resource.
482     return this.resolve(moduleName, referrer, initiator).then((key) => {
483         return this.requestInstantiateAll(key, initiator);
484     }).then((entry) => {
485         this.linkAndEvaluateModule(entry.key, initiator);
486         return this.getModuleNamespaceObject(entry.module);
487     });
488 }