Chromium Code Reviews| Index: third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinker.cpp | 
| diff --git a/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinker.cpp b/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinker.cpp | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..c200a181d489a0d62f9b0d71be97787800a65fe6 | 
| --- /dev/null | 
| +++ b/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinker.cpp | 
| @@ -0,0 +1,428 @@ | 
| +// Copyright 2017 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#include "core/loader/modulescript/ModuleTreeLinker.h" | 
| + | 
| +#include "bindings/core/v8/ScriptModule.h" | 
| +#include "core/dom/AncestorList.h" | 
| +#include "core/dom/ModuleScript.h" | 
| +#include "core/loader/modulescript/ModuleScriptFetchRequest.h" | 
| +#include "core/loader/modulescript/ModuleTreeLinkerRegistry.h" | 
| +#include "wtf/HashSet.h" | 
| + | 
| +namespace blink { | 
| + | 
| +ModuleTreeLinker* ModuleTreeLinker::fetch( | 
| + const ModuleScriptFetchRequest& request, | 
| + const AncestorList& ancestorList, | 
| + ModuleGraphLevel level, | 
| + Modulator* modulator, | 
| + ModuleTreeLinkerRegistry* registry, | 
| + ModuleTreeClient* client) { | 
| + AncestorList ancestorListWithUrl = ancestorList; | 
| + ancestorListWithUrl.insert(request.url()); | 
| + | 
| + ModuleTreeLinker* fetcher = | 
| + new ModuleTreeLinker(ancestorListWithUrl, modulator, registry, client); | 
| + fetcher->fetchSelf(request, level); | 
| + return fetcher; | 
| +} | 
| + | 
| +ModuleTreeLinker::ModuleTreeLinker(const AncestorList& ancestorListWithUrl, | 
| + Modulator* modulator, | 
| + ModuleTreeLinkerRegistry* registry, | 
| + ModuleTreeClient* client) | 
| + : m_modulator(modulator), | 
| + m_registry(registry), | 
| + m_client(client), | 
| + m_ancestorListWithUrl(ancestorListWithUrl) { | 
| + CHECK(modulator); | 
| + CHECK(registry); | 
| + CHECK(client); | 
| +} | 
| + | 
| +DEFINE_TRACE(ModuleTreeLinker) { | 
| + visitor->trace(m_modulator); | 
| + visitor->trace(m_registry); | 
| + visitor->trace(m_client); | 
| + visitor->trace(m_moduleScript); | 
| + visitor->trace(m_dependencyClients); | 
| + SingleModuleClient::trace(visitor); | 
| +} | 
| + | 
| +void ModuleTreeLinker::advanceState(State newState) { | 
| +#ifndef NDEBUG | 
| + printf("mtf %p adv state: %d\n", this, newState); | 
| +#endif | 
| + CHECK_EQ(m_numIncompleteDescendants, 0u); | 
| + switch (m_state) { | 
| + case State::Initial: | 
| + CHECK_EQ(newState, State::FetchingSelf); | 
| + break; | 
| + case State::FetchingSelf: | 
| + CHECK(newState == State::FetchingDependencies || | 
| + newState == State::Finished); | 
| + break; | 
| + case State::FetchingDependencies: | 
| + CHECK(newState == State::Instantiating || newState == State::Finished); | 
| + break; | 
| + case State::Instantiating: | 
| + CHECK_EQ(newState, State::Finished); | 
| + break; | 
| + case State::Finished: | 
| + NOTREACHED(); | 
| + break; | 
| + default: | 
| + NOTREACHED(); | 
| + } | 
| + | 
| + m_state = newState; | 
| + | 
| + if (m_state == State::Finished) { | 
| + m_registry->releaseFinishedFetcher(this); | 
| + m_client->notifyModuleTreeLoadFinished(m_moduleScript); | 
| + } | 
| +} | 
| + | 
| +void ModuleTreeLinker::fetchSelf(const ModuleScriptFetchRequest& request, | 
| + ModuleGraphLevel level) { | 
| + // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure | 
| + | 
| + // Step 1. Fetch a single module script given url, fetch client settings | 
| + // object, destination, cryptographic nonce, parser state, credentials mode, | 
| + // module map settings object, referrer, and the top-level module fetch flag. | 
| + // If the caller of this algorithm specified custom perform the fetch steps, | 
| + // pass those along while fetching a single module script. | 
| + advanceState(State::FetchingSelf); | 
| + m_modulator->fetchSingle(request, level, this); | 
| + | 
| + // Step 2. Return from this algorithm, and run the following steps when | 
| + // fetching a single module script asynchronously completes with result. | 
| + // Note: Modulator::fetchSingle asynchronously notifies result to | 
| + // ModuleTreeLinker::notifyModuleLoadFinished(). | 
| +} | 
| + | 
| +void ModuleTreeLinker::notifyModuleLoadFinished(ModuleScript* moduleScript) { | 
| + // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure | 
| + | 
| + // Step 3. If result is null, asynchronously complete this algorithm with null | 
| + // and abort these steps. | 
| + if (!moduleScript) { | 
| + advanceState(State::Finished); | 
| + return; | 
| + } | 
| + | 
| + // Step 4. Otherwise, result is a module script. Fetch the descendants of | 
| + // result given destination and an ancestor list obtained by appending url to | 
| + // ancestor list. Wait for fetching the descendants of a module script to | 
| + // asynchronously complete with descendants result before proceeding to the | 
| + // next step. | 
| + m_moduleScript = moduleScript; | 
| + | 
| + // [unspeced] If the instantiation of the module script is already attempt, | 
| + // early exit. | 
| + const auto state = m_moduleScript->instantiationState(); | 
| + if (state != ModuleInstantiationState::Uninstantiated) { | 
| + advanceState(State::Finished); | 
| + return; | 
| + } | 
| + | 
| + fetchDescendants(); | 
| + | 
| + // Note: Step 5- continues in instantiate() method, after | 
| + // "fetch the descendants of a module script" procedure completes. | 
| +} | 
| + | 
| +class ModuleTreeLinker::DependencyModuleClient | 
| + : public GarbageCollectedFinalized< | 
| + ModuleTreeLinker::DependencyModuleClient>, | 
| + public ModuleTreeClient { | 
| + USING_GARBAGE_COLLECTED_MIXIN(ModuleTreeLinker::DependencyModuleClient); | 
| + | 
| + public: | 
| + static DependencyModuleClient* create(ModuleTreeLinker* moduleTreeLinkers) { | 
| + return new DependencyModuleClient(moduleTreeLinkers); | 
| + } | 
| + virtual ~DependencyModuleClient() = default; | 
| + | 
| + DEFINE_INLINE_TRACE() { | 
| + visitor->trace(m_moduleTreeLinkers); | 
| + ModuleTreeClient::trace(visitor); | 
| + } | 
| + | 
| + private: | 
| + DependencyModuleClient(ModuleTreeLinker* moduleTreeLinkers) | 
| + : m_moduleTreeLinkers(moduleTreeLinkers) { | 
| + CHECK(moduleTreeLinkers); | 
| + } | 
| + | 
| + // Implements ModuleTreeClient | 
| + void notifyModuleTreeLoadFinished(ModuleScript*) override; | 
| + | 
| + Member<ModuleTreeLinker> m_moduleTreeLinkers; | 
| +}; | 
| + | 
| +void ModuleTreeLinker::fetchDescendants() { | 
| + CHECK(m_moduleScript); | 
| + advanceState(State::FetchingDependencies); | 
| + | 
| + // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-a-module-script | 
| + | 
| + // [unspeced] If the instantiation of the module script is already attempt, | 
| + // early exit. | 
| + const auto state = m_moduleScript->instantiationState(); | 
| + if (state != ModuleInstantiationState::Uninstantiated) { | 
| + advanceState(State::Finished); | 
| + return; | 
| + } | 
| + | 
| + // Step 1. Let record be module script's module record. | 
| + ScriptModule record = m_moduleScript->record(); | 
| + | 
| + // Step 2. If record.[[RequestedModules]] is empty, asynchronously complete | 
| + // this algorithm with module script. | 
| + Vector<String> moduleRequests = | 
| + m_modulator->moduleRequestsFromScriptModule(record); | 
| + | 
| + // Step 3. Let urls be a new empty list. | 
| + HashSet<KURL> urls; | 
| + | 
| + // Step 4. For each string requested of record.[[RequestedModules]], | 
| + for (const auto& moduleRequest : moduleRequests) { | 
| + // Step 4.1. Let url be the result of resolving a module specifier given | 
| + // module script and requested. | 
| + KURL url = Modulator::resolveModuleSpecifier(moduleRequest, | 
| + m_moduleScript->baseURL()); | 
| +#ifndef NDEBUG | 
| + printf("resolveModuleSpecifier \"%s\" -> \"%s\"\n", | 
| + moduleRequest.utf8().data(), url.getString().utf8().data()); | 
| +#endif | 
| + | 
| + // Step 4.2. If the result is error: ... | 
| + if (url.isNull()) { | 
| + // Let error be a new TypeError exception. | 
| + // Report the exception error for module script. | 
| + // TODO(kouhei): Implement exception | 
| + // Abort this algorithm, and asynchronously complete it with null. | 
| + m_moduleScript = nullptr; | 
| + advanceState(State::Finished); | 
| + return; | 
| + } | 
| + | 
| + // Step 4.3. Otherwise, if ancestor list does not contain url, append url to | 
| + // urls. | 
| + | 
| + // Modulator::resolveModuleSpecifier() impl must return either a valid url | 
| + // or null. | 
| + CHECK(url.isValid()); | 
| + if (!m_ancestorListWithUrl.contains(url)) | 
| + urls.insert(url); | 
| + } | 
| + | 
| + // Step 5. For each url in urls, perform the internal module script graph | 
| + // fetching procedure given url, module script's credentials mode, module | 
| + // script's cryptographic nonce, module script's parser state, destination, | 
| + // module script's settings object, module script's settings object, ancestor | 
| + // list, module script's base URL, and with the top-level module fetch flag | 
| + // unset. If the caller of this algorithm specified custom perform the fetch | 
| + // steps, pass those along while performing the internal module script graph | 
| + // fetching procedure. | 
| + // TODO(kouhei): handle "destination". | 
| + CHECK_EQ(m_numIncompleteDescendants, 0u); | 
| + if (urls.isEmpty()) { | 
| + // Continue to instantiate() to process "internal module script graph | 
| + // fetching procedure" Step 5-. | 
| + instantiate(); | 
| + return; | 
| + } | 
| + m_numIncompleteDescendants = urls.size(); | 
| + for (const KURL& url : urls) { | 
| + DependencyModuleClient* dependencyClient = | 
| + DependencyModuleClient::create(this); | 
| + m_dependencyClients.insert(dependencyClient); | 
| + | 
| + ModuleScriptFetchRequest request(url, m_moduleScript->nonce(), | 
| + m_moduleScript->parserState(), | 
| + m_moduleScript->credentialsMode(), | 
| + m_moduleScript->baseURL().getString()); | 
| + m_modulator->fetchTreeInternal(request, m_ancestorListWithUrl, | 
| + ModuleGraphLevel::DependentModuleFetch, | 
| + dependencyClient); | 
| + } | 
| + | 
| + // Asynchronously continue processing after notifyOneDescendantFinished() is | 
| + // called m_numIncompleteDescendants times. | 
| + CHECK_GT(m_numIncompleteDescendants, 0u); | 
| +} | 
| + | 
| +void ModuleTreeLinker::DependencyModuleClient::notifyModuleTreeLoadFinished( | 
| + ModuleScript* moduleScript) { | 
| + DescendantLoad wasSuccess = | 
| + !!moduleScript ? DescendantLoad::Success : DescendantLoad::Failed; | 
| + m_moduleTreeLinkers->notifyOneDescendantFinished(wasSuccess); | 
| +} | 
| + | 
| +void ModuleTreeLinker::notifyOneDescendantFinished(DescendantLoad wasSuccess) { | 
| + if (m_state == State::Finished) { | 
| + // We may reach here if one of the descendant failed to load, and the other | 
| + // descendants fetches were in flight. | 
| + CHECK(!m_moduleScript); | 
| + return; | 
| + } | 
| + | 
| + CHECK_EQ(m_state, State::FetchingDependencies); | 
| + | 
| + CHECK_GT(m_numIncompleteDescendants, 0u); | 
| + --m_numIncompleteDescendants; | 
| + | 
| + if (wasSuccess == DescendantLoad::Failed) { | 
| + // One descendant failure is enough to make this module graph node fail. | 
| + // Ignore incomplete descendants as of now. Their success/failure | 
| + // would no longer affect this node. | 
| + // TODO(kouhei): Cancel inflight descendants fetch if possible. | 
| + m_numIncompleteDescendants = 0; | 
| + | 
| + m_moduleScript = nullptr; | 
| + advanceState(State::Finished); | 
| + return; | 
| + } | 
| + DCHECK_EQ(wasSuccess, DescendantLoad::Success); | 
| + | 
| + CHECK(m_moduleScript); | 
| +#ifndef NDEBUG | 
| + printf("remaining desc %zu\n", m_numIncompleteDescendants); | 
| +#endif | 
| + if (!m_numIncompleteDescendants) | 
| + instantiate(); | 
| +} | 
| + | 
| +void ModuleTreeLinker::instantiate() { | 
| + CHECK(m_moduleScript); | 
| + advanceState(State::Instantiating); | 
| + | 
| + // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure | 
| + | 
| + // [unspeced] If the instantiation of the module script is already attempt, | 
| + // early exit. | 
| + const auto state = m_moduleScript->instantiationState(); | 
| + if (state != ModuleInstantiationState::Uninstantiated) { | 
| + advanceState(State::Finished); | 
| + return; | 
| + } | 
| + | 
| + // Step 5. Let record be result's module record. | 
| + ScriptModule record = m_moduleScript->record(); | 
| + | 
| + // Step 6. Let instantiationStatus be record.ModuleDeclarationInstantiation(). | 
| + ScriptValue error = m_modulator->instantiateModule(record); | 
| + if (!error.isEmpty()) { | 
| 
 
hiroshige
2017/03/31 06:21:47
According to the spec, Lines 319--325 is not neede
 
 | 
| + m_moduleScript->setInstantiationError(error.isolate(), error.v8Value()); | 
| + } else { | 
| + m_moduleScript->setInstantiationSuccess(); | 
| + } | 
| + ModuleInstantiationState instantiationStatus = | 
| + m_moduleScript->instantiationState(); | 
| + | 
| + // Step 7. For each module script script in result's uninstantiated inclusive | 
| + // descendant module scripts, perform the following steps: | 
| + HeapHashSet<Member<ModuleScript>> uninstantiatedSet = | 
| + uninstantiatedInclusiveDescendants(); | 
| + for (const auto& descendant : uninstantiatedSet) { | 
| + if (instantiationStatus == ModuleInstantiationState::Errored) { | 
| + // Step 7.1. If instantiationStatus is an abrupt completion, then set | 
| + // script's | 
| + // instantiation state to "errored", its instantiation error to | 
| + // instantiationStatus.[[Value]], and its module record to null. | 
| + DCHECK(!error.isEmpty()); | 
| + descendant->setInstantiationError(error.isolate(), error.v8Value()); | 
| + descendant->clearRecord(); | 
| + } else { | 
| + // Step 7.2. Otherwise, set script's instantiation state to | 
| + // "instantiated". | 
| + descendant->setInstantiationSuccess(); | 
| + } | 
| + } | 
| + | 
| + // Step 8. Asynchronously complete this algorithm with descendants result. | 
| + advanceState(State::Finished); | 
| +} | 
| + | 
| +HeapHashSet<Member<ModuleScript>> | 
| +ModuleTreeLinker::uninstantiatedInclusiveDescendants() { | 
| + // https://html.spec.whatwg.org/multipage/webappapis.html#uninstantiated-inclusive-descendant-module-scripts | 
| + // Step 1. Let moduleMap be script's settings object's module map. | 
| + // Note: Modulator is our "settings object". | 
| + // Note: We won't reference the ModuleMap directly here to aid testing. | 
| + | 
| + // Step 2. Let stack be the stack « script ». | 
| + // TODO(kouhei): Make stack a HeapLinkedHashSet for O(1) lookups. | 
| + HeapDeque<Member<ModuleScript>> stack; | 
| + stack.push_front(m_moduleScript); | 
| + | 
| + // Step 3. Let inclusive descendants be an empty set. | 
| + // Note: We use unordered set here as the order is not observable from web | 
| + // platform. | 
| + // This is allowed per spec: https://infra.spec.whatwg.org/#sets | 
| + HeapHashSet<Member<ModuleScript>> inclusiveDescendants; | 
| + | 
| + // Step 4. While stack is not empty: | 
| + while (!stack.isEmpty()) { | 
| + // Step 4.1. Let current the result of popping from stack. | 
| + ModuleScript* current = stack.takeFirst(); | 
| + | 
| + // Step 4.2. If inclusive descendants and stack both do not contain current, | 
| + // then: | 
| + if (inclusiveDescendants.contains(current)) | 
| + continue; | 
| + if (std::find(stack.begin(), stack.end(), current) != stack.end()) | 
| + continue; | 
| + | 
| + // Step 4.2.1. Append current to inclusive descendants. | 
| + inclusiveDescendants.insert(current); | 
| + | 
| + // TODO(kouhei): This implementation is a direct transliteration of the | 
| + // spec. Omit intermediate vectors at the least. | 
| + | 
| + // Step 4.2.2. Let child specifiers be the value of current's module | 
| + // record's [[RequestedModules]] internal slot. | 
| + Vector<String> childSpecifiers = | 
| + m_modulator->moduleRequestsFromScriptModule(current->record()); | 
| + // Step 4.2.3. Let child URLs be the list obtained by calling resolve a | 
| + // module specifier once for each item of child specifiers, given current | 
| + // and that item. Omit any failures. | 
| + Vector<KURL> childURLs; | 
| + for (const auto& childSpecifier : childSpecifiers) { | 
| + KURL childURL = m_modulator->resolveModuleSpecifier(childSpecifier, | 
| + current->baseURL()); | 
| + if (childURL.isValid()) | 
| + childURLs.push_back(childURL); | 
| + } | 
| + // Step 4.2.4. Let child modules be the list obtained by getting each value | 
| + // in moduleMap whose key is given by an item of child URLs. | 
| + HeapVector<Member<ModuleScript>> childModules; | 
| + for (const auto& childURL : childURLs) { | 
| + ModuleScript* moduleScript = | 
| + m_modulator->getFetchedModuleScript(childURL); | 
| + childModules.push_back(moduleScript); | 
| + } | 
| + // Step 4.2.5. For each s in child modules that is not contained by | 
| + // inclusive descendants, push s onto stack. | 
| + for (const auto& s : childModules) { | 
| + if (!inclusiveDescendants.contains(s)) | 
| + stack.push_front(s); | 
| + } | 
| + } | 
| + | 
| + // Step 5. Return a set containing all items of inclusive descendants whose | 
| + // instantiation state is "uninstantiated". | 
| + HeapHashSet<Member<ModuleScript>> uninstantiatedSet; | 
| + for (const auto& script : inclusiveDescendants) { | 
| + if (script->instantiationState() == | 
| + ModuleInstantiationState::Uninstantiated) | 
| + uninstantiatedSet.insert(script); | 
| + } | 
| + return uninstantiatedSet; | 
| +} | 
| + | 
| +} // namespace blink |