| // 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/WebTaskRunner.h" |
| #include "platform/bindings/V8ThrowException.h" |
| #include "platform/loader/fetch/ResourceLoadingLog.h" |
| #include "platform/wtf/Vector.h" |
| #include "v8/include/v8.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::FetchDescendantsForInlineScript( |
| ModuleScript* module_script, |
| Modulator* modulator, |
| ModuleTreeLinkerRegistry* registry, |
| ModuleTreeClient* client) { |
| AncestorList empty_ancestor_list; |
| |
| // Substep 4 in "module" case in Step 22 of "prepare a script":" |
| // https://html.spec.whatwg.org/#prepare-a-script |
| DCHECK(module_script); |
| |
| // 4. "Fetch the descendants of script (using an empty ancestor list)." |
| ModuleTreeLinker* fetcher = |
| new ModuleTreeLinker(empty_ancestor_list, modulator, registry, client); |
| fetcher->module_script_ = module_script; |
| fetcher->AdvanceState(State::kFetchingSelf); |
| |
| // "When this asynchronously completes, set the script's script to |
| // the result. At that time, the script is ready." |
| // |
| // Currently we execute "internal module script graph |
| // fetching procedure" Step 5- in addition to "fetch the descendants", |
| // which is not specced yet. https://github.com/whatwg/html/issues/2544 |
| // TODO(hiroshige): Fix the implementation and/or comments once the spec |
| // is updated. |
| modulator->TaskRunner()->PostTask( |
| BLINK_FROM_HERE, |
| WTF::Bind(&ModuleTreeLinker::FetchDescendants, WrapPersistent(fetcher))); |
| 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 || |
| new_state == State::kFinished); |
| break; |
| case State::kFetchingDependencies: |
| CHECK(new_state == State::kInstantiating || |
| new_state == State::kFinished); |
| break; |
| case State::kInstantiating: |
| CHECK_EQ(new_state, State::kFinished); |
| break; |
| case State::kFinished: |
| NOTREACHED(); |
| break; |
| } |
| |
| state_ = new_state; |
| |
| if (state_ == State::kFinished) { |
| RESOURCE_LOADING_DVLOG(1) |
| << "ModuleTreeLinker[" << this << "] finished with final result " |
| << descendants_module_script_; |
| |
| registry_->ReleaseFinishedFetcher(this); |
| |
| // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure |
| // Step 7. When the "fetch the descendants of and instantiate a module |
| // script" algorithm asynchronously completes with final result, |
| // asynchronously complete this algorithm with final 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 |
| // ModuleTreeLinker::NotifyModuleLoadFinished(). |
| } |
| |
| void ModuleTreeLinker::NotifyModuleLoadFinished(ModuleScript* result) { |
| // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure |
| |
| // Step 3. "If result is null, ..." |
| // Step 4. "If result's state is "instantiated" or "errored", ..." |
| if (!result || result->State() == ModuleInstantiationState::kInstantiated || |
| result->State() == ModuleInstantiationState::kErrored) { |
| // "asynchronously complete this algorithm with result, and abort these |
| // steps." |
| descendants_module_script_ = result; |
| AdvanceState(State::kFinished); |
| return; |
| } |
| |
| // Step 5. Assert: result's state is "uninstantiated". |
| DCHECK_EQ(ModuleInstantiationState::kUninstantiated, result->State()); |
| |
| // Step 6. Fetch the descendants of and instantiate result given destination |
| // and an ancestor list obtained by appending url to ancestor list. |
| module_script_ = result; |
| FetchDescendants(); |
| } |
| |
| 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: |
| explicit DependencyModuleClient(ModuleTreeLinker* module_tree_linker) |
| : 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); |
| |
| // [nospec] Abort the steps if the browsing context is discarded. |
| if (!modulator_->HasValidContext()) { |
| descendants_module_script_ = nullptr; |
| AdvanceState(State::kFinished); |
| return; |
| } |
| |
| // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-and-instantiate-a-module-script |
| // Step 1. If ancestor list was not given, let it be the empty list. |
| // Note: The "ancestor list" in spec corresponds to |ancestor_list_with_url_|. |
| |
| // Step 2. If module script's state is "instantiated" or "errored", |
| // asynchronously complete this algorithm with module script, and abort these |
| // steps. |
| if (module_script_->State() == ModuleInstantiationState::kInstantiated || |
| module_script_->State() == ModuleInstantiationState::kErrored) { |
| descendants_module_script_ = module_script_; |
| AdvanceState(State::kFinished); |
| return; |
| } |
| |
| // Step 3. Assert: module script's state is "uninstantiated" |
| DCHECK_EQ(ModuleInstantiationState::kUninstantiated, module_script_->State()); |
| |
| // Step 4. Let record be module script's module record. |
| ScriptModule record = module_script_->Record(); |
| DCHECK(!record.IsNull()); |
| |
| // Step 5. If record.[[RequestedModules]] is empty, asynchronously complete |
| // this algorithm with module script. |
| // Note: We defer this bail-out until Step 9. Step 6-9 will be no-op anyway if |
| // record.[[RequestedModules]] is empty. |
| |
| // Step 6. Let urls be a new empty list. |
| Vector<KURL> urls; |
| |
| // Step 7. For each string requested of record.[[RequestedModules]], |
| Vector<String> module_requests = |
| modulator_->ModuleRequestsFromScriptModule(record); |
| for (const auto& module_request : module_requests) { |
| // Step 7.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 7.2. If url is failure: ... |
| if (url.IsNull()) { |
| // Step 7.2.1. Let error be a new TypeError exception. |
| ScriptState::Scope scope(modulator_->GetScriptState()); |
| v8::Isolate* isolate = modulator_->GetScriptState()->GetIsolate(); |
| v8::Local<v8::Value> error = V8ThrowException::CreateTypeError( |
| isolate, |
| "Failed to resolve module specifier \'" + module_request + "'"); |
| |
| // Step 7.2.2. Error module script with error. |
| module_script_->SetErrorAndClearRecord( |
| ScriptValue(modulator_->GetScriptState(), error)); |
| |
| // Step 7.2.3. Abort this algorithm, and asynchronously complete it with |
| // module_sript_. Note: The return variable for "internal module script |
| // graph fetching procedure" is descendants_module_script_ per Step 7. |
| descendants_module_script_ = module_script_; |
| AdvanceState(State::kFinished); |
| return; |
| } |
| |
| // Step 7.3. Otherwise, if ancestor list does not contain url, append url to |
| // urls. |
| CHECK(url.IsValid()) << "Modulator::resolveModuleSpecifier() impl must " |
| "return either a valid url or null."; |
| if (!ancestor_list_with_url_.Contains(url)) |
| urls.push_back(url); |
| } |
| |
| // Step 8. Let descendants result be null. |
| DCHECK(!descendants_module_script_); |
| |
| // Step 9. 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. |
| |
| if (urls.IsEmpty()) { |
| // Step 5. If record.[[RequestedModules]] is empty, asynchronously |
| // complete this algorithm with module script. [spec text] |
| // Note: We actually proceed to Step 10 here. [nospec, but a pending PR |
| // fixes this.] |
| |
| // Also, if record.[[RequestedModules]] is not empty but |urls| is |
| // empty here, we can proceed to Step 10. |
| descendants_module_script_ = module_script_; |
| Instantiate(); |
| return; |
| } |
| |
| // Step 9, when "urls" is non-empty. |
| // These invocations of the internal module script graph fetching procedure |
| // should be performed in parallel to each other. [spec text] |
| CHECK_EQ(num_incomplete_descendants_, 0u); |
| 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 num_incomplete_descendants_ times. |
| CHECK_GT(num_incomplete_descendants_, 0u); |
| } |
| |
| void ModuleTreeLinker::DependencyModuleClient::NotifyModuleTreeLoadFinished( |
| ModuleScript* module_script) { |
| module_tree_linker_->NotifyOneDescendantFinished(module_script); |
| } |
| |
| void ModuleTreeLinker::NotifyOneDescendantFinished( |
| ModuleScript* module_script) { |
| if (state_ != State::kFetchingDependencies) { |
| // We may reach here if one of the descendant failed to load, and the other |
| // descendants fetches were in flight. |
| return; |
| } |
| DCHECK_EQ(state_, State::kFetchingDependencies); |
| DCHECK(module_script_); |
| |
| CHECK_GT(num_incomplete_descendants_, 0u); |
| --num_incomplete_descendants_; |
| |
| // Step 9 of |
| // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-and-instantiate-a-module-script |
| // "If any invocation of the internal module script graph fetching procedure |
| // asynchronously completes with null, then ..." [spec text] |
| if (!module_script) { |
| RESOURCE_LOADING_DVLOG(1) |
| << "ModuleTreeLinker[" << this |
| << "]::NotifyOneDescendantFinished with null. " |
| << num_incomplete_descendants_ << " remaining descendants."; |
| |
| // "optionally abort all other invocations, " [spec text] |
| // TODO(kouhei) Implement this. |
| |
| // "set descendants result to null, and ... " [spec text] |
| DCHECK(!descendants_module_script_); |
| |
| // "proceed to the next step. (The un-fetched descendant will cause errors |
| // during instantiation.)" [spec text] |
| Instantiate(); |
| return; |
| } |
| |
| RESOURCE_LOADING_DVLOG(1) |
| << "ModuleTreeLinker[" << this |
| << "]::NotifyOneDescendantFinished with a module script of state \"" |
| << ModuleInstantiationStateToString(module_script->State()) << "\". " |
| << num_incomplete_descendants_ << " remaining descendants."; |
| |
| // "If any invocation of the internal module script graph fetching procedure |
| // asynchronously completes with a module script whose state is "errored", |
| // then ..." [spec text] |
| if (module_script->State() == ModuleInstantiationState::kErrored) { |
| // "optionally abort all other invocations, ..." [spec text] |
| // TODO(kouhei) Implement this. |
| |
| // "set descendants result to module script, ..." [spec text] |
| descendants_module_script_ = module_script_; |
| |
| // "and proceed to the next step. (The errored descendant will cause errors |
| // during instantiation.)" [spec text] |
| Instantiate(); |
| return; |
| } |
| |
| // "Otherwise, wait for all of the internal module script graph fetching |
| // procedure invocations to asynchronously complete, with module scripts whose |
| // states are not "errored"." [spec text] |
| if (!num_incomplete_descendants_) { |
| // "Then, set descendants result to module script, and proceed to the next |
| // step." [spec text] |
| descendants_module_script_ = module_script_; |
| Instantiate(); |
| } |
| } |
| |
| void ModuleTreeLinker::Instantiate() { |
| CHECK(module_script_); |
| AdvanceState(State::kInstantiating); |
| |
| // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-and-instantiate-a-module-script |
| |
| // [nospec] Abort the steps if the browsing context is discarded. |
| if (!modulator_->HasValidContext()) { |
| descendants_module_script_ = nullptr; |
| AdvanceState(State::kFinished); |
| return; |
| } |
| |
| // Contrary to the spec, we don't store the "record" in Step 4 for its use in |
| // Step 10. If Instantiate() was called on descendant ModuleTreeLinker and |
| // failed, module_script_->Record() may be already cleared. |
| if (module_script_->State() == ModuleInstantiationState::kErrored) { |
| DCHECK(module_script_->HasEmptyRecord()); |
| AdvanceState(State::kFinished); |
| return; |
| } |
| |
| // Step 4. Let record be result's module record. |
| ScriptModule record = module_script_->Record(); |
| |
| // Step 10. 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 11. For each script in module script'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 11.1. If instantiationStatus is an abrupt completion, then |
| // error script with instantiationStatus.[[Value]] |
| descendant->SetErrorAndClearRecord(error); |
| } else { |
| // Step 11.2. Otherwise, set script's instantiation state to |
| // "instantiated". |
| descendant->SetInstantiationSuccess(); |
| } |
| } |
| |
| // Step 12. 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. If script's module record is null, return the empty set. |
| if (module_script_->HasEmptyRecord()) |
| return HeapHashSet<Member<ModuleScript>>(); |
| |
| // Step 2. 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 3. Let stack be the stack << script >>. |
| // TODO(kouhei): Make stack a HeapLinkedHashSet for O(1) lookups. |
| HeapDeque<Member<ModuleScript>> stack; |
| stack.push_front(module_script_); |
| |
| // Step 4. 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 5. While stack is not empty: |
| while (!stack.IsEmpty()) { |
| // Step 5.1. Let current the result of popping from stack. |
| ModuleScript* current = stack.TakeFirst(); |
| |
| // Step 5.2. Assert: current is a module script (i.e., it is not "fetching" |
| // or null). |
| DCHECK(current); |
| |
| // Step 5.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 5.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 5.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 5.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 5.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 5.3.5. For each s of child modules: |
| for (const auto& s : child_modules) { |
| // Step 5.3.5.2. If s is null, continue. |
| // Note: We do null check first, as Blink HashSet can't contain nullptr. |
| |
| // Step 5.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: |
| // GetFetchedModuleScript returns nullptr if "fetching" |
| if (!s) |
| continue; |
| |
| // Step 5.3.5.1. If inclusive descendants already contains s, continue. |
| if (inclusive_descendants.Contains(s)) |
| continue; |
| |
| // Step 5.3.5.4. Push s onto stack. |
| stack.push_front(s); |
| } |
| } |
| |
| // Step 6. 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->State() == ModuleInstantiationState::kUninstantiated) |
| uninstantiated_set.insert(script); |
| } |
| return uninstantiated_set; |
| } |
| |
| } // namespace blink |