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."); | |
hiroshige
2017/04/19 22:24:08
Here |descendants_module_script_| is always true w
kouhei (in TOK)
2017/04/24 01:06:21
num_incomplete_descendants_ may be 0?
hiroshige
2017/04/24 17:56:51
Er, I meant |(!!descendants_module_script_ ? "succ
| |
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(); | |
hiroshige
2017/04/19 22:24:08
As we test state transition by CHECK()s in other c
kouhei (in TOK)
2017/04/24 01:06:22
I'd leave this for now.
| |
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 | |
hiroshige
2017/04/19 22:24:07
nit: FetchSingle().
kouhei (in TOK)
2017/04/24 01:06:21
Done.
| |
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. | |
hiroshige
2017/04/19 22:24:07
Lines 144-145 should refer to our implementation (
kouhei (in TOK)
2017/04/24 01:06:22
Done.
| |
146 DCHECK(!descendants_module_script_); | |
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 | |
kinuko
2017/04/19 06:11:33
nit: instantiate() -> Instantiate()
kouhei (in TOK)
2017/04/24 01:06:22
Done.
| |
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) | |
kinuko
2017/04/19 06:11:33
nit: explicit
kouhei (in TOK)
2017/04/24 01:06:22
Done.
| |
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. | |
hiroshige
2017/04/19 22:24:07
The spec says "list" while the implementation is "
kouhei (in TOK)
2017/04/24 01:06:22
We don't actually require HashSet<> here, so chang
| |
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 DCHECK(!descendants_module_script_); | |
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()) { | |
hiroshige
2017/04/19 22:24:07
Actually, this if block implements Step 2 above.
(
kouhei (in TOK)
2017/04/24 01:06:22
Done.
hiroshige
2017/04/26 01:23:58
Sorry I was wrong:
|urls| can be empty here if the
| |
255 // Continue to instantiate() to process "internal module script graph | |
kinuko
2017/04/19 06:11:33
nit: instantiate() -> Instantiate()
kouhei (in TOK)
2017/04/24 01:06:22
Done.
| |
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; | |
hiroshige
2017/04/19 22:24:07
nit: do we need |!!|?
kouhei (in TOK)
2017/04/24 01:06:22
Removed.
This is the second time I got the comment
| |
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 DCHECK(!descendants_module_script_); | |
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..." | |
hiroshige
2017/04/19 22:24:07
Add "Otherwise, asynchronously complete this algor
kouhei (in TOK)
2017/04/24 01:06:22
Done.
| |
324 if (!num_incomplete_descendants_) { | |
325 descendants_module_script_ = module_script_; | |
326 Instantiate(); | |
327 return; | |
kinuko
2017/04/19 06:11:33
Not needed?
kouhei (in TOK)
2017/04/24 01:06:22
Done.
| |
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 ». | |
hiroshige
2017/04/19 22:24:07
nit: I prefer avoiding non-ASCII characters in the
kouhei (in TOK)
2017/04/24 01:06:22
Done.
| |
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: | |
hiroshige
2017/04/19 22:24:07
nit: line break before "Note:".
kouhei (in TOK)
2017/04/24 01:06:22
Done.
| |
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. | |
kinuko
2017/04/19 06:11:33
satck -> stack
kouhei (in TOK)
2017/04/24 01:06:22
Done.
| |
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 |