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