summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/builtins/ModuleLoaderObject.js
diff options
context:
space:
mode:
authorKonstantin Tokarev <annulen@yandex.ru>2016-08-25 19:20:41 +0300
committerKonstantin Tokarev <annulen@yandex.ru>2017-02-02 12:30:55 +0000
commit6882a04fb36642862b11efe514251d32070c3d65 (patch)
treeb7959826000b061fd5ccc7512035c7478742f7b0 /Source/JavaScriptCore/builtins/ModuleLoaderObject.js
parentab6df191029eeeb0b0f16f127d553265659f739e (diff)
downloadqtwebkit-6882a04fb36642862b11efe514251d32070c3d65.tar.gz
Imported QtWebKit TP3 (git b57bc6801f1876c3220d5a4bfea33d620d477443)
Change-Id: I3b1d8a2808782c9f34d50240000e20cb38d3680f Reviewed-by: Konstantin Tokarev <annulen@yandex.ru>
Diffstat (limited to 'Source/JavaScriptCore/builtins/ModuleLoaderObject.js')
-rw-r--r--Source/JavaScriptCore/builtins/ModuleLoaderObject.js562
1 files changed, 562 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/builtins/ModuleLoaderObject.js b/Source/JavaScriptCore/builtins/ModuleLoaderObject.js
new file mode 100644
index 000000000..568a307b7
--- /dev/null
+++ b/Source/JavaScriptCore/builtins/ModuleLoaderObject.js
@@ -0,0 +1,562 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// https://whatwg.github.io/loader/#loader-object
+// Module Loader has several hooks that can be customized by the platform.
+// For example, the [[Fetch]] hook can be provided by the JavaScriptCore shell
+// as fetching the payload from the local file system.
+// Currently, there are 4 hooks.
+// 1. Loader.resolve
+// 2. Loader.fetch
+// 3. Loader.translate
+// 4. Loader.instantiate
+
+function setStateToMax(entry, newState)
+{
+ // https://whatwg.github.io/loader/#set-state-to-max
+
+ "use strict";
+
+ if (entry.state < newState)
+ entry.state = newState;
+}
+
+function newRegistryEntry(key)
+{
+ // https://whatwg.github.io/loader/#registry
+ //
+ // Each registry entry becomes one of the 5 states.
+ // 1. Fetch
+ // Ready to fetch (or now fetching) the resource of this module.
+ // Typically, we fetch the source code over the network or from the file system.
+ // a. If the status is Fetch and there is no entry.fetch promise, the entry is ready to fetch.
+ // b. If the status is Fetch and there is the entry.fetch promise, the entry is just fetching the resource.
+ //
+ // 2. Translate
+ // Ready to translate (or now translating) the raw fetched resource to the ECMAScript source code.
+ // We can insert the hook that translates the resources e.g. transpilers.
+ // a. If the status is Translate and there is no entry.translate promise, the entry is ready to translate.
+ // b. If the status is Translate and there is the entry.translate promise, the entry is just translating
+ // the payload to the source code.
+ //
+ // 3. Instantiate (AnalyzeModule)
+ // Ready to instantiate (or now instantiating) the module record from the fetched (and translated)
+ // source code.
+ // Typically, we parse the module code, extract the dependencies and binding information.
+ // a. If the status is Instantiate and there is no entry.instantiate promise, the entry is ready to instantiate.
+ // b. If the status is Instantiate and there is the entry.translate promise, the entry is just instantiating
+ // the module record.
+ //
+ // 4. ResolveDependencies (not in the draft) https://github.com/whatwg/loader/issues/68
+ // Ready to request the dependent modules (or now requesting & resolving).
+ // Without this state, the current draft causes infinite recursion when there is circular dependency.
+ // a. If the status is ResolveDependencies and there is no entry.resolveDependencies promise, the entry is ready to resolve the dependencies.
+ // b. If the status is ResolveDependencies and there is the entry.resolveDependencies promise, the entry is just resolving
+ // the dependencies.
+ //
+ // 5. Link
+ // Ready to link the module with the other modules.
+ // Linking means that the module imports and exports the bindings from/to the other modules.
+ //
+ // 6. Ready
+ // The module is linked, so the module is ready to be executed.
+ //
+ // Each registry entry has the 4 promises; "fetch", "translate", "instantiate" and "resolveDependencies".
+ // They are assigned when starting the each phase. And they are fulfilled when the each phase is completed.
+ //
+ // In the current module draft, linking will be performed after the whole modules are instantiated and the dependencies are resolved.
+ // And execution is also done after the all modules are linked.
+ //
+ // TODO: We need to exploit the way to execute the module while fetching non-related modules.
+ // One solution; introducing the ready promise chain to execute the modules concurrently while keeping
+ // the execution order.
+
+ "use strict";
+
+ return {
+ key: key,
+ state: this.Fetch,
+ metadata: @undefined,
+ fetch: @undefined,
+ translate: @undefined,
+ instantiate: @undefined,
+ resolveDependencies: @undefined,
+ dependencies: [], // To keep the module order, we store the module keys in the array.
+ dependenciesMap: @undefined,
+ module: @undefined, // JSModuleRecord
+ error: @undefined,
+ };
+}
+
+function ensureRegistered(key)
+{
+ // https://whatwg.github.io/loader/#ensure-registered
+
+ "use strict";
+
+ var entry = this.registry.@get(key);
+ if (entry)
+ return entry;
+
+ entry = this.newRegistryEntry(key);
+ this.registry.@set(key, entry);
+
+ return entry;
+}
+
+function forceFulfillPromise(promise, value)
+{
+ "use strict";
+
+ if (promise.@promiseState === @promiseStatePending)
+ @fulfillPromise(promise, value);
+}
+
+function fulfillFetch(entry, payload)
+{
+ // https://whatwg.github.io/loader/#fulfill-fetch
+
+ "use strict";
+
+ if (!entry.fetch)
+ entry.fetch = @newPromiseCapability(@InternalPromise).@promise;
+ this.forceFulfillPromise(entry.fetch, payload);
+ this.setStateToMax(entry, this.Translate);
+}
+
+function fulfillTranslate(entry, source)
+{
+ // https://whatwg.github.io/loader/#fulfill-translate
+
+ "use strict";
+
+ if (!entry.translate)
+ entry.translate = @newPromiseCapability(@InternalPromise).@promise;
+ this.forceFulfillPromise(entry.translate, source);
+ this.setStateToMax(entry, this.Instantiate);
+}
+
+function fulfillInstantiate(entry, optionalInstance, source)
+{
+ // https://whatwg.github.io/loader/#fulfill-instantiate
+
+ "use strict";
+
+ if (!entry.instantiate)
+ entry.instantiate = @newPromiseCapability(@InternalPromise).@promise;
+ this.commitInstantiated(entry, optionalInstance, source);
+
+ // FIXME: The draft fulfills the promise in the CommitInstantiated operation.
+ // But it CommitInstantiated is also used in the requestInstantiate and
+ // we should not "force fulfill" there.
+ // So we separate "force fulfill" operation from the CommitInstantiated operation.
+ // https://github.com/whatwg/loader/pull/67
+ this.forceFulfillPromise(entry.instantiate, entry);
+}
+
+function commitInstantiated(entry, optionalInstance, source)
+{
+ // https://whatwg.github.io/loader/#commit-instantiated
+
+ "use strict";
+
+ var moduleRecord = this.instantiation(optionalInstance, source, entry);
+
+ // FIXME: Described in the draft,
+ // 4. Fulfill entry.[[Instantiate]] with instance.
+ // But, instantiate promise should be fulfilled with the entry.
+ // We remove this statement because instantiate promise will be
+ // fulfilled without this "force fulfill" operation.
+ // https://github.com/whatwg/loader/pull/67
+
+ var dependencies = [];
+ var dependenciesMap = moduleRecord.dependenciesMap;
+ moduleRecord.registryEntry = entry;
+ var requestedModules = this.requestedModules(moduleRecord);
+ for (var i = 0, length = requestedModules.length; i < length; ++i) {
+ var depKey = requestedModules[i];
+ var pair = {
+ key: depKey,
+ value: @undefined
+ };
+ @putByValDirect(dependencies, i, pair);
+ dependenciesMap.@set(depKey, pair);
+ }
+ entry.dependencies = dependencies;
+ entry.dependenciesMap = dependenciesMap;
+ entry.module = moduleRecord;
+ this.setStateToMax(entry, this.ResolveDependencies);
+}
+
+function instantiation(result, source, entry)
+{
+ // https://whatwg.github.io/loader/#instantiation
+ // FIXME: Current implementation does not support optionalInstance.
+ // https://bugs.webkit.org/show_bug.cgi?id=148171
+
+ "use strict";
+
+ return this.parseModule(entry.key, source);
+}
+
+// Loader.
+
+function requestFetch(key)
+{
+ // https://whatwg.github.io/loader/#request-fetch
+
+ "use strict";
+
+ var entry = this.ensureRegistered(key);
+ if (entry.state > this.Link) {
+ var deferred = @newPromiseCapability(@InternalPromise);
+ deferred.@reject.@call(@undefined, new @TypeError("Requested module is already ready to be executed."));
+ return deferred.@promise;
+ }
+
+ if (entry.fetch)
+ return entry.fetch;
+
+ var loader = this;
+
+ // Hook point.
+ // 2. Loader.fetch
+ // https://whatwg.github.io/loader/#browser-fetch
+ // Take the key and fetch the resource actually.
+ // For example, JavaScriptCore shell can provide the hook fetching the resource
+ // from the local file system.
+ var fetchPromise = this.fetch(key).then(function (payload) {
+ loader.setStateToMax(entry, loader.Translate);
+ return payload;
+ });
+ entry.fetch = fetchPromise;
+ return fetchPromise;
+}
+
+function requestTranslate(key)
+{
+ // https://whatwg.github.io/loader/#request-translate
+
+ "use strict";
+
+ var entry = this.ensureRegistered(key);
+ if (entry.state > this.Link) {
+ var deferred = @newPromiseCapability(@InternalPromise);
+ deferred.@reject.@call(@undefined, new @TypeError("Requested module is already ready to be executed."));
+ return deferred.@promise;
+ }
+
+ if (entry.translate)
+ return entry.translate;
+
+ var loader = this;
+ var translatePromise = this.requestFetch(key).then(function (payload) {
+ // Hook point.
+ // 3. Loader.translate
+ // https://whatwg.github.io/loader/#browser-translate
+ // Take the key and the fetched source code and translate it to the ES6 source code.
+ // Typically it is used by the transpilers.
+ return loader.translate(key, payload).then(function (source) {
+ loader.setStateToMax(entry, loader.Instantiate);
+ return source;
+ });
+ });
+ entry.translate = translatePromise;
+ return translatePromise;
+}
+
+function requestInstantiate(key)
+{
+ // https://whatwg.github.io/loader/#request-instantiate
+
+ "use strict";
+
+ var entry = this.ensureRegistered(key);
+ if (entry.state > this.Link) {
+ var deferred = @newPromiseCapability(@InternalPromise);
+ deferred.@reject.@call(@undefined, new @TypeError("Requested module is already ready to be executed."));
+ return deferred.@promise;
+ }
+
+ if (entry.instantiate)
+ return entry.instantiate;
+
+ var loader = this;
+ var instantiatePromise = this.requestTranslate(key).then(function (source) {
+ // Hook point.
+ // 4. Loader.instantiate
+ // https://whatwg.github.io/loader/#browser-instantiate
+ // Take the key and the translated source code, and instantiate the module record
+ // by parsing the module source code.
+ // It has the chance to provide the optional module instance that is different from
+ // the ordinary one.
+ return loader.instantiate(key, source).then(function (optionalInstance) {
+ loader.commitInstantiated(entry, optionalInstance, source);
+ return entry;
+ });
+ });
+ entry.instantiate = instantiatePromise;
+ return instantiatePromise;
+}
+
+function requestResolveDependencies(key)
+{
+ // FIXME: In the spec, after requesting instantiation, we will resolve
+ // the dependencies without any status change. As a result, when there
+ // is circular dependencies, instantiation is done only once, but
+ // repeatedly resolving the dependencies. This means that infinite
+ // recursion occur when the given modules have circular dependency. To
+ // avoid this situation, we introduce new state, "ResolveDependencies". This means
+ // "Now the module is instantiated, so ready to resolve the dependencies
+ // or now resolving them".
+ // https://github.com/whatwg/loader/issues/68
+
+ "use strict";
+
+ var entry = this.ensureRegistered(key);
+ if (entry.state > this.Link) {
+ var deferred = @newPromiseCapability(@InternalPromise);
+ deferred.@reject.@call(@undefined, new @TypeError("Requested module is already ready to be executed."));
+ return deferred.@promise;
+ }
+
+ if (entry.resolveDependencies)
+ return entry.resolveDependencies;
+
+ var loader = this;
+ var resolveDependenciesPromise = this.requestInstantiate(key).then(function (entry) {
+ var depLoads = [];
+ for (var i = 0, length = entry.dependencies.length; i < length; ++i) {
+ let pair = entry.dependencies[i];
+
+ // Hook point.
+ // 1. Loader.resolve.
+ // https://whatwg.github.io/loader/#browser-resolve
+ // Take the name and resolve it to the unique identifier for the resource location.
+ // For example, take the "jquery" and return the URL for the resource.
+ var promise = loader.resolve(pair.key, key).then(function (depKey) {
+ var depEntry = loader.ensureRegistered(depKey);
+
+ // Recursive resolving. The dependencies of this entry is being resolved or already resolved.
+ // Stop tracing the circular dependencies.
+ // But to retrieve the instantiated module record correctly,
+ // we need to wait for the instantiation for the dependent module.
+ // For example, reaching here, the module is starting resolving the dependencies.
+ // But the module may or may not reach the instantiation phase in the loader's pipeline.
+ // If we wait for the ResolveDependencies for this module, it construct the circular promise chain and
+ // rejected by the Promises runtime. Since only we need is the instantiated module, instead of waiting
+ // the ResolveDependencies for this module, we just wait Instantiate for this.
+ if (depEntry.resolveDependencies) {
+ return depEntry.instantiate.then(function (entry) {
+ pair.value = entry.module;
+ return entry;
+ });
+ }
+
+ return loader.requestResolveDependencies(depKey).then(function (entry) {
+ pair.value = entry.module;
+ return entry;
+ });
+ });
+ @putByValDirect(depLoads, i, promise);
+ }
+
+ return @InternalPromise.internalAll(depLoads).then(function (modules) {
+ loader.setStateToMax(entry, loader.Link);
+ return entry;
+ });
+ });
+
+ entry.resolveDependencies = resolveDependenciesPromise;
+ return resolveDependenciesPromise;
+}
+
+function requestInstantiateAll(key)
+{
+ // https://whatwg.github.io/loader/#request-instantiate-all
+
+ "use strict";
+
+ return this.requestResolveDependencies(key);
+}
+
+function requestLink(key)
+{
+ // https://whatwg.github.io/loader/#request-link
+
+ "use strict";
+
+ var entry = this.ensureRegistered(key);
+ if (entry.state > this.Link) {
+ var deferred = @newPromiseCapability(@InternalPromise);
+ deferred.@resolve.@call(@undefined, entry.module);
+ return deferred.@promise;
+ }
+
+ var loader = this;
+ return this.requestInstantiateAll(key).then(function (entry) {
+ loader.link(entry);
+ return entry;
+ });
+}
+
+function requestReady(key)
+{
+ // https://whatwg.github.io/loader/#request-ready
+
+ "use strict";
+
+ var loader = this;
+ return this.requestLink(key).then(function (entry) {
+ loader.moduleEvaluation(entry.module);
+ });
+}
+
+// Linking semantics.
+
+function link(entry)
+{
+ // https://whatwg.github.io/loader/#link
+
+ "use strict";
+
+ // FIXME: Current implementation does not support optionalInstance.
+ // So Link's step 3 is skipped.
+ // https://bugs.webkit.org/show_bug.cgi?id=148171
+
+ if (entry.state === this.Ready)
+ return;
+ this.setStateToMax(entry, this.Ready);
+
+ // Since we already have the "dependencies" field,
+ // we can call moduleDeclarationInstantiation with the correct order
+ // without constructing the dependency graph by calling dependencyGraph.
+ var dependencies = entry.dependencies;
+ for (var i = 0, length = dependencies.length; i < length; ++i) {
+ var pair = dependencies[i];
+ this.link(pair.value.registryEntry);
+ }
+
+ this.moduleDeclarationInstantiation(entry.module);
+}
+
+// Module semantics.
+
+function moduleEvaluation(moduleRecord)
+{
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-moduleevaluation
+
+ "use strict";
+
+ if (moduleRecord.evaluated)
+ return;
+ moduleRecord.evaluated = true;
+
+ var entry = moduleRecord.registryEntry;
+
+ // The contents of the [[RequestedModules]] is cloned into entry.dependencies.
+ var dependencies = entry.dependencies;
+ for (var i = 0, length = dependencies.length; i < length; ++i) {
+ var pair = dependencies[i];
+ var requiredModuleRecord = pair.value;
+ this.moduleEvaluation(requiredModuleRecord);
+ }
+ this.evaluate(entry.key, moduleRecord);
+}
+
+// APIs to control the module loader.
+
+function provide(key, stage, value)
+{
+ "use strict";
+
+ var entry = this.ensureRegistered(key);
+
+ if (stage === this.Fetch) {
+ if (entry.status > this.Fetch)
+ throw new @TypeError("Requested module is already fetched.");
+ this.fulfillFetch(entry, value);
+ return;
+ }
+
+ if (stage === this.Translate) {
+ if (entry.status > this.Translate)
+ throw new @TypeError("Requested module is already translated.");
+ this.fulfillFetch(entry, @undefined);
+ this.fulfillTranslate(entry, value);
+ return;
+ }
+
+ if (stage === this.Instantiate) {
+ if (entry.status > this.Instantiate)
+ throw new @TypeError("Requested module is already instantiated.");
+ this.fulfillFetch(entry, @undefined);
+ this.fulfillTranslate(entry, value);
+ var loader = this;
+ entry.translate.then(function (source) {
+ loader.fulfillInstantiate(entry, value, source);
+ });
+ return;
+ }
+
+ throw new @TypeError("Requested module is already ready to be executed.");
+}
+
+function loadAndEvaluateModule(moduleName, referrer)
+{
+ "use strict";
+
+ var loader = this;
+ // Loader.resolve hook point.
+ // resolve: moduleName => Promise(moduleKey)
+ // Take the name and resolve it to the unique identifier for the resource location.
+ // For example, take the "jquery" and return the URL for the resource.
+ return this.resolve(moduleName, referrer).then(function (key) {
+ return loader.requestReady(key);
+ });
+}
+
+function loadModule(moduleName, referrer)
+{
+ "use strict";
+
+ var loader = this;
+ // Loader.resolve hook point.
+ // resolve: moduleName => Promise(moduleKey)
+ // Take the name and resolve it to the unique identifier for the resource location.
+ // For example, take the "jquery" and return the URL for the resource.
+ return this.resolve(moduleName, referrer).then(function (key) {
+ return loader.requestInstantiateAll(key);
+ }).then(function (entry) {
+ return entry.key;
+ });
+}
+
+function linkAndEvaluateModule(key)
+{
+ "use strict";
+
+ return this.requestReady(key);
+}