Chromium Code Reviews| 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 "bindings/core/v8/ScriptStreamer.h" | 5 #include "bindings/core/v8/ScriptStreamer.h" |
| 6 | 6 |
| 7 #include "bindings/core/v8/ScriptStreamerThread.h" | 7 #include "bindings/core/v8/ScriptStreamerThread.h" |
| 8 #include "bindings/core/v8/V8ScriptRunner.h" | 8 #include "bindings/core/v8/V8ScriptRunner.h" |
| 9 #include "core/dom/Document.h" | 9 #include "core/dom/Document.h" |
| 10 #include "core/dom/Element.h" | 10 #include "core/dom/Element.h" |
| 11 #include "core/dom/PendingScript.h" | 11 #include "core/dom/PendingScript.h" |
| 12 #include "core/fetch/ScriptResource.h" | 12 #include "core/fetch/ScriptResource.h" |
| 13 #include "core/frame/Settings.h" | 13 #include "core/frame/Settings.h" |
| 14 #include "core/html/parser/TextResourceDecoder.h" | 14 #include "core/html/parser/TextResourceDecoder.h" |
| 15 #include "platform/Histogram.h" | |
| 15 #include "platform/SharedBuffer.h" | 16 #include "platform/SharedBuffer.h" |
| 16 #include "platform/Task.h" | 17 #include "platform/Task.h" |
| 17 #include "platform/ThreadSafeFunctional.h" | 18 #include "platform/ThreadSafeFunctional.h" |
| 18 #include "platform/TraceEvent.h" | 19 #include "platform/TraceEvent.h" |
| 19 #include "public/platform/Platform.h" | |
| 20 #include "public/platform/WebScheduler.h" | 20 #include "public/platform/WebScheduler.h" |
| 21 #include "wtf/MainThread.h" | 21 #include "wtf/MainThread.h" |
| 22 #include "wtf/text/TextEncodingRegistry.h" | 22 #include "wtf/text/TextEncodingRegistry.h" |
| 23 | 23 |
| 24 namespace blink { | 24 namespace blink { |
| 25 | 25 |
| 26 namespace { | 26 namespace { |
| 27 | 27 |
| 28 const char* startedStreamingHistogramName(ScriptStreamer::Type scriptType) | 28 void recordStartedStreamingHistogram(ScriptStreamer::Type scriptType, int reason ) |
| 29 { | 29 { |
| 30 switch (scriptType) { | 30 switch (scriptType) { |
| 31 case ScriptStreamer::ParsingBlocking: | 31 case ScriptStreamer::ParsingBlocking: { |
| 32 return "WebCore.Scripts.ParsingBlocking.StartedStreaming"; | 32 DEFINE_STATIC_LOCAL(EnumerationHistogram, parseBlockingHistogram, ("WebC ore.Scripts.ParsingBlocking.StartedStreaming", 2)); |
|
esprehn
2016/02/05 19:09:07
You might want to ask haraken about this one. Scri
dtapuska
2016/02/05 19:25:32
All of these methods are called before an ASSERT(i
| |
| 33 parseBlockingHistogram.count(reason); | |
| 33 break; | 34 break; |
| 34 case ScriptStreamer::Deferred: | 35 } |
| 35 return "WebCore.Scripts.Deferred.StartedStreaming"; | 36 case ScriptStreamer::Deferred: { |
| 37 DEFINE_STATIC_LOCAL(EnumerationHistogram, deferredHistogram, ("WebCore.S cripts.Deferred.StartedStreaming", 2)); | |
| 38 deferredHistogram.count(reason); | |
| 36 break; | 39 break; |
| 37 case ScriptStreamer::Async: | 40 } |
| 38 return "WebCore.Scripts.Async.StartedStreaming"; | 41 case ScriptStreamer::Async: { |
| 42 DEFINE_STATIC_LOCAL(EnumerationHistogram, asyncHistogram, ("WebCore.Scri pts.Async.StartedStreaming", 2)); | |
| 43 asyncHistogram.count(reason); | |
| 39 break; | 44 break; |
| 45 } | |
| 40 default: | 46 default: |
| 41 ASSERT_NOT_REACHED(); | 47 ASSERT_NOT_REACHED(); |
| 42 break; | 48 break; |
| 43 } | 49 } |
| 44 return 0; | |
| 45 } | 50 } |
| 46 | 51 |
| 47 // For tracking why some scripts are not streamed. Not streaming is part of | 52 // For tracking why some scripts are not streamed. Not streaming is part of |
| 48 // normal operation (e.g., script already loaded, script too small) and doesn't | 53 // normal operation (e.g., script already loaded, script too small) and doesn't |
| 49 // necessarily indicate a failure. | 54 // necessarily indicate a failure. |
| 50 enum NotStreamingReason { | 55 enum NotStreamingReason { |
| 51 AlreadyLoaded, | 56 AlreadyLoaded, |
| 52 NotHTTP, | 57 NotHTTP, |
| 53 Reload, | 58 Reload, |
| 54 ContextNotValid, | 59 ContextNotValid, |
| 55 EncodingNotSupported, | 60 EncodingNotSupported, |
| 56 ThreadBusy, | 61 ThreadBusy, |
| 57 V8CannotStream, | 62 V8CannotStream, |
| 58 ScriptTooSmall, | 63 ScriptTooSmall, |
| 59 NotStreamingReasonEnd | 64 NotStreamingReasonEnd |
| 60 }; | 65 }; |
| 61 | 66 |
| 62 const char* notStreamingReasonHistogramName(ScriptStreamer::Type scriptType) | 67 void recordNotStreamingReasonHistogram(ScriptStreamer::Type scriptType, NotStrea mingReason reason) |
| 63 { | 68 { |
| 64 switch (scriptType) { | 69 switch (scriptType) { |
| 65 case ScriptStreamer::ParsingBlocking: | 70 case ScriptStreamer::ParsingBlocking: { |
| 66 return "WebCore.Scripts.ParsingBlocking.NotStreamingReason"; | 71 DEFINE_STATIC_LOCAL(EnumerationHistogram, parseBlockingHistogram, ("WebC ore.Scripts.ParsingBlocking.NotStreamingReason", NotStreamingReasonEnd)); |
| 72 parseBlockingHistogram.count(reason); | |
| 67 break; | 73 break; |
| 68 case ScriptStreamer::Deferred: | 74 } |
| 69 return "WebCore.Scripts.Deferred.NotStreamingReason"; | 75 case ScriptStreamer::Deferred: { |
| 76 DEFINE_STATIC_LOCAL(EnumerationHistogram, deferredHistogram, ("WebCore.S cripts.Deferred.NotStreamingReason", NotStreamingReasonEnd)); | |
| 77 deferredHistogram.count(reason); | |
| 70 break; | 78 break; |
| 71 case ScriptStreamer::Async: | 79 } |
| 72 return "WebCore.Scripts.Async.NotStreamingReason"; | 80 case ScriptStreamer::Async: { |
| 81 DEFINE_STATIC_LOCAL(EnumerationHistogram, asyncHistogram, ("WebCore.Scri pts.Async.NotStreamingReason", NotStreamingReasonEnd)); | |
| 82 asyncHistogram.count(reason); | |
| 73 break; | 83 break; |
| 84 } | |
| 74 default: | 85 default: |
| 75 ASSERT_NOT_REACHED(); | 86 ASSERT_NOT_REACHED(); |
| 76 break; | 87 break; |
| 77 } | 88 } |
| 78 return 0; | |
| 79 } | 89 } |
| 80 | 90 |
| 81 } // namespace | 91 } // namespace |
| 82 | 92 |
| 83 // For passing data between the main thread (producer) and the streamer thread | 93 // For passing data between the main thread (producer) and the streamer thread |
| 84 // (consumer). The main thread prepares the data (copies it from Resource) and | 94 // (consumer). The main thread prepares the data (copies it from Resource) and |
| 85 // the streamer thread feeds it to V8. | 95 // the streamer thread feeds it to V8. |
| 86 class SourceStreamDataQueue { | 96 class SourceStreamDataQueue { |
| 87 WTF_MAKE_NONCOPYABLE(SourceStreamDataQueue); | 97 WTF_MAKE_NONCOPYABLE(SourceStreamDataQueue); |
| 88 public: | 98 public: |
| (...skipping 288 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 377 | 387 |
| 378 size_t ScriptStreamer::s_smallScriptThreshold = 30 * 1024; | 388 size_t ScriptStreamer::s_smallScriptThreshold = 30 * 1024; |
| 379 | 389 |
| 380 void ScriptStreamer::startStreaming(PendingScript* script, Type scriptType, Sett ings* settings, ScriptState* scriptState, WebTaskRunner* loadingTaskRunner) | 390 void ScriptStreamer::startStreaming(PendingScript* script, Type scriptType, Sett ings* settings, ScriptState* scriptState, WebTaskRunner* loadingTaskRunner) |
| 381 { | 391 { |
| 382 // We don't yet know whether the script will really be streamed. E.g., | 392 // We don't yet know whether the script will really be streamed. E.g., |
| 383 // suppressing streaming for short scripts is done later. Record only the | 393 // suppressing streaming for short scripts is done later. Record only the |
| 384 // sure negative cases here. | 394 // sure negative cases here. |
| 385 bool startedStreaming = startStreamingInternal(script, scriptType, settings, scriptState, loadingTaskRunner); | 395 bool startedStreaming = startStreamingInternal(script, scriptType, settings, scriptState, loadingTaskRunner); |
| 386 if (!startedStreaming) | 396 if (!startedStreaming) |
| 387 Platform::current()->histogramEnumeration(startedStreamingHistogramName( scriptType), 0, 2); | 397 recordStartedStreamingHistogram(scriptType, 0); |
| 388 } | 398 } |
| 389 | 399 |
| 390 bool ScriptStreamer::convertEncoding(const char* encodingName, v8::ScriptCompile r::StreamedSource::Encoding* encoding) | 400 bool ScriptStreamer::convertEncoding(const char* encodingName, v8::ScriptCompile r::StreamedSource::Encoding* encoding) |
| 391 { | 401 { |
| 392 // Here's a list of encodings we can use for streaming. These are | 402 // Here's a list of encodings we can use for streaming. These are |
| 393 // the canonical names. | 403 // the canonical names. |
| 394 if (strcmp(encodingName, "windows-1252") == 0 | 404 if (strcmp(encodingName, "windows-1252") == 0 |
| 395 || strcmp(encodingName, "ISO-8859-1") == 0 | 405 || strcmp(encodingName, "ISO-8859-1") == 0 |
| 396 || strcmp(encodingName, "US-ASCII") == 0) { | 406 || strcmp(encodingName, "US-ASCII") == 0) { |
| 397 *encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE; | 407 *encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE; |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 485 const char* data = 0; | 495 const char* data = 0; |
| 486 size_t length = resource->resourceBuffer()->getSomeData(data, static_cas t<size_t>(0)); | 496 size_t length = resource->resourceBuffer()->getSomeData(data, static_cas t<size_t>(0)); |
| 487 | 497 |
| 488 OwnPtr<TextResourceDecoder> decoder(TextResourceDecoder::create("applica tion/javascript", resource->encoding())); | 498 OwnPtr<TextResourceDecoder> decoder(TextResourceDecoder::create("applica tion/javascript", resource->encoding())); |
| 489 lengthOfBOM = decoder->checkForBOM(data, length); | 499 lengthOfBOM = decoder->checkForBOM(data, length); |
| 490 | 500 |
| 491 // Maybe the encoding changed because we saw the BOM; get the encoding | 501 // Maybe the encoding changed because we saw the BOM; get the encoding |
| 492 // from the decoder. | 502 // from the decoder. |
| 493 if (!convertEncoding(decoder->encoding().name(), &m_encoding)) { | 503 if (!convertEncoding(decoder->encoding().name(), &m_encoding)) { |
| 494 suppressStreaming(); | 504 suppressStreaming(); |
| 495 Platform::current()->histogramEnumeration(notStreamingReasonHistogra mName(m_scriptType), EncodingNotSupported, NotStreamingReasonEnd); | 505 recordNotStreamingReasonHistogram(m_scriptType, EncodingNotSupported ); |
| 496 Platform::current()->histogramEnumeration(startedStreamingHistogramN ame(m_scriptType), 0, 2); | 506 recordStartedStreamingHistogram(m_scriptType, 0); |
| 497 return; | 507 return; |
| 498 } | 508 } |
| 499 if (ScriptStreamerThread::shared()->isRunningTask()) { | 509 if (ScriptStreamerThread::shared()->isRunningTask()) { |
| 500 // At the moment we only have one thread for running the tasks. A | 510 // At the moment we only have one thread for running the tasks. A |
| 501 // new task shouldn't be queued before the running task completes, | 511 // new task shouldn't be queued before the running task completes, |
| 502 // because the running task can block and wait for data from the | 512 // because the running task can block and wait for data from the |
| 503 // network. | 513 // network. |
| 504 suppressStreaming(); | 514 suppressStreaming(); |
| 505 Platform::current()->histogramEnumeration(notStreamingReasonHistogra mName(m_scriptType), ThreadBusy, NotStreamingReasonEnd); | 515 recordNotStreamingReasonHistogram(m_scriptType, ThreadBusy); |
| 506 Platform::current()->histogramEnumeration(startedStreamingHistogramN ame(m_scriptType), 0, 2); | 516 recordStartedStreamingHistogram(m_scriptType, 0); |
| 507 return; | 517 return; |
| 508 } | 518 } |
| 509 | 519 |
| 510 if (!m_scriptState->contextIsValid()) { | 520 if (!m_scriptState->contextIsValid()) { |
| 511 suppressStreaming(); | 521 suppressStreaming(); |
| 512 Platform::current()->histogramEnumeration(notStreamingReasonHistogra mName(m_scriptType), ContextNotValid, NotStreamingReasonEnd); | 522 recordNotStreamingReasonHistogram(m_scriptType, ContextNotValid); |
| 513 Platform::current()->histogramEnumeration(startedStreamingHistogramN ame(m_scriptType), 0, 2); | 523 recordStartedStreamingHistogram(m_scriptType, 0); |
| 514 return; | 524 return; |
| 515 } | 525 } |
| 516 | 526 |
| 517 ASSERT(!m_stream); | 527 ASSERT(!m_stream); |
| 518 ASSERT(!m_source); | 528 ASSERT(!m_source); |
| 519 m_stream = new SourceStream(m_loadingTaskRunner.get()); | 529 m_stream = new SourceStream(m_loadingTaskRunner.get()); |
| 520 // m_source takes ownership of m_stream. | 530 // m_source takes ownership of m_stream. |
| 521 m_source = adoptPtr(new v8::ScriptCompiler::StreamedSource(m_stream, m_e ncoding)); | 531 m_source = adoptPtr(new v8::ScriptCompiler::StreamedSource(m_stream, m_e ncoding)); |
| 522 | 532 |
| 523 ScriptState::Scope scope(m_scriptState.get()); | 533 ScriptState::Scope scope(m_scriptState.get()); |
| 524 WTF::OwnPtr<v8::ScriptCompiler::ScriptStreamingTask> scriptStreamingTask (adoptPtr(v8::ScriptCompiler::StartStreamingScript(m_scriptState->isolate(), m_s ource.get(), m_compileOptions))); | 534 WTF::OwnPtr<v8::ScriptCompiler::ScriptStreamingTask> scriptStreamingTask (adoptPtr(v8::ScriptCompiler::StartStreamingScript(m_scriptState->isolate(), m_s ource.get(), m_compileOptions))); |
| 525 if (!scriptStreamingTask) { | 535 if (!scriptStreamingTask) { |
| 526 // V8 cannot stream the script. | 536 // V8 cannot stream the script. |
| 527 suppressStreaming(); | 537 suppressStreaming(); |
| 528 m_stream = 0; | 538 m_stream = 0; |
| 529 m_source.clear(); | 539 m_source.clear(); |
| 530 Platform::current()->histogramEnumeration(notStreamingReasonHistogra mName(m_scriptType), V8CannotStream, NotStreamingReasonEnd); | 540 recordNotStreamingReasonHistogram(m_scriptType, V8CannotStream); |
| 531 Platform::current()->histogramEnumeration(startedStreamingHistogramN ame(m_scriptType), 0, 2); | 541 recordStartedStreamingHistogram(m_scriptType, 0); |
| 532 return; | 542 return; |
| 533 } | 543 } |
| 534 | 544 |
| 535 // ScriptStreamer needs to stay alive as long as the background task is | 545 // ScriptStreamer needs to stay alive as long as the background task is |
| 536 // running. This is taken care of with a manual ref() & deref() pair; | 546 // running. This is taken care of with a manual ref() & deref() pair; |
| 537 // the corresponding deref() is in streamingComplete. | 547 // the corresponding deref() is in streamingComplete. |
| 538 ref(); | 548 ref(); |
| 539 ScriptStreamerThread::shared()->postTask(new Task(threadSafeBind(&Script StreamerThread::runScriptStreamingTask, scriptStreamingTask.release(), AllowCros sThreadAccess(this)))); | 549 ScriptStreamerThread::shared()->postTask(new Task(threadSafeBind(&Script StreamerThread::runScriptStreamingTask, scriptStreamingTask.release(), AllowCros sThreadAccess(this)))); |
| 540 Platform::current()->histogramEnumeration(startedStreamingHistogramName( m_scriptType), 1, 2); | 550 recordStartedStreamingHistogram(m_scriptType, 1); |
| 541 } | 551 } |
| 542 if (m_stream) | 552 if (m_stream) |
| 543 m_stream->didReceiveData(this, lengthOfBOM); | 553 m_stream->didReceiveData(this, lengthOfBOM); |
| 544 } | 554 } |
| 545 | 555 |
| 546 void ScriptStreamer::notifyFinished(Resource* resource) | 556 void ScriptStreamer::notifyFinished(Resource* resource) |
| 547 { | 557 { |
| 548 ASSERT(isMainThread()); | 558 ASSERT(isMainThread()); |
| 549 ASSERT(m_resource == resource); | 559 ASSERT(m_resource == resource); |
| 550 // A special case: empty and small scripts. We didn't receive enough data to | 560 // A special case: empty and small scripts. We didn't receive enough data to |
| 551 // start the streaming before this notification. In that case, there won't | 561 // start the streaming before this notification. In that case, there won't |
| 552 // be a "parsing complete" notification either, and we should not wait for | 562 // be a "parsing complete" notification either, and we should not wait for |
| 553 // it. | 563 // it. |
| 554 if (!m_haveEnoughDataForStreaming) { | 564 if (!m_haveEnoughDataForStreaming) { |
| 555 Platform::current()->histogramEnumeration(notStreamingReasonHistogramNam e(m_scriptType), ScriptTooSmall, NotStreamingReasonEnd); | 565 recordNotStreamingReasonHistogram(m_scriptType, ScriptTooSmall); |
| 556 Platform::current()->histogramEnumeration(startedStreamingHistogramName( m_scriptType), 0, 2); | 566 recordStartedStreamingHistogram(m_scriptType, 0); |
| 557 suppressStreaming(); | 567 suppressStreaming(); |
| 558 } | 568 } |
| 559 if (m_stream) | 569 if (m_stream) |
| 560 m_stream->didFinishLoading(); | 570 m_stream->didFinishLoading(); |
| 561 m_loadingFinished = true; | 571 m_loadingFinished = true; |
| 562 | 572 |
| 563 // Calling notifyFinishedToClient can result into the upper layers dropping | 573 // Calling notifyFinishedToClient can result into the upper layers dropping |
| 564 // references to ScriptStreamer. Keep it alive until this function ends. | 574 // references to ScriptStreamer. Keep it alive until this function ends. |
| 565 RefPtrWillBeRawPtr<ScriptStreamer> protect(this); | 575 RefPtrWillBeRawPtr<ScriptStreamer> protect(this); |
| 566 | 576 |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 635 if (m_client) | 645 if (m_client) |
| 636 m_client->notifyFinished(m_resource); | 646 m_client->notifyFinished(m_resource); |
| 637 } | 647 } |
| 638 | 648 |
| 639 bool ScriptStreamer::startStreamingInternal(PendingScript* script, Type scriptTy pe, Settings* settings, ScriptState* scriptState, WebTaskRunner* loadingTaskRunn er) | 649 bool ScriptStreamer::startStreamingInternal(PendingScript* script, Type scriptTy pe, Settings* settings, ScriptState* scriptState, WebTaskRunner* loadingTaskRunn er) |
| 640 { | 650 { |
| 641 ASSERT(isMainThread()); | 651 ASSERT(isMainThread()); |
| 642 ASSERT(scriptState->contextIsValid()); | 652 ASSERT(scriptState->contextIsValid()); |
| 643 ScriptResource* resource = script->resource(); | 653 ScriptResource* resource = script->resource(); |
| 644 if (resource->isLoaded()) { | 654 if (resource->isLoaded()) { |
| 645 Platform::current()->histogramEnumeration(notStreamingReasonHistogramNam e(scriptType), AlreadyLoaded, NotStreamingReasonEnd); | 655 recordNotStreamingReasonHistogram(scriptType, AlreadyLoaded); |
| 646 return false; | 656 return false; |
| 647 } | 657 } |
| 648 if (!resource->url().protocolIsInHTTPFamily()) { | 658 if (!resource->url().protocolIsInHTTPFamily()) { |
| 649 Platform::current()->histogramEnumeration(notStreamingReasonHistogramNam e(scriptType), NotHTTP, NotStreamingReasonEnd); | 659 recordNotStreamingReasonHistogram(scriptType, NotHTTP); |
| 650 return false; | 660 return false; |
| 651 } | 661 } |
| 652 if (resource->isCacheValidator()) { | 662 if (resource->isCacheValidator()) { |
| 653 Platform::current()->histogramEnumeration(notStreamingReasonHistogramNam e(scriptType), Reload, NotStreamingReasonEnd); | 663 recordNotStreamingReasonHistogram(scriptType, Reload); |
| 654 // This happens e.g., during reloads. We're actually not going to load | 664 // This happens e.g., during reloads. We're actually not going to load |
| 655 // the current Resource of the PendingScript but switch to another | 665 // the current Resource of the PendingScript but switch to another |
| 656 // Resource -> don't stream. | 666 // Resource -> don't stream. |
| 657 return false; | 667 return false; |
| 658 } | 668 } |
| 659 // We cannot filter out short scripts, even if we wait for the HTTP headers | 669 // We cannot filter out short scripts, even if we wait for the HTTP headers |
| 660 // to arrive: the Content-Length HTTP header is not sent for chunked | 670 // to arrive: the Content-Length HTTP header is not sent for chunked |
| 661 // downloads. | 671 // downloads. |
| 662 | 672 |
| 663 // Decide what kind of cached data we should produce while streaming. Only | 673 // Decide what kind of cached data we should produce while streaming. Only |
| 664 // produce parser cache if the non-streaming compile takes advantage of it. | 674 // produce parser cache if the non-streaming compile takes advantage of it. |
| 665 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kNoCo mpileOptions; | 675 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kNoCo mpileOptions; |
| 666 if (settings->v8CacheOptions() == V8CacheOptionsParse) | 676 if (settings->v8CacheOptions() == V8CacheOptionsParse) |
| 667 compileOption = v8::ScriptCompiler::kProduceParserCache; | 677 compileOption = v8::ScriptCompiler::kProduceParserCache; |
| 668 | 678 |
| 669 // The Resource might go out of scope if the script is no longer | 679 // The Resource might go out of scope if the script is no longer |
| 670 // needed. This makes PendingScript notify the ScriptStreamer when it is | 680 // needed. This makes PendingScript notify the ScriptStreamer when it is |
| 671 // destroyed. | 681 // destroyed. |
| 672 script->setStreamer(ScriptStreamer::create(resource, scriptType, scriptState , compileOption, loadingTaskRunner)); | 682 script->setStreamer(ScriptStreamer::create(resource, scriptType, scriptState , compileOption, loadingTaskRunner)); |
| 673 | 683 |
| 674 return true; | 684 return true; |
| 675 } | 685 } |
| 676 | 686 |
| 677 } // namespace blink | 687 } // namespace blink |
| OLD | NEW |