Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(214)

Unified Diff: Source/bindings/core/v8/ScriptStreamer.cpp

Issue 368283002: Stream scripts to V8 as they load - Blink side. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: removed untrue assert Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « Source/bindings/core/v8/ScriptStreamer.h ('k') | Source/bindings/core/v8/ScriptStreamerTest.cpp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: Source/bindings/core/v8/ScriptStreamer.cpp
diff --git a/Source/bindings/core/v8/ScriptStreamer.cpp b/Source/bindings/core/v8/ScriptStreamer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c92040954d5c931b2a01a019972d3a6842543185
--- /dev/null
+++ b/Source/bindings/core/v8/ScriptStreamer.cpp
@@ -0,0 +1,360 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "config.h"
+#include "bindings/core/v8/ScriptStreamer.h"
+
+#include "bindings/core/v8/ScriptStreamerThread.h"
+#include "bindings/core/v8/V8ScriptRunner.h"
+#include "core/dom/Document.h"
+#include "core/dom/Element.h"
+#include "core/dom/PendingScript.h"
+#include "core/fetch/ScriptResource.h"
+#include "core/frame/Settings.h"
+#include "platform/SharedBuffer.h"
+#include "wtf/MainThread.h"
+#include "wtf/text/TextEncodingRegistry.h"
+
+namespace blink {
+
+// SourceStream implements the streaming interface towards V8. The main
+// functionality is preparing the data to give to V8 on main thread, and
+// actually giving the data (via GetMoreData which is called on a background
+// thread).
+class SourceStream : public v8::ScriptCompiler::ExternalSourceStream {
+ WTF_MAKE_NONCOPYABLE(SourceStream);
+public:
+ SourceStream(ScriptStreamer* streamer)
+ : v8::ScriptCompiler::ExternalSourceStream()
+ , m_streamer(streamer)
+ , m_backgroundThreadWaitingForData(false)
+ , m_dataPointer(0)
+ , m_dataLength(0)
+ , m_dataPosition(0)
+ , m_loadingFinished(false)
+ , m_cancelled(false)
+ , m_allDataReturned(false) { }
+
+ virtual ~SourceStream() { }
+
+ // Called by V8 on a background thread. Should block until we can return
+ // some data.
+ virtual size_t GetMoreData(const uint8_t** src) OVERRIDE
+ {
+ ASSERT(!isMainThread());
+ MutexLocker locker(m_mutex);
+ if (m_cancelled || m_allDataReturned)
+ return 0;
+ // The main thread might be modifying the data buffer of the Resource,
+ // so we cannot read it here. Post a task to the main thread and wait
+ // for it to complete.
+ m_backgroundThreadWaitingForData = true;
+ m_dataPointer = src;
+ m_dataLength = 0;
+ callOnMainThread(WTF::bind(&SourceStream::prepareDataOnMainThread, this));
+ m_condition.wait(m_mutex);
+ if (m_cancelled || m_allDataReturned)
+ return 0;
+ return m_dataLength;
+ }
+
+ void didFinishLoading()
+ {
+ ASSERT(isMainThread());
+ if (m_streamer->resource()->errorOccurred()) {
+ // The background thread might be waiting for data. Wake it up. V8
+ // will perceive this as EOS and call the completion callback. At
+ // that point we will resume the normal "script loaded" code path
+ // (see HTMLScriptRunner::notifyFinished which doesn't differentiate
+ // between scripts which loaded successfully and scripts which
+ // failed to load).
+ cancel();
+ return;
+ }
+ {
+ MutexLocker locker(m_mutex);
+ m_loadingFinished = true;
+ if (!m_backgroundThreadWaitingForData)
+ return;
+ }
+ prepareDataOnMainThread();
+ }
+
+ void didReceiveData()
+ {
+ ASSERT(isMainThread());
+ prepareDataOnMainThread();
+ }
+
+ void cancel()
+ {
+ ASSERT(isMainThread());
+ // The script is no longer needed by the upper layers. Stop streaming
+ // it. The next time GetMoreData is called (or woken up), it will return
+ // 0, which will be interpreted as EOS by V8 and the parsing will
+ // fail. ScriptStreamer::streamingComplete will be called, and at that
+ // point we will release the references to SourceStream.
+ MutexLocker locker(m_mutex);
+ m_cancelled = true;
+ m_backgroundThreadWaitingForData = false;
+ m_condition.signal();
+ }
+
+private:
+ void prepareDataOnMainThread()
+ {
+ ASSERT(isMainThread());
+ MutexLocker locker(m_mutex);
+ if (!m_backgroundThreadWaitingForData) {
+ // It's possible that the background thread is not waiting. This
+ // happens when 1) data arrives but V8 hasn't requested data yet, 2)
+ // we have previously scheduled prepareDataOnMainThread, but some
+ // other event (load cancelled or finished) already retrieved the
+ // data and set m_backgroundThreadWaitingForData to false.
+ return;
+ }
+ // The Resource must still be alive; otherwise we should've cancelled
+ // the streaming (if we have cancelled, the background thread is not
+ // waiting).
+ ASSERT(m_streamer->resource());
+
+ if (m_streamer->resource()->cachedMetadata(V8ScriptRunner::tagForCodeCache())) {
+ // The resource has a code cache, so it's unnecessary to stream and
+ // parse the code. Cancel the streaming and resume the non-streaming
+ // code path.
+ m_streamer->suppressStreaming();
+ m_cancelled = true;
+ m_backgroundThreadWaitingForData = false;
+ m_condition.signal();
+ return;
+ }
+
+ if (!m_resourceBuffer) {
+ // We don't have a buffer yet. Try to get it from the resource.
+ SharedBuffer* buffer = m_streamer->resource()->resourceBuffer();
+ if (!buffer) {
+ // The Resource hasn't created its SharedBuffer yet. At some
+ // point (at latest when the script loading finishes) this
+ // function will be called again and we will have data.
+ if (m_loadingFinished) {
+ // This happens only in special cases (e.g., involving empty
+ // scripts).
+ m_allDataReturned = true;
+ m_backgroundThreadWaitingForData = false;
+ m_condition.signal();
+ }
+ return;
+ }
+ m_resourceBuffer = RefPtr<SharedBuffer>(buffer);
+ }
+
+ // Get as much data from the ResourceBuffer as we can.
+ const char* data = 0;
+ Vector<const char*> chunks;
+ Vector<unsigned> chunkLengths;
+ while (unsigned length = m_resourceBuffer->getSomeData(data, m_dataPosition)) {
+ // FIXME: Here we can limit based on the total length, if it turns
+ // out that we don't want to give all the data we have (memory
+ // vs. speed).
+ chunks.append(data);
+ chunkLengths.append(length);
+ m_dataLength += length;
+ m_dataPosition += length;
+ }
+ // Copy the data chunks into a new buffer, since we're going to give the
+ // data to a background thread.
+ if (m_dataLength > 0) {
+ uint8_t* copiedData = new uint8_t[m_dataLength];
+ unsigned offset = 0;
+ for (size_t i = 0; i < chunks.size(); ++i) {
+ memcpy(copiedData + offset, chunks[i], chunkLengths[i]);
+ offset += chunkLengths[i];
+ }
+ *m_dataPointer = copiedData;
+ m_backgroundThreadWaitingForData = false;
+ m_condition.signal();
+ } else if (m_loadingFinished) {
+ // We need to return data length 0 to V8 to signal that the data
+ // ends.
+ m_allDataReturned = true;
+ m_backgroundThreadWaitingForData = false;
+ m_condition.signal();
+ }
+ // Otherwise, we had scheduled prepareDataOnMainThread but we didn't
+ // have any data to return. It will be scheduled again when the data
+ // arrives.
+ }
+
+ ScriptStreamer* m_streamer;
+
+ // For coordinating between the main thread and background thread tasks.
+ // Guarded by m_mutex.
+ bool m_backgroundThreadWaitingForData;
+ const uint8_t** m_dataPointer;
+ unsigned m_dataLength;
+ unsigned m_dataPosition;
+ bool m_loadingFinished;
+ bool m_cancelled;
+ bool m_allDataReturned;
+ RefPtr<SharedBuffer> m_resourceBuffer; // Only used by the main thread.
+
+ ThreadCondition m_condition;
+ Mutex m_mutex;
+};
+
+
+bool ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, ScriptState* scriptState)
+{
+ ASSERT(isMainThread());
+ if (!settings || !settings->v8ScriptStreamingEnabled())
+ return false;
+ if (ScriptStreamerThread::shared()->isRunningTask()) {
+ // At the moment we only have one thread for running the tasks. A new
+ // task shouldn't be queued before the running task completes, because
+ // the running task can block and wait for data from the network. At the
+ // moment we are only streaming parser blocking scripts, but this code
+ // can still be hit when multiple frames are loading simultaneously.
+ return false;
+ }
+ ScriptResource* resource = script.resource();
+ if (!resource->url().protocolIsInHTTPFamily())
+ return false;
+ if (resource->resourceToRevalidate()) {
+ // This happens e.g., during reloads. We're actually not going to load
+ // the current Resource of the PendingScript but switch to another
+ // Resource -> don't stream.
+ return false;
+ }
+ // We cannot filter out short scripts, even if we wait for the HTTP headers
+ // to arrive. In general, the web servers don't seem to send the
+ // Content-Length HTTP header for scripts.
+
+ WTF::TextEncoding textEncoding(resource->encoding());
+ const char* encodingName = textEncoding.name();
+
+ // Here's a list of encodings we can use for streaming. These are
+ // the canonical names.
+ v8::ScriptCompiler::StreamedSource::Encoding encoding;
+ if (strcmp(encodingName, "windows-1252") == 0
+ || strcmp(encodingName, "ISO-8859-1") == 0
+ || strcmp(encodingName, "US-ASCII") == 0) {
+ encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE;
+ } else if (strcmp(encodingName, "UTF-8") == 0) {
+ encoding = v8::ScriptCompiler::StreamedSource::UTF8;
+ } else {
+ // We don't stream other encodings; especially we don't stream two byte
+ // scripts to avoid the handling of byte order marks. Most scripts are
+ // Latin1 or UTF-8 anyway, so this should be enough for most real world
+ // purposes.
+ return false;
+ }
+
+ if (scriptState->contextIsValid())
+ return false;
+ ScriptState::Scope scope(scriptState);
+
+ // The Resource might go out of scope if the script is no longer needed. We
+ // will soon call PendingScript::setStreamer, which makes the PendingScript
+ // notify the ScriptStreamer when it is destroyed.
+ RefPtr<ScriptStreamer> streamer = adoptRef(new ScriptStreamer(resource, encoding));
+
+ // Decide what kind of cached data we should produce while streaming. By
+ // default, we generate the parser cache for streamed scripts, to emulate
+ // the non-streaming behavior (seeV8ScriptRunner::compileScript).
+ v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProduceParserCache;
+ if (settings->v8CacheOptions() == V8CacheOptionsCode)
+ compileOption = v8::ScriptCompiler::kProduceCodeCache;
+ v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCompiler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), compileOption);
+ if (scriptStreamingTask) {
+ ScriptStreamingTask* task = new ScriptStreamingTask(scriptStreamingTask, streamer.get());
+ // ScriptStreamer needs to stay alive as long as the background task is
+ // running. This is taken care of with a manual ref() & deref() pair;
+ // the corresponding deref() is in streamingComplete.
+ streamer->ref();
+ script.setStreamer(streamer.release());
+ ScriptStreamerThread::shared()->postTask(task);
+ return true;
+ }
+ // Otherwise, V8 cannot stream the script.
+ return false;
+}
+
+void ScriptStreamer::streamingComplete()
+{
+ ASSERT(isMainThread());
+ // It's possible that the corresponding Resource was deleted before V8
+ // finished streaming. In that case, the data or the notification is not
+ // needed.
+ if (m_detached) {
+ deref();
+ return;
+ }
+
+ // We have now streamed the whole script to V8 and it has parsed the
+ // script. We're ready for the next step: compiling and executing the
+ // script.
+ m_parsingFinished = true;
+
+ notifyFinishedToClient();
+
+ // The background thread no longer holds an implicit reference.
+ deref();
+}
+
+void ScriptStreamer::cancel()
+{
+ ASSERT(isMainThread());
+ // The upper layer doesn't need the script any more, but streaming might
+ // still be ongoing. Tell SourceStream to try to cancel it whenever it gets
+ // the control the next time. It can also be that V8 has already completed
+ // its operations and streamingComplete will be called soon.
+ m_detached = true;
+ m_resource = 0;
+ m_stream->cancel();
+}
+
+void ScriptStreamer::notifyAppendData(ScriptResource* resource)
+{
+ ASSERT(isMainThread());
+ ASSERT(m_resource == resource);
+ m_stream->didReceiveData();
+}
+
+void ScriptStreamer::notifyFinished(Resource* resource)
+{
+ ASSERT(isMainThread());
+ ASSERT(m_resource == resource);
+ m_stream->didFinishLoading();
+ m_loadingFinished = true;
+ notifyFinishedToClient();
+}
+
+ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::StreamedSource::Encoding encoding)
+ : m_resource(resource)
+ , m_detached(false)
+ , m_stream(new SourceStream(this))
+ , m_source(m_stream, encoding) // m_source takes ownership of m_stream.
+ , m_client(0)
+ , m_loadingFinished(false)
+ , m_parsingFinished(false)
+ , m_streamingSuppressed(false)
+{
+}
+
+void ScriptStreamer::notifyFinishedToClient()
+{
+ ASSERT(isMainThread());
+ // Usually, the loading will be finished first, and V8 will still need some
+ // time to catch up. But the other way is possible too: if V8 detects a
+ // parse error, the V8 side can complete before loading has finished. Send
+ // the notification after both loading and V8 side operations have
+ // completed. Here we also check that we have a client: it can happen that a
+ // function calling notifyFinishedToClient was already scheduled in the task
+ // queue and the upper layer decided that it's not interested in the script
+ // and called removeClient.
+ if (m_loadingFinished && m_parsingFinished && m_client)
+ m_client->notifyFinished(m_resource);
+}
+
+} // namespace blink
« no previous file with comments | « Source/bindings/core/v8/ScriptStreamer.h ('k') | Source/bindings/core/v8/ScriptStreamerTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698