| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "config.h" | 5 #include "config.h" |
| 6 #include "bindings/core/v8/ScriptStreamer.h" | 6 #include "bindings/core/v8/ScriptStreamer.h" |
| 7 | 7 |
| 8 #include "bindings/core/v8/ScriptStreamerThread.h" | 8 #include "bindings/core/v8/ScriptStreamerThread.h" |
| 9 #include "bindings/core/v8/V8ScriptRunner.h" | 9 #include "bindings/core/v8/V8ScriptRunner.h" |
| 10 #include "core/dom/Document.h" | 10 #include "core/dom/Document.h" |
| 11 #include "core/dom/Element.h" | 11 #include "core/dom/Element.h" |
| 12 #include "core/dom/PendingScript.h" | 12 #include "core/dom/PendingScript.h" |
| 13 #include "core/fetch/ScriptResource.h" | 13 #include "core/fetch/ScriptResource.h" |
| 14 #include "core/frame/Settings.h" | 14 #include "core/frame/Settings.h" |
| 15 #include "platform/SharedBuffer.h" | 15 #include "platform/SharedBuffer.h" |
| 16 #include "platform/TraceEvent.h" |
| 16 #include "public/platform/Platform.h" | 17 #include "public/platform/Platform.h" |
| 17 #include "wtf/MainThread.h" | 18 #include "wtf/MainThread.h" |
| 18 #include "wtf/text/TextEncodingRegistry.h" | 19 #include "wtf/text/TextEncodingRegistry.h" |
| 19 | 20 |
| 20 namespace blink { | 21 namespace blink { |
| 21 | 22 |
| 22 // For passing data between the main thread (producer) and the streamer thread | 23 // 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 // (consumer). The main thread prepares the data (copies it from Resource) and |
| 24 // the streamer thread feeds it to V8. | 25 // the streamer thread feeds it to V8. |
| 25 class SourceStreamDataQueue { | 26 class SourceStreamDataQueue { |
| (...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 void ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, S
criptState* scriptState, PendingScript::Type scriptType) | 218 void ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, S
criptState* scriptState, PendingScript::Type scriptType) |
| 218 { | 219 { |
| 219 // We don't yet know whether the script will really be streamed. E.g., | 220 // 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 // suppressing streaming for short scripts is done later. Record only the |
| 221 // sure negative cases here. | 222 // sure negative cases here. |
| 222 bool startedStreaming = startStreamingInternal(script, settings, scriptState
, scriptType); | 223 bool startedStreaming = startStreamingInternal(script, settings, scriptState
, scriptType); |
| 223 if (!startedStreaming) | 224 if (!startedStreaming) |
| 224 blink::Platform::current()->histogramEnumeration(startedStreamingHistogr
amName(scriptType), 0, 2); | 225 blink::Platform::current()->histogramEnumeration(startedStreamingHistogr
amName(scriptType), 0, 2); |
| 225 } | 226 } |
| 226 | 227 |
| 227 void ScriptStreamer::streamingComplete() | 228 void ScriptStreamer::streamingCompleteOnBackgroundThread() |
| 228 { | 229 { |
| 229 ASSERT(isMainThread()); | 230 ASSERT(!isMainThread()); |
| 230 // It's possible that the corresponding Resource was deleted before V8 | 231 MutexLocker locker(m_mutex); |
| 231 // finished streaming. In that case, the data or the notification is not | 232 m_parsingFinished = true; |
| 232 // needed. In addition, if the streaming is suppressed, the non-streaming | 233 // In the blocking case, the main thread is normally waiting at this |
| 233 // code path will resume after the resource has loaded, before the | 234 // point, but it can also happen that the load is not yet finished |
| 234 // background task finishes. | 235 // (e.g., a parse error). In that case, notifyFinished will be called |
| 235 if (m_detached || m_streamingSuppressed) { | 236 // eventually and it will not wait on m_parsingFinishedCondition. |
| 236 deref(); | 237 |
| 237 return; | 238 // In the non-blocking case, notifyFinished might already be called, or it |
| 239 // might be called in the future. In any case, do the cleanup here. |
| 240 if (m_mainThreadWaitingForParserThread) { |
| 241 m_parsingFinishedCondition.signal(); |
| 242 } else { |
| 243 callOnMainThread(WTF::bind(&ScriptStreamer::streamingComplete, this)); |
| 238 } | 244 } |
| 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 } | 245 } |
| 250 | 246 |
| 251 void ScriptStreamer::cancel() | 247 void ScriptStreamer::cancel() |
| 252 { | 248 { |
| 253 ASSERT(isMainThread()); | 249 ASSERT(isMainThread()); |
| 254 // The upper layer doesn't need the script any more, but streaming might | 250 // 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 | 251 // 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 | 252 // the control the next time. It can also be that V8 has already completed |
| 257 // its operations and streamingComplete will be called soon. | 253 // its operations and streamingComplete will be called soon. |
| 258 m_detached = true; | 254 m_detached = true; |
| 259 m_resource = 0; | 255 m_resource = 0; |
| 260 m_stream->cancel(); | 256 m_stream->cancel(); |
| 261 } | 257 } |
| 262 | 258 |
| 263 void ScriptStreamer::suppressStreaming() | 259 void ScriptStreamer::suppressStreaming() |
| 264 { | 260 { |
| 265 ASSERT(!m_parsingFinished); | 261 MutexLocker locker(m_mutex); |
| 266 ASSERT(!m_loadingFinished); | 262 ASSERT(!m_loadingFinished); |
| 263 // It can be that the parsing task has already finished (e.g., if there was |
| 264 // a parse error). |
| 267 m_streamingSuppressed = true; | 265 m_streamingSuppressed = true; |
| 268 } | 266 } |
| 269 | 267 |
| 270 void ScriptStreamer::notifyAppendData(ScriptResource* resource) | 268 void ScriptStreamer::notifyAppendData(ScriptResource* resource) |
| 271 { | 269 { |
| 272 ASSERT(isMainThread()); | 270 ASSERT(isMainThread()); |
| 273 ASSERT(m_resource == resource); | 271 ASSERT(m_resource == resource); |
| 274 if (m_streamingSuppressed) | 272 { |
| 275 return; | 273 MutexLocker locker(m_mutex); |
| 274 if (m_streamingSuppressed) |
| 275 return; |
| 276 } |
| 276 if (!m_haveEnoughDataForStreaming) { | 277 if (!m_haveEnoughDataForStreaming) { |
| 277 // Even if the first data chunk is small, the script can still be big | 278 // Even if the first data chunk is small, the script can still be big |
| 278 // enough - wait until the next data chunk comes before deciding whether | 279 // enough - wait until the next data chunk comes before deciding whether |
| 279 // to start the streaming. | 280 // to start the streaming. |
| 280 if (resource->resourceBuffer()->size() < kSmallScriptThreshold) { | 281 if (resource->resourceBuffer()->size() < kSmallScriptThreshold) { |
| 281 return; | 282 return; |
| 282 } | 283 } |
| 283 m_haveEnoughDataForStreaming = true; | 284 m_haveEnoughDataForStreaming = true; |
| 284 const char* histogramName = startedStreamingHistogramName(m_scriptType); | 285 const char* histogramName = startedStreamingHistogramName(m_scriptType); |
| 285 if (ScriptStreamerThread::shared()->isRunningTask()) { | 286 if (ScriptStreamerThread::shared()->isRunningTask()) { |
| 286 // At the moment we only have one thread for running the tasks. A | 287 // At the moment we only have one thread for running the tasks. A |
| 287 // new task shouldn't be queued before the running task completes, | 288 // new task shouldn't be queued before the running task completes, |
| 288 // because the running task can block and wait for data from the | 289 // because the running task can block and wait for data from the |
| 289 // network. | 290 // network. |
| 290 suppressStreaming(); | 291 suppressStreaming(); |
| 291 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2
); | 292 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2
); |
| 292 return; | 293 return; |
| 293 } | 294 } |
| 294 ASSERT(m_task); | 295 ASSERT(m_task); |
| 295 // ScriptStreamer needs to stay alive as long as the background task is | 296 // ScriptStreamer needs to stay alive as long as the background task is |
| 296 // running. This is taken care of with a manual ref() & deref() pair; | 297 // running. This is taken care of with a manual ref() & deref() pair; |
| 297 // the corresponding deref() is in streamingComplete. | 298 // the corresponding deref() is in streamingComplete or in |
| 299 // notifyFinished. |
| 298 ref(); | 300 ref(); |
| 299 ScriptStreamingTask* task = new ScriptStreamingTask(m_task.release(), th
is); | 301 ScriptStreamingTask* task = new ScriptStreamingTask(m_task.release(), th
is); |
| 300 ScriptStreamerThread::shared()->postTask(task); | 302 ScriptStreamerThread::shared()->postTask(task); |
| 301 blink::Platform::current()->histogramEnumeration(histogramName, 1, 2); | 303 blink::Platform::current()->histogramEnumeration(histogramName, 1, 2); |
| 302 } | 304 } |
| 303 m_stream->didReceiveData(); | 305 m_stream->didReceiveData(); |
| 304 } | 306 } |
| 305 | 307 |
| 306 void ScriptStreamer::notifyFinished(Resource* resource) | 308 void ScriptStreamer::notifyFinished(Resource* resource) |
| 307 { | 309 { |
| 308 ASSERT(isMainThread()); | 310 ASSERT(isMainThread()); |
| 309 ASSERT(m_resource == resource); | 311 ASSERT(m_resource == resource); |
| 310 // A special case: empty and small scripts. We didn't receive enough data to | 312 // A special case: empty and small scripts. We didn't receive enough data to |
| 311 // start the streaming before this notification. In that case, there won't | 313 // start the streaming before this notification. In that case, there won't |
| 312 // be a "parsing complete" notification either, and we should not wait for | 314 // be a "parsing complete" notification either, and we should not wait for |
| 313 // it. | 315 // it. |
| 314 if (!m_haveEnoughDataForStreaming) { | 316 if (!m_haveEnoughDataForStreaming) { |
| 315 const char* histogramName = startedStreamingHistogramName(m_scriptType); | 317 const char* histogramName = startedStreamingHistogramName(m_scriptType); |
| 316 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2); | 318 blink::Platform::current()->histogramEnumeration(histogramName, 0, 2); |
| 317 suppressStreaming(); | 319 suppressStreaming(); |
| 318 } | 320 } |
| 319 m_stream->didFinishLoading(); | 321 m_stream->didFinishLoading(); |
| 320 m_loadingFinished = true; | 322 m_loadingFinished = true; |
| 323 |
| 324 if (shouldBlockMainThread()) { |
| 325 // Make the main thead wait until the streaming is complete, to make |
| 326 // sure that the script gets the main thread's attention as early as |
| 327 // possible (for possible compiling, if the client wants to do it |
| 328 // right away). Note that blocking here is not any worse than the |
| 329 // non-streaming code path where the main thread eventually blocks |
| 330 // to parse the script. |
| 331 TRACE_EVENT0("v8", "v8.mainThreadWaitingForParserThread"); |
| 332 MutexLocker locker(m_mutex); |
| 333 while (!isFinished()) { |
| 334 ASSERT(!m_parsingFinished); |
| 335 ASSERT(!m_streamingSuppressed); |
| 336 m_mainThreadWaitingForParserThread = true; |
| 337 m_parsingFinishedCondition.wait(m_mutex); |
| 338 } |
| 339 } |
| 340 |
| 341 // Calling notifyFinishedToClient can result into the upper layers dropping |
| 342 // references to ScriptStreamer. Keep it alive until this function ends. |
| 343 RefPtr<ScriptStreamer> protect(this); |
| 344 |
| 321 notifyFinishedToClient(); | 345 notifyFinishedToClient(); |
| 346 |
| 347 if (m_mainThreadWaitingForParserThread) { |
| 348 ASSERT(m_parsingFinished); |
| 349 ASSERT(!m_streamingSuppressed); |
| 350 // streamingComplete won't be called, so do the ramp-down work |
| 351 // here. |
| 352 deref(); |
| 353 } |
| 322 } | 354 } |
| 323 | 355 |
| 324 ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::Str
eamedSource::Encoding encoding, PendingScript::Type scriptType) | 356 ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::Str
eamedSource::Encoding encoding, PendingScript::Type scriptType, ScriptStreamingM
ode mode) |
| 325 : m_resource(resource) | 357 : m_resource(resource) |
| 326 , m_detached(false) | 358 , m_detached(false) |
| 327 , m_stream(new SourceStream(this)) | 359 , m_stream(new SourceStream(this)) |
| 328 , m_source(m_stream, encoding) // m_source takes ownership of m_stream. | 360 , m_source(m_stream, encoding) // m_source takes ownership of m_stream. |
| 329 , m_client(0) | 361 , m_client(0) |
| 330 , m_loadingFinished(false) | 362 , m_loadingFinished(false) |
| 331 , m_parsingFinished(false) | 363 , m_parsingFinished(false) |
| 332 , m_haveEnoughDataForStreaming(false) | 364 , m_haveEnoughDataForStreaming(false) |
| 333 , m_streamingSuppressed(false) | 365 , m_streamingSuppressed(false) |
| 334 , m_scriptType(scriptType) | 366 , m_scriptType(scriptType) |
| 367 , m_scriptStreamingMode(mode) |
| 368 , m_mainThreadWaitingForParserThread(false) |
| 335 { | 369 { |
| 336 } | 370 } |
| 337 | 371 |
| 372 void ScriptStreamer::streamingComplete() |
| 373 { |
| 374 // The background task is completed; do the necessary ramp-down in the main |
| 375 // thread. |
| 376 ASSERT(isMainThread()); |
| 377 |
| 378 // It's possible that the corresponding Resource was deleted before V8 |
| 379 // finished streaming. In that case, the data or the notification is not |
| 380 // needed. In addition, if the streaming is suppressed, the non-streaming |
| 381 // code path will resume after the resource has loaded, before the |
| 382 // background task finishes. |
| 383 if (m_detached || m_streamingSuppressed) { |
| 384 deref(); |
| 385 return; |
| 386 } |
| 387 |
| 388 // We have now streamed the whole script to V8 and it has parsed the |
| 389 // script. We're ready for the next step: compiling and executing the |
| 390 // script. |
| 391 notifyFinishedToClient(); |
| 392 |
| 393 // The background thread no longer holds an implicit reference. |
| 394 deref(); |
| 395 } |
| 396 |
| 338 void ScriptStreamer::notifyFinishedToClient() | 397 void ScriptStreamer::notifyFinishedToClient() |
| 339 { | 398 { |
| 340 ASSERT(isMainThread()); | 399 ASSERT(isMainThread()); |
| 341 // Usually, the loading will be finished first, and V8 will still need some | 400 // Usually, the loading will be finished first, and V8 will still need some |
| 342 // time to catch up. But the other way is possible too: if V8 detects a | 401 // time to catch up. But the other way is possible too: if V8 detects a |
| 343 // parse error, the V8 side can complete before loading has finished. Send | 402 // parse error, the V8 side can complete before loading has finished. Send |
| 344 // the notification after both loading and V8 side operations have | 403 // the notification after both loading and V8 side operations have |
| 345 // completed. Here we also check that we have a client: it can happen that a | 404 // completed. Here we also check that we have a client: it can happen that a |
| 346 // function calling notifyFinishedToClient was already scheduled in the task | 405 // function calling notifyFinishedToClient was already scheduled in the task |
| 347 // queue and the upper layer decided that it's not interested in the script | 406 // queue and the upper layer decided that it's not interested in the script |
| 348 // and called removeClient. | 407 // and called removeClient. |
| 349 if (isFinished() && m_client) | 408 { |
| 409 MutexLocker locker(m_mutex); |
| 410 if (!isFinished()) |
| 411 return; |
| 412 } |
| 413 if (m_client) |
| 350 m_client->notifyFinished(m_resource); | 414 m_client->notifyFinished(m_resource); |
| 351 } | 415 } |
| 352 | 416 |
| 353 const char* ScriptStreamer::startedStreamingHistogramName(PendingScript::Type sc
riptType) | 417 const char* ScriptStreamer::startedStreamingHistogramName(PendingScript::Type sc
riptType) |
| 354 { | 418 { |
| 355 switch (scriptType) { | 419 switch (scriptType) { |
| 356 case PendingScript::ParsingBlocking: | 420 case PendingScript::ParsingBlocking: |
| 357 return "WebCore.Scripts.ParsingBlocking.StartedStreaming"; | 421 return "WebCore.Scripts.ParsingBlocking.StartedStreaming"; |
| 358 break; | 422 break; |
| 359 case PendingScript::Deferred: | 423 case PendingScript::Deferred: |
| 360 return "WebCore.Scripts.Deferred.StartedStreaming"; | 424 return "WebCore.Scripts.Deferred.StartedStreaming"; |
| 361 break; | 425 break; |
| 362 case PendingScript::Async: | 426 case PendingScript::Async: |
| 363 return "WebCore.Scripts.Async.StartedStreaming"; | 427 return "WebCore.Scripts.Async.StartedStreaming"; |
| 364 break; | 428 break; |
| 365 default: | 429 default: |
| 366 ASSERT_NOT_REACHED(); | 430 ASSERT_NOT_REACHED(); |
| 367 break; | 431 break; |
| 368 } | 432 } |
| 369 return 0; | 433 return 0; |
| 370 } | 434 } |
| 371 | 435 |
| 372 bool ScriptStreamer::startStreamingInternal(PendingScript& script, Settings* set
tings, ScriptState* scriptState, PendingScript::Type scriptType) | 436 bool ScriptStreamer::startStreamingInternal(PendingScript& script, Settings* set
tings, ScriptState* scriptState, PendingScript::Type scriptType) |
| 373 { | 437 { |
| 374 ASSERT(isMainThread()); | 438 ASSERT(isMainThread()); |
| 375 if (!settings || !settings->v8ScriptStreamingEnabled()) | 439 if (!settings || !settings->v8ScriptStreamingEnabled()) |
| 376 return false; | 440 return false; |
| 441 if (settings->v8ScriptStreamingMode() == ScriptStreamingModeOnlyAsyncAndDefe
r |
| 442 && scriptType == PendingScript::ParsingBlocking) |
| 443 return false; |
| 444 |
| 377 ScriptResource* resource = script.resource(); | 445 ScriptResource* resource = script.resource(); |
| 378 ASSERT(!resource->isLoaded()); | 446 ASSERT(!resource->isLoaded()); |
| 379 if (!resource->url().protocolIsInHTTPFamily()) | 447 if (!resource->url().protocolIsInHTTPFamily()) |
| 380 return false; | 448 return false; |
| 381 if (resource->resourceToRevalidate()) { | 449 if (resource->resourceToRevalidate()) { |
| 382 // This happens e.g., during reloads. We're actually not going to load | 450 // This happens e.g., during reloads. We're actually not going to load |
| 383 // the current Resource of the PendingScript but switch to another | 451 // the current Resource of the PendingScript but switch to another |
| 384 // Resource -> don't stream. | 452 // Resource -> don't stream. |
| 385 return false; | 453 return false; |
| 386 } | 454 } |
| (...skipping 21 matching lines...) Expand all Loading... |
| 408 return false; | 476 return false; |
| 409 } | 477 } |
| 410 | 478 |
| 411 if (!scriptState->contextIsValid()) | 479 if (!scriptState->contextIsValid()) |
| 412 return false; | 480 return false; |
| 413 ScriptState::Scope scope(scriptState); | 481 ScriptState::Scope scope(scriptState); |
| 414 | 482 |
| 415 // The Resource might go out of scope if the script is no longer needed. We | 483 // The Resource might go out of scope if the script is no longer needed. We |
| 416 // will soon call PendingScript::setStreamer, which makes the PendingScript | 484 // will soon call PendingScript::setStreamer, which makes the PendingScript |
| 417 // notify the ScriptStreamer when it is destroyed. | 485 // notify the ScriptStreamer when it is destroyed. |
| 418 RefPtr<ScriptStreamer> streamer = adoptRef(new ScriptStreamer(resource, enco
ding, scriptType)); | 486 RefPtr<ScriptStreamer> streamer = adoptRef(new ScriptStreamer(resource, enco
ding, scriptType, settings->v8ScriptStreamingMode())); |
| 419 | 487 |
| 420 // Decide what kind of cached data we should produce while streaming. By | 488 // Decide what kind of cached data we should produce while streaming. By |
| 421 // default, we generate the parser cache for streamed scripts, to emulate | 489 // default, we generate the parser cache for streamed scripts, to emulate |
| 422 // the non-streaming behavior (see V8ScriptRunner::compileScript). | 490 // the non-streaming behavior (see V8ScriptRunner::compileScript). |
| 423 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProd
uceParserCache; | 491 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProd
uceParserCache; |
| 424 if (settings->v8CacheOptions() == V8CacheOptionsCode) | 492 if (settings->v8CacheOptions() == V8CacheOptionsCode) |
| 425 compileOption = v8::ScriptCompiler::kProduceCodeCache; | 493 compileOption = v8::ScriptCompiler::kProduceCodeCache; |
| 426 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCom
piler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), compi
leOption); | 494 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCom
piler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), compi
leOption); |
| 427 if (scriptStreamingTask) { | 495 if (scriptStreamingTask) { |
| 428 streamer->m_task = adoptPtr(scriptStreamingTask); | 496 streamer->m_task = adoptPtr(scriptStreamingTask); |
| 429 script.setStreamer(streamer.release()); | 497 script.setStreamer(streamer.release()); |
| 430 return true; | 498 return true; |
| 431 } | 499 } |
| 432 // Otherwise, V8 cannot stream the script. | 500 // Otherwise, V8 cannot stream the script. |
| 433 return false; | 501 return false; |
| 434 } | 502 } |
| 435 | 503 |
| 436 } // namespace blink | 504 } // namespace blink |
| OLD | NEW |