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()) { | |
hiroshige
2017/03/31 06:21:47
According to the spec, Lines 319--325 is not neede
| |
320 m_moduleScript->setInstantiationError(error.isolate(), error.v8Value()); | |
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.isolate(), error.v8Value()); | |
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.push_front(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->getFetchedModuleScript(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.push_front(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 |