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..733d84320ee6a21941d5be5c3ea2e654a5118f56 |
| --- /dev/null |
| +++ b/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinker.cpp |
| @@ -0,0 +1,347 @@ |
| +// 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/ModuleController.h" |
| +#include "bindings/core/v8/ScriptModule.h" |
| +#include "core/dom/ModuleScript.h" |
| +#include "core/loader/modulescript/ModuleTreeLinkerRegistry.h" |
| + |
| +namespace blink { |
| + |
| +ModuleTreeLinker::ModuleTreeLinker(Modulator* modulator, |
| + ModuleTreeLinkerRegistry* registry, |
| + ModuleTreeClient* client) |
| + : m_modulator(modulator), m_registry(registry), m_client(client) { |
| + DCHECK(modulator); |
| + DCHECK(registry); |
| + DCHECK(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) { |
| + printf("mtf %p adv state: %d\n", this, newState); |
| + switch (m_state) { |
| + case State::Initial: |
| + DCHECK_EQ(newState, State::FetchingSelf); |
| + break; |
| + case State::FetchingSelf: |
| + DCHECK(newState == State::FetchingDependencies || |
| + newState == State::Finished); |
| + break; |
| + case State::FetchingDependencies: |
| + DCHECK(newState == State::Instantiating || newState == State::Finished); |
| + break; |
| + case State::Instantiating: |
| + DCHECK_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->notifyFinishedModuleTree(m_moduleScript); |
| + } |
| +} |
| + |
| +ModuleTreeLinker* ModuleTreeLinker::fetch(const KURL& url, |
| + const KURL& baseURL, |
| + Modulator* modulator, |
| + ModuleTreeLinkerRegistry* registry, |
| + ModuleTreeClient* client) { |
| + ModuleTreeLinker* fetcher = new ModuleTreeLinker(modulator, registry, client); |
| + fetcher->fetchSelf(url, baseURL); |
| + return fetcher; |
| +} |
| + |
| +void ModuleTreeLinker::fetchSelf(const KURL& url, const KURL& baseURL) { |
| + // 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. |
| + // Step 2. Return from this algorithm, and run the following steps when |
| + // fetching a single module script asynchronously completes with result. |
| + advanceState(State::FetchingSelf); |
| + m_modulator->fetchSingle(url, baseURL, this); |
| +} |
| + |
| +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) { |
| + DCHECK(moduleTreeLinkers); |
| + } |
| + |
| + // Implements ModuleTreeClient |
| + void notifyFinishedModuleTree(ModuleScript*) override; |
| + |
| + Member<ModuleTreeLinker> m_moduleTreeLinkers; |
| +}; |
| + |
| +void ModuleTreeLinker::notifyFinishedSingleModule(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; |
| + fetchDescendants(); |
| + |
| + // Note: Step 5- continues in instantiate() method, after |
| + // "fetch the descendants of a module script" procedure completes. |
| +} |
| + |
| +void ModuleTreeLinker::fetchDescendants() { |
| + DCHECK(m_moduleScript); |
| + advanceState(State::FetchingDependencies); |
| + |
| + // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-a-module-script |
| + |
| + // 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->moduleController()->moduleRequestsFromScriptModule(record); |
| + if (moduleRequests.isEmpty()) { |
| + // Continue to instantiate() to process "internal module script graph |
| + // fetching procedure" Step 5-. |
| + instantiate(); |
| + return; |
| + } |
| + |
| + // Step 3. Let urls be a new empty list. |
| + Vector<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 = m_modulator->resolveModuleSpecifier(moduleRequest, |
| + m_moduleScript->baseURL()); |
| + printf("resolveModuleSpecifier \"%s\" -> \"%s\"\n", |
| + moduleRequest.utf8().data(), url.getString().utf8().data()); |
| + |
| + // 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. |
| + // Abort this algorithm, and asynchronously complete it with null. |
| + m_moduleScript = nullptr; |
| + advanceState(State::Finished); |
| + } |
| + |
| + // Step 4.3. Otherwise, if ancestor list does not contain url, append url to |
|
yhirano
2017/01/06 07:27:01
Where is the ancestor list? Is it guaranteed that
kouhei (in TOK)
2017/01/11 01:41:58
Implemented "ancestor list" algorithm.
kouhei (in TOK)
2017/01/11 02:30:17
Sorry this was still incomplete. Fixing.
|
| + // urls. |
| + DCHECK(url.isValid()); |
| + urls.push_back(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. |
| + DCHECK_EQ(m_numIncompleteDescendants, 0u); |
| + m_numIncompleteDescendants = urls.size(); |
| + for (const KURL& url : urls) { |
| + DependencyModuleClient* dependencyClient = |
| + DependencyModuleClient::create(this); |
| + m_dependencyClients.add(dependencyClient); |
| + m_modulator->fetchTree(url, m_moduleScript->baseURL(), dependencyClient); |
| + } |
| + |
| + // Asynchronously continue processing after notifyOneDescendantFinished() is |
| + // called m_numIncompleteDescendants times. |
| + DCHECK(m_numIncompleteDescendants); |
| +} |
| + |
| +void ModuleTreeLinker::DependencyModuleClient::notifyFinishedModuleTree( |
| + ModuleScript* moduleScript) { |
| + bool wasSuccess = !!moduleScript; |
| + m_moduleTreeLinkers->notifyOneDescendantFinished(wasSuccess); |
| +} |
| + |
| +void ModuleTreeLinker::notifyOneDescendantFinished(bool 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. |
| + DCHECK(!m_moduleScript); |
| + return; |
| + } |
| + |
| + DCHECK(m_state == State::FetchingDependencies); |
| + |
| + DCHECK_GT(m_numIncompleteDescendants, 0u); |
| + --m_numIncompleteDescendants; |
| + |
| + if (!wasSuccess) { |
| + m_moduleScript = nullptr; |
| + advanceState(State::Finished); |
| + return; |
| + } |
| + |
| + DCHECK(m_moduleScript); |
| + printf("remaining desc %zu\n", m_numIncompleteDescendants); |
| + if (!m_numIncompleteDescendants) |
| + instantiate(); |
| +} |
| + |
| +void ModuleTreeLinker::instantiate() { |
| + DCHECK(m_moduleScript); |
| + // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure |
| + |
| + // Step 5. Let record be result's module record. |
| + ScriptModule record = m_moduleScript->record(); |
| + |
| + // Step 6. Let instantiationStatus be record.ModuleDeclarationInstantiation(). |
| + bool instantiateSuccess = |
| + m_modulator->moduleController()->instantiateModule(record); |
| + DCHECK(instantiateSuccess); // TODO(kouhei) |
| + m_moduleScript->updateStateAfterInstantiation(ScriptValue()); |
| + InstantiationState 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 = |
|
yhirano
2017/01/06 07:27:01
Is there an ordering issue? "set" in the spec look
kouhei (in TOK)
2017/01/11 01:41:58
No. Added a spec note.
|
| + uninstantiatedInclusiveDescendants(); |
| + for (const auto& descendant : uninstantiatedSet) { |
| + if (instantiationStatus == InstantiationState::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. |
| + descendant->propagateUpstreamError(m_moduleScript->instantiationError()); |
| + } else { |
| + // Step 7.2. Otherwise, set script's instantiation state to |
| + // "instantiated". |
| + descendant->propagateUpstreamSuccess(); |
| + } |
| + } |
| + |
| + // 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 ». |
| + HeapDeque<Member<ModuleScript>> stack; |
| + stack.prepend(m_moduleScript); |
| + |
| + // Step 3. Let inclusive descendants be an empty set. |
| + 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()) |
|
yhirano
2017/01/06 07:27:01
!=
kouhei (in TOK)
2017/01/11 01:41:58
Done.
|
| + continue; |
| + |
| + // Step 4.2.1. Append current to inclusive descendants. |
| + inclusiveDescendants.add(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->moduleController()->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->retrieveFetchedModuleScript(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.prepend(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() == InstantiationState::Uninstantiated) |
| + uninstantiatedSet.add(script); |
| + } |
| + return uninstantiatedSet; |
| +} |
| + |
| +} // namespace blink |