Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 | |
| OLD | NEW |