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

Side by Side Diff: Source/bindings/core/v8/V8ScriptStreamer.cpp

Issue 368283002: Stream scripts to V8 as they load - Blink side. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: renaming Created 6 years, 4 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 /*
2 * Copyright (C) 2014 Google Inc. All rights reserved.
haraken 2014/08/17 16:05:27 Nit: Use the 3-line copyright.
marja 2014/08/20 11:45:55 Done.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "bindings/core/v8/V8ScriptStreamer.h"
28
29 #include "bindings/core/v8/V8ScriptRunner.h"
30 #include "bindings/core/v8/V8ScriptStreamerThread.h"
31 #include "core/dom/Document.h"
32 #include "core/dom/Element.h"
33 #include "core/dom/PendingScript.h"
34 #include "core/fetch/ScriptResource.h"
35 #include "core/frame/LocalFrame.h"
36 #include "core/frame/Settings.h"
37 #include "core/html/parser/HTMLScriptRunner.h"
38 #include "platform/SharedBuffer.h"
39 #include "wtf/MainThread.h"
40 #include "wtf/text/TextEncodingRegistry.h"
41
42 #include <string.h>
43
44 namespace blink {
45
46 class LocalFrame;
47
48 class SourceStream : public v8::ExternalSourceStream {
haraken 2014/08/17 16:05:26 (I haven't reviewed this class yet. I'll take a cl
marja 2014/08/20 11:45:55 Acknowledged.
49 public:
50 SourceStream(ScriptResource* resource)
51 : m_resource(resource)
52 , m_backgroundThreadWaitingForData(false)
53 , m_dataPointer(0)
54 , m_dataLength(0)
55 , m_dataPosition(0)
56 , m_finished(false)
57 , m_cancelled(false) { }
58
59 virtual ~SourceStream() { }
60
61 // Called by V8 on a background thread. Should block until we can return
62 // some data.
63 virtual unsigned GetMoreData(const char** src) OVERRIDE
64 {
65 MutexLocker locker(m_mutex);
66 if (m_cancelled) {
67 return 0;
68 }
69 // The main thread might be modifying the data buffer of the Resource,
70 // so we cannot read it here. Post a task to the main thread and wait
71 // for it to complete.
72 m_backgroundThreadWaitingForData = true;
73 m_dataPointer = src;
74 m_dataLength = 0;
75 callOnMainThread(WTF::bind(&SourceStream::getMoreDataOnMainThread, this) );
76 m_condition.wait(m_mutex);
77 if (m_cancelled) {
78 return 0;
79 }
80 return m_dataLength;
81 }
82
83 // Called on the main thread.
84 void didFinishLoading()
85 {
86 if (m_resource->errorOccurred()) {
87 // The background thread might be waiting for data. Wake it up. V8
88 // will perceive this as EOS and call the completion callback. At
89 // that point we will resume the normal "script loaded" code path
90 // (see HTMLScriptRunner::notifyFinished which doesn't differentiate
91 // between scripts which loaded successfully and scripts which
92 // failed to load).
93 cancel();
94 return;
95 }
96 {
97 MutexLocker locker(m_mutex);
98 m_finished = true;
99 if (!m_backgroundThreadWaitingForData)
100 return;
101 }
102 getMoreDataOnMainThread();
103 }
104
105 // Called on the main thread.
106 void didReceiveData()
107 {
108 getMoreDataOnMainThread();
109 }
110
111 // Called on the main thread.
112 void cancel()
113 {
114 // The script is no longer needed. Stop streaming it. The next time
115 // GetMoreData is called (or woken up), it will return 0, which will be
116 // interpreted as EOS by V8 and the parsing will fail. Our "streaming
117 // complete" callback will be called, and at that point we will release
118 // the references to SourceStream.
119 MutexLocker locker(m_mutex);
120 m_cancelled = true;
121 m_backgroundThreadWaitingForData = false;
122 m_condition.signal();
123 }
124
125 private:
126 // Called on the main thread.
127 void getMoreDataOnMainThread()
128 {
129 MutexLocker locker(m_mutex);
130 if (!m_backgroundThreadWaitingForData) {
131 // It's possible that the background thread is not waiting. This
132 // happens when 1) data arrives but V8 hasn't requested data yet, 2)
133 // we have previously scheduled getMoreDataOnMainThread, but some
134 // other event (load cancelled or finished) already retrieved the
135 // data and set m_backgroundThreadWaitingForData to false.
136 return;
137 }
138 if (!m_resourceBuffer) {
139 // We don't have a buffer yet. Try to get it from the resource.
140 SharedBuffer* buffer = m_resource->resourceBuffer();
141 if (!buffer) {
142 // The Resource hasn't created its SharedBuffer yet. At some
143 // point (at latest when the script loading finishes) this
144 // function will be called again and we will have data.
145 if (m_finished) {
146 // The load finished and we never had a buffer. In normal
147 // operations, this shouldn't happen, but it can probably
148 // happen for empty scripts.
149 m_backgroundThreadWaitingForData = false;
150 m_condition.signal();
151 }
152 return;
153 }
154 m_resourceBuffer = RefPtr<SharedBuffer>(buffer);
155 }
156
157 if (m_dataPosition == 0) {
158 // This is the first time data is requested. Check the encoding.
159 const char* encoding = WTF::atomicCanonicalTextEncodingName(m_resour ce->encoding());
160
161 // Here's a list of encodings we can use for streaming. These are
162 // the canonical names.
163 if (strcmp(encoding, "windows-1252") == 0
164 || strcmp(encoding, "ISO-8859-1") == 0
165 || strcmp(encoding, "US-ASCII") == 0) {
166 SetEncoding(v8::ExternalSourceStream::ONE_BYTE);
167 } else if (strcmp(encoding, "UTF-8") == 0) {
168 SetEncoding(v8::ExternalSourceStream::UTF8);
169 } else {
170 // Otherwise, cancel the streaming. We don't stream two byte
171 // scripts to avoid the handling of byte order marks. Most
172 // scripts are Latin1 or UTF-8 anyway, so this should be enough
173 // for most real world purposes.
174 m_cancelled = true;
175 m_backgroundThreadWaitingForData = false;
176 m_condition.signal();
177 }
178 }
179
180 // Get as much data from the ResourceBuffer as we can.
181 const char* data = 0;
182 Vector<const char*> chunks;
183 Vector<unsigned> chunkLengths;
184 while (unsigned length = m_resourceBuffer->getSomeData(data, m_dataPosit ion)) {
185 // TODO(marja): Here we can limit based on the total length, if it
haraken 2014/08/17 16:05:27 Nit: TODO(marja) => FIXME
marja 2014/08/20 11:45:55 Done.
186 // turns out that we don't want to give all the data we have (memory
187 // vs. speed).
188 chunks.append(data);
189 chunkLengths.append(length);
190 m_dataLength += length;
191 m_dataPosition += length;
192 }
193 // Copy the data chunks into a new buffer, since we're going to give the
194 // data to a background thread.
195 if (m_dataLength > 0) {
196 char* copiedData = new char[m_dataLength];
197 unsigned offset = 0;
198 for (size_t i = 0; i < chunks.size(); ++i) {
199 memcpy(copiedData + offset, chunks[i], chunkLengths[i]);
200 offset += chunkLengths[i];
201 }
202 *m_dataPointer = copiedData;
203 m_backgroundThreadWaitingForData = false;
204 m_condition.signal();
205 } else if (m_finished) {
206 // We need to return data length 0 to V8 to signal that the data
207 // ends.
208 m_condition.signal();
209 }
210 // Otherwise, we had scheduled getMoreDataOnMainThread but we didn't
211 // have any data to return. It will be scheduled again when the data
212 // arrives.
213 }
214
215 ScriptResource* m_resource;
216
217 // For coordinating between the main thread and background thread tasks.
218 // Guarded by m_mutex.
219 bool m_backgroundThreadWaitingForData;
220 const char** m_dataPointer;
221 unsigned m_dataLength;
222 unsigned m_dataPosition;
223 bool m_finished;
224 bool m_cancelled;
225 RefPtr<SharedBuffer> m_resourceBuffer; // Only used by the main thread.
226
227 ThreadCondition m_condition;
228 Mutex m_mutex;
229 };
230
231
232 bool V8ScriptStreamer::startStreaming(PendingScript* script, HTMLScriptRunner* r unner)
233 {
234 if (V8ScriptStreamerThread::shared()->isRunningTask()) {
haraken 2014/08/17 16:05:26 Given that we currently support only parser-blocki
marja 2014/08/20 11:45:55 It can still be that we're already running a task.
235 // At the moment we only have one thread for running the tasks. A new
236 // task shouldn't be queued before the running task completes, because
237 // the running task can block and wait for data from the network.
haraken 2014/08/17 16:05:26 Just to confirm: Is this the reason it's not easy
marja 2014/08/20 11:45:55 There are several modifications needed when we wan
haraken 2014/08/20 15:00:18 Understood.
238 return false;
239 }
240 ScriptResource* resource = script->resource();
241 if (!resource->url().protocolIsInHTTPFamily()) {
242 return false;
243 }
244 // We cannot filter out short scripts, even if we wait for the HTTP headers
245 // to arrive. In general, the web servers don't seem to send the
246 // Content-Length HTTP header for scripts.
247
248 LocalFrame* frame = script->element()->document().frame();
249 ScriptState* scriptState = ScriptState::forMainWorld(frame);
haraken 2014/08/17 16:05:26 Just to confirm: Script streaming is used only in
marja 2014/08/20 11:45:55 Only for parser blocking scripts of HTMLScriptRunn
haraken 2014/08/20 15:00:18 Yes, I believe so. I also believe that non-parser-
250 if (scriptState->contextIsEmpty())
251 return false;
252 ScriptState::Scope scope(scriptState);
253
254 // script is a pointer to m_parserBlockingScript of HTMLScriptRunner, which
255 // might go out of scope if HTMLScriptRunner is destroyed. We will soon call
256 // PendingScript::setStreamer, which makes the PendingScript notify the
257 // V8ScriptStreamer when it is destroyed.
258 RefPtr<V8ScriptStreamer> streamer = adoptRef(new V8ScriptStreamer(script, ru nner));
259
260 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kNoCo mpileOptions;
261 if (frame->settings()) {
262 V8CacheOptions v8CacheOptions = frame->settings()->v8CacheOptions();
263 bool ignore;
264 V8ScriptRunner::CacheDecider(0, resource, v8CacheOptions, &streamer->m_c achedDataDataType, &compileOption, &ignore);
haraken 2014/08/17 16:05:25 I don't fully understand why you can decide whethe
marja 2014/08/20 11:45:55 (As discussed offline), this code path only decide
265 }
266 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCom piler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), compi leOption);
267 if (scriptStreamingTask) {
268 // The background thread holds an implicit reference to streamer,
269 // because it's passed as a callback parameter. The corresponding deref
270 // is in V8ScriptStreamer::streamingComplete().
271 ScriptStreamingTask* task = new ScriptStreamingTask(scriptStreamingTask, streamer.get());
272 streamer->ref();
273 script->setStreamer(streamer.release());
274 V8ScriptStreamerThread::shared()->postTask(task);
275 return true;
276 }
277 return false;
278 }
279
280 void V8ScriptStreamer::notifyAppendData()
281 {
282 m_stream->didReceiveData();
283 }
284
285 void V8ScriptStreamer::notifyFinished()
286 {
287 m_stream->didFinishLoading();
288 m_loadingDone = true;
289 // The load complete notification can come before or after V8 has processed
290 // all the data. Send the notification after both loading and parsing have
291 // completed.
haraken 2014/08/17 16:05:26 Can we create a helper method like notifyFinishedT
haraken 2014/08/17 16:05:26 Help me understand: In what scenario is it possibl
marja 2014/08/20 11:45:55 Done.
marja 2014/08/20 11:45:55 The V8 side can finish before loading e.g., when V
292 if (m_v8Done) {
293 m_runner->notifyFinished(m_script->resource());
294 }
295 }
296
297 void V8ScriptStreamer::streamingComplete()
298 {
299 // It's possible that the corresponding PendingScript was deleted before V8
300 // finished streaming.
301 if (refCount() == 1) {
haraken 2014/08/17 16:05:25 It's fragile to rely on the reference counter to j
marja 2014/08/20 11:45:55 Done (I ended up needing a more explicit way anywa
302 deref();
303 return;
304 }
305
306 // We have now streamed the whole script to V8 and it has parsed the
307 // script. We're ready for the next step: compiling and executing the
308 // script.
309 m_v8Done = true;
310 // V8 no longer holds an implicit reference.
311 deref();
312
313 // The load complete notification can come before or after V8 has processed
314 // all the data. Send the notification after both loading and parsing have
315 // completed.
haraken 2014/08/17 16:05:26 Ditto. Use notifyFinishedToHTMLScriptRunner().
marja 2014/08/20 11:45:55 Done.
316 if (m_loadingDone) {
317 m_runner->notifyFinished(m_script->resource());
318 }
319 }
320
321 void V8ScriptStreamer::cancel()
322 {
323 m_script = 0;
324 m_runner = 0;
325 // The streaming might still be ongoing. Tell SourceStream to try to cancel
326 // it whenever it gets the control the next time. It can also be that V8 has
327 // already completed its operations and our callback will be called soon.
328 m_stream->cancel();
329 }
330
331 V8ScriptStreamer::V8ScriptStreamer(PendingScript* script, HTMLScriptRunner* runn er)
332 : m_script(script)
333 , m_runner(runner)
334 , m_stream(new SourceStream(script->resource()))
335 , m_source(m_stream) // Source takes ownership of m_stream.
haraken 2014/08/17 16:05:26 BTW, "StreamedSource" and "SourceStream" are confu
haraken 2014/08/17 16:05:26 Given that StreamedSource has an ownership of the
marja 2014/08/20 11:45:55 I could add an accessor for getting the ExternalSo
marja 2014/08/20 11:45:55 By StreamedSource you mean v8::ScriptCompiler::Str
haraken 2014/08/20 15:00:18 OK, then let's keep your current code as is.
haraken 2014/08/20 15:00:18 Got it; StreamedSource makes sense. However, it's
marja 2014/09/09 17:00:26 I tried to think of the naming a bit, but didn't f
336 , m_loadingDone(false)
337 , m_v8Done(false)
338 { }
haraken 2014/08/17 16:05:26 Nit: Write { and } in separate lines.
marja 2014/08/20 11:45:55 Done.
339
340 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698