Commit 5578c07d authored by Daniel Vogelheim's avatar Daniel Vogelheim Committed by Commit Bot

ScriptRunner can initiate script streaming.

In this implementation, all script streamer actions are controlled by
ClassicPendingScript, and ScriptRunner ends up asking ClassicPendingScript
to perform the streaming. To facilitate this, ClassicPendingScript has a
new state kReadyStreaming, for cases where we start streaming while the
script has already been 'ready' (and stays 'ready').

Bug: 557466
Change-Id: I990920830af3a820f530f360347f6fa98d8754c2
Reviewed-on: https://chromium-review.googlesource.com/519168Reviewed-by: default avatarJochen Eisinger <jochen@chromium.org>
Reviewed-by: default avatarHiroshige Hayashizaki <hiroshige@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#488648}
parent 1d90c817
...@@ -19,11 +19,7 @@ ScriptSourceCode::ScriptSourceCode(const String& source, ...@@ -19,11 +19,7 @@ ScriptSourceCode::ScriptSourceCode(const String& source,
} }
ScriptSourceCode::ScriptSourceCode(ScriptResource* resource) ScriptSourceCode::ScriptSourceCode(ScriptResource* resource)
: source_(resource->SourceText()), : ScriptSourceCode(nullptr, resource) {}
resource_(resource),
start_position_(TextPosition::MinimumPosition()) {
TreatNullSourceAsEmpty();
}
ScriptSourceCode::ScriptSourceCode(ScriptStreamer* streamer, ScriptSourceCode::ScriptSourceCode(ScriptStreamer* streamer,
ScriptResource* resource) ScriptResource* resource)
......
...@@ -67,6 +67,7 @@ enum NotStreamingReason { ...@@ -67,6 +67,7 @@ enum NotStreamingReason {
kThreadBusy, kThreadBusy,
kV8CannotStream, kV8CannotStream,
kScriptTooSmall, kScriptTooSmall,
kNoResourceBuffer,
kNotStreamingReasonEnd kNotStreamingReasonEnd
}; };
...@@ -339,6 +340,57 @@ void ScriptStreamer::StartStreaming(ClassicPendingScript* script, ...@@ -339,6 +340,57 @@ void ScriptStreamer::StartStreaming(ClassicPendingScript* script,
RecordStartedStreamingHistogram(script_type, 0); RecordStartedStreamingHistogram(script_type, 0);
} }
void ScriptStreamer::StartStreamingLoadedScript(
ClassicPendingScript* script,
Type script_type,
Settings* settings,
ScriptState* script_state,
RefPtr<WebTaskRunner> loading_task_runner) {
DCHECK(IsMainThread());
DCHECK(script_state->ContextIsValid());
ScriptResource* resource = script->GetResource();
if (!resource->Url().ProtocolIsInHTTPFamily()) {
RecordNotStreamingReasonHistogram(script_type, kNotHTTP);
return;
}
if (resource->IsCacheValidator()) {
RecordNotStreamingReasonHistogram(script_type, kReload);
// This happens e.g., during reloads. We're actually not going to load
// the current Resource of the ClassicPendingScript but switch to another
// Resource -> don't stream.
return;
}
if (!resource->ResourceBuffer()) {
// This happens for already loaded resources, e.g. if resource
// validation fails. In that case, the loading subsystem will discard
// the resource buffer.
RecordNotStreamingReasonHistogram(script_type, kNoResourceBuffer);
return;
}
// Decide what kind of cached data we should produce while streaming. Only
// produce parser cache if the non-streaming compile takes advantage of it.
v8::ScriptCompiler::CompileOptions compile_option =
v8::ScriptCompiler::kNoCompileOptions;
if (settings->GetV8CacheOptions() == kV8CacheOptionsParse)
compile_option = v8::ScriptCompiler::kProduceParserCache;
ScriptStreamer* streamer =
ScriptStreamer::Create(script, script_type, script_state, compile_option,
std::move(loading_task_runner));
// Since the script has already loaded, we will not receive any
// notificatations for incoming data. So we will just emulate those right
// here.
DCHECK(resource->IsLoaded());
streamer->NotifyAppendData(resource);
if (!streamer->StreamingSuppressed()) {
script->SetStreamer(streamer);
streamer->NotifyFinished(resource);
}
}
bool ScriptStreamer::ConvertEncoding( bool ScriptStreamer::ConvertEncoding(
const char* encoding_name, const char* encoding_name,
v8::ScriptCompiler::StreamedSource::Encoding* encoding) { v8::ScriptCompiler::StreamedSource::Encoding* encoding) {
...@@ -366,6 +418,11 @@ bool ScriptStreamer::IsFinished() const { ...@@ -366,6 +418,11 @@ bool ScriptStreamer::IsFinished() const {
return loading_finished_ && (parsing_finished_ || streaming_suppressed_); return loading_finished_ && (parsing_finished_ || streaming_suppressed_);
} }
bool ScriptStreamer::IsStreamingFinished() const {
DCHECK(IsMainThread());
return parsing_finished_ || streaming_suppressed_;
}
void ScriptStreamer::StreamingCompleteOnBackgroundThread() { void ScriptStreamer::StreamingCompleteOnBackgroundThread() {
DCHECK(!IsMainThread()); DCHECK(!IsMainThread());
......
...@@ -48,11 +48,20 @@ class CORE_EXPORT ScriptStreamer final ...@@ -48,11 +48,20 @@ class CORE_EXPORT ScriptStreamer final
ScriptState*, ScriptState*,
RefPtr<WebTaskRunner>); RefPtr<WebTaskRunner>);
// Like StartStreaming, but assume that the resource has already been
// fully loaded.
static void StartStreamingLoadedScript(ClassicPendingScript*,
Type,
Settings*,
ScriptState*,
RefPtr<WebTaskRunner>);
// Returns false if we cannot stream the given encoding. // Returns false if we cannot stream the given encoding.
static bool ConvertEncoding(const char* encoding_name, static bool ConvertEncoding(const char* encoding_name,
v8::ScriptCompiler::StreamedSource::Encoding*); v8::ScriptCompiler::StreamedSource::Encoding*);
bool IsFinished() const; bool IsFinished() const; // Has loading & streaming finished?
bool IsStreamingFinished() const; // Has streaming finished?
v8::ScriptCompiler::StreamedSource* Source() { return source_.get(); } v8::ScriptCompiler::StreamedSource* Source() { return source_.get(); }
ScriptResource* GetResource() const { return resource_; } ScriptResource* GetResource() const { return resource_; }
......
...@@ -67,6 +67,23 @@ void ClassicPendingScript::DisposeInternal() { ...@@ -67,6 +67,23 @@ void ClassicPendingScript::DisposeInternal() {
} }
void ClassicPendingScript::StreamingFinished() { void ClassicPendingScript::StreamingFinished() {
CheckState();
DCHECK(streamer_); // Should only be called by ScriptStreamer.
std::unique_ptr<WTF::Closure> done(std::move(streamer_done_));
if (ready_state_ == kWaitingForStreaming) {
FinishWaitingForStreaming();
} else if (ready_state_ == kReadyStreaming) {
FinishReadyStreaming();
} else {
NOTREACHED();
}
if (done)
(*done)();
}
void ClassicPendingScript::FinishWaitingForStreaming() {
CheckState(); CheckState();
DCHECK(GetResource()); DCHECK(GetResource());
DCHECK_EQ(ready_state_, kWaitingForStreaming); DCHECK_EQ(ready_state_, kWaitingForStreaming);
...@@ -75,6 +92,13 @@ void ClassicPendingScript::StreamingFinished() { ...@@ -75,6 +92,13 @@ void ClassicPendingScript::StreamingFinished() {
AdvanceReadyState(error_occurred ? kErrorOccurred : kReady); AdvanceReadyState(error_occurred ? kErrorOccurred : kReady);
} }
void ClassicPendingScript::FinishReadyStreaming() {
CheckState();
DCHECK(GetResource());
DCHECK_EQ(ready_state_, kReadyStreaming);
AdvanceReadyState(kReady);
}
void ClassicPendingScript::NotifyFinished(Resource* resource) { void ClassicPendingScript::NotifyFinished(Resource* resource) {
// The following SRI checks need to be here because, unfortunately, fetches // The following SRI checks need to be here because, unfortunately, fetches
// are not done purely according to the Fetch spec. In particular, // are not done purely according to the Fetch spec. In particular,
...@@ -120,7 +144,7 @@ void ClassicPendingScript::NotifyFinished(Resource* resource) { ...@@ -120,7 +144,7 @@ void ClassicPendingScript::NotifyFinished(Resource* resource) {
if (streamer_) if (streamer_)
streamer_->NotifyFinished(resource); streamer_->NotifyFinished(resource);
else else
StreamingFinished(); FinishWaitingForStreaming();
} }
void ClassicPendingScript::NotifyAppendData(ScriptResource* resource) { void ClassicPendingScript::NotifyAppendData(ScriptResource* resource) {
...@@ -141,23 +165,29 @@ ClassicScript* ClassicPendingScript::GetSource(const KURL& document_url, ...@@ -141,23 +165,29 @@ ClassicScript* ClassicPendingScript::GetSource(const KURL& document_url,
DCHECK(IsReady()); DCHECK(IsReady());
error_occurred = ErrorOccurred(); error_occurred = ErrorOccurred();
if (GetResource()) { if (!GetResource()) {
DCHECK(GetResource()->IsLoaded()); return ClassicScript::Create(ScriptSourceCode(
if (streamer_ && !streamer_->StreamingSuppressed()) GetElement()->TextFromChildren(), document_url, StartingPosition()));
return ClassicScript::Create(ScriptSourceCode(streamer_, GetResource()));
return ClassicScript::Create(ScriptSourceCode(GetResource()));
} }
return ClassicScript::Create(ScriptSourceCode( DCHECK(GetResource()->IsLoaded());
GetElement()->TextFromChildren(), document_url, StartingPosition())); bool streamer_ready = (ready_state_ == kReady) && streamer_ &&
!streamer_->StreamingSuppressed();
return ClassicScript::Create(
ScriptSourceCode(streamer_ready ? streamer_ : nullptr, GetResource()));
} }
void ClassicPendingScript::SetStreamer(ScriptStreamer* streamer) { void ClassicPendingScript::SetStreamer(ScriptStreamer* streamer) {
DCHECK(streamer);
DCHECK(!streamer_); DCHECK(!streamer_);
DCHECK(!IsWatchingForLoad()); DCHECK(!IsWatchingForLoad() || ready_state_ != kWaitingForResource);
DCHECK(!streamer->IsFinished()); DCHECK(!streamer->IsFinished());
DCHECK_LT(ready_state_, kWaitingForStreaming); DCHECK(ready_state_ == kWaitingForResource || ready_state_ == kReady);
streamer_ = streamer; streamer_ = streamer;
if (streamer && ready_state_ == kReady)
AdvanceReadyState(kReadyStreaming);
CheckState(); CheckState();
} }
...@@ -172,17 +202,33 @@ bool ClassicPendingScript::ErrorOccurred() const { ...@@ -172,17 +202,33 @@ bool ClassicPendingScript::ErrorOccurred() const {
} }
void ClassicPendingScript::AdvanceReadyState(ReadyState new_ready_state) { void ClassicPendingScript::AdvanceReadyState(ReadyState new_ready_state) {
CHECK_GT(new_ready_state, ready_state_) // We will allow exactly these state transitions:
<< "The ready state should monotonically advance."; //
// kWaitingForResource -> kWaitingForStreaming -> [kReady, kErrorOccurred]
if (new_ready_state >= kReady) { // kReady -> kReadyStreaming -> kReady
CHECK_LT(ready_state_, kReady) switch (ready_state_) {
<< "The state should not advance from one completed state to another."; case kWaitingForResource:
CHECK_EQ(new_ready_state, kWaitingForStreaming);
break;
case kWaitingForStreaming:
CHECK(new_ready_state == kReady || new_ready_state == kErrorOccurred);
break;
case kReady:
CHECK_EQ(new_ready_state, kReadyStreaming);
break;
case kReadyStreaming:
CHECK_EQ(new_ready_state, kReady);
break;
case kErrorOccurred:
NOTREACHED();
break;
} }
bool old_is_ready = IsReady();
ready_state_ = new_ready_state; ready_state_ = new_ready_state;
if (ready_state_ >= kReady && IsWatchingForLoad()) // Did we transition into a 'ready' state?
if (IsReady() && !old_is_ready && IsWatchingForLoad())
Client()->PendingScriptFinished(this); Client()->PendingScriptFinished(this);
} }
...@@ -194,19 +240,76 @@ void ClassicPendingScript::OnPurgeMemory() { ...@@ -194,19 +240,76 @@ void ClassicPendingScript::OnPurgeMemory() {
streamer_ = nullptr; streamer_ = nullptr;
} }
void ClassicPendingScript::StartStreamingIfPossible( bool ClassicPendingScript::StartStreamingIfPossible(
Document* document, ScriptStreamer::Type streamer_type,
ScriptStreamer::Type streamer_type) { std::unique_ptr<WTF::Closure> done) {
if (!document->GetFrame()) // We can start streaming in two states: While still loading
return; // (kWaitingForResource), or after having loaded (kReady).
if (ready_state_ != kWaitingForResource && ready_state_ != kReady)
return false;
Document* document = &GetElement()->GetDocument();
if (!document || !document->GetFrame())
return false;
ScriptState* script_state = ToScriptStateForMainWorld(document->GetFrame()); ScriptState* script_state = ToScriptStateForMainWorld(document->GetFrame());
if (!script_state) if (!script_state)
return; return false;
// To support streaming re-try, we'll clear the existing streamer if
// it exists; it claims to be finished; but it's finished because streaming
// has been suppressed.
if (streamer_ && streamer_->StreamingSuppressed() &&
streamer_->IsFinished()) {
DCHECK_EQ(ready_state_, kReady);
DCHECK(!streamer_done_);
streamer_.Clear();
}
if (streamer_)
return false;
// The two checks above should imply that we're not presently streaming.
DCHECK(!IsCurrentlyStreaming());
// Parser blocking scripts tend to do a lot of work in the 'finished'
// callbacks, while async + in-order scripts all do control-like activities
// (like posting new tasks). Use the 'control' queue only for control tasks.
// (More details in discussion for cl 500147.)
auto task_type = streamer_type == ScriptStreamer::kParsingBlocking
? TaskType::kNetworking
: TaskType::kNetworkingControl;
DCHECK_EQ(ready_state_ == kReady, GetResource()->IsLoaded());
DCHECK(!streamer_);
DCHECK(!streamer_done_);
bool success = false;
if (ready_state_ == kReady) {
ScriptStreamer::StartStreamingLoadedScript(
this, streamer_type, document->GetFrame()->GetSettings(), script_state,
TaskRunnerHelper::Get(task_type, document));
success = streamer_ && !streamer_->IsStreamingFinished();
} else {
ScriptStreamer::StartStreaming(
this, streamer_type, document->GetFrame()->GetSettings(), script_state,
TaskRunnerHelper::Get(task_type, document));
success = streamer_;
}
// If we have successfully started streaming, we are required to call the
// callback.
if (success) {
streamer_done_ = std::move(done);
}
return success;
}
ScriptStreamer::StartStreaming( bool ClassicPendingScript::IsCurrentlyStreaming() const {
this, streamer_type, document->GetFrame()->GetSettings(), script_state, // We could either check our local state, or ask the streamer. Let's
TaskRunnerHelper::Get(TaskType::kNetworking, document)); // double-check these are consistent.
DCHECK_EQ(streamer_ && !streamer_->IsStreamingFinished(),
ready_state_ == kReadyStreaming || (streamer_ && !IsReady()));
return ready_state_ == kReadyStreaming || (streamer_ && !IsReady());
} }
bool ClassicPendingScript::WasCanceled() const { bool ClassicPendingScript::WasCanceled() const {
......
...@@ -37,6 +37,7 @@ class CORE_EXPORT ClassicPendingScript final ...@@ -37,6 +37,7 @@ class CORE_EXPORT ClassicPendingScript final
~ClassicPendingScript() override; ~ClassicPendingScript() override;
// ScriptStreamer callbacks.
void SetStreamer(ScriptStreamer*); void SetStreamer(ScriptStreamer*);
void StreamingFinished(); void StreamingFinished();
...@@ -52,7 +53,9 @@ class CORE_EXPORT ClassicPendingScript final ...@@ -52,7 +53,9 @@ class CORE_EXPORT ClassicPendingScript final
bool IsExternal() const override { return GetResource(); } bool IsExternal() const override { return GetResource(); }
bool ErrorOccurred() const override; bool ErrorOccurred() const override;
bool WasCanceled() const override; bool WasCanceled() const override;
void StartStreamingIfPossible(Document*, ScriptStreamer::Type) override; bool StartStreamingIfPossible(ScriptStreamer::Type,
std::unique_ptr<WTF::Closure>) override;
bool IsCurrentlyStreaming() const override;
KURL UrlForClassicScript() const override; KURL UrlForClassicScript() const override;
void RemoveFromMemoryCache() override; void RemoveFromMemoryCache() override;
void DisposeInternal() override; void DisposeInternal() override;
...@@ -60,12 +63,14 @@ class CORE_EXPORT ClassicPendingScript final ...@@ -60,12 +63,14 @@ class CORE_EXPORT ClassicPendingScript final
void Prefinalize(); void Prefinalize();
private: private:
// See AdvanceReadyState implementation for valid state transitions.
enum ReadyState { enum ReadyState {
// These states are considered "not ready". // These states are considered "not ready".
kWaitingForResource, kWaitingForResource,
kWaitingForStreaming, kWaitingForStreaming,
// These states are considered "ready". // These states are considered "ready".
kReady, kReady,
kReadyStreaming,
kErrorOccurred, kErrorOccurred,
}; };
...@@ -78,6 +83,10 @@ class CORE_EXPORT ClassicPendingScript final ...@@ -78,6 +83,10 @@ class CORE_EXPORT ClassicPendingScript final
// appropriate. // appropriate.
void AdvanceReadyState(ReadyState); void AdvanceReadyState(ReadyState);
// Handle the end of streaming.
void FinishWaitingForStreaming();
void FinishReadyStreaming();
void CheckState() const override; void CheckState() const override;
// ScriptResourceClient // ScriptResourceClient
...@@ -90,7 +99,9 @@ class CORE_EXPORT ClassicPendingScript final ...@@ -90,7 +99,9 @@ class CORE_EXPORT ClassicPendingScript final
ReadyState ready_state_; ReadyState ready_state_;
bool integrity_failure_; bool integrity_failure_;
Member<ScriptStreamer> streamer_; Member<ScriptStreamer> streamer_;
std::unique_ptr<WTF::Closure> streamer_done_;
// This is a temporary flag to confirm that ClassicPendingScript is not // This is a temporary flag to confirm that ClassicPendingScript is not
// touched after its refinalizer call and thus https://crbug.com/715309 // touched after its refinalizer call and thus https://crbug.com/715309
......
...@@ -81,7 +81,12 @@ class CORE_EXPORT ModulePendingScript : public PendingScript { ...@@ -81,7 +81,12 @@ class CORE_EXPORT ModulePendingScript : public PendingScript {
bool ErrorOccurred() const override; bool ErrorOccurred() const override;
bool WasCanceled() const override { return false; } bool WasCanceled() const override { return false; }
void StartStreamingIfPossible(Document*, ScriptStreamer::Type) override {} bool StartStreamingIfPossible(ScriptStreamer::Type,
std::unique_ptr<WTF::Closure>) override {
return false;
}
bool IsCurrentlyStreaming() const override { return false; }
KURL UrlForClassicScript() const override { KURL UrlForClassicScript() const override {
NOTREACHED(); NOTREACHED();
return KURL(); return KURL();
......
...@@ -38,7 +38,6 @@ ...@@ -38,7 +38,6 @@
namespace blink { namespace blink {
class Document;
class PendingScript; class PendingScript;
class CORE_EXPORT PendingScriptClient : public GarbageCollectedMixin { class CORE_EXPORT PendingScriptClient : public GarbageCollectedMixin {
...@@ -88,11 +87,14 @@ class CORE_EXPORT PendingScript ...@@ -88,11 +87,14 @@ class CORE_EXPORT PendingScript
// https://html.spec.whatwg.org/#the-script-is-ready // https://html.spec.whatwg.org/#the-script-is-ready
virtual bool IsReady() const = 0; virtual bool IsReady() const = 0;
virtual bool IsExternal() const = 0; virtual bool IsExternal() const = 0;
virtual bool ErrorOccurred() const = 0; virtual bool ErrorOccurred() const = 0;
virtual bool WasCanceled() const = 0; virtual bool WasCanceled() const = 0;
virtual void StartStreamingIfPossible(Document*, ScriptStreamer::Type) = 0;
// Support for script streaming.
virtual bool StartStreamingIfPossible(ScriptStreamer::Type,
std::unique_ptr<WTF::Closure>) = 0;
virtual bool IsCurrentlyStreaming() const = 0;
// The following two methods are used for document.write() intervention and // The following two methods are used for document.write() intervention and
// have effects only for classic scripts. // have effects only for classic scripts.
......
...@@ -620,8 +620,6 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, ...@@ -620,8 +620,6 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
// time the prepare a script algorithm started." // time the prepare a script algorithm started."
pending_script_ = CreatePendingScript(); pending_script_ = CreatePendingScript();
async_exec_type_ = ScriptRunner::kAsync; async_exec_type_ = ScriptRunner::kAsync;
pending_script_->StartStreamingIfPossible(&element_->GetDocument(),
ScriptStreamer::kAsync);
// TODO(hiroshige): Here |contextDocument| is used as "node document" // TODO(hiroshige): Here |contextDocument| is used as "node document"
// while Step 14 uses |elementDocument| as "node document". Fix this. // while Step 14 uses |elementDocument| as "node document". Fix this.
context_document->GetScriptRunner()->QueueScriptForExecution( context_document->GetScriptRunner()->QueueScriptForExecution(
...@@ -1026,4 +1024,10 @@ bool ScriptLoader::IsScriptForEventSupported() const { ...@@ -1026,4 +1024,10 @@ bool ScriptLoader::IsScriptForEventSupported() const {
DeprecatedEqualIgnoringCase(event_attribute, "onload()"); DeprecatedEqualIgnoringCase(event_attribute, "onload()");
} }
PendingScript* ScriptLoader::GetPendingScriptIfScriptIsAsync() {
if (pending_script_ && async_exec_type_ == ScriptRunner::kAsync)
return pending_script_;
return nullptr;
}
} // namespace blink } // namespace blink
...@@ -140,6 +140,11 @@ class CORE_EXPORT ScriptLoader : public GarbageCollectedFinalized<ScriptLoader>, ...@@ -140,6 +140,11 @@ class CORE_EXPORT ScriptLoader : public GarbageCollectedFinalized<ScriptLoader>,
} }
void SetFetchDocWrittenScriptDeferIdle(); void SetFetchDocWrittenScriptDeferIdle();
// To support script streaming, the ScriptRunner may need to access the
// PendingScript. This breaks the intended layering, so please use with
// care. (Method is virtual to support testing.)
virtual PendingScript* GetPendingScriptIfScriptIsAsync();
protected: protected:
ScriptLoader(ScriptElementBase*, ScriptLoader(ScriptElementBase*,
bool created_by_parser, bool created_by_parser,
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include "core/dom/ScriptRunner.h" #include "core/dom/ScriptRunner.h"
#include <algorithm> #include <algorithm>
#include "bindings/core/v8/ScriptStreamer.h"
#include "core/dom/Document.h" #include "core/dom/Document.h"
#include "core/dom/ScriptLoader.h" #include "core/dom/ScriptLoader.h"
#include "core/dom/TaskRunnerHelper.h" #include "core/dom/TaskRunnerHelper.h"
...@@ -43,7 +44,7 @@ ScriptRunner::ScriptRunner(Document* document) ...@@ -43,7 +44,7 @@ ScriptRunner::ScriptRunner(Document* document)
is_suspended_(false) { is_suspended_(false) {
DCHECK(document); DCHECK(document);
#ifndef NDEBUG #ifndef NDEBUG
has_ever_been_suspended_ = false; number_of_extra_tasks_ = 0;
#endif #endif
} }
...@@ -54,6 +55,7 @@ void ScriptRunner::QueueScriptForExecution(ScriptLoader* script_loader, ...@@ -54,6 +55,7 @@ void ScriptRunner::QueueScriptForExecution(ScriptLoader* script_loader,
switch (execution_type) { switch (execution_type) {
case kAsync: case kAsync:
pending_async_scripts_.insert(script_loader); pending_async_scripts_.insert(script_loader);
TryStream(script_loader);
break; break;
case kInOrder: case kInOrder:
...@@ -74,7 +76,9 @@ void ScriptRunner::PostTask(const WebTraceLocation& web_trace_location) { ...@@ -74,7 +76,9 @@ void ScriptRunner::PostTask(const WebTraceLocation& web_trace_location) {
void ScriptRunner::Suspend() { void ScriptRunner::Suspend() {
#ifndef NDEBUG #ifndef NDEBUG
has_ever_been_suspended_ = true; // Resume will re-post tasks for all available scripts.
number_of_extra_tasks_ += async_scripts_to_execute_soon_.size() +
in_order_scripts_to_execute_soon_.size();
#endif #endif
is_suspended_ = true; is_suspended_ = true;
...@@ -117,7 +121,7 @@ void ScriptRunner::NotifyScriptReady(ScriptLoader* script_loader, ...@@ -117,7 +121,7 @@ void ScriptRunner::NotifyScriptReady(ScriptLoader* script_loader,
async_scripts_to_execute_soon_.push_back(script_loader); async_scripts_to_execute_soon_.push_back(script_loader);
PostTask(BLINK_FROM_HERE); PostTask(BLINK_FROM_HERE);
TryStreamAny();
break; break;
case kInOrder: case kInOrder:
...@@ -144,6 +148,11 @@ bool ScriptRunner::RemovePendingInOrderScript(ScriptLoader* script_loader) { ...@@ -144,6 +148,11 @@ bool ScriptRunner::RemovePendingInOrderScript(ScriptLoader* script_loader) {
return true; return true;
} }
void ScriptRunner::NotifyScriptStreamerFinished() {
PostTask(BLINK_FROM_HERE);
TryStreamAny();
}
void ScriptRunner::MovePendingScript(Document& old_document, void ScriptRunner::MovePendingScript(Document& old_document,
Document& new_document, Document& new_document,
ScriptLoader* script_loader) { ScriptLoader* script_loader) {
...@@ -187,31 +196,100 @@ void ScriptRunner::MovePendingScript(ScriptRunner* new_runner, ...@@ -187,31 +196,100 @@ void ScriptRunner::MovePendingScript(ScriptRunner* new_runner,
} }
} }
// Returns true if task was run, and false otherwise. bool ScriptRunner::ExecuteInOrderTask() {
bool ScriptRunner::ExecuteTaskFromQueue( if (in_order_scripts_to_execute_soon_.IsEmpty())
HeapDeque<Member<ScriptLoader>>* task_queue) {
if (task_queue->IsEmpty())
return false; return false;
task_queue->TakeFirst()->Execute();
PendingScript* pending_script = in_order_scripts_to_execute_soon_.front()
->GetPendingScriptIfScriptIsAsync();
if (pending_script && pending_script->IsCurrentlyStreaming())
return false;
in_order_scripts_to_execute_soon_.TakeFirst()->Execute();
document_->DecrementLoadEventDelayCount(); document_->DecrementLoadEventDelayCount();
return true; return true;
} }
bool ScriptRunner::ExecuteAsyncTask() {
for (auto iter = async_scripts_to_execute_soon_.begin();
iter != async_scripts_to_execute_soon_.end(); ++iter) {
PendingScript* pending_script = (*iter)->GetPendingScriptIfScriptIsAsync();
if (!pending_script || !pending_script->IsCurrentlyStreaming()) {
ScriptLoader* loader = *iter;
async_scripts_to_execute_soon_.erase(iter);
loader->Execute();
document_->DecrementLoadEventDelayCount();
return true;
}
}
return false;
}
void ScriptRunner::ExecuteTask() { void ScriptRunner::ExecuteTask() {
if (is_suspended_) if (is_suspended_)
return; return;
if (ExecuteTaskFromQueue(&async_scripts_to_execute_soon_)) if (ExecuteAsyncTask())
return;
if (ExecuteInOrderTask())
return;
#ifndef NDEBUG
// Extra tasks should be posted only when we resume after suspending,
// or when we stream a script. These should all be accounted for in
// number_of_extra_tasks_.
DCHECK_GT(number_of_extra_tasks_--, 0);
#endif
}
void ScriptRunner::TryStreamAny() {
if (is_suspended_)
return; return;
if (ExecuteTaskFromQueue(&in_order_scripts_to_execute_soon_)) if (!RuntimeEnabledFeatures::WorkStealingInScriptRunnerEnabled())
return; return;
// Look through async_scripts_to_execute_soon_, and stream any one of them.
for (auto script_loader : async_scripts_to_execute_soon_) {
if (DoTryStream(script_loader))
return;
}
}
void ScriptRunner::TryStream(ScriptLoader* script_loader) {
if (!is_suspended_)
DoTryStream(script_loader);
}
bool ScriptRunner::DoTryStream(ScriptLoader* script_loader) {
// Checks that all callers should have already done.
DCHECK(!is_suspended_);
DCHECK(script_loader);
// Currently, we support streaming only for async scripts.
DCHECK(pending_async_scripts_.find(script_loader) !=
pending_async_scripts_.end() ||
std::find(async_scripts_to_execute_soon_.begin(),
async_scripts_to_execute_soon_.end(),
script_loader) != async_scripts_to_execute_soon_.end());
PendingScript* pending_script =
script_loader->GetPendingScriptIfScriptIsAsync();
if (!pending_script)
return false;
bool success = pending_script->StartStreamingIfPossible(
ScriptStreamer::kAsync,
WTF::Bind(&ScriptRunner::NotifyScriptStreamerFinished,
WrapPersistent(this)));
DCHECK_EQ(success, pending_script->IsCurrentlyStreaming());
#ifndef NDEBUG #ifndef NDEBUG
// Extra tasks should be posted only when we resume after suspending. if (success)
DCHECK(has_ever_been_suspended_); number_of_extra_tasks_++;
#endif #endif
return success;
} }
DEFINE_TRACE(ScriptRunner) { DEFINE_TRACE(ScriptRunner) {
......
...@@ -61,6 +61,7 @@ class CORE_EXPORT ScriptRunner final ...@@ -61,6 +61,7 @@ class CORE_EXPORT ScriptRunner final
void Suspend(); void Suspend();
void Resume(); void Resume();
void NotifyScriptReady(ScriptLoader*, AsyncExecutionType); void NotifyScriptReady(ScriptLoader*, AsyncExecutionType);
void NotifyScriptStreamerFinished();
static void MovePendingScript(Document&, Document&, ScriptLoader*); static void MovePendingScript(Document&, Document&, ScriptLoader*);
...@@ -77,10 +78,20 @@ class CORE_EXPORT ScriptRunner final ...@@ -77,10 +78,20 @@ class CORE_EXPORT ScriptRunner final
void PostTask(const WebTraceLocation&); void PostTask(const WebTraceLocation&);
bool ExecuteTaskFromQueue(HeapDeque<Member<ScriptLoader>>*); // Execute the first task in in_order_scripts_to_execute_soon_.
// Returns true if task was run, and false otherwise.
bool ExecuteInOrderTask();
// Execute any task in async_scripts_to_execute_soon_.
// Returns true if task was run, and false otherwise.
bool ExecuteAsyncTask();
void ExecuteTask(); void ExecuteTask();
// Try to start streaming a specific script or any available script.
void TryStream(ScriptLoader*);
void TryStreamAny();
bool DoTryStream(ScriptLoader*); // Implementation for both Try* methods.
Member<Document> document_; Member<Document> document_;
HeapDeque<Member<ScriptLoader>> pending_in_order_scripts_; HeapDeque<Member<ScriptLoader>> pending_in_order_scripts_;
...@@ -95,8 +106,14 @@ class CORE_EXPORT ScriptRunner final ...@@ -95,8 +106,14 @@ class CORE_EXPORT ScriptRunner final
int number_of_in_order_scripts_with_pending_notification_; int number_of_in_order_scripts_with_pending_notification_;
bool is_suspended_; bool is_suspended_;
#ifndef NDEBUG #ifndef NDEBUG
bool has_ever_been_suspended_; // We expect to have one posted task in flight for each script in either
// .._to_be_executed_soon_ queue. This invariant will be temporarily violated
// when the ScriptRunner is suspended, or when we take a Script out the
// async_scripts_to_be_executed_soon_ queue for streaming. We'll use this
// variable to account & check this invariant for debugging.
int number_of_extra_tasks_;
#endif #endif
}; };
......
...@@ -19,23 +19,110 @@ using ::testing::ElementsAre; ...@@ -19,23 +19,110 @@ using ::testing::ElementsAre;
using ::testing::Return; using ::testing::Return;
using ::testing::WhenSorted; using ::testing::WhenSorted;
using ::testing::ElementsAreArray; using ::testing::ElementsAreArray;
using ::testing::_;
namespace blink { namespace blink {
class MockPendingScript : public PendingScript {
public:
static MockPendingScript* Create() { return new MockPendingScript; }
~MockPendingScript() override {}
MOCK_CONST_METHOD0(GetScriptType, ScriptType());
MOCK_CONST_METHOD2(GetSource, Script*(const KURL&, bool&));
MOCK_CONST_METHOD0(IsReady, bool());
MOCK_CONST_METHOD0(IsExternal, bool());
MOCK_CONST_METHOD0(ErrorOccurred, bool());
MOCK_CONST_METHOD0(WasCanceled, bool());
MOCK_CONST_METHOD0(UrlForClassicScript, KURL());
MOCK_METHOD0(RemoveFromMemoryCache, void());
MOCK_CONST_METHOD0(IsCurrentlyStreaming, bool());
// The currently released googletest cannot mock methods with C++ move-only
// types like std::unique_ptr. This has been promised to be fixed in a
// future release of googletest, but for now we use the recommended
// workaround of 'bumping' the method-to-be-mocked to another method.
bool StartStreamingIfPossible(
ScriptStreamer::Type type,
std::unique_ptr<WTF::Closure> closure) override {
bool started = DoStartStreamingIfPossible(type, closure.get());
if (started)
closure.release();
return started;
}
MOCK_METHOD2(DoStartStreamingIfPossible,
bool(ScriptStreamer::Type, WTF::Closure*));
protected:
MOCK_METHOD0(DisposeInternal, void());
MOCK_CONST_METHOD0(CheckState, void());
private:
MockPendingScript() : PendingScript(nullptr, TextPosition()) {}
};
class MockScriptLoader final : public ScriptLoader { class MockScriptLoader final : public ScriptLoader {
public: public:
static MockScriptLoader* Create() { return new MockScriptLoader(); } static MockScriptLoader* Create() {
return (new MockScriptLoader())->SetupForNonStreaming();
}
~MockScriptLoader() override {} ~MockScriptLoader() override {}
MOCK_METHOD0(Execute, void()); MOCK_METHOD0(Execute, void());
MOCK_CONST_METHOD0(IsReady, bool()); MOCK_CONST_METHOD0(IsReady, bool());
MOCK_METHOD0(GetPendingScriptIfScriptIsAsync, PendingScript*());
// Set up the mock for streaming. The closure passed in will receive the
// 'finish streaming' callback, so that the test case can control when
// the mock streaming has mock finished.
MockScriptLoader* SetupForStreaming(
std::unique_ptr<WTF::Closure>& finished_callback);
MockScriptLoader* SetupForNonStreaming();
DECLARE_VIRTUAL_TRACE();
private: private:
explicit MockScriptLoader() explicit MockScriptLoader()
: ScriptLoader(MockScriptElementBase::Create(), false, false, false) {} : ScriptLoader(MockScriptElementBase::Create(), false, false, false) {}
Member<MockPendingScript> mock_pending_script_;
}; };
class ScriptRunnerTest : public ::testing::Test { void MockScriptLoader::Trace(blink::Visitor* visitor) {
ScriptLoader::Trace(visitor);
visitor->Trace(mock_pending_script_);
}
MockScriptLoader* MockScriptLoader::SetupForStreaming(
std::unique_ptr<WTF::Closure>& finished_callback) {
mock_pending_script_ = MockPendingScript::Create();
SetupForNonStreaming();
// Mock the streaming functions and save the 'streaming done' callback
// into the callback done variable. This way, we can control when
// streamig is done.
EXPECT_CALL(*mock_pending_script_, DoStartStreamingIfPossible(_, _))
.WillOnce(Return(false))
.WillOnce(Invoke([&finished_callback](ScriptStreamer::Type,
WTF::Closure* callback) -> bool {
finished_callback.reset(callback);
return true;
}))
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_pending_script_, IsCurrentlyStreaming())
.WillRepeatedly(
Invoke([&finished_callback] { return !!finished_callback; }));
return this;
}
MockScriptLoader* MockScriptLoader::SetupForNonStreaming() {
EXPECT_CALL(*this, GetPendingScriptIfScriptIsAsync())
.WillRepeatedly(Invoke([this]() -> PendingScript* {
return this->mock_pending_script_.Get();
}));
return this;
}
class ScriptRunnerTest : public testing::Test {
public: public:
ScriptRunnerTest() : document_(Document::Create()) {} ScriptRunnerTest() : document_(Document::Create()) {}
...@@ -433,4 +520,38 @@ TEST_F(ScriptRunnerTest, TasksWithDeadScriptRunner) { ...@@ -433,4 +520,38 @@ TEST_F(ScriptRunnerTest, TasksWithDeadScriptRunner) {
platform_->RunUntilIdle(); platform_->RunUntilIdle();
} }
TEST_F(ScriptRunnerTest, TryStreamWhenEnqueingScript) {
Persistent<MockScriptLoader> script_loader1 = MockScriptLoader::Create();
EXPECT_CALL(*script_loader1, IsReady()).WillRepeatedly(Return(true));
script_runner_->QueueScriptForExecution(script_loader1, ScriptRunner::kAsync);
}
TEST_F(ScriptRunnerTest, DontExecuteWhileStreaming) {
std::unique_ptr<WTF::Closure> callback;
Persistent<MockScriptLoader> script_loader1 =
MockScriptLoader::Create()->SetupForStreaming(callback);
EXPECT_CALL(*script_loader1, IsReady()).WillRepeatedly(Return(true));
// Enqueue script & make it ready.
script_runner_->QueueScriptForExecution(script_loader1, ScriptRunner::kAsync);
script_runner_->NotifyScriptReady(script_loader1, ScriptRunner::kAsync);
// We should have started streaming by now.
CHECK(callback);
// Note that there is no expectation for ScriptLoader::Execute() yet,
// so the mock will fail if it's called anyway.
platform_->RunUntilIdle();
// Finish streaming. Note that 'callback' must be empty when the callback
// is called, since out mock uses it to determine whether we're still
// streaming.
std::unique_ptr<WTF::Closure> tmp_callback(std::move(callback));
(*tmp_callback)();
// Now that streaming is finished, expect Execute() to be called.
EXPECT_CALL(*script_loader1, Execute()).Times(1);
platform_->RunUntilIdle();
}
} // namespace blink } // namespace blink
...@@ -540,7 +540,7 @@ void HTMLParserScriptRunner::RequestParsingBlockingScript(Element* element) { ...@@ -540,7 +540,7 @@ void HTMLParserScriptRunner::RequestParsingBlockingScript(Element* element) {
// returning control to the parser. // returning control to the parser.
if (!ParserBlockingScript()->IsReady()) { if (!ParserBlockingScript()->IsReady()) {
parser_blocking_script_->StartStreamingIfPossible( parser_blocking_script_->StartStreamingIfPossible(
document_, ScriptStreamer::kParsingBlocking); ScriptStreamer::kParsingBlocking, nullptr);
parser_blocking_script_->WatchForLoad(this); parser_blocking_script_->WatchForLoad(this);
} }
} }
...@@ -552,8 +552,8 @@ void HTMLParserScriptRunner::RequestDeferredScript(Element* element) { ...@@ -552,8 +552,8 @@ void HTMLParserScriptRunner::RequestDeferredScript(Element* element) {
return; return;
if (!pending_script->IsReady()) { if (!pending_script->IsReady()) {
pending_script->StartStreamingIfPossible(document_, pending_script->StartStreamingIfPossible(ScriptStreamer::kDeferred,
ScriptStreamer::kDeferred); nullptr);
} }
DCHECK(pending_script->IsExternalOrModule()); DCHECK(pending_script->IsExternalOrModule());
......
...@@ -1155,6 +1155,10 @@ ...@@ -1155,6 +1155,10 @@
name: "Worklet", name: "Worklet",
status: "experimental", status: "experimental",
}, },
{
name: "WorkStealingInScriptRunner",
status: "experimental",
},
{ {
name: "XSLT", name: "XSLT",
status: "stable", status: "stable",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment