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
20namespace 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.
25class SourceStreamDataQueue {
26    WTF_MAKE_NONCOPYABLE(SourceStreamDataQueue);
27public:
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
60private:
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).
87class SourceStream : public v8::ScriptCompiler::ExternalSourceStream {
88    WTF_MAKE_NONCOPYABLE(SourceStream);
89public:
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
146private:
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
215size_t ScriptStreamer::kSmallScriptThreshold = 30 * 1024;
216
217void 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
227void 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
251void 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
263void ScriptStreamer::suppressStreaming()
264{
265    ASSERT(!m_parsingFinished);
266    ASSERT(!m_loadingFinished);
267    m_streamingSuppressed = true;
268}
269
270void 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
311void 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
325ScriptStreamer::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
340void 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
355const 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
374bool 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