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