OLD | NEW |
---|---|
(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 | |
OLD | NEW |