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 |