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

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

Powered by Google App Engine
This is Rietveld 408576698