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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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 "wtf/MainThread.h"
17 #include "wtf/text/TextEncodingRegistry.h"
18
19 namespace blink {
20
21 // SourceStream implements the streaming interface towards V8. The main
22 // functionality is preparing the data to give to V8 on main thread, and
23 // actually giving the data (via GetMoreData which is called on a background
24 // thread).
25 class SourceStream : public v8::ScriptCompiler::ExternalSourceStream {
26 WTF_MAKE_NONCOPYABLE(SourceStream);
27 public:
28 SourceStream(ScriptStreamer* streamer)
29 : v8::ScriptCompiler::ExternalSourceStream()
30 , m_streamer(streamer)
31 , m_backgroundThreadWaitingForData(false)
32 , m_dataPointer(0)
33 , m_dataLength(0)
34 , m_dataPosition(0)
35 , m_loadingFinished(false)
36 , m_cancelled(false)
37 , m_allDataReturned(false) { }
38
39 virtual ~SourceStream() { }
40
41 // Called by V8 on a background thread. Should block until we can return
42 // some data.
43 virtual size_t GetMoreData(const uint8_t** src) OVERRIDE
44 {
45 ASSERT(!isMainThread());
46 MutexLocker locker(m_mutex);
47 if (m_cancelled || m_allDataReturned)
48 return 0;
49 // The main thread might be modifying the data buffer of the Resource,
50 // so we cannot read it here. Post a task to the main thread and wait
51 // for it to complete.
52 m_backgroundThreadWaitingForData = true;
53 m_dataPointer = src;
54 m_dataLength = 0;
55 callOnMainThread(WTF::bind(&SourceStream::prepareDataOnMainThread, this) );
56 m_condition.wait(m_mutex);
57 if (m_cancelled || m_allDataReturned)
58 return 0;
59 return m_dataLength;
60 }
61
62 void didFinishLoading()
63 {
64 ASSERT(isMainThread());
65 if (m_streamer->resource()->errorOccurred()) {
66 // The background thread might be waiting for data. Wake it up. V8
67 // will perceive this as EOS and call the completion callback. At
68 // that point we will resume the normal "script loaded" code path
69 // (see HTMLScriptRunner::notifyFinished which doesn't differentiate
70 // between scripts which loaded successfully and scripts which
71 // failed to load).
72 cancel();
73 return;
74 }
75 {
76 MutexLocker locker(m_mutex);
77 m_loadingFinished = true;
78 if (!m_backgroundThreadWaitingForData)
79 return;
80 }
81 prepareDataOnMainThread();
82 }
83
84 void didReceiveData()
85 {
86 ASSERT(isMainThread());
87 prepareDataOnMainThread();
88 }
89
90 void cancel()
91 {
92 ASSERT(isMainThread());
93 // The script is no longer needed by the upper layers. Stop streaming
94 // it. The next time GetMoreData is called (or woken up), it will return
95 // 0, which will be interpreted as EOS by V8 and the parsing will
96 // fail. ScriptStreamer::streamingComplete will be called, and at that
97 // point we will release the references to SourceStream.
98 MutexLocker locker(m_mutex);
99 m_cancelled = true;
100 m_backgroundThreadWaitingForData = false;
101 m_condition.signal();
102 }
103
104 private:
105 void prepareDataOnMainThread()
106 {
107 ASSERT(isMainThread());
108 MutexLocker locker(m_mutex);
109 if (!m_backgroundThreadWaitingForData) {
110 // It's possible that the background thread is not waiting. This
111 // happens when 1) data arrives but V8 hasn't requested data yet, 2)
112 // we have previously scheduled prepareDataOnMainThread, but some
113 // other event (load cancelled or finished) already retrieved the
114 // data and set m_backgroundThreadWaitingForData to false.
115 return;
116 }
117 // The Resource must still be alive; otherwise we should've cancelled
118 // the streaming (if we have cancelled, the background thread is not
119 // waiting).
120 ASSERT(m_streamer->resource());
121
122 if (m_streamer->resource()->cachedMetadata(V8ScriptRunner::tagForCodeCac he())) {
123 // The resource has a code cache, so it's unnecessary to stream and
124 // parse the code. Cancel the streaming and resume the non-streaming
125 // code path.
126 m_streamer->suppressStreaming();
127 m_cancelled = true;
128 m_backgroundThreadWaitingForData = false;
129 m_condition.signal();
130 return;
131 }
132
133 if (!m_resourceBuffer) {
134 // We don't have a buffer yet. Try to get it from the resource.
135 SharedBuffer* buffer = m_streamer->resource()->resourceBuffer();
136 if (!buffer) {
137 // The Resource hasn't created its SharedBuffer yet. At some
138 // point (at latest when the script loading finishes) this
139 // function will be called again and we will have data.
140 if (m_loadingFinished) {
141 // This happens only in special cases (e.g., involving empty
142 // scripts).
143 m_allDataReturned = true;
144 m_backgroundThreadWaitingForData = false;
145 m_condition.signal();
146 }
147 return;
148 }
149 m_resourceBuffer = RefPtr<SharedBuffer>(buffer);
150 }
151
152 // Get as much data from the ResourceBuffer as we can.
153 const char* data = 0;
154 Vector<const char*> chunks;
155 Vector<unsigned> chunkLengths;
156 while (unsigned length = m_resourceBuffer->getSomeData(data, m_dataPosit ion)) {
157 // FIXME: Here we can limit based on the total length, if it turns
158 // out that we don't want to give all the data we have (memory
159 // vs. speed).
160 chunks.append(data);
161 chunkLengths.append(length);
162 m_dataLength += length;
163 m_dataPosition += length;
164 }
165 // Copy the data chunks into a new buffer, since we're going to give the
166 // data to a background thread.
167 if (m_dataLength > 0) {
168 uint8_t* copiedData = new uint8_t[m_dataLength];
169 unsigned offset = 0;
170 for (size_t i = 0; i < chunks.size(); ++i) {
171 memcpy(copiedData + offset, chunks[i], chunkLengths[i]);
172 offset += chunkLengths[i];
173 }
174 *m_dataPointer = copiedData;
175 m_backgroundThreadWaitingForData = false;
176 m_condition.signal();
177 } else if (m_loadingFinished) {
178 // We need to return data length 0 to V8 to signal that the data
179 // ends.
180 m_allDataReturned = true;
181 m_backgroundThreadWaitingForData = false;
182 m_condition.signal();
183 }
184 // Otherwise, we had scheduled prepareDataOnMainThread but we didn't
185 // have any data to return. It will be scheduled again when the data
186 // arrives.
187 }
188
189 ScriptStreamer* m_streamer;
190
191 // For coordinating between the main thread and background thread tasks.
192 // Guarded by m_mutex.
193 bool m_backgroundThreadWaitingForData;
194 const uint8_t** m_dataPointer;
195 unsigned m_dataLength;
196 unsigned m_dataPosition;
197 bool m_loadingFinished;
198 bool m_cancelled;
199 bool m_allDataReturned;
200 RefPtr<SharedBuffer> m_resourceBuffer; // Only used by the main thread.
201
202 ThreadCondition m_condition;
203 Mutex m_mutex;
204 };
205
206
207 bool ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, S criptState* scriptState)
208 {
209 ASSERT(isMainThread());
210 if (!settings || !settings->v8ScriptStreamingEnabled())
211 return false;
212 if (ScriptStreamerThread::shared()->isRunningTask()) {
213 // At the moment we only have one thread for running the tasks. A new
214 // task shouldn't be queued before the running task completes, because
215 // the running task can block and wait for data from the network. At the
216 // moment we are only streaming parser blocking scripts, but this code
217 // can still be hit when multiple frames are loading simultaneously.
218 return false;
219 }
220 ScriptResource* resource = script.resource();
221 if (!resource->url().protocolIsInHTTPFamily())
222 return false;
223 if (resource->resourceToRevalidate()) {
224 // This happens e.g., during reloads. We're actually not going to load
225 // the current Resource of the PendingScript but switch to another
226 // Resource -> don't stream.
227 return false;
228 }
229 // We cannot filter out short scripts, even if we wait for the HTTP headers
230 // to arrive. In general, the web servers don't seem to send the
231 // Content-Length HTTP header for scripts.
232
233 WTF::TextEncoding textEncoding(resource->encoding());
234 const char* encodingName = textEncoding.name();
235
236 // Here's a list of encodings we can use for streaming. These are
237 // the canonical names.
238 v8::ScriptCompiler::StreamedSource::Encoding encoding;
239 if (strcmp(encodingName, "windows-1252") == 0
240 || strcmp(encodingName, "ISO-8859-1") == 0
241 || strcmp(encodingName, "US-ASCII") == 0) {
242 encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE;
243 } else if (strcmp(encodingName, "UTF-8") == 0) {
244 encoding = v8::ScriptCompiler::StreamedSource::UTF8;
245 } else {
246 // We don't stream other encodings; especially we don't stream two byte
247 // scripts to avoid the handling of byte order marks. Most scripts are
248 // Latin1 or UTF-8 anyway, so this should be enough for most real world
249 // purposes.
250 return false;
251 }
252
253 if (scriptState->contextIsValid())
254 return false;
255 ScriptState::Scope scope(scriptState);
256
257 // The Resource might go out of scope if the script is no longer needed. We
258 // will soon call PendingScript::setStreamer, which makes the PendingScript
259 // notify the ScriptStreamer when it is destroyed.
260 RefPtr<ScriptStreamer> streamer = adoptRef(new ScriptStreamer(resource, enco ding));
261
262 // Decide what kind of cached data we should produce while streaming. By
263 // default, we generate the parser cache for streamed scripts, to emulate
264 // the non-streaming behavior (seeV8ScriptRunner::compileScript).
265 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProd uceParserCache;
266 if (settings->v8CacheOptions() == V8CacheOptionsCode)
267 compileOption = v8::ScriptCompiler::kProduceCodeCache;
268 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCom piler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), compi leOption);
269 if (scriptStreamingTask) {
270 ScriptStreamingTask* task = new ScriptStreamingTask(scriptStreamingTask, streamer.get());
271 // ScriptStreamer needs to stay alive as long as the background task is
272 // running. This is taken care of with a manual ref() & deref() pair;
273 // the corresponding deref() is in streamingComplete.
274 streamer->ref();
275 script.setStreamer(streamer.release());
276 ScriptStreamerThread::shared()->postTask(task);
277 return true;
278 }
279 // Otherwise, V8 cannot stream the script.
280 return false;
281 }
282
283 void ScriptStreamer::streamingComplete()
284 {
285 ASSERT(isMainThread());
286 // It's possible that the corresponding Resource was deleted before V8
287 // finished streaming. In that case, the data or the notification is not
288 // needed.
289 if (m_detached) {
290 deref();
291 return;
292 }
293
294 // We have now streamed the whole script to V8 and it has parsed the
295 // script. We're ready for the next step: compiling and executing the
296 // script.
297 m_parsingFinished = true;
298
299 notifyFinishedToClient();
300
301 // The background thread no longer holds an implicit reference.
302 deref();
303 }
304
305 void ScriptStreamer::cancel()
306 {
307 ASSERT(isMainThread());
308 // The upper layer doesn't need the script any more, but streaming might
309 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets
310 // the control the next time. It can also be that V8 has already completed
311 // its operations and streamingComplete will be called soon.
312 m_detached = true;
313 m_resource = 0;
314 m_stream->cancel();
315 }
316
317 void ScriptStreamer::notifyAppendData(ScriptResource* resource)
318 {
319 ASSERT(isMainThread());
320 ASSERT(m_resource == resource);
321 m_stream->didReceiveData();
322 }
323
324 void ScriptStreamer::notifyFinished(Resource* resource)
325 {
326 ASSERT(isMainThread());
327 ASSERT(m_resource == resource);
328 m_stream->didFinishLoading();
329 m_loadingFinished = true;
330 notifyFinishedToClient();
331 }
332
333 ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::Str eamedSource::Encoding encoding)
334 : m_resource(resource)
335 , m_detached(false)
336 , m_stream(new SourceStream(this))
337 , m_source(m_stream, encoding) // m_source takes ownership of m_stream.
338 , m_client(0)
339 , m_loadingFinished(false)
340 , m_parsingFinished(false)
341 , m_streamingSuppressed(false)
342 {
343 }
344
345 void ScriptStreamer::notifyFinishedToClient()
346 {
347 ASSERT(isMainThread());
348 // Usually, the loading will be finished first, and V8 will still need some
349 // time to catch up. But the other way is possible too: if V8 detects a
350 // parse error, the V8 side can complete before loading has finished. Send
351 // the notification after both loading and V8 side operations have
352 // completed. Here we also check that we have a client: it can happen that a
353 // function calling notifyFinishedToClient was already scheduled in the task
354 // queue and the upper layer decided that it's not interested in the script
355 // and called removeClient.
356 if (m_loadingFinished && m_parsingFinished && m_client)
357 m_client->notifyFinished(m_resource);
358 }
359
360 } // namespace blink
OLDNEW
« 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