Home | History | Annotate | Download | only in v8
      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:
     28     SourceStreamDataQueue()
     29         : m_finished(false) { }
     30 
     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 
     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 
     46     void finish()
     47     {
     48         MutexLocker locker(m_mutex);
     49         m_finished = true;
     50         m_haveData.signal();
     51     }
     52 
     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:
     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:
     90     SourceStream(ScriptStreamer* streamer)
     91         : v8::ScriptCompiler::ExternalSourceStream()
     92         , m_streamer(streamer)
     93         , m_cancelled(false)
     94         , m_dataPosition(0) { }
     95 
     96     virtual ~SourceStream() { }
     97 
     98     // Called by V8 on a background thread. Should block until we can return
     99     // some data.
    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 
    119     void didFinishLoading()
    120     {
    121         ASSERT(isMainThread());
    122         m_dataQueue.finish();
    123     }
    124 
    125     void didReceiveData()
    126     {
    127         ASSERT(isMainThread());
    128         prepareDataOnMainThread();
    129     }
    130 
    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:
    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 
    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 
    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 
    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 
    263 void ScriptStreamer::suppressStreaming()
    264 {
    265     ASSERT(!m_parsingFinished);
    266     ASSERT(!m_loadingFinished);
    267     m_streamingSuppressed = true;
    268 }
    269 
    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 
    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 
    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 
    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 
    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 
    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