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/Settings.h" 15 #include "platform/SharedBuffer.h" 16 #include "public/platform/Platform.h" 17 #include "wtf/MainThread.h" 18 #include "wtf/text/TextEncodingRegistry.h" 19 20 namespace blink { 21 22 // For passing data between the main thread (producer) and the streamer thread 23 // (consumer). The main thread prepares the data (copies it from Resource) and 24 // the streamer thread feeds it to V8. 25 class SourceStreamDataQueue { 26 WTF_MAKE_NONCOPYABLE(SourceStreamDataQueue); 27 public: 28 SourceStreamDataQueue() 29 : m_finished(false) { } 30 31 ~SourceStreamDataQueue() 32 { 33 while (!m_data.isEmpty()) { 34 std::pair<const uint8_t*, size_t> next_data = m_data.takeFirst(); 35 delete[] next_data.first; 36 } 37 } 38 39 void produce(const uint8_t* data, size_t length) 40 { 41 MutexLocker locker(m_mutex); 42 m_data.append(std::make_pair(data, length)); 43 m_haveData.signal(); 44 } 45 46 void finish() 47 { 48 MutexLocker locker(m_mutex); 49 m_finished = true; 50 m_haveData.signal(); 51 } 52 53 void consume(const uint8_t** data, size_t* length) 54 { 55 MutexLocker locker(m_mutex); 56 while (!tryGetData(data, length)) 57 m_haveData.wait(m_mutex); 58 } 59 60 private: 61 bool tryGetData(const uint8_t** data, size_t* length) 62 { 63 if (!m_data.isEmpty()) { 64 std::pair<const uint8_t*, size_t> next_data = m_data.takeFirst(); 65 *data = next_data.first; 66 *length = next_data.second; 67 return true; 68 } 69 if (m_finished) { 70 *length = 0; 71 return true; 72 } 73 return false; 74 } 75 76 WTF::Deque<std::pair<const uint8_t*, size_t> > m_data; 77 bool m_finished; 78 Mutex m_mutex; 79 ThreadCondition m_haveData; 80 }; 81 82 83 // SourceStream implements the streaming interface towards V8. The main 84 // functionality is preparing the data to give to V8 on main thread, and 85 // actually giving the data (via GetMoreData which is called on a background 86 // thread). 87 class SourceStream : public v8::ScriptCompiler::ExternalSourceStream { 88 WTF_MAKE_NONCOPYABLE(SourceStream); 89 public: 90 SourceStream(ScriptStreamer* streamer) 91 : v8::ScriptCompiler::ExternalSourceStream() 92 , m_streamer(streamer) 93 , m_cancelled(false) 94 , m_dataPosition(0) { } 95 96 virtual ~SourceStream() { } 97 98 // Called by V8 on a background thread. Should block until we can return 99 // some data. 100 virtual size_t GetMoreData(const uint8_t** src) OVERRIDE 101 { 102 ASSERT(!isMainThread()); 103 { 104 MutexLocker locker(m_mutex); 105 if (m_cancelled) 106 return 0; 107 } 108 size_t length = 0; 109 // This will wait until there is data. 110 m_dataQueue.consume(src, &length); 111 { 112 MutexLocker locker(m_mutex); 113 if (m_cancelled) 114 return 0; 115 } 116 return length; 117 } 118 119 void didFinishLoading() 120 { 121 ASSERT(isMainThread()); 122 m_dataQueue.finish(); 123 } 124 125 void didReceiveData() 126 { 127 ASSERT(isMainThread()); 128 prepareDataOnMainThread(); 129 } 130 131 void cancel() 132 { 133 ASSERT(isMainThread()); 134 // The script is no longer needed by the upper layers. Stop streaming 135 // it. The next time GetMoreData is called (or woken up), it will return 136 // 0, which will be interpreted as EOS by V8 and the parsing will 137 // fail. ScriptStreamer::streamingComplete will be called, and at that 138 // point we will release the references to SourceStream. 139 { 140 MutexLocker locker(m_mutex); 141 m_cancelled = true; 142 } 143 m_dataQueue.finish(); 144 } 145 146 private: 147 void prepareDataOnMainThread() 148 { 149 ASSERT(isMainThread()); 150 // The Resource must still be alive; otherwise we should've cancelled 151 // the streaming (if we have cancelled, the background thread is not 152 // waiting). 153 ASSERT(m_streamer->resource()); 154 155 if (m_streamer->resource()->cachedMetadata(V8ScriptRunner::tagForCodeCache())) { 156 // The resource has a code cache, so it's unnecessary to stream and 157 // parse the code. Cancel the streaming and resume the non-streaming 158 // code path. 159 m_streamer->suppressStreaming(); 160 { 161 MutexLocker locker(m_mutex); 162 m_cancelled = true; 163 } 164 m_dataQueue.finish(); 165 return; 166 } 167 168 if (!m_resourceBuffer) { 169 // We don't have a buffer yet. Try to get it from the resource. 170 SharedBuffer* buffer = m_streamer->resource()->resourceBuffer(); 171 if (!buffer) 172 return; 173 m_resourceBuffer = RefPtr<SharedBuffer>(buffer); 174 } 175 176 // Get as much data from the ResourceBuffer as we can. 177 const char* data = 0; 178 Vector<const char*> chunks; 179 Vector<unsigned> chunkLengths; 180 size_t dataLength = 0; 181 while (unsigned length = m_resourceBuffer->getSomeData(data, m_dataPosition)) { 182 // FIXME: Here we can limit based on the total length, if it turns 183 // out that we don't want to give all the data we have (memory 184 // vs. speed). 185 chunks.append(data); 186 chunkLengths.append(length); 187 dataLength += length; 188 m_dataPosition += length; 189 } 190 // Copy the data chunks into a new buffer, since we're going to give the 191 // data to a background thread. 192 if (dataLength > 0) { 193 uint8_t* copiedData = new uint8_t[dataLength]; 194 unsigned offset = 0; 195 for (size_t i = 0; i < chunks.size(); ++i) { 196 memcpy(copiedData + offset, chunks[i], chunkLengths[i]); 197 offset += chunkLengths[i]; 198 } 199 m_dataQueue.produce(copiedData, dataLength); 200 } 201 } 202 203 ScriptStreamer* m_streamer; 204 205 // For coordinating between the main thread and background thread tasks. 206 // Guarded by m_mutex. 207 bool m_cancelled; 208 Mutex m_mutex; 209 210 unsigned m_dataPosition; // Only used by the main thread. 211 RefPtr<SharedBuffer> m_resourceBuffer; // Only used by the main thread. 212 SourceStreamDataQueue m_dataQueue; // Thread safe. 213 }; 214 215 size_t ScriptStreamer::kSmallScriptThreshold = 30 * 1024; 216 217 void ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, ScriptState* scriptState, PendingScript::Type scriptType) 218 { 219 // We don't yet know whether the script will really be streamed. E.g., 220 // suppressing streaming for short scripts is done later. Record only the 221 // sure negative cases here. 222 bool startedStreaming = startStreamingInternal(script, settings, scriptState, scriptType); 223 if (!startedStreaming) 224 blink::Platform::current()->histogramEnumeration(startedStreamingHistogramName(scriptType), 0, 2); 225 } 226 227 void ScriptStreamer::streamingComplete() 228 { 229 ASSERT(isMainThread()); 230 // It's possible that the corresponding Resource was deleted before V8 231 // finished streaming. In that case, the data or the notification is not 232 // needed. In addition, if the streaming is suppressed, the non-streaming 233 // code path will resume after the resource has loaded, before the 234 // background task finishes. 235 if (m_detached || m_streamingSuppressed) { 236 deref(); 237 return; 238 } 239 240 // We have now streamed the whole script to V8 and it has parsed the 241 // script. We're ready for the next step: compiling and executing the 242 // script. 243 m_parsingFinished = true; 244 245 notifyFinishedToClient(); 246 247 // The background thread no longer holds an implicit reference. 248 deref(); 249 } 250 251 void ScriptStreamer::cancel() 252 { 253 ASSERT(isMainThread()); 254 // The upper layer doesn't need the script any more, but streaming might 255 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets 256 // the control the next time. It can also be that V8 has already completed 257 // its operations and streamingComplete will be called soon. 258 m_detached = true; 259 m_resource = 0; 260 m_stream->cancel(); 261 } 262 263 void ScriptStreamer::suppressStreaming() 264 { 265 ASSERT(!m_parsingFinished); 266 ASSERT(!m_loadingFinished); 267 m_streamingSuppressed = true; 268 } 269 270 void ScriptStreamer::notifyAppendData(ScriptResource* resource) 271 { 272 ASSERT(isMainThread()); 273 ASSERT(m_resource == resource); 274 if (m_streamingSuppressed) 275 return; 276 if (!m_firstDataChunkReceived) { 277 m_firstDataChunkReceived = true; 278 const char* histogramName = startedStreamingHistogramName(m_scriptType); 279 // Check the size of the first data chunk. The expectation is that if 280 // the first chunk is small, there won't be a second one. In those 281 // cases, it doesn't make sense to stream at all. 282 if (resource->resourceBuffer()->size() < kSmallScriptThreshold) { 283 suppressStreaming(); 284 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2); 285 return; 286 } 287 if (ScriptStreamerThread::shared()->isRunningTask()) { 288 // At the moment we only have one thread for running the tasks. A 289 // new task shouldn't be queued before the running task completes, 290 // because the running task can block and wait for data from the 291 // network. At the moment we are only streaming parser blocking 292 // scripts, but this code can still be hit when multiple frames are 293 // loading simultaneously. 294 suppressStreaming(); 295 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2); 296 return; 297 } 298 ASSERT(m_task); 299 // ScriptStreamer needs to stay alive as long as the background task is 300 // running. This is taken care of with a manual ref() & deref() pair; 301 // the corresponding deref() is in streamingComplete. 302 ref(); 303 ScriptStreamingTask* task = new ScriptStreamingTask(m_task, this); 304 ScriptStreamerThread::shared()->postTask(task); 305 m_task = 0; 306 blink::Platform::current()->histogramEnumeration(histogramName, 1, 2); 307 } 308 m_stream->didReceiveData(); 309 } 310 311 void ScriptStreamer::notifyFinished(Resource* resource) 312 { 313 ASSERT(isMainThread()); 314 ASSERT(m_resource == resource); 315 // A special case: empty scripts. We didn't receive any data before this 316 // notification. In that case, there won't be a "parsing complete" 317 // notification either, and we should not wait for it. 318 if (!m_firstDataChunkReceived) 319 suppressStreaming(); 320 m_stream->didFinishLoading(); 321 m_loadingFinished = true; 322 notifyFinishedToClient(); 323 } 324 325 ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::StreamedSource::Encoding encoding, PendingScript::Type scriptType) 326 : m_resource(resource) 327 , m_detached(false) 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_task(0) 332 , m_loadingFinished(false) 333 , m_parsingFinished(false) 334 , m_firstDataChunkReceived(false) 335 , m_streamingSuppressed(false) 336 , m_scriptType(scriptType) 337 { 338 } 339 340 void ScriptStreamer::notifyFinishedToClient() 341 { 342 ASSERT(isMainThread()); 343 // Usually, the loading will be finished first, and V8 will still need some 344 // time to catch up. But the other way is possible too: if V8 detects a 345 // parse error, the V8 side can complete before loading has finished. Send 346 // the notification after both loading and V8 side operations have 347 // completed. Here we also check that we have a client: it can happen that a 348 // function calling notifyFinishedToClient was already scheduled in the task 349 // queue and the upper layer decided that it's not interested in the script 350 // and called removeClient. 351 if (isFinished() && m_client) 352 m_client->notifyFinished(m_resource); 353 } 354 355 const char* ScriptStreamer::startedStreamingHistogramName(PendingScript::Type scriptType) 356 { 357 switch (scriptType) { 358 case PendingScript::ParsingBlocking: 359 return "WebCore.Scripts.ParsingBlocking.StartedStreaming"; 360 break; 361 case PendingScript::Deferred: 362 return "WebCore.Scripts.Deferred.StartedStreaming"; 363 break; 364 case PendingScript::Async: 365 return "WebCore.Scripts.Async.StartedStreaming"; 366 break; 367 default: 368 ASSERT_NOT_REACHED(); 369 break; 370 } 371 return 0; 372 } 373 374 bool ScriptStreamer::startStreamingInternal(PendingScript& script, Settings* settings, ScriptState* scriptState, PendingScript::Type scriptType) 375 { 376 ASSERT(isMainThread()); 377 if (!settings || !settings->v8ScriptStreamingEnabled()) 378 return false; 379 ScriptResource* resource = script.resource(); 380 ASSERT(!resource->isLoaded()); 381 if (!resource->url().protocolIsInHTTPFamily()) 382 return false; 383 if (resource->resourceToRevalidate()) { 384 // This happens e.g., during reloads. We're actually not going to load 385 // the current Resource of the PendingScript but switch to another 386 // Resource -> don't stream. 387 return false; 388 } 389 // We cannot filter out short scripts, even if we wait for the HTTP headers 390 // to arrive. In general, the web servers don't seem to send the 391 // Content-Length HTTP header for scripts. 392 393 WTF::TextEncoding textEncoding(resource->encoding()); 394 const char* encodingName = textEncoding.name(); 395 396 // Here's a list of encodings we can use for streaming. These are 397 // the canonical names. 398 v8::ScriptCompiler::StreamedSource::Encoding encoding; 399 if (strcmp(encodingName, "windows-1252") == 0 400 || strcmp(encodingName, "ISO-8859-1") == 0 401 || strcmp(encodingName, "US-ASCII") == 0) { 402 encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE; 403 } else if (strcmp(encodingName, "UTF-8") == 0) { 404 encoding = v8::ScriptCompiler::StreamedSource::UTF8; 405 } else { 406 // We don't stream other encodings; especially we don't stream two byte 407 // scripts to avoid the handling of byte order marks. Most scripts are 408 // Latin1 or UTF-8 anyway, so this should be enough for most real world 409 // purposes. 410 return false; 411 } 412 413 if (scriptState->contextIsValid()) 414 return false; 415 ScriptState::Scope scope(scriptState); 416 417 // The Resource might go out of scope if the script is no longer needed. We 418 // will soon call PendingScript::setStreamer, which makes the PendingScript 419 // notify the ScriptStreamer when it is destroyed. 420 RefPtr<ScriptStreamer> streamer = adoptRef(new ScriptStreamer(resource, encoding, scriptType)); 421 422 // Decide what kind of cached data we should produce while streaming. By 423 // default, we generate the parser cache for streamed scripts, to emulate 424 // the non-streaming behavior (see V8ScriptRunner::compileScript). 425 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProduceParserCache; 426 if (settings->v8CacheOptions() == V8CacheOptionsCode) 427 compileOption = v8::ScriptCompiler::kProduceCodeCache; 428 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCompiler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), compileOption); 429 if (scriptStreamingTask) { 430 streamer->m_task = scriptStreamingTask; 431 script.setStreamer(streamer.release()); 432 return true; 433 } 434 // Otherwise, V8 cannot stream the script. 435 return false; 436 } 437 438 } // namespace blink 439