Chromium Code Reviews| 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 |