OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "core/loader/modulescript/ModuleTreeLinker.h" |
| 6 |
| 7 #include "bindings/core/v8/ScriptModule.h" |
| 8 #include "core/dom/AncestorList.h" |
| 9 #include "core/dom/ModuleScript.h" |
| 10 #include "core/loader/modulescript/ModuleScriptFetchRequest.h" |
| 11 #include "core/loader/modulescript/ModuleTreeLinkerRegistry.h" |
| 12 #include "platform/loader/fetch/ResourceLoadingLog.h" |
| 13 #include "platform/wtf/HashSet.h" |
| 14 |
| 15 namespace blink { |
| 16 |
| 17 ModuleTreeLinker* ModuleTreeLinker::Fetch( |
| 18 const ModuleScriptFetchRequest& request, |
| 19 const AncestorList& ancestor_list, |
| 20 ModuleGraphLevel level, |
| 21 Modulator* modulator, |
| 22 ModuleTreeLinkerRegistry* registry, |
| 23 ModuleTreeClient* client) { |
| 24 AncestorList ancestor_list_with_url = ancestor_list; |
| 25 ancestor_list_with_url.insert(request.Url()); |
| 26 |
| 27 ModuleTreeLinker* fetcher = |
| 28 new ModuleTreeLinker(ancestor_list_with_url, modulator, registry, client); |
| 29 fetcher->FetchSelf(request, level); |
| 30 return fetcher; |
| 31 } |
| 32 |
| 33 ModuleTreeLinker::ModuleTreeLinker(const AncestorList& ancestor_list_with_url, |
| 34 Modulator* modulator, |
| 35 ModuleTreeLinkerRegistry* registry, |
| 36 ModuleTreeClient* client) |
| 37 : modulator_(modulator), |
| 38 registry_(registry), |
| 39 client_(client), |
| 40 ancestor_list_with_url_(ancestor_list_with_url) { |
| 41 CHECK(modulator); |
| 42 CHECK(registry); |
| 43 CHECK(client); |
| 44 } |
| 45 |
| 46 DEFINE_TRACE(ModuleTreeLinker) { |
| 47 visitor->Trace(modulator_); |
| 48 visitor->Trace(registry_); |
| 49 visitor->Trace(client_); |
| 50 visitor->Trace(module_script_); |
| 51 visitor->Trace(descendants_module_script_); |
| 52 visitor->Trace(dependency_clients_); |
| 53 SingleModuleClient::Trace(visitor); |
| 54 } |
| 55 |
| 56 #if DCHECK_IS_ON() |
| 57 const char* ModuleTreeLinker::StateToString(ModuleTreeLinker::State state) { |
| 58 switch (state) { |
| 59 case State::kInitial: |
| 60 return "Initial"; |
| 61 case State::kFetchingSelf: |
| 62 return "FetchingSelf"; |
| 63 case State::kFetchingDependencies: |
| 64 return "FetchingDependencies"; |
| 65 case State::kInstantiating: |
| 66 return "Instantiating"; |
| 67 case State::kFinished: |
| 68 return "Finished"; |
| 69 } |
| 70 NOTREACHED(); |
| 71 return ""; |
| 72 } |
| 73 #endif |
| 74 |
| 75 void ModuleTreeLinker::AdvanceState(State new_state) { |
| 76 #if DCHECK_IS_ON() |
| 77 RESOURCE_LOADING_DVLOG(1) |
| 78 << "ModuleTreeLinker[" << this << "]::advanceState(" |
| 79 << StateToString(state_) << " -> " << StateToString(new_state) << ")"; |
| 80 #endif |
| 81 |
| 82 switch (state_) { |
| 83 case State::kInitial: |
| 84 CHECK_EQ(num_incomplete_descendants_, 0u); |
| 85 CHECK_EQ(new_state, State::kFetchingSelf); |
| 86 break; |
| 87 case State::kFetchingSelf: |
| 88 CHECK_EQ(num_incomplete_descendants_, 0u); |
| 89 CHECK(new_state == State::kFetchingDependencies || |
| 90 (!descendants_module_script_ && new_state == State::kFinished)); |
| 91 break; |
| 92 case State::kFetchingDependencies: |
| 93 CHECK_EQ(new_state, State::kInstantiating); |
| 94 DCHECK(!num_incomplete_descendants_ || !descendants_module_script_) |
| 95 << num_incomplete_descendants_ |
| 96 << " outstanding descendant loads found, but the descendant module " |
| 97 "script load procedure unexpectedly finished with " |
| 98 << (!!descendants_module_script_ ? "success." : "failure."); |
| 99 break; |
| 100 case State::kInstantiating: |
| 101 CHECK(num_incomplete_descendants_ == 0u || !descendants_module_script_); |
| 102 CHECK_EQ(new_state, State::kFinished); |
| 103 break; |
| 104 case State::kFinished: |
| 105 NOTREACHED(); |
| 106 break; |
| 107 } |
| 108 |
| 109 state_ = new_state; |
| 110 |
| 111 if (state_ == State::kFinished) { |
| 112 registry_->ReleaseFinishedFetcher(this); |
| 113 |
| 114 // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-sc
ript-graph-fetching-procedure |
| 115 // Step 8. Asynchronously complete this algorithm with descendants result. |
| 116 client_->NotifyModuleTreeLoadFinished(descendants_module_script_); |
| 117 } |
| 118 } |
| 119 |
| 120 void ModuleTreeLinker::FetchSelf(const ModuleScriptFetchRequest& request, |
| 121 ModuleGraphLevel level) { |
| 122 // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-scri
pt-graph-fetching-procedure |
| 123 |
| 124 // Step 1. Fetch a single module script given url, fetch client settings |
| 125 // object, destination, cryptographic nonce, parser state, credentials mode, |
| 126 // module map settings object, referrer, and the top-level module fetch flag. |
| 127 // If the caller of this algorithm specified custom perform the fetch steps, |
| 128 // pass those along while fetching a single module script. |
| 129 AdvanceState(State::kFetchingSelf); |
| 130 modulator_->FetchSingle(request, level, this); |
| 131 |
| 132 // Step 2. Return from this algorithm, and run the following steps when |
| 133 // fetching a single module script asynchronously completes with result. |
| 134 // Note: Modulator::fetchSingle asynchronously notifies result to |
| 135 // ModuleTreeLinker::notifyModuleLoadFinished(). |
| 136 } |
| 137 |
| 138 void ModuleTreeLinker::NotifyModuleLoadFinished(ModuleScript* module_script) { |
| 139 // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-scri
pt-graph-fetching-procedure |
| 140 |
| 141 // Step 3. "If result is null, ..." |
| 142 if (!module_script) { |
| 143 // "asynchronously complete this algorithm with null and abort these steps." |
| 144 // Note: The return variable for "internal module script graph fetching |
| 145 // procedure" is descendants_module_script_ per Step 8. |
| 146 descendants_module_script_ = nullptr; |
| 147 AdvanceState(State::kFinished); |
| 148 return; |
| 149 } |
| 150 |
| 151 // Step 4. Otherwise, result is a module script. Fetch the descendants of |
| 152 // result given destination and an ancestor list obtained by appending url to |
| 153 // ancestor list. Wait for fetching the descendants of a module script to |
| 154 // asynchronously complete with descendants result before proceeding to the |
| 155 // next step. |
| 156 module_script_ = module_script; |
| 157 |
| 158 FetchDescendants(); |
| 159 |
| 160 // Note: Step 5- continues in instantiate() method, after |
| 161 // "fetch the descendants of a module script" procedure completes. |
| 162 } |
| 163 |
| 164 class ModuleTreeLinker::DependencyModuleClient |
| 165 : public GarbageCollectedFinalized< |
| 166 ModuleTreeLinker::DependencyModuleClient>, |
| 167 public ModuleTreeClient { |
| 168 USING_GARBAGE_COLLECTED_MIXIN(ModuleTreeLinker::DependencyModuleClient); |
| 169 |
| 170 public: |
| 171 static DependencyModuleClient* Create(ModuleTreeLinker* module_tree_linker) { |
| 172 return new DependencyModuleClient(module_tree_linker); |
| 173 } |
| 174 virtual ~DependencyModuleClient() = default; |
| 175 |
| 176 DEFINE_INLINE_TRACE() { |
| 177 visitor->Trace(module_tree_linker_); |
| 178 ModuleTreeClient::Trace(visitor); |
| 179 } |
| 180 |
| 181 private: |
| 182 DependencyModuleClient(ModuleTreeLinker* module_tree_linker) |
| 183 : module_tree_linker_(module_tree_linker) { |
| 184 CHECK(module_tree_linker); |
| 185 } |
| 186 |
| 187 // Implements ModuleTreeClient |
| 188 void NotifyModuleTreeLoadFinished(ModuleScript*) override; |
| 189 |
| 190 Member<ModuleTreeLinker> module_tree_linker_; |
| 191 }; |
| 192 |
| 193 void ModuleTreeLinker::FetchDescendants() { |
| 194 CHECK(module_script_); |
| 195 AdvanceState(State::kFetchingDependencies); |
| 196 |
| 197 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendant
s-of-a-module-script |
| 198 |
| 199 // Step 1. Let record be module script's module record. |
| 200 ScriptModule record = module_script_->Record(); |
| 201 |
| 202 // Step 2. If record.[[RequestedModules]] is empty, asynchronously complete |
| 203 // this algorithm with module script. |
| 204 Vector<String> module_requests = |
| 205 modulator_->ModuleRequestsFromScriptModule(record); |
| 206 |
| 207 // Step 3. Let urls be a new empty list. |
| 208 HashSet<KURL> urls; |
| 209 |
| 210 // Step 4. For each string requested of record.[[RequestedModules]], |
| 211 for (const auto& module_request : module_requests) { |
| 212 // Step 4.1. Let url be the result of resolving a module specifier given |
| 213 // module script and requested. |
| 214 KURL url = Modulator::ResolveModuleSpecifier(module_request, |
| 215 module_script_->BaseURL()); |
| 216 |
| 217 // Step 4.2. If the result is error: ... |
| 218 if (url.IsNull()) { |
| 219 // Let error be a new TypeError exception. |
| 220 // Report the exception error for module script. |
| 221 // TODO(kouhei): Implement the exception. |
| 222 |
| 223 // Abort this algorithm, and asynchronously complete it with null. |
| 224 // Note: The return variable for "internal module script graph fetching |
| 225 // procedure" is descendants_module_script_ per Step 8. |
| 226 descendants_module_script_ = nullptr; |
| 227 // Note: while we complete "fetch the descendants of a module script" |
| 228 // algorithm here, we still need to continue to the rest of the |
| 229 // steps in "internal module script graph fetching procedure" |
| 230 Instantiate(); |
| 231 return; |
| 232 } |
| 233 |
| 234 // Step 4.3. Otherwise, if ancestor list does not contain url, append url to |
| 235 // urls. |
| 236 |
| 237 // Modulator::resolveModuleSpecifier() impl must return either a valid url |
| 238 // or null. |
| 239 CHECK(url.IsValid()); |
| 240 if (!ancestor_list_with_url_.Contains(url)) |
| 241 urls.insert(url); |
| 242 } |
| 243 |
| 244 // Step 5. For each url in urls, perform the internal module script graph |
| 245 // fetching procedure given url, module script's credentials mode, module |
| 246 // script's cryptographic nonce, module script's parser state, destination, |
| 247 // module script's settings object, module script's settings object, ancestor |
| 248 // list, module script's base URL, and with the top-level module fetch flag |
| 249 // unset. If the caller of this algorithm specified custom perform the fetch |
| 250 // steps, pass those along while performing the internal module script graph |
| 251 // fetching procedure. |
| 252 // TODO(kouhei): handle "destination". |
| 253 CHECK_EQ(num_incomplete_descendants_, 0u); |
| 254 if (urls.IsEmpty()) { |
| 255 // Continue to instantiate() to process "internal module script graph |
| 256 // fetching procedure" Step 5-. |
| 257 descendants_module_script_ = module_script_; |
| 258 Instantiate(); |
| 259 return; |
| 260 } |
| 261 num_incomplete_descendants_ = urls.size(); |
| 262 for (const KURL& url : urls) { |
| 263 DependencyModuleClient* dependency_client = |
| 264 DependencyModuleClient::Create(this); |
| 265 dependency_clients_.insert(dependency_client); |
| 266 |
| 267 ModuleScriptFetchRequest request(url, module_script_->Nonce(), |
| 268 module_script_->ParserState(), |
| 269 module_script_->CredentialsMode(), |
| 270 module_script_->BaseURL().GetString()); |
| 271 modulator_->FetchTreeInternal(request, ancestor_list_with_url_, |
| 272 ModuleGraphLevel::kDependentModuleFetch, |
| 273 dependency_client); |
| 274 } |
| 275 |
| 276 // Asynchronously continue processing after notifyOneDescendantFinished() is |
| 277 // called m_numIncompleteDescendants times. |
| 278 CHECK_GT(num_incomplete_descendants_, 0u); |
| 279 } |
| 280 |
| 281 void ModuleTreeLinker::DependencyModuleClient::NotifyModuleTreeLoadFinished( |
| 282 ModuleScript* module_script) { |
| 283 DescendantLoad was_success = |
| 284 !!module_script ? DescendantLoad::kSuccess : DescendantLoad::kFailed; |
| 285 module_tree_linker_->NotifyOneDescendantFinished(was_success); |
| 286 } |
| 287 |
| 288 void ModuleTreeLinker::NotifyOneDescendantFinished(DescendantLoad was_success) { |
| 289 CHECK(!descendants_module_script_); |
| 290 |
| 291 if (state_ == State::kFinished) { |
| 292 // We may reach here if one of the descendant failed to load, and the other |
| 293 // descendants fetches were in flight. |
| 294 return; |
| 295 } |
| 296 CHECK_EQ(state_, State::kFetchingDependencies); |
| 297 |
| 298 CHECK_GT(num_incomplete_descendants_, 0u); |
| 299 --num_incomplete_descendants_; |
| 300 |
| 301 // TODO(kouhei): Potential room for optimization. Cancel inflight descendants |
| 302 // fetch if a descendant load failed. |
| 303 |
| 304 CHECK(module_script_); |
| 305 RESOURCE_LOADING_DVLOG(1) |
| 306 << "ModuleTreeLinker[" << this << "]::NotifyOneDescendantFinished with " |
| 307 << (was_success == DescendantLoad::kSuccess ? "success. " : "failure. ") |
| 308 << num_incomplete_descendants_ << " remaining descendants."; |
| 309 |
| 310 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendant
s-of-a-module-script |
| 311 // Step 5. "... If any of them asynchronously complete with null, then |
| 312 // asynchronously complete this algorithm with null" |
| 313 if (was_success == DescendantLoad::kFailed) { |
| 314 descendants_module_script_ = nullptr; |
| 315 // Note: while we complete "fetch the descendants of a module script" |
| 316 // algorithm here, we still need to continue to the rest of the steps |
| 317 // in "internal module script graph fetching procedure" |
| 318 Instantiate(); |
| 319 return; |
| 320 } |
| 321 |
| 322 // Step 5. "Wait for all of the internal module script graph fetching |
| 323 // procedure invocations to asynchronously complete..." |
| 324 if (!num_incomplete_descendants_) { |
| 325 descendants_module_script_ = module_script_; |
| 326 Instantiate(); |
| 327 return; |
| 328 } |
| 329 } |
| 330 |
| 331 void ModuleTreeLinker::Instantiate() { |
| 332 CHECK(module_script_); |
| 333 AdvanceState(State::kInstantiating); |
| 334 |
| 335 // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-scri
pt-graph-fetching-procedure |
| 336 |
| 337 // Step 5. Let record be result's module record. |
| 338 ScriptModule record = module_script_->Record(); |
| 339 |
| 340 // Step 6. Let instantiationStatus be record.ModuleDeclarationInstantiation(). |
| 341 // Note: The |error| variable corresponds to spec variable |
| 342 // "instantiationStatus". If |error| is empty, it indicates successful |
| 343 // completion. |
| 344 ScriptValue error = modulator_->InstantiateModule(record); |
| 345 |
| 346 // Step 7. For each module script script in result's uninstantiated inclusive |
| 347 // descendant module scripts, perform the following steps: |
| 348 HeapHashSet<Member<ModuleScript>> uninstantiated_set = |
| 349 UninstantiatedInclusiveDescendants(); |
| 350 for (const auto& descendant : uninstantiated_set) { |
| 351 if (!error.IsEmpty()) { |
| 352 // Step 7.1. If instantiationStatus is an abrupt completion, then set |
| 353 // script's instantiation state to "errored", its instantiation error to |
| 354 // instantiationStatus.[[Value]], and its module record to null. |
| 355 descendant->SetInstantiationErrorAndClearRecord(error); |
| 356 } else { |
| 357 // Step 7.2. Otherwise, set script's instantiation state to |
| 358 // "instantiated". |
| 359 descendant->SetInstantiationSuccess(); |
| 360 } |
| 361 } |
| 362 |
| 363 // Step 8. Asynchronously complete this algorithm with descendants result. |
| 364 AdvanceState(State::kFinished); |
| 365 } |
| 366 |
| 367 HeapHashSet<Member<ModuleScript>> |
| 368 ModuleTreeLinker::UninstantiatedInclusiveDescendants() { |
| 369 // https://html.spec.whatwg.org/multipage/webappapis.html#uninstantiated-inclu
sive-descendant-module-scripts |
| 370 // Step 1. Let moduleMap be script's settings object's module map. |
| 371 // Note: Modulator is our "settings object". |
| 372 // Note: We won't reference the ModuleMap directly here to aid testing. |
| 373 |
| 374 // Step 2. Let stack be the stack « script ». |
| 375 // TODO(kouhei): Make stack a HeapLinkedHashSet for O(1) lookups. |
| 376 HeapDeque<Member<ModuleScript>> stack; |
| 377 stack.push_front(module_script_); |
| 378 |
| 379 // Step 3. Let inclusive descendants be an empty set. |
| 380 // Note: We use unordered set here as the order is not observable from web |
| 381 // platform. |
| 382 // This is allowed per spec: https://infra.spec.whatwg.org/#sets |
| 383 HeapHashSet<Member<ModuleScript>> inclusive_descendants; |
| 384 |
| 385 // Step 4. While stack is not empty: |
| 386 while (!stack.IsEmpty()) { |
| 387 // Step 4.1. Let current the result of popping from stack. |
| 388 ModuleScript* current = stack.TakeFirst(); |
| 389 |
| 390 // Step 4.2. Assert: current is a module script (i.e., it is not "fetching" |
| 391 // or null). |
| 392 DCHECK(current); |
| 393 |
| 394 // Step 4.3. If inclusive descendants and stack both do not contain current, |
| 395 // then: |
| 396 if (inclusive_descendants.Contains(current)) |
| 397 continue; |
| 398 if (std::find(stack.begin(), stack.end(), current) != stack.end()) |
| 399 continue; |
| 400 |
| 401 // Step 4.3.1. Append current to inclusive descendants. |
| 402 inclusive_descendants.insert(current); |
| 403 |
| 404 // TODO(kouhei): This implementation is a direct transliteration of the |
| 405 // spec. Omit intermediate vectors at the least. |
| 406 |
| 407 // Step 4.3.2. Let child specifiers be the value of current's module |
| 408 // record's [[RequestedModules]] internal slot. |
| 409 Vector<String> child_specifiers = |
| 410 modulator_->ModuleRequestsFromScriptModule(current->Record()); |
| 411 // Step 4.3.3. Let child URLs be the list obtained by calling resolve a |
| 412 // module specifier once for each item of child specifiers, given current |
| 413 // and that item. Omit any failures. |
| 414 Vector<KURL> child_urls; |
| 415 for (const auto& child_specifier : child_specifiers) { |
| 416 KURL child_url = modulator_->ResolveModuleSpecifier(child_specifier, |
| 417 current->BaseURL()); |
| 418 if (child_url.IsValid()) |
| 419 child_urls.push_back(child_url); |
| 420 } |
| 421 // Step 4.3.4. Let child modules be the list obtained by getting each value |
| 422 // in moduleMap whose key is given by an item of child URLs. |
| 423 HeapVector<Member<ModuleScript>> child_modules; |
| 424 for (const auto& child_url : child_urls) { |
| 425 ModuleScript* module_script = |
| 426 modulator_->GetFetchedModuleScript(child_url); |
| 427 |
| 428 child_modules.push_back(module_script); |
| 429 } |
| 430 // Step 4.3.5. For each s of child modules: |
| 431 for (const auto& s : child_modules) { |
| 432 // Step 4.3.5.2. If s is null, continue. |
| 433 // Note: We do null check first, as Blink HashSet can't contain nullptr. |
| 434 // Step 4.3.5.3. Assert: s is a module script (i.e., it is not "fetching", |
| 435 // since by this point all child modules must have been fetched). Note: |
| 436 // GetFetchedModuleScript returns nullptr if "fetching" |
| 437 if (!s) |
| 438 continue; |
| 439 |
| 440 // Step 4.3.5.1. If inclusive descendants already contains s, continue. |
| 441 if (inclusive_descendants.Contains(s)) |
| 442 continue; |
| 443 |
| 444 // Step 4.3.5.4. Push s onto satck. |
| 445 stack.push_front(s); |
| 446 } |
| 447 } |
| 448 |
| 449 // Step 5. Return a set containing all items of inclusive descendants whose |
| 450 // instantiation state is "uninstantiated". |
| 451 HeapHashSet<Member<ModuleScript>> uninstantiated_set; |
| 452 for (const auto& script : inclusive_descendants) { |
| 453 if (script->InstantiationState() == |
| 454 ModuleInstantiationState::kUninstantiated) |
| 455 uninstantiated_set.insert(script); |
| 456 } |
| 457 return uninstantiated_set; |
| 458 } |
| 459 |
| 460 } // namespace blink |
OLD | NEW |