83d70b8a2b88f0e9dbe4a6d478710108790e9fc5
[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
34 @globalPrivate
35 function setStateToMax(entry, newState)
36 {
37     // https://whatwg.github.io/loader/#set-state-to-max
38
39     "use strict";
40
41     if (entry.state < newState)
42         entry.state = newState;
43 }
44
45 @globalPrivate
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. Instantiate (AnalyzeModule)
58     //     Ready to instantiate (or now instantiating) the module record from the fetched
59     //     source code.
60     //     Typically, we parse the module code, extract the dependencies and binding information.
61     //     a. If the status is Instantiate and there is no entry.instantiate promise, the entry is ready to instantiate.
62     //     b. If the status is Instantiate and there is the entry.fetch promise, the entry is just instantiating
63     //        the module record.
64     //
65     // 3. Satisfy
66     //     Ready to request the dependent modules (or now requesting & resolving).
67     //     Without this state, the current draft causes infinite recursion when there is circular dependency.
68     //     a. If the status is Satisfy and there is no entry.satisfy promise, the entry is ready to resolve the dependencies.
69     //     b. If the status is Satisfy and there is the entry.satisfy promise, the entry is just resolving
70     //        the dependencies.
71     //
72     // 4. Link
73     //     Ready to link the module with the other modules.
74     //     Linking means that the module imports and exports the bindings from/to the other modules.
75     //
76     // 5. Ready
77     //     The module is linked, so the module is ready to be executed.
78     //
79     // Each registry entry has the 4 promises; "fetch", "instantiate" and "satisfy".
80     // They are assigned when starting the each phase. And they are fulfilled when the each phase is completed.
81     //
82     // In the current module draft, linking will be performed after the whole modules are instantiated and the dependencies are resolved.
83     // And execution is also done after the all modules are linked.
84     //
85     // TODO: We need to exploit the way to execute the module while fetching non-related modules.
86     // One solution; introducing the ready promise chain to execute the modules concurrently while keeping
87     // the execution order.
88
89     "use strict";
90
91     return {
92         key: key,
93         state: @ModuleFetch,
94         fetch: @undefined,
95         instantiate: @undefined,
96         satisfy: @undefined,
97         dependencies: [], // To keep the module order, we store the module keys in the array.
98         dependenciesMap: @undefined,
99         module: @undefined, // JSModuleRecord
100         linkError: @undefined,
101         linkSucceeded: true,
102         evaluated: false,
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 // Loader.
143
144 function requestFetch(entry, parameters, fetcher)
145 {
146     // https://whatwg.github.io/loader/#request-fetch
147
148     "use strict";
149
150     if (entry.fetch)
151         return entry.fetch;
152
153     // Hook point.
154     // 2. Loader.fetch
155     //     https://whatwg.github.io/loader/#browser-fetch
156     //     Take the key and fetch the resource actually.
157     //     For example, JavaScriptCore shell can provide the hook fetching the resource
158     //     from the local file system.
159     var fetchPromise = this.fetch(entry.key, parameters, fetcher).then((source) => {
160         @setStateToMax(entry, @ModuleInstantiate);
161         return source;
162     });
163     entry.fetch = fetchPromise;
164     return fetchPromise;
165 }
166
167 function requestInstantiate(entry, parameters, fetcher)
168 {
169     // https://whatwg.github.io/loader/#request-instantiate
170
171     "use strict";
172
173     if (entry.instantiate)
174         return entry.instantiate;
175
176     var instantiatePromise = this.requestFetch(entry, parameters, fetcher).then((source) => {
177         var key = entry.key;
178         var moduleRecord = this.parseModule(key, source);
179
180         // FIXME: Described in the draft,
181         //   4. Fulfill entry.[[Instantiate]] with instance.
182         // But, instantiate promise should be fulfilled with the entry.
183         // We remove this statement because instantiate promise will be
184         // fulfilled without this "force fulfill" operation.
185         // https://github.com/whatwg/loader/pull/67
186
187         var dependenciesMap = moduleRecord.dependenciesMap;
188         var requestedModules = this.requestedModules(moduleRecord);
189         var dependencies = @newArrayWithSize(requestedModules.length);
190         for (var i = 0, length = requestedModules.length; i < length; ++i) {
191             var depName = requestedModules[i];
192             var depKey = this.resolveSync(depName, key, fetcher);
193             var depEntry = this.ensureRegistered(depKey);
194             @putByValDirect(dependencies, i, depEntry);
195             dependenciesMap.@set(depName, depEntry);
196         }
197         entry.dependencies = dependencies;
198         entry.dependenciesMap = dependenciesMap;
199         entry.module = moduleRecord;
200         @setStateToMax(entry, @ModuleSatisfy);
201         return entry;
202     });
203     entry.instantiate = instantiatePromise;
204     return instantiatePromise;
205 }
206
207 function requestSatisfy(entry, parameters, fetcher)
208 {
209     // https://whatwg.github.io/loader/#satisfy-instance
210
211     "use strict";
212
213     if (entry.satisfy)
214         return entry.satisfy;
215
216     var satisfyPromise = this.requestInstantiate(entry, parameters, fetcher).then((entry) => {
217         var depLoads = @newArrayWithSize(entry.dependencies.length);
218         for (var i = 0, length = entry.dependencies.length; i < length; ++i) {
219             var depEntry = entry.dependencies[i];
220             var promise = @undefined;
221
222             // Recursive resolving. The dependencies of this entry is being resolved or already resolved.
223             // Stop tracing the circular dependencies.
224             // But to retrieve the instantiated module record correctly,
225             // we need to wait for the instantiation for the dependent module.
226             // For example, reaching here, the module is starting resolving the dependencies.
227             // But the module may or may not reach the instantiation phase in the loader's pipeline.
228             // If we wait for the Satisfy for this module, it construct the circular promise chain and
229             // rejected by the Promises runtime. Since only we need is the instantiated module, instead of waiting
230             // the Satisfy for this module, we just wait Instantiate for this.
231             if (depEntry.satisfy)
232                 promise = depEntry.instantiate;
233             else {
234                 // Currently, module loader do not pass any information for non-top-level module fetching.
235                 promise = this.requestSatisfy(depEntry, @undefined, fetcher);
236             }
237             @putByValDirect(depLoads, i, promise);
238         }
239
240         return @InternalPromise.internalAll(depLoads).then((modules) => {
241             @setStateToMax(entry, @ModuleLink);
242             return entry;
243         });
244     });
245
246     entry.satisfy = satisfyPromise;
247     return satisfyPromise;
248 }
249
250 // Linking semantics.
251
252 function link(entry, fetcher)
253 {
254     // https://whatwg.github.io/loader/#link
255
256     "use strict";
257
258     if (!entry.linkSucceeded)
259         throw entry.linkError;
260     if (entry.state === @ModuleReady)
261         return;
262     @setStateToMax(entry, @ModuleReady);
263
264     try {
265         // Since we already have the "dependencies" field,
266         // we can call moduleDeclarationInstantiation with the correct order
267         // without constructing the dependency graph by calling dependencyGraph.
268         var dependencies = entry.dependencies;
269         for (var i = 0, length = dependencies.length; i < length; ++i)
270             this.link(dependencies[i], fetcher);
271
272         this.moduleDeclarationInstantiation(entry.module, entry.key, fetcher);
273     } catch (error) {
274         entry.linkSucceeded = false;
275         entry.linkError = error;
276         throw error;
277     }
278 }
279
280 // Module semantics.
281
282 function moduleEvaluation(entry, fetcher)
283 {
284     // http://www.ecma-international.org/ecma-262/6.0/#sec-moduleevaluation
285
286     "use strict";
287
288     if (entry.evaluated)
289         return;
290     entry.evaluated = true;
291
292     // The contents of the [[RequestedModules]] is cloned into entry.dependencies.
293     var dependencies = entry.dependencies;
294     for (var i = 0, length = dependencies.length; i < length; ++i)
295         this.moduleEvaluation(dependencies[i], fetcher);
296
297     this.evaluate(entry.key, entry.module, fetcher);
298 }
299
300 // APIs to control the module loader.
301
302 function provideFetch(key, value)
303 {
304     "use strict";
305
306     var entry = this.ensureRegistered(key);
307
308     if (entry.state > @ModuleFetch)
309         @throwTypeError("Requested module is already fetched.");
310     this.fulfillFetch(entry, value);
311 }
312
313 function loadModule(moduleName, parameters, fetcher)
314 {
315     "use strict";
316
317     // Loader.resolve hook point.
318     // resolve: moduleName => Promise(moduleKey)
319     // Take the name and resolve it to the unique identifier for the resource location.
320     // For example, take the "jquery" and return the URL for the resource.
321     return this.resolve(moduleName, @undefined, fetcher).then((key) => {
322         return this.requestSatisfy(this.ensureRegistered(key), parameters, fetcher);
323     }).then(
324         (entry) => {
325             this.notifyCompleted(fetcher, entry.key);
326             return entry;
327         },
328         (error) => {
329             this.notifyFailed(fetcher, error);
330             throw error;
331         });
332 }
333
334 function linkAndEvaluateModule(key, fetcher)
335 {
336     "use strict";
337
338     var entry = this.ensureRegistered(key);
339     if (entry.state < @ModuleLink)
340         @throwTypeError("Requested module is not instantiated yet.");
341
342     this.link(entry, fetcher);
343     return this.moduleEvaluation(entry, fetcher);
344 }
345
346 function loadAndEvaluateModule(moduleName, parameters, fetcher)
347 {
348     "use strict";
349
350     return this.loadModule(moduleName, parameters, fetcher).then((entry) => {
351         return this.linkAndEvaluateModule(entry.key, fetcher);
352     });
353 }
354
355 function requestImportModule(key, parameters, fetcher)
356 {
357     "use strict";
358
359     return this.requestSatisfy(this.ensureRegistered(key), parameters, fetcher).then((entry) => {
360         this.linkAndEvaluateModule(entry.key, fetcher);
361         return this.getModuleNamespaceObject(entry.module);
362     });
363 }