OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 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/dom/ModuleMap.h" | |
6 | |
7 #include "bindings/core/v8/ScriptController.h" | |
8 #include "bindings/core/v8/V8PerIsolateData.h" | |
9 #include "core/fetch/ResourceFetcher.h" | |
10 #include "platform/network/mime/MIMETypeRegistry.h" | |
11 #include "wtf/AutoReset.h" | |
12 #include <v8.h> | |
13 | |
14 namespace blink { | |
15 | |
16 ModuleLoaderClient::~ModuleLoaderClient() {} | |
17 | |
18 void ModuleLoaderClient::catchUpToLatestStateIfNeeded( | |
19 ModuleLoaderState latestState) { | |
20 if (m_state == ModuleLoaderState::Finished) | |
21 return; | |
22 if (latestState == ModuleLoaderState::Finished) { | |
23 notifyFinished(); | |
24 m_state = latestState; | |
25 } | |
26 } | |
27 | |
28 ModuleLoader::ModuleLoader(const KURL& url, ScriptController* scriptController) | |
29 : m_url(url), m_scriptController(scriptController) {} | |
30 | |
31 ModuleLoader::~ModuleLoader() {} | |
32 | |
33 void ModuleLoader::advanceState(ModuleLoaderState newState) { | |
34 switch (m_state) { | |
35 case ModuleLoaderState::Initial: | |
36 DCHECK_EQ(newState, ModuleLoaderState::Fetching); | |
37 break; | |
38 case ModuleLoaderState::Fetching: | |
39 DCHECK_EQ(newState, ModuleLoaderState::Finished); | |
40 break; | |
41 case ModuleLoaderState::Finished: | |
42 default: | |
43 NOTREACHED(); | |
44 } | |
45 | |
46 m_state = newState; | |
47 | |
48 notifyAndFlushPendingClients(); | |
49 } | |
50 | |
51 ModuleLoader* ModuleLoader::fetch(const KURL& url, | |
52 ScriptController* scriptController, | |
53 ResourceFetcher* fetcher) { | |
54 ModuleLoader* loader = new ModuleLoader(url, scriptController); | |
55 loader->fetchInternal(fetcher); | |
56 return loader; | |
57 } | |
58 | |
59 void ModuleLoader::fetchInternal(ResourceFetcher* fetcher) { | |
60 // Step 4. Set moduleMap[url] to "fetching". | |
61 advanceState(ModuleLoaderState::Fetching); | |
62 | |
63 // Step 5. Let request be a new request whose url is url, destination is | |
64 // destination, type is "script", mode is "cors", credentials mode is | |
65 // credentials mode, cryptographic nonce metadata is cryptographic nonce, | |
66 // parser metadata is parser state, referrer is referrer, and client is | |
67 // fetch | |
68 // client settings object. | |
69 FetchRequest request(ResourceRequest(m_url), "module"); | |
70 // request.setDefer(FetchRequest::LazyLoad); //? always async -> defer? | |
71 Member<ScriptResource> resource = ScriptResource::fetch(request, fetcher); | |
72 setResource(resource); | |
73 | |
74 // Step 6. If the caller specified custom steps to perform the fetch, | |
75 // perform | |
76 // them on request, setting the is top-level flag if the top-level module | |
77 // fetch flag is set. Return from this algorithm, and when the custom | |
78 // perform | |
79 // the fetch steps complete with response response, run the remaining steps. | |
80 // Otherwise, fetch request. Return from this algorithm, and run the | |
81 // remaining | |
82 // steps as part of the fetch's process response for the response response. | |
83 // Note: response is always CORS-same-origin. | |
84 | |
85 // TODO(kouhei): I don't know what Step 6 means. ScriptLoader seems to | |
86 // ignore | |
87 // the step? | |
88 } | |
89 | |
90 bool ModuleLoader::wasModuleLoadSuccessful(Resource* resource) { | |
91 // Implements conditions in Step 7 of | |
92 // https://html.spec.whatwg.org/#fetch-a-single-module-script | |
93 | |
94 // - response's type is "error" | |
95 if (resource->errorOccurred()) | |
96 return false; | |
97 | |
98 const auto& response = resource->response(); | |
99 // - response's status is not an ok status | |
100 if (response.isHTTP() && | |
101 (response.httpStatusCode() < 200 || response.httpStatusCode() >= 300)) { | |
102 return false; | |
103 } | |
104 | |
105 // The result of extracting a MIME type from response's header list | |
106 // (ignoring parameters) is not a JavaScript MIME type | |
107 // Note: For historical reasons, fetching a classic script does not include | |
108 // MIME type checking. In contrast, module scripts will fail to load if they | |
109 // are not of a correct MIME type. | |
110 if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(response.mimeType())) | |
111 return false; | |
112 | |
113 return true; | |
114 } | |
115 | |
116 void ModuleLoader::notifyFinished(Resource*) { | |
117 printf("notifyFinished!\n"); | |
118 | |
119 // Note: "conditions" referred in Step 7 is implemented in | |
120 // wasModuleLoadSuccessful(). | |
121 // Step 7. If any of the following conditions are met, set moduleMap[url] to | |
122 // null, asynchronously complete this algorithm with null, and abort these | |
123 // steps. | |
124 if (!wasModuleLoadSuccessful(resource())) { | |
125 printf("notifyFinished but load was not successful\n"); | |
126 | |
127 m_wasLoadSuccessful = false; | |
128 advanceState(ModuleLoaderState::Finished); | |
129 setResource(nullptr); | |
130 return; | |
131 } | |
132 | |
133 // Step 8. Let source text be the result of UTF-8 decoding response's body. | |
134 String script = resource()->script(); | |
135 printf("fetched script: %s\n", script.utf8().data()); | |
136 | |
137 // Step 9. Let module script be the result of creating a module script given | |
138 // source text, module map settings object, response's url, cryptographic | |
139 // nonce, parser state, and credentials mode. | |
140 | |
141 // TODO(kouhei): post a task to compile module? | |
142 m_scriptModule = m_scriptController->compileModule(script, m_url.getString()); | |
143 m_wasLoadSuccessful = !m_scriptModule.isNull(); | |
144 advanceState(ModuleLoaderState::Finished); | |
145 setResource(nullptr); | |
146 } | |
147 | |
148 void ModuleLoader::addClient(ModuleLoaderClient* newClient) { | |
149 DCHECK(!m_pendingClients.contains(newClient)); | |
150 DCHECK(!m_clients.contains(newClient)); | |
151 newClient->setLoader(this); | |
152 m_pendingClients.add(newClient); | |
153 | |
154 notifyAndFlushPendingClients(); | |
155 } | |
156 | |
157 void ModuleLoader::notifyAndFlushPendingClients() { | |
158 if (m_isFlushingPendingClients) { | |
159 // This notifyAndFlushPendingClients call was reentrant. | |
160 // Defer to first notifyAndFlushPendingClients to do the job. | |
161 return; | |
162 } | |
163 | |
164 AutoReset<bool> forbidClientModificationScope(&m_isFlushingPendingClients, | |
165 true); | |
166 | |
167 if (m_lastClientNotifiedState != m_state) { | |
168 // if m_state has changed since last update, put all m_clients back into | |
169 // m_pendingClients so that they catchUpToLatestStateIfNeeded() | |
170 m_pendingClients.swap(m_clients); | |
171 while (!m_clients.isEmpty()) { | |
172 m_pendingClients.add(m_clients.takeAny()); | |
173 } | |
174 | |
175 m_lastClientNotifiedState = m_state; | |
176 } | |
177 | |
178 while (!m_pendingClients.isEmpty()) { | |
179 // Iterate on copy as m_pendingClients may be added in the notification | |
180 // loop. | |
181 HeapHashSet<Member<ModuleLoaderClient>> clientsToCatchUp; | |
182 clientsToCatchUp.swap(m_pendingClients); | |
183 for (const auto& client : clientsToCatchUp) { | |
184 client->catchUpToLatestStateIfNeeded(m_state); | |
185 } | |
186 | |
187 // Caught up clients do not have any notifications to receive after the | |
188 // load is finished. | |
189 if (hasFinished()) | |
190 continue; | |
191 | |
192 while (!clientsToCatchUp.isEmpty()) | |
193 m_clients.add(clientsToCatchUp.takeAny()); | |
194 } | |
195 } | |
196 | |
197 DEFINE_TRACE(ModuleLoader) { | |
198 visitor->trace(m_scriptController); | |
199 visitor->trace(m_pendingClients); | |
200 visitor->trace(m_clients); | |
201 ResourceOwner<ScriptResource>::trace(visitor); | |
202 } | |
203 | |
204 ModuleMap::ModuleMap(ScriptController* scriptController, | |
205 ResourceFetcher* fetcher) | |
206 : m_scriptController(scriptController), m_fetcher(fetcher) { | |
207 DCHECK(m_scriptController); | |
208 DCHECK(m_fetcher); | |
209 } | |
210 | |
211 void ModuleMap::fetchSingle(const KURL& url, ModuleLoaderClient* client) { | |
212 printf("ModuleMap::fetch(%s)\n", url.getString().utf8().data()); | |
213 | |
214 MapImpl::AddResult result = m_map.set(url, nullptr); | |
215 Member<ModuleLoader>& loaderEntry = result.storedValue->value; | |
216 if (!result.isNewEntry) { | |
217 printf("not new entry\n"); | |
218 | |
219 // - If moduleMap[url] is "fetching", wait (in parallel) until that | |
220 // loaderEntry's value changes, then proceed to the next step. | |
221 // - If moduleMap[url] exists, asynchronously complete this algorithm | |
222 // with moduleMap[url], and abort these steps. | |
223 loaderEntry->addClient(client); | |
dominicc (has gone to gerrit)
2016/12/13 07:45:29
I think this design has to be careful with the tra
kouhei (in TOK)
2016/12/15 04:56:27
I'd like to discuss this offline (probably need a
| |
224 return; | |
225 } | |
226 | |
227 ModuleLoader* loader = | |
228 ModuleLoader::fetch(url, m_scriptController.get(), m_fetcher.get()); | |
229 | |
230 // Step 10. Set moduleMap[url] to module script, and asynchronously complete | |
231 // this algorithm with module script. | |
232 loaderEntry = loader; | |
233 loaderEntry->addClient(client); | |
234 } | |
235 | |
236 DEFINE_TRACE(ModuleMap) { | |
237 visitor->trace(m_map); | |
238 visitor->trace(m_scriptController); | |
239 visitor->trace(m_fetcher); | |
240 } | |
241 | |
242 } // namespace blink | |
OLD | NEW |