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 |