• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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