blob: c5ce3179dbbe35062c4073d01516b5de739674de [file] [log] [blame]
/*
* 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 === @promisePending)
@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);
}