OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 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 "config.h" | |
6 #include "bindings/core/v8/ScriptStreamer.h" | |
7 | |
8 #include "bindings/core/v8/ScriptStreamerThread.h" | |
9 #include "bindings/core/v8/V8ScriptRunner.h" | |
10 #include "core/dom/Document.h" | |
11 #include "core/dom/Element.h" | |
12 #include "core/dom/PendingScript.h" | |
13 #include "core/fetch/ScriptResource.h" | |
14 #include "core/frame/Settings.h" | |
15 #include "platform/SharedBuffer.h" | |
16 #include "wtf/MainThread.h" | |
17 #include "wtf/text/TextEncodingRegistry.h" | |
18 | |
19 namespace blink { | |
20 | |
21 // SourceStream implements the streaming interface towards V8. The main | |
22 // functionality is preparing the data to give to V8 on main thread, and | |
23 // actually giving the data (via GetMoreData which is called on a background | |
24 // thread). | |
25 class SourceStream : public v8::ScriptCompiler::ExternalSourceStream { | |
26 public: | |
jochen (gone - plz use gerrit)
2014/09/11 12:45:08
wtf_make_noncopyable
marja
2014/09/15 17:45:26
Done.
| |
27 SourceStream(ScriptStreamer* streamer) | |
28 : v8::ScriptCompiler::ExternalSourceStream() | |
29 , m_streamer(streamer) | |
30 , m_backgroundThreadWaitingForData(false) | |
31 , m_dataPointer(0) | |
32 , m_dataLength(0) | |
33 , m_dataPosition(0) | |
34 , m_loadingFinished(false) | |
35 , m_cancelled(false) | |
36 , m_allDataReturned(false) { } | |
37 | |
38 virtual ~SourceStream() { } | |
39 | |
40 // Called by V8 on a background thread. Should block until we can return | |
41 // some data. | |
42 virtual size_t GetMoreData(const uint8_t** src) OVERRIDE | |
43 { | |
44 ASSERT(!isMainThread()); | |
45 MutexLocker locker(m_mutex); | |
46 if (m_cancelled || m_allDataReturned) { | |
47 return 0; | |
48 } | |
49 // The main thread might be modifying the data buffer of the Resource, | |
50 // so we cannot read it here. Post a task to the main thread and wait | |
51 // for it to complete. | |
52 m_backgroundThreadWaitingForData = true; | |
53 m_dataPointer = src; | |
54 m_dataLength = 0; | |
55 callOnMainThread(WTF::bind(&SourceStream::prepareDataOnMainThread, this) ); | |
56 m_condition.wait(m_mutex); | |
57 if (m_cancelled || m_allDataReturned) { | |
58 return 0; | |
59 } | |
60 return m_dataLength; | |
61 } | |
62 | |
63 void didFinishLoading() | |
64 { | |
65 ASSERT(isMainThread()); | |
66 if (m_streamer->resource()->errorOccurred()) { | |
67 // The background thread might be waiting for data. Wake it up. V8 | |
68 // will perceive this as EOS and call the completion callback. At | |
69 // that point we will resume the normal "script loaded" code path | |
70 // (see HTMLScriptRunner::notifyFinished which doesn't differentiate | |
71 // between scripts which loaded successfully and scripts which | |
72 // failed to load). | |
73 cancel(); | |
74 return; | |
75 } | |
76 { | |
77 MutexLocker locker(m_mutex); | |
78 m_loadingFinished = true; | |
79 if (!m_backgroundThreadWaitingForData) { | |
80 return; | |
81 } | |
82 } | |
83 prepareDataOnMainThread(); | |
84 } | |
85 | |
86 void didReceiveData() | |
87 { | |
88 ASSERT(isMainThread()); | |
89 prepareDataOnMainThread(); | |
90 } | |
91 | |
92 void cancel() | |
93 { | |
94 ASSERT(isMainThread()); | |
95 // The script is no longer needed by the upper layers. Stop streaming | |
96 // it. The next time GetMoreData is called (or woken up), it will return | |
97 // 0, which will be interpreted as EOS by V8 and the parsing will | |
98 // fail. ScriptStreamer::streamingComplete will be called, and at that | |
99 // point we will release the references to SourceStream. | |
100 MutexLocker locker(m_mutex); | |
101 m_cancelled = true; | |
102 m_backgroundThreadWaitingForData = false; | |
103 m_condition.signal(); | |
104 } | |
105 | |
106 private: | |
107 void prepareDataOnMainThread() | |
108 { | |
109 ASSERT(isMainThread()); | |
110 MutexLocker locker(m_mutex); | |
111 if (!m_backgroundThreadWaitingForData) { | |
112 // It's possible that the background thread is not waiting. This | |
113 // happens when 1) data arrives but V8 hasn't requested data yet, 2) | |
114 // we have previously scheduled prepareDataOnMainThread, but some | |
115 // other event (load cancelled or finished) already retrieved the | |
116 // data and set m_backgroundThreadWaitingForData to false. | |
117 return; | |
118 } | |
119 // The Resource must still be alive; otherwise we should've cancelled | |
120 // the streaming (if we have cancelled, the background thread is not | |
121 // waiting). | |
122 ASSERT(m_streamer->resource()); | |
123 | |
124 if (m_streamer->resource()->cachedMetadata(V8ScriptRunner::tagForCodeCac he())) { | |
125 // The resource has a code cache, so it's unnecessary to stream and | |
126 // parse the code. Cancel the streaming and resume the non-streaming | |
127 // code path. | |
128 m_streamer->suppressStreaming(); | |
129 m_cancelled = true; | |
130 m_backgroundThreadWaitingForData = false; | |
131 m_condition.signal(); | |
132 return; | |
133 } | |
134 | |
135 if (!m_resourceBuffer) { | |
136 // We don't have a buffer yet. Try to get it from the resource. | |
137 SharedBuffer* buffer = m_streamer->resource()->resourceBuffer(); | |
138 if (!buffer) { | |
139 // The Resource hasn't created its SharedBuffer yet. At some | |
140 // point (at latest when the script loading finishes) this | |
141 // function will be called again and we will have data. | |
142 if (m_loadingFinished) { | |
143 // This happens only in special cases (e.g., involving empty | |
144 // scripts). | |
145 m_allDataReturned = true; | |
146 m_backgroundThreadWaitingForData = false; | |
147 m_condition.signal(); | |
148 } | |
149 return; | |
150 } | |
151 m_resourceBuffer = RefPtr<SharedBuffer>(buffer); | |
152 } | |
153 | |
154 // Get as much data from the ResourceBuffer as we can. | |
155 const char* data = 0; | |
156 Vector<const char*> chunks; | |
157 Vector<unsigned> chunkLengths; | |
158 while (unsigned length = m_resourceBuffer->getSomeData(data, m_dataPosit ion)) { | |
159 // FIXME: Here we can limit based on the total length, if it turns | |
160 // out that we don't want to give all the data we have (memory | |
161 // vs. speed). | |
162 chunks.append(data); | |
163 chunkLengths.append(length); | |
164 m_dataLength += length; | |
165 m_dataPosition += length; | |
166 } | |
167 // Copy the data chunks into a new buffer, since we're going to give the | |
168 // data to a background thread. | |
169 if (m_dataLength > 0) { | |
170 uint8_t* copiedData = new uint8_t[m_dataLength]; | |
171 unsigned offset = 0; | |
172 for (size_t i = 0; i < chunks.size(); ++i) { | |
173 memcpy(copiedData + offset, chunks[i], chunkLengths[i]); | |
174 offset += chunkLengths[i]; | |
175 } | |
176 *m_dataPointer = copiedData; | |
177 m_backgroundThreadWaitingForData = false; | |
178 m_condition.signal(); | |
179 } else if (m_loadingFinished) { | |
180 // We need to return data length 0 to V8 to signal that the data | |
181 // ends. | |
182 m_allDataReturned = true; | |
183 m_backgroundThreadWaitingForData = false; | |
184 m_condition.signal(); | |
185 } | |
186 // Otherwise, we had scheduled prepareDataOnMainThread but we didn't | |
187 // have any data to return. It will be scheduled again when the data | |
188 // arrives. | |
189 } | |
190 | |
191 ScriptStreamer* m_streamer; | |
192 | |
193 // For coordinating between the main thread and background thread tasks. | |
194 // Guarded by m_mutex. | |
195 bool m_backgroundThreadWaitingForData; | |
196 const uint8_t** m_dataPointer; | |
197 unsigned m_dataLength; | |
198 unsigned m_dataPosition; | |
199 bool m_loadingFinished; | |
200 bool m_cancelled; | |
201 bool m_allDataReturned; | |
202 RefPtr<SharedBuffer> m_resourceBuffer; // Only used by the main thread. | |
203 | |
204 ThreadCondition m_condition; | |
205 Mutex m_mutex; | |
206 }; | |
207 | |
208 | |
209 bool ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, S criptState* scriptState) | |
210 { | |
211 ASSERT(isMainThread()); | |
212 if (!settings || !settings->v8ScriptStreamingEnabled()) { | |
213 return false; | |
214 } | |
215 if (ScriptStreamerThread::shared()->isRunningTask()) { | |
216 // At the moment we only have one thread for running the tasks. A new | |
217 // task shouldn't be queued before the running task completes, because | |
218 // the running task can block and wait for data from the network. At the | |
219 // moment we are only streaming parser blocking scripts, but this code | |
220 // can still be hit when multiple frames are loading simultaneously. | |
221 return false; | |
222 } | |
223 ScriptResource* resource = script.resource(); | |
224 if (!resource->url().protocolIsInHTTPFamily()) { | |
225 return false; | |
226 } | |
227 if (resource->resourceToRevalidate()) { | |
228 // This happens e.g., during reloads. We're actually not going to load | |
229 // the current Resource of the PendingScript but switch to another | |
230 // Resource -> don't stream. | |
231 return false; | |
232 } | |
233 // We cannot filter out short scripts, even if we wait for the HTTP headers | |
234 // to arrive. In general, the web servers don't seem to send the | |
235 // Content-Length HTTP header for scripts. | |
236 | |
237 const char* encodingName = WTF::atomicCanonicalTextEncodingName(resource->en coding()); | |
238 | |
239 // Here's a list of encodings we can use for streaming. These are | |
240 // the canonical names. | |
241 v8::ScriptCompiler::StreamedSource::Encoding encoding; | |
242 if (strcmp(encodingName, "windows-1252") == 0 | |
243 || strcmp(encodingName, "ISO-8859-1") == 0 | |
244 || strcmp(encodingName, "US-ASCII") == 0) { | |
245 encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE; | |
246 } else if (strcmp(encodingName, "UTF-8") == 0) { | |
247 encoding = v8::ScriptCompiler::StreamedSource::UTF8; | |
248 } else { | |
249 // We don't stream other encodings; especially we don't stream two byte | |
250 // scripts to avoid the handling of byte order marks. Most scripts are | |
251 // Latin1 or UTF-8 anyway, so this should be enough for most real world | |
252 // purposes. | |
253 return false; | |
254 } | |
255 | |
256 if (scriptState->contextIsValid()) { | |
257 return false; | |
258 } | |
259 ScriptState::Scope scope(scriptState); | |
260 | |
261 // The Resource might go out of scope if the script is no longer needed. We | |
262 // will soon call PendingScript::setStreamer, which makes the PendingScript | |
263 // notify the ScriptStreamer when it is destroyed. | |
264 RefPtr<ScriptStreamer> streamer = adoptRef(new ScriptStreamer(resource, enco ding)); | |
265 | |
266 // Decide what kind of cached data we should produce while streaming. By | |
267 // default, we generate the parser cache for streamed scripts, to emulate | |
268 // the non-streaming behavior (seeV8ScriptRunner::compileScript). | |
269 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProd uceParserCache; | |
270 if (settings->v8CacheOptions() == V8CacheOptionsCode) { | |
271 compileOption = v8::ScriptCompiler::kProduceCodeCache; | |
272 } | |
273 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCom piler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), v8::S criptCompiler::kProduceParserCache); | |
274 if (scriptStreamingTask) { | |
275 ScriptStreamingTask* task = new ScriptStreamingTask(scriptStreamingTask, streamer.get()); | |
276 streamer->ref(); | |
jochen (gone - plz use gerrit)
2014/09/11 12:45:08
add a comment that this is balanced in streamingCo
marja
2014/09/15 17:45:26
Done.
| |
277 script.setStreamer(streamer.release()); | |
278 ScriptStreamerThread::shared()->postTask(task); | |
279 return true; | |
280 } | |
281 // Otherwise, V8 cannot stream the script. | |
282 return false; | |
283 } | |
284 | |
285 void ScriptStreamer::streamingComplete() | |
286 { | |
287 ASSERT(isMainThread()); | |
288 // It's possible that the corresponding Resource was deleted before V8 | |
289 // finished streaming. In that case, the data or the notification is not | |
290 // needed. | |
291 if (m_detached) { | |
292 deref(); | |
293 return; | |
294 } | |
295 | |
296 // We have now streamed the whole script to V8 and it has parsed the | |
297 // script. We're ready for the next step: compiling and executing the | |
298 // script. | |
299 m_parsingFinished = true; | |
300 | |
301 notifyFinishedToClient(); | |
302 | |
303 // The background thread no longer holds an implicit reference. | |
304 deref(); | |
305 } | |
306 | |
307 void ScriptStreamer::cancel() | |
308 { | |
309 ASSERT(isMainThread()); | |
310 // The upper layer doesn't need the script any more, but streaming might | |
311 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets | |
312 // the control the next time. It can also be that V8 has already completed | |
313 // its operations and streamingComplete will be called soon. | |
314 m_detached = true; | |
315 m_resource = 0; | |
316 m_stream->cancel(); | |
317 } | |
318 | |
319 void ScriptStreamer::notifyAppendData(ScriptResource* resource) | |
320 { | |
321 ASSERT(isMainThread()); | |
322 ASSERT(m_resource == resource); | |
323 m_stream->didReceiveData(); | |
324 } | |
325 | |
326 void ScriptStreamer::notifyFinished(Resource* resource) | |
327 { | |
328 ASSERT(isMainThread()); | |
329 ASSERT(m_resource == resource); | |
330 m_stream->didFinishLoading(); | |
331 m_loadingFinished = true; | |
332 notifyFinishedToClient(); | |
333 } | |
334 | |
335 ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::Str eamedSource::Encoding encoding) | |
336 : m_resource(resource) | |
337 , m_detached(false) | |
338 , m_stream(new SourceStream(this)) | |
339 , m_source(m_stream, encoding) // m_source takes ownership of m_stream. | |
340 , m_client(0) | |
341 , m_loadingFinished(false) | |
342 , m_parsingFinished(false) | |
343 , m_streamingSuppressed(false) | |
344 { | |
345 } | |
346 | |
347 void ScriptStreamer::notifyFinishedToClient() | |
348 { | |
349 ASSERT(isMainThread()); | |
350 // Usually, the loading will be finished first, and V8 will still need some | |
351 // time to catch up. But the other way is possible too: if V8 detects a | |
352 // parse error, the V8 side can complete before loading has finished. Send | |
353 // the notification after both loading and V8 side operations have | |
354 // completed. Here we also check that we have a client: it can happen that a | |
355 // function calling notifyFinishedToClient was already scheduled in the task | |
356 // queue and the upper layer decided that it's not interested in the script | |
357 // and called removeClient. | |
358 if (m_loadingFinished && m_parsingFinished && m_client) { | |
jochen (gone - plz use gerrit)
2014/09/11 12:45:08
single-line if bodies shouldn't have { } - here an
marja
2014/09/15 17:45:26
Done; but kept the { } in cases where comments mak
| |
359 m_client->notifyFinished(m_resource); | |
360 } | |
361 } | |
362 | |
363 } // namespace blink | |
OLD | NEW |