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

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: cleanup 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/LocalFrame.h"
15 #include "core/frame/Settings.h"
16 #include "platform/SharedBuffer.h"
17 #include "wtf/MainThread.h"
18 #include "wtf/text/TextEncodingRegistry.h"
19
20 namespace blink {
21
22 // SourceStream implements the streaming interface towards V8. The main
23 // functionality is preparing the data to give to V8 on main thread, and
24 // actually giving the data (via GetMoreData which is called on a background
25 // thread).
26 class SourceStream : public v8::ScriptCompiler::ExternalSourceStream {
haraken 2014/09/10 05:57:51 I'd like to have V8 reviewers to take a look at th
marja 2014/09/11 09:15:37 Acknowledged; will ask jochen@.
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
haraken 2014/09/10 05:57:51 Can we add an ASSERT about the thread correctness?
marja 2014/09/11 09:15:36 Done.
42 // some data.
43 virtual size_t GetMoreData(const uint8_t** src) OVERRIDE
44 {
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 // Called on the main thread.
haraken 2014/09/10 05:57:51 ASSERT(isMainThread()). The same comment for other
marja 2014/09/11 09:15:36 Done.
64 void didFinishLoading()
65 {
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 prepareDataOnMainThread();
83 }
84
85 // Called on the main thread.
86 void didReceiveData()
87 {
88 prepareDataOnMainThread();
89 }
90
91 // Called on the main thread.
92 void cancel()
93 {
94 // The script is no longer needed by the upper layers. Stop streaming
95 // it. The next time GetMoreData is called (or woken up), it will return
96 // 0, which will be interpreted as EOS by V8 and the parsing will
97 // fail. ScriptStreamer::streamingComplete will be called, and at that
98 // point we will release the references to SourceStream.
99 MutexLocker locker(m_mutex);
100 m_cancelled = true;
101 m_backgroundThreadWaitingForData = false;
102 m_condition.signal();
103 }
104
105 private:
106 // Called on the main thread.
107 void prepareDataOnMainThread()
108 {
109 MutexLocker locker(m_mutex);
110 if (!m_backgroundThreadWaitingForData) {
111 // It's possible that the background thread is not waiting. This
112 // happens when 1) data arrives but V8 hasn't requested data yet, 2)
113 // we have previously scheduled prepareDataOnMainThread, but some
114 // other event (load cancelled or finished) already retrieved the
115 // data and set m_backgroundThreadWaitingForData to false.
116 return;
117 }
118 // The Resource must still be alive; otherwise we should've cancelled
119 // the streaming (if we have cancelled, the background thread is not
120 // waiting).
121 ASSERT(m_streamer->resource());
122
123 if (m_streamer->resource()->cachedMetadata(V8ScriptRunner::tagForCodeCac he())) {
124 // The resource has a code cache, so it's unnecessary to stream and
125 // parse the code. Cancel the streaming and resume the non-streaming
126 // code path.
127 m_streamer->suppressStreaming();
128 m_cancelled = true;
129 m_backgroundThreadWaitingForData = false;
130 m_condition.signal();
131 return;
132 }
133
134 if (!m_resourceBuffer) {
135 // We don't have a buffer yet. Try to get it from the resource.
136 SharedBuffer* buffer = m_streamer->resource()->resourceBuffer();
137 if (!buffer) {
138 // The Resource hasn't created its SharedBuffer yet. At some
139 // point (at latest when the script loading finishes) this
140 // function will be called again and we will have data.
141 if (m_loadingFinished) {
142 // The load finished and we never had a buffer. This happens
143 // e.g., when reloading. In this case, suppress the
144 // streaming and let the non-streaming code path handle
145 // compiling the script.
146 m_streamer->suppressStreaming();
147 m_cancelled = true;
148 m_backgroundThreadWaitingForData = false;
149 m_condition.signal();
150 }
151 return;
152 }
153 m_resourceBuffer = RefPtr<SharedBuffer>(buffer);
154 }
155
156 // Get as much data from the ResourceBuffer as we can.
157 const char* data = 0;
158 Vector<const char*> chunks;
159 Vector<unsigned> chunkLengths;
160 while (unsigned length = m_resourceBuffer->getSomeData(data, m_dataPosit ion)) {
161 // FIXME: Here we can limit based on the total length, if it turns
162 // out that we don't want to give all the data we have (memory
163 // vs. speed).
164 chunks.append(data);
165 chunkLengths.append(length);
166 m_dataLength += length;
167 m_dataPosition += length;
168 }
169 // Copy the data chunks into a new buffer, since we're going to give the
170 // data to a background thread.
171 if (m_dataLength > 0) {
172 uint8_t* copiedData = new uint8_t[m_dataLength];
173 unsigned offset = 0;
174 for (size_t i = 0; i < chunks.size(); ++i) {
175 memcpy(copiedData + offset, chunks[i], chunkLengths[i]);
176 offset += chunkLengths[i];
177 }
178 *m_dataPointer = copiedData;
179 m_backgroundThreadWaitingForData = false;
180 m_condition.signal();
181 } else if (m_loadingFinished) {
182 // We need to return data length 0 to V8 to signal that the data
183 // ends.
184 m_allDataReturned = true;
185 m_condition.signal();
186 }
187 // Otherwise, we had scheduled prepareDataOnMainThread but we didn't
188 // have any data to return. It will be scheduled again when the data
189 // arrives.
190 }
191
192 ScriptStreamer* m_streamer;
193
194 // For coordinating between the main thread and background thread tasks.
195 // Guarded by m_mutex.
196 bool m_backgroundThreadWaitingForData;
197 const uint8_t** m_dataPointer;
198 unsigned m_dataLength;
199 unsigned m_dataPosition;
200 bool m_loadingFinished;
201 bool m_cancelled;
202 bool m_allDataReturned;
203 RefPtr<SharedBuffer> m_resourceBuffer; // Only used by the main thread.
204
205 ThreadCondition m_condition;
206 Mutex m_mutex;
207 };
208
209
210 bool ScriptStreamer::startStreaming(PendingScript& script)
211 {
212 LocalFrame* frame = script.element()->document().frame();
213 if (!frame->settings() || !frame->settings()->v8ScriptStreamingEnabled()) {
214 return false;
215 }
216 if (ScriptStreamerThread::shared()->isRunningTask()) {
217 // At the moment we only have one thread for running the tasks. A new
218 // task shouldn't be queued before the running task completes, because
219 // the running task can block and wait for data from the network. At the
220 // moment we are only streaming parser blocking scripts, but this code
221 // can still be hit when multiple frames are loading simultaneously.
222 return false;
223 }
224 ScriptResource* resource = script.resource();
225 if (!resource->url().protocolIsInHTTPFamily()) {
haraken 2014/09/10 05:57:51 Just help me understand: What is this check for?
marja 2014/09/11 09:15:37 So that we don't stream e.g., file:///stuff.
226 return false;
227 }
228 // We cannot filter out short scripts, even if we wait for the HTTP headers
229 // to arrive. In general, the web servers don't seem to send the
230 // Content-Length HTTP header for scripts.
231
232 const char* encodingName = WTF::atomicCanonicalTextEncodingName(resource->en coding());
233
234 // Here's a list of encodings we can use for streaming. These are
235 // the canonical names.
236 v8::ScriptCompiler::StreamedSource::Encoding encoding;
237 if (strcmp(encodingName, "windows-1252") == 0
238 || strcmp(encodingName, "ISO-8859-1") == 0
239 || strcmp(encodingName, "US-ASCII") == 0) {
240 encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE;
241 } else if (strcmp(encodingName, "UTF-8") == 0) {
242 encoding = v8::ScriptCompiler::StreamedSource::UTF8;
243 } else {
244 // We don't stream other encodings; especially we don't stream two byte
245 // scripts to avoid the handling of byte order marks. Most scripts are
246 // Latin1 or UTF-8 anyway, so this should be enough for most real world
247 // purposes.
haraken 2014/09/10 05:57:51 If the script has a comment written in Japanese, i
marja 2014/09/11 09:15:36 Unless it's UTF-8 :)
248 return false;
249 }
250
251 ScriptState* scriptState = ScriptState::forMainWorld(frame);
252 if (scriptState->contextIsValid()) {
253 return false;
254 }
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 (frame->settings()->v8CacheOptions() == V8CacheOptionsCode) {
267 compileOption = v8::ScriptCompiler::kProduceCodeCache;
268 }
269 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCom piler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), v8::S criptCompiler::kProduceParserCache);
270 if (scriptStreamingTask) {
haraken 2014/09/10 05:57:51 When can scriptStreamingTask be 0?
marja 2014/09/11 09:15:37 When V8 refuses streaming; added a comment about t
271 // The background thread holds an implicit reference to streamer, since
272 // it's running a task which uses it. Add the ref manually here. The
273 // corresponding deref is in ScriptStreamer::streamingComplete().
274 ScriptStreamingTask* task = new ScriptStreamingTask(scriptStreamingTask, streamer.get());
275 streamer->ref();
276 script.setStreamer(streamer.release());
277 ScriptStreamerThread::shared()->postTask(task);
278 return true;
279 }
280 return false;
281 }
282
283 void ScriptStreamer::streamingComplete()
284 {
285 // It's possible that the corresponding Resource was deleted before V8
286 // finished streaming. In that case, the data or the notification is not
287 // needed.
288 if (!m_resource) {
289 deref();
290 return;
291 }
292
293 // We have now streamed the whole script to V8 and it has parsed the
294 // script. We're ready for the next step: compiling and executing the
295 // script.
296 m_v8Done = true;
297
298 notifyFinishedToClient();
299
300 // The background thread no longer holds an implicit reference.
301 deref();
302 }
303
304 void ScriptStreamer::cancel()
305 {
306 // The upper layer doesn't need the script any more, but streaming might
307 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets
308 // the control the next time. It can also be that V8 has already completed
309 // its operations and streamingComplete will be called soon.
310 m_resource = 0;
haraken 2014/09/10 05:57:51 As mentioned in the header file, it would be bette
marja 2014/09/11 09:15:36 Here the m_resource == 0 was carrying a special me
311 m_stream->cancel();
312 }
313
314 void ScriptStreamer::notifyAppendData()
315 {
316 m_stream->didReceiveData();
317 }
318
319 void ScriptStreamer::notifyFinished()
320 {
321 m_stream->didFinishLoading();
322 m_loadingFinished = true;
323 notifyFinishedToClient();
324 }
325
326 ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::Str eamedSource::Encoding encoding)
327 : m_resource(resource)
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_loadingFinished(false)
332 , m_v8Done(false)
333 , m_streamingSuppressed(false)
334 {
335 }
336
337 void ScriptStreamer::notifyFinishedToClient()
338 {
339 // Usually, the loading will be finished first, and V8 will still need some
340 // time to catch up. But the other way is possible too: if V8 detects a
341 // parse error, the V8 side can complete before loading has finished. Send
342 // the notification after both loading and V8 side operations have
343 // completed. Here we also check that we have a client: it can happen that a
344 // function calling notifyFinishedToClient was already scheduled in the task
345 // queue and the upper layer decided that it's not interested in the script
346 // and called removeClient.
347 if (m_loadingFinished && m_v8Done && m_client) {
348 m_client->notifyFinished(m_resource);
349 }
350 }
351
352 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698