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 "public/platform/Platform.h"
17 #include "wtf/MainThread.h"
18 #include "wtf/text/TextEncodingRegistry.h"
19
20 namespace blink {
21
22 // For passing data between the main thread (producer) and the streamer thread
23 // (consumer). The main thread prepares the data (copies it from Resource) and
24 // the streamer thread feeds it to V8.
25 class SourceStreamDataQueue {
26 WTF_MAKE_NONCOPYABLE(SourceStreamDataQueue);
27 public:
SourceStreamDataQueue()28 SourceStreamDataQueue()
29 : m_finished(false) { }
30
~SourceStreamDataQueue()31 ~SourceStreamDataQueue()
32 {
33 while (!m_data.isEmpty()) {
34 std::pair<const uint8_t*, size_t> next_data = m_data.takeFirst();
35 delete[] next_data.first;
36 }
37 }
38
produce(const uint8_t * data,size_t length)39 void produce(const uint8_t* data, size_t length)
40 {
41 MutexLocker locker(m_mutex);
42 m_data.append(std::make_pair(data, length));
43 m_haveData.signal();
44 }
45
finish()46 void finish()
47 {
48 MutexLocker locker(m_mutex);
49 m_finished = true;
50 m_haveData.signal();
51 }
52
consume(const uint8_t ** data,size_t * length)53 void consume(const uint8_t** data, size_t* length)
54 {
55 MutexLocker locker(m_mutex);
56 while (!tryGetData(data, length))
57 m_haveData.wait(m_mutex);
58 }
59
60 private:
tryGetData(const uint8_t ** data,size_t * length)61 bool tryGetData(const uint8_t** data, size_t* length)
62 {
63 if (!m_data.isEmpty()) {
64 std::pair<const uint8_t*, size_t> next_data = m_data.takeFirst();
65 *data = next_data.first;
66 *length = next_data.second;
67 return true;
68 }
69 if (m_finished) {
70 *length = 0;
71 return true;
72 }
73 return false;
74 }
75
76 WTF::Deque<std::pair<const uint8_t*, size_t> > m_data;
77 bool m_finished;
78 Mutex m_mutex;
79 ThreadCondition m_haveData;
80 };
81
82
83 // SourceStream implements the streaming interface towards V8. The main
84 // functionality is preparing the data to give to V8 on main thread, and
85 // actually giving the data (via GetMoreData which is called on a background
86 // thread).
87 class SourceStream : public v8::ScriptCompiler::ExternalSourceStream {
88 WTF_MAKE_NONCOPYABLE(SourceStream);
89 public:
SourceStream(ScriptStreamer * streamer)90 SourceStream(ScriptStreamer* streamer)
91 : v8::ScriptCompiler::ExternalSourceStream()
92 , m_streamer(streamer)
93 , m_cancelled(false)
94 , m_dataPosition(0) { }
95
~SourceStream()96 virtual ~SourceStream() { }
97
98 // Called by V8 on a background thread. Should block until we can return
99 // some data.
GetMoreData(const uint8_t ** src)100 virtual size_t GetMoreData(const uint8_t** src) OVERRIDE
101 {
102 ASSERT(!isMainThread());
103 {
104 MutexLocker locker(m_mutex);
105 if (m_cancelled)
106 return 0;
107 }
108 size_t length = 0;
109 // This will wait until there is data.
110 m_dataQueue.consume(src, &length);
111 {
112 MutexLocker locker(m_mutex);
113 if (m_cancelled)
114 return 0;
115 }
116 return length;
117 }
118
didFinishLoading()119 void didFinishLoading()
120 {
121 ASSERT(isMainThread());
122 m_dataQueue.finish();
123 }
124
didReceiveData()125 void didReceiveData()
126 {
127 ASSERT(isMainThread());
128 prepareDataOnMainThread();
129 }
130
cancel()131 void cancel()
132 {
133 ASSERT(isMainThread());
134 // The script is no longer needed by the upper layers. Stop streaming
135 // it. The next time GetMoreData is called (or woken up), it will return
136 // 0, which will be interpreted as EOS by V8 and the parsing will
137 // fail. ScriptStreamer::streamingComplete will be called, and at that
138 // point we will release the references to SourceStream.
139 {
140 MutexLocker locker(m_mutex);
141 m_cancelled = true;
142 }
143 m_dataQueue.finish();
144 }
145
146 private:
prepareDataOnMainThread()147 void prepareDataOnMainThread()
148 {
149 ASSERT(isMainThread());
150 // The Resource must still be alive; otherwise we should've cancelled
151 // the streaming (if we have cancelled, the background thread is not
152 // waiting).
153 ASSERT(m_streamer->resource());
154
155 if (m_streamer->resource()->cachedMetadata(V8ScriptRunner::tagForCodeCache())) {
156 // The resource has a code cache, so it's unnecessary to stream and
157 // parse the code. Cancel the streaming and resume the non-streaming
158 // code path.
159 m_streamer->suppressStreaming();
160 {
161 MutexLocker locker(m_mutex);
162 m_cancelled = true;
163 }
164 m_dataQueue.finish();
165 return;
166 }
167
168 if (!m_resourceBuffer) {
169 // We don't have a buffer yet. Try to get it from the resource.
170 SharedBuffer* buffer = m_streamer->resource()->resourceBuffer();
171 if (!buffer)
172 return;
173 m_resourceBuffer = RefPtr<SharedBuffer>(buffer);
174 }
175
176 // Get as much data from the ResourceBuffer as we can.
177 const char* data = 0;
178 Vector<const char*> chunks;
179 Vector<unsigned> chunkLengths;
180 size_t dataLength = 0;
181 while (unsigned length = m_resourceBuffer->getSomeData(data, m_dataPosition)) {
182 // FIXME: Here we can limit based on the total length, if it turns
183 // out that we don't want to give all the data we have (memory
184 // vs. speed).
185 chunks.append(data);
186 chunkLengths.append(length);
187 dataLength += length;
188 m_dataPosition += length;
189 }
190 // Copy the data chunks into a new buffer, since we're going to give the
191 // data to a background thread.
192 if (dataLength > 0) {
193 uint8_t* copiedData = new uint8_t[dataLength];
194 unsigned offset = 0;
195 for (size_t i = 0; i < chunks.size(); ++i) {
196 memcpy(copiedData + offset, chunks[i], chunkLengths[i]);
197 offset += chunkLengths[i];
198 }
199 m_dataQueue.produce(copiedData, dataLength);
200 }
201 }
202
203 ScriptStreamer* m_streamer;
204
205 // For coordinating between the main thread and background thread tasks.
206 // Guarded by m_mutex.
207 bool m_cancelled;
208 Mutex m_mutex;
209
210 unsigned m_dataPosition; // Only used by the main thread.
211 RefPtr<SharedBuffer> m_resourceBuffer; // Only used by the main thread.
212 SourceStreamDataQueue m_dataQueue; // Thread safe.
213 };
214
215 size_t ScriptStreamer::kSmallScriptThreshold = 30 * 1024;
216
startStreaming(PendingScript & script,Settings * settings,ScriptState * scriptState,PendingScript::Type scriptType)217 void ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, ScriptState* scriptState, PendingScript::Type scriptType)
218 {
219 // We don't yet know whether the script will really be streamed. E.g.,
220 // suppressing streaming for short scripts is done later. Record only the
221 // sure negative cases here.
222 bool startedStreaming = startStreamingInternal(script, settings, scriptState, scriptType);
223 if (!startedStreaming)
224 blink::Platform::current()->histogramEnumeration(startedStreamingHistogramName(scriptType), 0, 2);
225 }
226
streamingComplete()227 void ScriptStreamer::streamingComplete()
228 {
229 ASSERT(isMainThread());
230 // It's possible that the corresponding Resource was deleted before V8
231 // finished streaming. In that case, the data or the notification is not
232 // needed. In addition, if the streaming is suppressed, the non-streaming
233 // code path will resume after the resource has loaded, before the
234 // background task finishes.
235 if (m_detached || m_streamingSuppressed) {
236 deref();
237 return;
238 }
239
240 // We have now streamed the whole script to V8 and it has parsed the
241 // script. We're ready for the next step: compiling and executing the
242 // script.
243 m_parsingFinished = true;
244
245 notifyFinishedToClient();
246
247 // The background thread no longer holds an implicit reference.
248 deref();
249 }
250
cancel()251 void ScriptStreamer::cancel()
252 {
253 ASSERT(isMainThread());
254 // The upper layer doesn't need the script any more, but streaming might
255 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets
256 // the control the next time. It can also be that V8 has already completed
257 // its operations and streamingComplete will be called soon.
258 m_detached = true;
259 m_resource = 0;
260 m_stream->cancel();
261 }
262
suppressStreaming()263 void ScriptStreamer::suppressStreaming()
264 {
265 ASSERT(!m_parsingFinished);
266 ASSERT(!m_loadingFinished);
267 m_streamingSuppressed = true;
268 }
269
notifyAppendData(ScriptResource * resource)270 void ScriptStreamer::notifyAppendData(ScriptResource* resource)
271 {
272 ASSERT(isMainThread());
273 ASSERT(m_resource == resource);
274 if (m_streamingSuppressed)
275 return;
276 if (!m_firstDataChunkReceived) {
277 m_firstDataChunkReceived = true;
278 const char* histogramName = startedStreamingHistogramName(m_scriptType);
279 // Check the size of the first data chunk. The expectation is that if
280 // the first chunk is small, there won't be a second one. In those
281 // cases, it doesn't make sense to stream at all.
282 if (resource->resourceBuffer()->size() < kSmallScriptThreshold) {
283 suppressStreaming();
284 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2);
285 return;
286 }
287 if (ScriptStreamerThread::shared()->isRunningTask()) {
288 // At the moment we only have one thread for running the tasks. A
289 // new task shouldn't be queued before the running task completes,
290 // because the running task can block and wait for data from the
291 // network. At the moment we are only streaming parser blocking
292 // scripts, but this code can still be hit when multiple frames are
293 // loading simultaneously.
294 suppressStreaming();
295 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2);
296 return;
297 }
298 ASSERT(m_task);
299 // ScriptStreamer needs to stay alive as long as the background task is
300 // running. This is taken care of with a manual ref() & deref() pair;
301 // the corresponding deref() is in streamingComplete.
302 ref();
303 ScriptStreamingTask* task = new ScriptStreamingTask(m_task, this);
304 ScriptStreamerThread::shared()->postTask(task);
305 m_task = 0;
306 blink::Platform::current()->histogramEnumeration(histogramName, 1, 2);
307 }
308 m_stream->didReceiveData();
309 }
310
notifyFinished(Resource * resource)311 void ScriptStreamer::notifyFinished(Resource* resource)
312 {
313 ASSERT(isMainThread());
314 ASSERT(m_resource == resource);
315 // A special case: empty scripts. We didn't receive any data before this
316 // notification. In that case, there won't be a "parsing complete"
317 // notification either, and we should not wait for it.
318 if (!m_firstDataChunkReceived)
319 suppressStreaming();
320 m_stream->didFinishLoading();
321 m_loadingFinished = true;
322 notifyFinishedToClient();
323 }
324
ScriptStreamer(ScriptResource * resource,v8::ScriptCompiler::StreamedSource::Encoding encoding,PendingScript::Type scriptType)325 ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::StreamedSource::Encoding encoding, PendingScript::Type scriptType)
326 : m_resource(resource)
327 , m_detached(false)
328 , m_stream(new SourceStream(this))
329 , m_source(m_stream, encoding) // m_source takes ownership of m_stream.
330 , m_client(0)
331 , m_task(0)
332 , m_loadingFinished(false)
333 , m_parsingFinished(false)
334 , m_firstDataChunkReceived(false)
335 , m_streamingSuppressed(false)
336 , m_scriptType(scriptType)
337 {
338 }
339
notifyFinishedToClient()340 void ScriptStreamer::notifyFinishedToClient()
341 {
342 ASSERT(isMainThread());
343 // Usually, the loading will be finished first, and V8 will still need some
344 // time to catch up. But the other way is possible too: if V8 detects a
345 // parse error, the V8 side can complete before loading has finished. Send
346 // the notification after both loading and V8 side operations have
347 // completed. Here we also check that we have a client: it can happen that a
348 // function calling notifyFinishedToClient was already scheduled in the task
349 // queue and the upper layer decided that it's not interested in the script
350 // and called removeClient.
351 if (isFinished() && m_client)
352 m_client->notifyFinished(m_resource);
353 }
354
startedStreamingHistogramName(PendingScript::Type scriptType)355 const char* ScriptStreamer::startedStreamingHistogramName(PendingScript::Type scriptType)
356 {
357 switch (scriptType) {
358 case PendingScript::ParsingBlocking:
359 return "WebCore.Scripts.ParsingBlocking.StartedStreaming";
360 break;
361 case PendingScript::Deferred:
362 return "WebCore.Scripts.Deferred.StartedStreaming";
363 break;
364 case PendingScript::Async:
365 return "WebCore.Scripts.Async.StartedStreaming";
366 break;
367 default:
368 ASSERT_NOT_REACHED();
369 break;
370 }
371 return 0;
372 }
373
startStreamingInternal(PendingScript & script,Settings * settings,ScriptState * scriptState,PendingScript::Type scriptType)374 bool ScriptStreamer::startStreamingInternal(PendingScript& script, Settings* settings, ScriptState* scriptState, PendingScript::Type scriptType)
375 {
376 ASSERT(isMainThread());
377 if (!settings || !settings->v8ScriptStreamingEnabled())
378 return false;
379 ScriptResource* resource = script.resource();
380 ASSERT(!resource->isLoaded());
381 if (!resource->url().protocolIsInHTTPFamily())
382 return false;
383 if (resource->resourceToRevalidate()) {
384 // This happens e.g., during reloads. We're actually not going to load
385 // the current Resource of the PendingScript but switch to another
386 // Resource -> don't stream.
387 return false;
388 }
389 // We cannot filter out short scripts, even if we wait for the HTTP headers
390 // to arrive. In general, the web servers don't seem to send the
391 // Content-Length HTTP header for scripts.
392
393 WTF::TextEncoding textEncoding(resource->encoding());
394 const char* encodingName = textEncoding.name();
395
396 // Here's a list of encodings we can use for streaming. These are
397 // the canonical names.
398 v8::ScriptCompiler::StreamedSource::Encoding encoding;
399 if (strcmp(encodingName, "windows-1252") == 0
400 || strcmp(encodingName, "ISO-8859-1") == 0
401 || strcmp(encodingName, "US-ASCII") == 0) {
402 encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE;
403 } else if (strcmp(encodingName, "UTF-8") == 0) {
404 encoding = v8::ScriptCompiler::StreamedSource::UTF8;
405 } else {
406 // We don't stream other encodings; especially we don't stream two byte
407 // scripts to avoid the handling of byte order marks. Most scripts are
408 // Latin1 or UTF-8 anyway, so this should be enough for most real world
409 // purposes.
410 return false;
411 }
412
413 if (scriptState->contextIsValid())
414 return false;
415 ScriptState::Scope scope(scriptState);
416
417 // The Resource might go out of scope if the script is no longer needed. We
418 // will soon call PendingScript::setStreamer, which makes the PendingScript
419 // notify the ScriptStreamer when it is destroyed.
420 RefPtr<ScriptStreamer> streamer = adoptRef(new ScriptStreamer(resource, encoding, scriptType));
421
422 // Decide what kind of cached data we should produce while streaming. By
423 // default, we generate the parser cache for streamed scripts, to emulate
424 // the non-streaming behavior (see V8ScriptRunner::compileScript).
425 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProduceParserCache;
426 if (settings->v8CacheOptions() == V8CacheOptionsCode)
427 compileOption = v8::ScriptCompiler::kProduceCodeCache;
428 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCompiler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), compileOption);
429 if (scriptStreamingTask) {
430 streamer->m_task = scriptStreamingTask;
431 script.setStreamer(streamer.release());
432 return true;
433 }
434 // Otherwise, V8 cannot stream the script.
435 return false;
436 }
437
438 } // namespace blink
439