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.
6 #include "bindings/core/v8/ScriptStreamer.h"
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 "platform/TraceEvent.h"
17 #include "public/platform/Platform.h"
18 #include "wtf/MainThread.h"
19 #include "wtf/text/TextEncodingRegistry.h"
23 // For passing data between the main thread (producer) and the streamer thread
24 // (consumer). The main thread prepares the data (copies it from Resource) and
25 // the streamer thread feeds it to V8.
26 class SourceStreamDataQueue {
27 WTF_MAKE_NONCOPYABLE(SourceStreamDataQueue);
29 SourceStreamDataQueue()
30 : m_finished(false) { }
32 ~SourceStreamDataQueue()
34 while (!m_data.isEmpty()) {
35 std::pair<const uint8_t*, size_t> next_data = m_data.takeFirst();
36 delete[] next_data.first;
40 void produce(const uint8_t* data, size_t length)
42 MutexLocker locker(m_mutex);
43 m_data.append(std::make_pair(data, length));
49 MutexLocker locker(m_mutex);
54 void consume(const uint8_t** data, size_t* length)
56 MutexLocker locker(m_mutex);
57 while (!tryGetData(data, length))
58 m_haveData.wait(m_mutex);
62 bool tryGetData(const uint8_t** data, size_t* length)
64 if (!m_data.isEmpty()) {
65 std::pair<const uint8_t*, size_t> next_data = m_data.takeFirst();
66 *data = next_data.first;
67 *length = next_data.second;
77 WTF::Deque<std::pair<const uint8_t*, size_t> > m_data;
80 ThreadCondition m_haveData;
84 // SourceStream implements the streaming interface towards V8. The main
85 // functionality is preparing the data to give to V8 on main thread, and
86 // actually giving the data (via GetMoreData which is called on a background
88 class SourceStream : public v8::ScriptCompiler::ExternalSourceStream {
89 WTF_MAKE_NONCOPYABLE(SourceStream);
91 SourceStream(ScriptStreamer* streamer)
92 : v8::ScriptCompiler::ExternalSourceStream()
93 , m_streamer(streamer)
95 , m_dataPosition(0) { }
97 virtual ~SourceStream() { }
99 // Called by V8 on a background thread. Should block until we can return
101 virtual size_t GetMoreData(const uint8_t** src) override
103 ASSERT(!isMainThread());
105 MutexLocker locker(m_mutex);
110 // This will wait until there is data.
111 m_dataQueue.consume(src, &length);
113 MutexLocker locker(m_mutex);
120 void didFinishLoading()
122 ASSERT(isMainThread());
123 m_dataQueue.finish();
126 void didReceiveData()
128 ASSERT(isMainThread());
129 prepareDataOnMainThread();
134 ASSERT(isMainThread());
135 // The script is no longer needed by the upper layers. Stop streaming
136 // it. The next time GetMoreData is called (or woken up), it will return
137 // 0, which will be interpreted as EOS by V8 and the parsing will
138 // fail. ScriptStreamer::streamingComplete will be called, and at that
139 // point we will release the references to SourceStream.
141 MutexLocker locker(m_mutex);
144 m_dataQueue.finish();
148 void prepareDataOnMainThread()
150 ASSERT(isMainThread());
151 // The Resource must still be alive; otherwise we should've cancelled
152 // the streaming (if we have cancelled, the background thread is not
154 ASSERT(m_streamer->resource());
156 if (m_streamer->resource()->cachedMetadata(V8ScriptRunner::tagForCodeCache())) {
157 // The resource has a code cache, so it's unnecessary to stream and
158 // parse the code. Cancel the streaming and resume the non-streaming
160 m_streamer->suppressStreaming();
162 MutexLocker locker(m_mutex);
165 m_dataQueue.finish();
169 if (!m_resourceBuffer) {
170 // We don't have a buffer yet. Try to get it from the resource.
171 SharedBuffer* buffer = m_streamer->resource()->resourceBuffer();
174 m_resourceBuffer = RefPtr<SharedBuffer>(buffer);
177 // Get as much data from the ResourceBuffer as we can.
178 const char* data = 0;
179 Vector<const char*> chunks;
180 Vector<unsigned> chunkLengths;
181 size_t dataLength = 0;
182 while (unsigned length = m_resourceBuffer->getSomeData(data, m_dataPosition)) {
183 // FIXME: Here we can limit based on the total length, if it turns
184 // out that we don't want to give all the data we have (memory
187 chunkLengths.append(length);
188 dataLength += length;
189 m_dataPosition += length;
191 // Copy the data chunks into a new buffer, since we're going to give the
192 // data to a background thread.
193 if (dataLength > 0) {
194 uint8_t* copiedData = new uint8_t[dataLength];
196 for (size_t i = 0; i < chunks.size(); ++i) {
197 memcpy(copiedData + offset, chunks[i], chunkLengths[i]);
198 offset += chunkLengths[i];
200 m_dataQueue.produce(copiedData, dataLength);
204 ScriptStreamer* m_streamer;
206 // For coordinating between the main thread and background thread tasks.
207 // Guarded by m_mutex.
211 unsigned m_dataPosition; // Only used by the main thread.
212 RefPtr<SharedBuffer> m_resourceBuffer; // Only used by the main thread.
213 SourceStreamDataQueue m_dataQueue; // Thread safe.
216 size_t ScriptStreamer::kSmallScriptThreshold = 30 * 1024;
218 void ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, ScriptState* scriptState, PendingScript::Type scriptType)
220 // We don't yet know whether the script will really be streamed. E.g.,
221 // suppressing streaming for short scripts is done later. Record only the
222 // sure negative cases here.
223 bool startedStreaming = startStreamingInternal(script, settings, scriptState, scriptType);
224 if (!startedStreaming)
225 blink::Platform::current()->histogramEnumeration(startedStreamingHistogramName(scriptType), 0, 2);
228 void ScriptStreamer::streamingCompleteOnBackgroundThread()
230 ASSERT(!isMainThread());
231 MutexLocker locker(m_mutex);
232 m_parsingFinished = true;
233 // In the blocking case, the main thread is normally waiting at this
234 // point, but it can also happen that the load is not yet finished
235 // (e.g., a parse error). In that case, notifyFinished will be called
236 // eventually and it will not wait on m_parsingFinishedCondition.
238 // In the non-blocking case, notifyFinished might already be called, or it
239 // might be called in the future. In any case, do the cleanup here.
240 if (m_mainThreadWaitingForParserThread) {
241 m_parsingFinishedCondition.signal();
243 callOnMainThread(WTF::bind(&ScriptStreamer::streamingComplete, this));
247 void ScriptStreamer::cancel()
249 ASSERT(isMainThread());
250 // The upper layer doesn't need the script any more, but streaming might
251 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets
252 // the control the next time. It can also be that V8 has already completed
253 // its operations and streamingComplete will be called soon.
260 void ScriptStreamer::suppressStreaming()
262 MutexLocker locker(m_mutex);
263 ASSERT(!m_loadingFinished);
264 // It can be that the parsing task has already finished (e.g., if there was
266 m_streamingSuppressed = true;
269 unsigned ScriptStreamer::cachedDataType() const
271 if (m_compileOptions == v8::ScriptCompiler::kProduceParserCache) {
272 return V8ScriptRunner::tagForParserCache();
274 if (m_compileOptions == v8::ScriptCompiler::kProduceCodeCache) {
275 return V8ScriptRunner::tagForCodeCache();
280 void ScriptStreamer::notifyAppendData(ScriptResource* resource)
282 ASSERT(isMainThread());
283 ASSERT(m_resource == resource);
285 MutexLocker locker(m_mutex);
286 if (m_streamingSuppressed)
289 if (!m_haveEnoughDataForStreaming) {
290 // Even if the first data chunk is small, the script can still be big
291 // enough - wait until the next data chunk comes before deciding whether
292 // to start the streaming.
293 if (resource->resourceBuffer()->size() < kSmallScriptThreshold) {
296 m_haveEnoughDataForStreaming = true;
297 const char* histogramName = startedStreamingHistogramName(m_scriptType);
299 // Encoding should be detected only when we have some data. It's
300 // possible that resource->encoding() returns a different encoding
301 // before the loading has started and after we got some data.
302 WTF::TextEncoding textEncoding(resource->encoding());
303 const char* encodingName = textEncoding.name();
305 // Here's a list of encodings we can use for streaming. These are
306 // the canonical names.
307 v8::ScriptCompiler::StreamedSource::Encoding encoding;
308 if (strcmp(encodingName, "windows-1252") == 0
309 || strcmp(encodingName, "ISO-8859-1") == 0
310 || strcmp(encodingName, "US-ASCII") == 0) {
311 encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE;
312 } else if (strcmp(encodingName, "UTF-8") == 0) {
313 encoding = v8::ScriptCompiler::StreamedSource::UTF8;
315 // We don't stream other encodings; especially we don't stream two
316 // byte scripts to avoid the handling of byte order marks. Most
317 // scripts are Latin1 or UTF-8 anyway, so this should be enough for
318 // most real world purposes.
320 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2);
323 if (ScriptStreamerThread::shared()->isRunningTask()) {
324 // At the moment we only have one thread for running the tasks. A
325 // new task shouldn't be queued before the running task completes,
326 // because the running task can block and wait for data from the
329 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2);
333 if (!m_scriptState->contextIsValid()) {
335 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2);
341 m_stream = new SourceStream(this);
342 // m_source takes ownership of m_stream.
343 m_source = adoptPtr(new v8::ScriptCompiler::StreamedSource(m_stream, encoding));
345 ScriptState::Scope scope(m_scriptState.get());
346 WTF::OwnPtr<v8::ScriptCompiler::ScriptStreamingTask> scriptStreamingTask(adoptPtr(v8::ScriptCompiler::StartStreamingScript(m_scriptState->isolate(), m_source.get(), m_compileOptions)));
347 if (!scriptStreamingTask) {
348 // V8 cannot stream the script.
352 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2);
356 // ScriptStreamer needs to stay alive as long as the background task is
357 // running. This is taken care of with a manual ref() & deref() pair;
358 // the corresponding deref() is in streamingComplete or in
361 ScriptStreamingTask* task = new ScriptStreamingTask(scriptStreamingTask.release(), this);
362 ScriptStreamerThread::shared()->postTask(task);
363 blink::Platform::current()->histogramEnumeration(histogramName, 1, 2);
366 m_stream->didReceiveData();
369 void ScriptStreamer::notifyFinished(Resource* resource)
371 ASSERT(isMainThread());
372 ASSERT(m_resource == resource);
373 // A special case: empty and small scripts. We didn't receive enough data to
374 // start the streaming before this notification. In that case, there won't
375 // be a "parsing complete" notification either, and we should not wait for
377 if (!m_haveEnoughDataForStreaming) {
378 const char* histogramName = startedStreamingHistogramName(m_scriptType);
379 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2);
383 m_stream->didFinishLoading();
384 m_loadingFinished = true;
386 if (shouldBlockMainThread()) {
387 // Make the main thead wait until the streaming is complete, to make
388 // sure that the script gets the main thread's attention as early as
389 // possible (for possible compiling, if the client wants to do it
390 // right away). Note that blocking here is not any worse than the
391 // non-streaming code path where the main thread eventually blocks
392 // to parse the script.
393 TRACE_EVENT0("v8", "v8.mainThreadWaitingForParserThread");
394 MutexLocker locker(m_mutex);
395 while (!isFinished()) {
396 ASSERT(!m_parsingFinished);
397 ASSERT(!m_streamingSuppressed);
398 m_mainThreadWaitingForParserThread = true;
399 m_parsingFinishedCondition.wait(m_mutex);
403 // Calling notifyFinishedToClient can result into the upper layers dropping
404 // references to ScriptStreamer. Keep it alive until this function ends.
405 RefPtr<ScriptStreamer> protect(this);
407 notifyFinishedToClient();
409 if (m_mainThreadWaitingForParserThread) {
410 ASSERT(m_parsingFinished);
411 ASSERT(!m_streamingSuppressed);
412 // streamingComplete won't be called, so do the ramp-down work
418 ScriptStreamer::ScriptStreamer(ScriptResource* resource, PendingScript::Type scriptType, ScriptStreamingMode mode, ScriptState* scriptState, v8::ScriptCompiler::CompileOptions compileOptions)
419 : m_resource(resource)
423 , m_loadingFinished(false)
424 , m_parsingFinished(false)
425 , m_haveEnoughDataForStreaming(false)
426 , m_streamingSuppressed(false)
427 , m_compileOptions(compileOptions)
428 , m_scriptState(scriptState)
429 , m_scriptType(scriptType)
430 , m_scriptStreamingMode(mode)
431 , m_mainThreadWaitingForParserThread(false)
435 void ScriptStreamer::streamingComplete()
437 // The background task is completed; do the necessary ramp-down in the main
439 ASSERT(isMainThread());
441 // It's possible that the corresponding Resource was deleted before V8
442 // finished streaming. In that case, the data or the notification is not
443 // needed. In addition, if the streaming is suppressed, the non-streaming
444 // code path will resume after the resource has loaded, before the
445 // background task finishes.
446 if (m_detached || m_streamingSuppressed) {
451 // We have now streamed the whole script to V8 and it has parsed the
452 // script. We're ready for the next step: compiling and executing the
454 notifyFinishedToClient();
456 // The background thread no longer holds an implicit reference.
460 void ScriptStreamer::notifyFinishedToClient()
462 ASSERT(isMainThread());
463 // Usually, the loading will be finished first, and V8 will still need some
464 // time to catch up. But the other way is possible too: if V8 detects a
465 // parse error, the V8 side can complete before loading has finished. Send
466 // the notification after both loading and V8 side operations have
467 // completed. Here we also check that we have a client: it can happen that a
468 // function calling notifyFinishedToClient was already scheduled in the task
469 // queue and the upper layer decided that it's not interested in the script
470 // and called removeClient.
472 MutexLocker locker(m_mutex);
477 m_client->notifyFinished(m_resource);
480 const char* ScriptStreamer::startedStreamingHistogramName(PendingScript::Type scriptType)
482 switch (scriptType) {
483 case PendingScript::ParsingBlocking:
484 return "WebCore.Scripts.ParsingBlocking.StartedStreaming";
486 case PendingScript::Deferred:
487 return "WebCore.Scripts.Deferred.StartedStreaming";
489 case PendingScript::Async:
490 return "WebCore.Scripts.Async.StartedStreaming";
493 ASSERT_NOT_REACHED();
499 bool ScriptStreamer::startStreamingInternal(PendingScript& script, Settings* settings, ScriptState* scriptState, PendingScript::Type scriptType)
501 ASSERT(isMainThread());
502 if (!settings || !settings->v8ScriptStreamingEnabled())
504 if (settings->v8ScriptStreamingMode() == ScriptStreamingModeOnlyAsyncAndDefer
505 && scriptType == PendingScript::ParsingBlocking)
508 ScriptResource* resource = script.resource();
509 if (resource->isLoaded())
511 if (!resource->url().protocolIsInHTTPFamily())
513 if (resource->resourceToRevalidate()) {
514 // This happens e.g., during reloads. We're actually not going to load
515 // the current Resource of the PendingScript but switch to another
516 // Resource -> don't stream.
519 // We cannot filter out short scripts, even if we wait for the HTTP headers
520 // to arrive. In general, the web servers don't seem to send the
521 // Content-Length HTTP header for scripts.
523 if (!scriptState->contextIsValid())
526 // Decide what kind of cached data we should produce while streaming. By
527 // default, we generate the parser cache for streamed scripts, to emulate
528 // the non-streaming behavior (see V8ScriptRunner::compileScript).
529 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProduceParserCache;
530 if (settings->v8CacheOptions() == V8CacheOptionsCode)
531 compileOption = v8::ScriptCompiler::kProduceCodeCache;
533 // The Resource might go out of scope if the script is no longer
534 // needed. This makes PendingScript notify the ScriptStreamer when it is
536 script.setStreamer(adoptRef(new ScriptStreamer(resource, scriptType, settings->v8ScriptStreamingMode(), scriptState, compileOption)));