Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Unified Diff: third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinker.cpp

Issue 2823803003: [ES6 modules] Introduce ModuleTreeLinker (Closed)
Patch Set: yhirano2 Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698