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,
}
ScriptSourceCode::ScriptSourceCode(ScriptResource* resource)
: source_(resource->SourceText()),
resource_(resource),
start_position_(TextPosition::MinimumPosition()) {
TreatNullSourceAsEmpty();
}
: ScriptSourceCode(nullptr, resource) {}
ScriptSourceCode::ScriptSourceCode(ScriptStreamer* streamer,
ScriptResource* resource)
......
......@@ -67,6 +67,7 @@ enum NotStreamingReason {
kThreadBusy,
kV8CannotStream,
kScriptTooSmall,
kNoResourceBuffer,
kNotStreamingReasonEnd
};
......@@ -339,6 +340,57 @@ void ScriptStreamer::StartStreaming(ClassicPendingScript* script,
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(
const char* encoding_name,
v8::ScriptCompiler::StreamedSource::Encoding* encoding) {
......@@ -366,6 +418,11 @@ bool ScriptStreamer::IsFinished() const {
return loading_finished_ && (parsing_finished_ || streaming_suppressed_);
}
bool ScriptStreamer::IsStreamingFinished() const {
DCHECK(IsMainThread());
return parsing_finished_ || streaming_suppressed_;
}
void ScriptStreamer::StreamingCompleteOnBackgroundThread() {
DCHECK(!IsMainThread());
......
......@@ -48,11 +48,20 @@ class CORE_EXPORT ScriptStreamer final
ScriptState*,
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.
static bool ConvertEncoding(const char* encoding_name,
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(); }
ScriptResource* GetResource() const { return resource_; }
......
......@@ -67,6 +67,23 @@ void ClassicPendingScript::DisposeInternal() {
}
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();
DCHECK(GetResource());
DCHECK_EQ(ready_state_, kWaitingForStreaming);
......@@ -75,6 +92,13 @@ void ClassicPendingScript::StreamingFinished() {
AdvanceReadyState(error_occurred ? kErrorOccurred : kReady);
}
void ClassicPendingScript::FinishReadyStreaming() {
CheckState();
DCHECK(GetResource());
DCHECK_EQ(ready_state_, kReadyStreaming);
AdvanceReadyState(kReady);
}
void ClassicPendingScript::NotifyFinished(Resource* resource) {
// The following SRI checks need to be here because, unfortunately, fetches
// are not done purely according to the Fetch spec. In particular,
......@@ -120,7 +144,7 @@ void ClassicPendingScript::NotifyFinished(Resource* resource) {
if (streamer_)
streamer_->NotifyFinished(resource);
else
StreamingFinished();
FinishWaitingForStreaming();
}
void ClassicPendingScript::NotifyAppendData(ScriptResource* resource) {
......@@ -141,23 +165,29 @@ ClassicScript* ClassicPendingScript::GetSource(const KURL& document_url,
DCHECK(IsReady());
error_occurred = ErrorOccurred();
if (GetResource()) {
DCHECK(GetResource()->IsLoaded());
if (streamer_ && !streamer_->StreamingSuppressed())
return ClassicScript::Create(ScriptSourceCode(streamer_, GetResource()));
return ClassicScript::Create(ScriptSourceCode(GetResource()));
}
if (!GetResource()) {
return ClassicScript::Create(ScriptSourceCode(
GetElement()->TextFromChildren(), document_url, StartingPosition()));
}
DCHECK(GetResource()->IsLoaded());
bool streamer_ready = (ready_state_ == kReady) && streamer_ &&
!streamer_->StreamingSuppressed();
return ClassicScript::Create(
ScriptSourceCode(streamer_ready ? streamer_ : nullptr, GetResource()));
}
void ClassicPendingScript::SetStreamer(ScriptStreamer* streamer) {
DCHECK(streamer);
DCHECK(!streamer_);
DCHECK(!IsWatchingForLoad());
DCHECK(!IsWatchingForLoad() || ready_state_ != kWaitingForResource);
DCHECK(!streamer->IsFinished());
DCHECK_LT(ready_state_, kWaitingForStreaming);
DCHECK(ready_state_ == kWaitingForResource || ready_state_ == kReady);
streamer_ = streamer;
if (streamer && ready_state_ == kReady)
AdvanceReadyState(kReadyStreaming);
CheckState();
}
......@@ -172,17 +202,33 @@ bool ClassicPendingScript::ErrorOccurred() const {
}
void ClassicPendingScript::AdvanceReadyState(ReadyState new_ready_state) {
CHECK_GT(new_ready_state, ready_state_)
<< "The ready state should monotonically advance.";
if (new_ready_state >= kReady) {
CHECK_LT(ready_state_, kReady)
<< "The state should not advance from one completed state to another.";
// We will allow exactly these state transitions:
//
// kWaitingForResource -> kWaitingForStreaming -> [kReady, kErrorOccurred]
// kReady -> kReadyStreaming -> kReady
switch (ready_state_) {
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;
if (ready_state_ >= kReady && IsWatchingForLoad())
// Did we transition into a 'ready' state?
if (IsReady() && !old_is_ready && IsWatchingForLoad())
Client()->PendingScriptFinished(this);
}
......@@ -194,19 +240,76 @@ void ClassicPendingScript::OnPurgeMemory() {
streamer_ = nullptr;
}
void ClassicPendingScript::StartStreamingIfPossible(
Document* document,
ScriptStreamer::Type streamer_type) {
if (!document->GetFrame())
return;
bool ClassicPendingScript::StartStreamingIfPossible(
ScriptStreamer::Type streamer_type,
std::unique_ptr<WTF::Closure> done) {
// We can start streaming in two states: While still loading
// (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());
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(TaskType::kNetworking, document));
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;
}
bool ClassicPendingScript::IsCurrentlyStreaming() const {
// We could either check our local state, or ask the streamer. Let's
// 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 {
......
......@@ -37,6 +37,7 @@ class CORE_EXPORT ClassicPendingScript final
~ClassicPendingScript() override;
// ScriptStreamer callbacks.
void SetStreamer(ScriptStreamer*);
void StreamingFinished();
......@@ -52,7 +53,9 @@ class CORE_EXPORT ClassicPendingScript final
bool IsExternal() const override { return GetResource(); }
bool ErrorOccurred() 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;
void RemoveFromMemoryCache() override;
void DisposeInternal() override;
......@@ -60,12 +63,14 @@ class CORE_EXPORT ClassicPendingScript final
void Prefinalize();
private:
// See AdvanceReadyState implementation for valid state transitions.
enum ReadyState {
// These states are considered "not ready".
kWaitingForResource,
kWaitingForStreaming,
// These states are considered "ready".
kReady,
kReadyStreaming,
kErrorOccurred,
};
......@@ -78,6 +83,10 @@ class CORE_EXPORT ClassicPendingScript final
// appropriate.
void AdvanceReadyState(ReadyState);
// Handle the end of streaming.
void FinishWaitingForStreaming();
void FinishReadyStreaming();
void CheckState() const override;
// ScriptResourceClient
......@@ -90,7 +99,9 @@ class CORE_EXPORT ClassicPendingScript final
ReadyState ready_state_;
bool integrity_failure_;
Member<ScriptStreamer> streamer_;
std::unique_ptr<WTF::Closure> streamer_done_;
// This is a temporary flag to confirm that ClassicPendingScript is not
// touched after its refinalizer call and thus https://crbug.com/715309
......
......@@ -81,7 +81,12 @@ class CORE_EXPORT ModulePendingScript : public PendingScript {
bool ErrorOccurred() const override;
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 {
NOTREACHED();
return KURL();
......
......@@ -38,7 +38,6 @@
namespace blink {
class Document;
class PendingScript;
class CORE_EXPORT PendingScriptClient : public GarbageCollectedMixin {
......@@ -88,11 +87,14 @@ class CORE_EXPORT PendingScript
// https://html.spec.whatwg.org/#the-script-is-ready
virtual bool IsReady() const = 0;
virtual bool IsExternal() const = 0;
virtual bool ErrorOccurred() 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
// have effects only for classic scripts.
......
......@@ -620,8 +620,6 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
// time the prepare a script algorithm started."
pending_script_ = CreatePendingScript();
async_exec_type_ = ScriptRunner::kAsync;
pending_script_->StartStreamingIfPossible(&element_->GetDocument(),
ScriptStreamer::kAsync);
// TODO(hiroshige): Here |contextDocument| is used as "node document"
// while Step 14 uses |elementDocument| as "node document". Fix this.
context_document->GetScriptRunner()->QueueScriptForExecution(
......@@ -1026,4 +1024,10 @@ bool ScriptLoader::IsScriptForEventSupported() const {
DeprecatedEqualIgnoringCase(event_attribute, "onload()");
}
PendingScript* ScriptLoader::GetPendingScriptIfScriptIsAsync() {
if (pending_script_ && async_exec_type_ == ScriptRunner::kAsync)
return pending_script_;
return nullptr;
}
} // namespace blink
......@@ -140,6 +140,11 @@ class CORE_EXPORT ScriptLoader : public GarbageCollectedFinalized<ScriptLoader>,
}
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:
ScriptLoader(ScriptElementBase*,
bool created_by_parser,
......
......@@ -26,6 +26,7 @@
#include "core/dom/ScriptRunner.h"
#include <algorithm>
#include "bindings/core/v8/ScriptStreamer.h"
#include "core/dom/Document.h"
#include "core/dom/ScriptLoader.h"
#include "core/dom/TaskRunnerHelper.h"
......@@ -43,7 +44,7 @@ ScriptRunner::ScriptRunner(Document* document)
is_suspended_(false) {
DCHECK(document);
#ifndef NDEBUG
has_ever_been_suspended_ = false;
number_of_extra_tasks_ = 0;
#endif
}
......@@ -54,6 +55,7 @@ void ScriptRunner::QueueScriptForExecution(ScriptLoader* script_loader,
switch (execution_type) {
case kAsync:
pending_async_scripts_.insert(script_loader);
TryStream(script_loader);
break;
case kInOrder:
......@@ -74,7 +76,9 @@ void ScriptRunner::PostTask(const WebTraceLocation& web_trace_location) {
void ScriptRunner::Suspend() {
#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
is_suspended_ = true;
......@@ -117,7 +121,7 @@ void ScriptRunner::NotifyScriptReady(ScriptLoader* script_loader,
async_scripts_to_execute_soon_.push_back(script_loader);
PostTask(BLINK_FROM_HERE);
TryStreamAny();
break;
case kInOrder:
......@@ -144,6 +148,11 @@ bool ScriptRunner::RemovePendingInOrderScript(ScriptLoader* script_loader) {
return true;
}
void ScriptRunner::NotifyScriptStreamerFinished() {
PostTask(BLINK_FROM_HERE);
TryStreamAny();
}
void ScriptRunner::MovePendingScript(Document& old_document,
Document& new_document,
ScriptLoader* script_loader) {
......@@ -187,31 +196,100 @@ void ScriptRunner::MovePendingScript(ScriptRunner* new_runner,
}
}
// Returns true if task was run, and false otherwise.
bool ScriptRunner::ExecuteTaskFromQueue(
HeapDeque<Member<ScriptLoader>>* task_queue) {
if (task_queue->IsEmpty())
bool ScriptRunner::ExecuteInOrderTask() {
if (in_order_scripts_to_execute_soon_.IsEmpty())
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();
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() {
if (is_suspended_)
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;
if (!RuntimeEnabledFeatures::WorkStealingInScriptRunnerEnabled())
return;
if (ExecuteTaskFromQueue(&in_order_scripts_to_execute_soon_))
// 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
// Extra tasks should be posted only when we resume after suspending.
DCHECK(has_ever_been_suspended_);
if (success)
number_of_extra_tasks_++;
#endif
return success;
}
DEFINE_TRACE(ScriptRunner) {
......
......@@ -61,6 +61,7 @@ class CORE_EXPORT ScriptRunner final
void Suspend();
void Resume();
void NotifyScriptReady(ScriptLoader*, AsyncExecutionType);
void NotifyScriptStreamerFinished();
static void MovePendingScript(Document&, Document&, ScriptLoader*);
......@@ -77,10 +78,20 @@ class CORE_EXPORT ScriptRunner final
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();
// 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_;
HeapDeque<Member<ScriptLoader>> pending_in_order_scripts_;
......@@ -95,8 +106,14 @@ class CORE_EXPORT ScriptRunner final
int number_of_in_order_scripts_with_pending_notification_;
bool is_suspended_;
#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
};
......
......@@ -19,23 +19,110 @@ using ::testing::ElementsAre;
using ::testing::Return;
using ::testing::WhenSorted;
using ::testing::ElementsAreArray;
using ::testing::_;
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 {
public:
static MockScriptLoader* Create() { return new MockScriptLoader(); }
static MockScriptLoader* Create() {
return (new MockScriptLoader())->SetupForNonStreaming();
}
~MockScriptLoader() override {}
MOCK_METHOD0(Execute, void());
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:
explicit MockScriptLoader()
: 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:
ScriptRunnerTest() : document_(Document::Create()) {}
......@@ -433,4 +520,38 @@ TEST_F(ScriptRunnerTest, TasksWithDeadScriptRunner) {
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
......@@ -540,7 +540,7 @@ void HTMLParserScriptRunner::RequestParsingBlockingScript(Element* element) {
// returning control to the parser.
if (!ParserBlockingScript()->IsReady()) {
parser_blocking_script_->StartStreamingIfPossible(
document_, ScriptStreamer::kParsingBlocking);
ScriptStreamer::kParsingBlocking, nullptr);
parser_blocking_script_->WatchForLoad(this);
}
}
......@@ -552,8 +552,8 @@ void HTMLParserScriptRunner::RequestDeferredScript(Element* element) {
return;
if (!pending_script->IsReady()) {
pending_script->StartStreamingIfPossible(document_,
ScriptStreamer::kDeferred);
pending_script->StartStreamingIfPossible(ScriptStreamer::kDeferred,
nullptr);
}
DCHECK(pending_script->IsExternalOrModule());
......
......@@ -1155,6 +1155,10 @@
name: "Worklet",
status: "experimental",
},
{
name: "WorkStealingInScriptRunner",
status: "experimental",
},
{
name: "XSLT",
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