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..470f28b073cbdedea78f0b19279049f26f67ae49 |
--- /dev/null |
+++ b/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinker.cpp |
@@ -0,0 +1,460 @@ |
+// 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 "platform/loader/fetch/ResourceLoadingLog.h" |
+#include "platform/wtf/HashSet.h" |
+ |
+namespace blink { |
+ |
+ModuleTreeLinker* ModuleTreeLinker::Fetch( |
+ const ModuleScriptFetchRequest& request, |
+ const AncestorList& ancestor_list, |
+ ModuleGraphLevel level, |
+ Modulator* modulator, |
+ ModuleTreeLinkerRegistry* registry, |
+ ModuleTreeClient* client) { |
+ AncestorList ancestor_list_with_url = ancestor_list; |
+ ancestor_list_with_url.insert(request.Url()); |
+ |
+ ModuleTreeLinker* fetcher = |
+ new ModuleTreeLinker(ancestor_list_with_url, modulator, registry, client); |
+ fetcher->FetchSelf(request, level); |
+ return fetcher; |
+} |
+ |
+ModuleTreeLinker::ModuleTreeLinker(const AncestorList& ancestor_list_with_url, |
+ Modulator* modulator, |
+ ModuleTreeLinkerRegistry* registry, |
+ ModuleTreeClient* client) |
+ : modulator_(modulator), |
+ registry_(registry), |
+ client_(client), |
+ ancestor_list_with_url_(ancestor_list_with_url) { |
+ CHECK(modulator); |
+ CHECK(registry); |
+ CHECK(client); |
+} |
+ |
+DEFINE_TRACE(ModuleTreeLinker) { |
+ visitor->Trace(modulator_); |
+ visitor->Trace(registry_); |
+ visitor->Trace(client_); |
+ visitor->Trace(module_script_); |
+ visitor->Trace(descendants_module_script_); |
+ visitor->Trace(dependency_clients_); |
+ SingleModuleClient::Trace(visitor); |
+} |
+ |
+#if DCHECK_IS_ON() |
+const char* ModuleTreeLinker::StateToString(ModuleTreeLinker::State state) { |
+ switch (state) { |
+ case State::kInitial: |
+ return "Initial"; |
+ case State::kFetchingSelf: |
+ return "FetchingSelf"; |
+ case State::kFetchingDependencies: |
+ return "FetchingDependencies"; |
+ case State::kInstantiating: |
+ return "Instantiating"; |
+ case State::kFinished: |
+ return "Finished"; |
+ } |
+ NOTREACHED(); |
+ return ""; |
+} |
+#endif |
+ |
+void ModuleTreeLinker::AdvanceState(State new_state) { |
+#if DCHECK_IS_ON() |
+ RESOURCE_LOADING_DVLOG(1) |
+ << "ModuleTreeLinker[" << this << "]::advanceState(" |
+ << StateToString(state_) << " -> " << StateToString(new_state) << ")"; |
+#endif |
+ |
+ switch (state_) { |
+ case State::kInitial: |
+ CHECK_EQ(num_incomplete_descendants_, 0u); |
+ CHECK_EQ(new_state, State::kFetchingSelf); |
+ break; |
+ case State::kFetchingSelf: |
+ CHECK_EQ(num_incomplete_descendants_, 0u); |
+ CHECK(new_state == State::kFetchingDependencies || |
+ (!descendants_module_script_ && new_state == State::kFinished)); |
+ break; |
+ case State::kFetchingDependencies: |
+ CHECK_EQ(new_state, State::kInstantiating); |
+ DCHECK(!num_incomplete_descendants_ || !descendants_module_script_) |
+ << num_incomplete_descendants_ |
+ << " outstanding descendant loads found, but the descendant module " |
+ "script load procedure unexpectedly finished with " |
+ << (!!descendants_module_script_ ? "success." : "failure."); |
hiroshige
2017/04/19 22:24:08
Here |descendants_module_script_| is always true w
kouhei (in TOK)
2017/04/24 01:06:21
num_incomplete_descendants_ may be 0?
hiroshige
2017/04/24 17:56:51
Er, I meant |(!!descendants_module_script_ ? "succ
|
+ break; |
+ case State::kInstantiating: |
+ CHECK(num_incomplete_descendants_ == 0u || !descendants_module_script_); |
+ CHECK_EQ(new_state, State::kFinished); |
+ break; |
+ case State::kFinished: |
+ NOTREACHED(); |
hiroshige
2017/04/19 22:24:08
As we test state transition by CHECK()s in other c
kouhei (in TOK)
2017/04/24 01:06:22
I'd leave this for now.
|
+ break; |
+ } |
+ |
+ state_ = new_state; |
+ |
+ if (state_ == State::kFinished) { |
+ registry_->ReleaseFinishedFetcher(this); |
+ |
+ // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure |
+ // Step 8. Asynchronously complete this algorithm with descendants result. |
+ client_->NotifyModuleTreeLoadFinished(descendants_module_script_); |
+ } |
+} |
+ |
+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::kFetchingSelf); |
+ 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 |
hiroshige
2017/04/19 22:24:07
nit: FetchSingle().
kouhei (in TOK)
2017/04/24 01:06:21
Done.
|
+ // ModuleTreeLinker::notifyModuleLoadFinished(). |
+} |
+ |
+void ModuleTreeLinker::NotifyModuleLoadFinished(ModuleScript* module_script) { |
+ // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure |
+ |
+ // Step 3. "If result is null, ..." |
+ if (!module_script) { |
+ // "asynchronously complete this algorithm with null and abort these steps." |
+ // Note: The return variable for "internal module script graph fetching |
+ // procedure" is descendants_module_script_ per Step 8. |
hiroshige
2017/04/19 22:24:07
Lines 144-145 should refer to our implementation (
kouhei (in TOK)
2017/04/24 01:06:22
Done.
|
+ DCHECK(!descendants_module_script_); |
+ AdvanceState(State::kFinished); |
+ 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. |
+ module_script_ = module_script; |
+ |
+ FetchDescendants(); |
+ |
+ // Note: Step 5- continues in instantiate() method, after |
kinuko
2017/04/19 06:11:33
nit: instantiate() -> Instantiate()
kouhei (in TOK)
2017/04/24 01:06:22
Done.
|
+ // "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* module_tree_linker) { |
+ return new DependencyModuleClient(module_tree_linker); |
+ } |
+ virtual ~DependencyModuleClient() = default; |
+ |
+ DEFINE_INLINE_TRACE() { |
+ visitor->Trace(module_tree_linker_); |
+ ModuleTreeClient::Trace(visitor); |
+ } |
+ |
+ private: |
+ DependencyModuleClient(ModuleTreeLinker* module_tree_linker) |
kinuko
2017/04/19 06:11:33
nit: explicit
kouhei (in TOK)
2017/04/24 01:06:22
Done.
|
+ : module_tree_linker_(module_tree_linker) { |
+ CHECK(module_tree_linker); |
+ } |
+ |
+ // Implements ModuleTreeClient |
+ void NotifyModuleTreeLoadFinished(ModuleScript*) override; |
+ |
+ Member<ModuleTreeLinker> module_tree_linker_; |
+}; |
+ |
+void ModuleTreeLinker::FetchDescendants() { |
+ CHECK(module_script_); |
+ AdvanceState(State::kFetchingDependencies); |
+ |
+ // 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 = module_script_->Record(); |
+ |
+ // Step 2. If record.[[RequestedModules]] is empty, asynchronously complete |
+ // this algorithm with module script. |
+ Vector<String> module_requests = |
+ modulator_->ModuleRequestsFromScriptModule(record); |
+ |
+ // Step 3. Let urls be a new empty list. |
hiroshige
2017/04/19 22:24:07
The spec says "list" while the implementation is "
kouhei (in TOK)
2017/04/24 01:06:22
We don't actually require HashSet<> here, so chang
|
+ HashSet<KURL> urls; |
+ |
+ // Step 4. For each string requested of record.[[RequestedModules]], |
+ for (const auto& module_request : module_requests) { |
+ // Step 4.1. Let url be the result of resolving a module specifier given |
+ // module script and requested. |
+ KURL url = Modulator::ResolveModuleSpecifier(module_request, |
+ module_script_->BaseURL()); |
+ |
+ // 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 the exception. |
+ |
+ // Abort this algorithm, and asynchronously complete it with null. |
+ // Note: The return variable for "internal module script graph fetching |
+ // procedure" is descendants_module_script_ per Step 8. |
+ DCHECK(!descendants_module_script_); |
+ // Note: while we complete "fetch the descendants of a module script" |
+ // algorithm here, we still need to continue to the rest of the |
+ // steps in "internal module script graph fetching procedure" |
+ Instantiate(); |
+ 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 (!ancestor_list_with_url_.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(num_incomplete_descendants_, 0u); |
+ if (urls.IsEmpty()) { |
hiroshige
2017/04/19 22:24:07
Actually, this if block implements Step 2 above.
(
kouhei (in TOK)
2017/04/24 01:06:22
Done.
hiroshige
2017/04/26 01:23:58
Sorry I was wrong:
|urls| can be empty here if the
|
+ // Continue to instantiate() to process "internal module script graph |
kinuko
2017/04/19 06:11:33
nit: instantiate() -> Instantiate()
kouhei (in TOK)
2017/04/24 01:06:22
Done.
|
+ // fetching procedure" Step 5-. |
+ descendants_module_script_ = module_script_; |
+ Instantiate(); |
+ return; |
+ } |
+ num_incomplete_descendants_ = urls.size(); |
+ for (const KURL& url : urls) { |
+ DependencyModuleClient* dependency_client = |
+ DependencyModuleClient::Create(this); |
+ dependency_clients_.insert(dependency_client); |
+ |
+ ModuleScriptFetchRequest request(url, module_script_->Nonce(), |
+ module_script_->ParserState(), |
+ module_script_->CredentialsMode(), |
+ module_script_->BaseURL().GetString()); |
+ modulator_->FetchTreeInternal(request, ancestor_list_with_url_, |
+ ModuleGraphLevel::kDependentModuleFetch, |
+ dependency_client); |
+ } |
+ |
+ // Asynchronously continue processing after notifyOneDescendantFinished() is |
+ // called m_numIncompleteDescendants times. |
+ CHECK_GT(num_incomplete_descendants_, 0u); |
+} |
+ |
+void ModuleTreeLinker::DependencyModuleClient::NotifyModuleTreeLoadFinished( |
+ ModuleScript* module_script) { |
+ DescendantLoad was_success = |
+ !!module_script ? DescendantLoad::kSuccess : DescendantLoad::kFailed; |
hiroshige
2017/04/19 22:24:07
nit: do we need |!!|?
kouhei (in TOK)
2017/04/24 01:06:22
Removed.
This is the second time I got the comment
|
+ module_tree_linker_->NotifyOneDescendantFinished(was_success); |
+} |
+ |
+void ModuleTreeLinker::NotifyOneDescendantFinished(DescendantLoad was_success) { |
+ CHECK(!descendants_module_script_); |
+ |
+ if (state_ == State::kFinished) { |
+ // We may reach here if one of the descendant failed to load, and the other |
+ // descendants fetches were in flight. |
+ return; |
+ } |
+ CHECK_EQ(state_, State::kFetchingDependencies); |
+ |
+ CHECK_GT(num_incomplete_descendants_, 0u); |
+ --num_incomplete_descendants_; |
+ |
+ // TODO(kouhei): Potential room for optimization. Cancel inflight descendants |
+ // fetch if a descendant load failed. |
+ |
+ CHECK(module_script_); |
+ RESOURCE_LOADING_DVLOG(1) |
+ << "ModuleTreeLinker[" << this << "]::NotifyOneDescendantFinished with " |
+ << (was_success == DescendantLoad::kSuccess ? "success. " : "failure. ") |
+ << num_incomplete_descendants_ << " remaining descendants."; |
+ |
+ // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-a-module-script |
+ // Step 5. "... If any of them asynchronously complete with null, then |
+ // asynchronously complete this algorithm with null" |
+ if (was_success == DescendantLoad::kFailed) { |
+ DCHECK(!descendants_module_script_); |
+ // Note: while we complete "fetch the descendants of a module script" |
+ // algorithm here, we still need to continue to the rest of the steps |
+ // in "internal module script graph fetching procedure" |
+ Instantiate(); |
+ return; |
+ } |
+ |
+ // Step 5. "Wait for all of the internal module script graph fetching |
+ // procedure invocations to asynchronously complete..." |
hiroshige
2017/04/19 22:24:07
Add "Otherwise, asynchronously complete this algor
kouhei (in TOK)
2017/04/24 01:06:22
Done.
|
+ if (!num_incomplete_descendants_) { |
+ descendants_module_script_ = module_script_; |
+ Instantiate(); |
+ return; |
kinuko
2017/04/19 06:11:33
Not needed?
kouhei (in TOK)
2017/04/24 01:06:22
Done.
|
+ } |
+} |
+ |
+void ModuleTreeLinker::Instantiate() { |
+ CHECK(module_script_); |
+ AdvanceState(State::kInstantiating); |
+ |
+ // 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 = module_script_->Record(); |
+ |
+ // Step 6. Let instantiationStatus be record.ModuleDeclarationInstantiation(). |
+ // Note: The |error| variable corresponds to spec variable |
+ // "instantiationStatus". If |error| is empty, it indicates successful |
+ // completion. |
+ ScriptValue error = modulator_->InstantiateModule(record); |
+ |
+ // Step 7. For each module script script in result's uninstantiated inclusive |
+ // descendant module scripts, perform the following steps: |
+ HeapHashSet<Member<ModuleScript>> uninstantiated_set = |
+ UninstantiatedInclusiveDescendants(); |
+ for (const auto& descendant : uninstantiated_set) { |
+ if (!error.IsEmpty()) { |
+ // 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->SetInstantiationErrorAndClearRecord(error); |
+ } 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::kFinished); |
+} |
+ |
+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 ». |
hiroshige
2017/04/19 22:24:07
nit: I prefer avoiding non-ASCII characters in the
kouhei (in TOK)
2017/04/24 01:06:22
Done.
|
+ // TODO(kouhei): Make stack a HeapLinkedHashSet for O(1) lookups. |
+ HeapDeque<Member<ModuleScript>> stack; |
+ stack.push_front(module_script_); |
+ |
+ // 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>> inclusive_descendants; |
+ |
+ // 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. Assert: current is a module script (i.e., it is not "fetching" |
+ // or null). |
+ DCHECK(current); |
+ |
+ // Step 4.3. If inclusive descendants and stack both do not contain current, |
+ // then: |
+ if (inclusive_descendants.Contains(current)) |
+ continue; |
+ if (std::find(stack.begin(), stack.end(), current) != stack.end()) |
+ continue; |
+ |
+ // Step 4.3.1. Append current to inclusive descendants. |
+ inclusive_descendants.insert(current); |
+ |
+ // TODO(kouhei): This implementation is a direct transliteration of the |
+ // spec. Omit intermediate vectors at the least. |
+ |
+ // Step 4.3.2. Let child specifiers be the value of current's module |
+ // record's [[RequestedModules]] internal slot. |
+ Vector<String> child_specifiers = |
+ modulator_->ModuleRequestsFromScriptModule(current->Record()); |
+ // Step 4.3.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> child_urls; |
+ for (const auto& child_specifier : child_specifiers) { |
+ KURL child_url = modulator_->ResolveModuleSpecifier(child_specifier, |
+ current->BaseURL()); |
+ if (child_url.IsValid()) |
+ child_urls.push_back(child_url); |
+ } |
+ // Step 4.3.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>> child_modules; |
+ for (const auto& child_url : child_urls) { |
+ ModuleScript* module_script = |
+ modulator_->GetFetchedModuleScript(child_url); |
+ |
+ child_modules.push_back(module_script); |
+ } |
+ // Step 4.3.5. For each s of child modules: |
+ for (const auto& s : child_modules) { |
+ // Step 4.3.5.2. If s is null, continue. |
+ // Note: We do null check first, as Blink HashSet can't contain nullptr. |
+ // Step 4.3.5.3. Assert: s is a module script (i.e., it is not "fetching", |
+ // since by this point all child modules must have been fetched). Note: |
hiroshige
2017/04/19 22:24:07
nit: line break before "Note:".
kouhei (in TOK)
2017/04/24 01:06:22
Done.
|
+ // GetFetchedModuleScript returns nullptr if "fetching" |
+ if (!s) |
+ continue; |
+ |
+ // Step 4.3.5.1. If inclusive descendants already contains s, continue. |
+ if (inclusive_descendants.Contains(s)) |
+ continue; |
+ |
+ // Step 4.3.5.4. Push s onto satck. |
kinuko
2017/04/19 06:11:33
satck -> stack
kouhei (in TOK)
2017/04/24 01:06:22
Done.
|
+ stack.push_front(s); |
+ } |
+ } |
+ |
+ // Step 5. Return a set containing all items of inclusive descendants whose |
+ // instantiation state is "uninstantiated". |
+ HeapHashSet<Member<ModuleScript>> uninstantiated_set; |
+ for (const auto& script : inclusive_descendants) { |
+ if (script->InstantiationState() == |
+ ModuleInstantiationState::kUninstantiated) |
+ uninstantiated_set.insert(script); |
+ } |
+ return uninstantiated_set; |
+} |
+ |
+} // namespace blink |