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