Commit 322942d0 authored by nhiroki's avatar nhiroki Committed by Commit bot

Worklet: Introduce "pending tasks struct" concept defined in the Worklet spec

This is split from https://codereview.chromium.org/2840523002/

This CL introduces "pending tasks struct" concept defined in the Worklet spec:
https://drafts.css-houdini.org/worklets/#pending-tasks-struct

The pending tasks struct is used as a kind of barrier closure: when
Worklet::addModule() is called, multiple WorkletGlobalScopes associated with the
Worklet may start module loading. The struct waits until they all are completed
and then resolves a promise. If one of them is failed, the struct immediately
rejects a promise and ignores following completions.

This CL also removes WorkletObjectProxy and the request id mechanism in
MainThreadWorklet using the pending tasks struct.

BUG=627945

Review-Url: https://codereview.chromium.org/2839123003
Cr-Commit-Position: refs/heads/master@{#467908}
parent fca892fe
......@@ -54,9 +54,6 @@ class WorkletScriptLoader final
// after Client::notifyWorkletScriptLoadingFinished() is called.
bool WasScriptLoadSuccessful() const;
void set_request_id(int32_t request_id) { request_id_ = request_id; }
int32_t request_id() const { return request_id_; }
DECLARE_TRACE();
private:
......@@ -69,10 +66,6 @@ class WorkletScriptLoader final
Member<ResourceFetcher> fetcher_;
Member<Client> client_;
// The client of this loader can freely use this field to identify a fetch
// request.
int32_t request_id_ = -1;
bool was_script_load_successful_ = false;
bool was_script_load_complete_ = false;
};
......
......@@ -84,7 +84,8 @@ blink_core_sources("workers") {
"WorkletGlobalScope.cpp",
"WorkletGlobalScope.h",
"WorkletGlobalScopeProxy.h",
"WorkletObjectProxy.h",
"WorkletPendingTasks.cpp",
"WorkletPendingTasks.h",
"WorkletThreadHolder.h",
]
......
......@@ -11,27 +11,17 @@
#include "core/dom/ExceptionCode.h"
#include "core/frame/LocalFrame.h"
#include "core/workers/WorkletGlobalScopeProxy.h"
#include "core/workers/WorkletPendingTasks.h"
#include "platform/wtf/WTF.h"
namespace blink {
namespace {
int32_t GetNextRequestId() {
DCHECK(IsMainThread());
static int32_t next_request_id = 1;
CHECK_LT(next_request_id, std::numeric_limits<int32_t>::max());
return next_request_id++;
}
} // namespace
MainThreadWorklet::MainThreadWorklet(LocalFrame* frame) : Worklet(frame) {
DCHECK(resolver_map_.IsEmpty());
}
MainThreadWorklet::MainThreadWorklet(LocalFrame* frame) : Worklet(frame) {}
// Implementation of the "addModule(moduleURL, options)" algorithm:
// https://drafts.css-houdini.org/worklets/#dom-worklet-addmodule
ScriptPromise MainThreadWorklet::addModule(ScriptState* script_state,
const String& url) {
const String& module_url) {
DCHECK(IsMainThread());
if (!GetExecutionContext()) {
return ScriptPromise::RejectWithDOMException(
......@@ -39,44 +29,44 @@ ScriptPromise MainThreadWorklet::addModule(ScriptState* script_state,
"This frame is already detached"));
}
KURL script_url = GetExecutionContext()->CompleteURL(url);
if (!script_url.IsValid()) {
KURL module_url_record = GetExecutionContext()->CompleteURL(module_url);
if (!module_url_record.IsValid()) {
return ScriptPromise::RejectWithDOMException(
script_state, DOMException::Create(
kSyntaxError, "'" + url + "' is not a valid URL."));
script_state,
DOMException::Create(kSyntaxError,
"'" + module_url + "' is not a valid URL."));
}
int32_t request_id = GetNextRequestId();
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
resolver_map_.Set(request_id, resolver);
GetWorkletGlobalScopeProxy()->FetchAndInvokeScript(request_id, script_url);
return promise;
}
void MainThreadWorklet::DidFetchAndInvokeScript(int32_t request_id,
bool success) {
DCHECK(IsMainThread());
ScriptPromiseResolver* resolver = resolver_map_.at(request_id);
if (!resolver)
return;
resolver_map_.erase(request_id);
if (!success) {
resolver->Reject(DOMException::Create(kNetworkError));
return;
}
resolver->Resolve();
// Step 11: "Let pendingTaskStruct be a new pending tasks struct with counter
// initialized to the length of worklet's WorkletGlobalScopes."
// TODO(nhiroki): Introduce the concept of "worklet's WorkletGlobalScopes" and
// use the length of it here.
constexpr int number_of_global_scopes = 1;
WorkletPendingTasks* pending_tasks =
new WorkletPendingTasks(number_of_global_scopes, resolver);
// Step 12: "For each workletGlobalScope in the worklet's
// WorkletGlobalScopes, queue a task on the workletGlobalScope to fetch and
// invoke a worklet script given workletGlobalScope, moduleURLRecord,
// moduleResponsesMap, credentialOptions, outsideSettings, pendingTaskStruct,
// and promise."
// TODO(nhiroki): Pass the remaining parameters (e.g., credentialOptions).
// TODO(nhiroki): Queue a task instead of executing this here.
GetWorkletGlobalScopeProxy()->FetchAndInvokeScript(module_url_record,
pending_tasks);
return promise;
}
void MainThreadWorklet::ContextDestroyed(ExecutionContext* execution_context) {
DCHECK(IsMainThread());
resolver_map_.clear();
GetWorkletGlobalScopeProxy()->TerminateWorkletGlobalScope();
Worklet::ContextDestroyed(execution_context);
}
DEFINE_TRACE(MainThreadWorklet) {
visitor->Trace(resolver_map_);
Worklet::Trace(visitor);
}
......
......@@ -9,7 +9,6 @@
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/CoreExport.h"
#include "core/workers/WorkletObjectProxy.h"
#include "platform/heap/Handle.h"
namespace blink {
......@@ -20,8 +19,7 @@ class LocalFrame;
// TODO(nhiroki): This is a temporary class to support module loading for main
// thread worklets. This and ThreadedWorklet will be merged into the base
// Worklet class once threaded worklets are ready to use module loading.
class CORE_EXPORT MainThreadWorklet : public Worklet,
public WorkletObjectProxy {
class CORE_EXPORT MainThreadWorklet : public Worklet {
USING_GARBAGE_COLLECTED_MIXIN(MainThreadWorklet);
WTF_MAKE_NONCOPYABLE(MainThreadWorklet);
......@@ -29,22 +27,15 @@ class CORE_EXPORT MainThreadWorklet : public Worklet,
virtual ~MainThreadWorklet() = default;
// Worklet
ScriptPromise addModule(ScriptState*, const String& url) final;
ScriptPromise addModule(ScriptState*, const String& module_url) final;
// ContextLifecycleObserver
void ContextDestroyed(ExecutionContext*) final;
// WorkletObjectProxy
void DidFetchAndInvokeScript(int32_t request_id, bool success) final;
DECLARE_VIRTUAL_TRACE();
protected:
explicit MainThreadWorklet(LocalFrame*);
private:
HeapHashMap<int32_t /* request_id */, Member<ScriptPromiseResolver>>
resolver_map_;
};
} // namespace blink
......
......@@ -19,11 +19,9 @@ MainThreadWorkletGlobalScope::MainThreadWorkletGlobalScope(
const KURL& url,
const String& user_agent,
PassRefPtr<SecurityOrigin> security_origin,
v8::Isolate* isolate,
WorkletObjectProxy* object_proxy)
v8::Isolate* isolate)
: WorkletGlobalScope(url, user_agent, std::move(security_origin), isolate),
ContextClient(frame),
object_proxy_(object_proxy) {}
ContextClient(frame) {}
MainThreadWorkletGlobalScope::~MainThreadWorkletGlobalScope() {}
......@@ -48,16 +46,23 @@ WorkerThread* MainThreadWorkletGlobalScope::GetThread() const {
return nullptr;
}
// Implementation of the first half of the "fetch and invoke a worklet script"
// algorithm:
// https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script
void MainThreadWorkletGlobalScope::FetchAndInvokeScript(
int32_t request_id,
const KURL& script_url) {
const KURL& module_url_record,
WorkletPendingTasks* pending_tasks) {
DCHECK(IsMainThread());
// Step 1: "Let insideSettings be the workletGlobalScope's associated
// environment settings object."
// Step 2: "Let script by the result of fetch a worklet script given
// moduleURLRecord, moduleResponsesMap, credentialOptions, outsideSettings,
// and insideSettings when it asynchronously completes."
// TODO(nhiroki): Replace this with module script loading.
WorkletScriptLoader* script_loader =
WorkletScriptLoader::Create(GetFrame()->GetDocument()->Fetcher(), this);
script_loader->set_request_id(request_id);
loader_set_.insert(script_loader);
script_loader->FetchScript(script_url);
loader_map_.Set(script_loader, pending_tasks);
script_loader->FetchScript(module_url_record);
}
void MainThreadWorkletGlobalScope::EvaluateScript(
......@@ -67,22 +72,47 @@ void MainThreadWorkletGlobalScope::EvaluateScript(
NOTREACHED();
}
// TODO(nhiroki): Add tests for termination.
void MainThreadWorkletGlobalScope::TerminateWorkletGlobalScope() {
for (const auto& script_loader : loader_set_)
for (auto it = loader_map_.begin(); it != loader_map_.end();) {
WorkletScriptLoader* script_loader = it->key;
// Cancel() eventually calls NotifyWorkletScriptLoadingFinished() and
// removes |it| from |loader_map_|, so increment it in advance.
++it;
script_loader->Cancel();
}
Dispose();
}
// Implementation of the second half of the "fetch and invoke a worklet script"
// algorithm:
// https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script
void MainThreadWorkletGlobalScope::NotifyWorkletScriptLoadingFinished(
WorkletScriptLoader* script_loader,
const ScriptSourceCode& source_code) {
DCHECK(IsMainThread());
int32_t request_id = script_loader->request_id();
loader_set_.erase(script_loader);
bool success = script_loader->WasScriptLoadSuccessful();
if (success)
ScriptController()->Evaluate(source_code);
object_proxy_->DidFetchAndInvokeScript(request_id, success);
auto it = loader_map_.find(script_loader);
DCHECK(it != loader_map_.end());
WorkletPendingTasks* pending_tasks = it->value;
loader_map_.erase(it);
if (!script_loader->WasScriptLoadSuccessful()) {
// Step 3: "If script is null, then queue a task on outsideSettings's
// responsible event loop to run these steps:"
// The steps are implemented in WorkletPendingTasks::Abort().
// TODO(nhiroki): Queue a task instead of executing this here.
pending_tasks->Abort();
return;
}
// Step 4: "Run a module script given script."
ScriptController()->Evaluate(source_code);
// Step 5: "Queue a task on outsideSettings's responsible event loop to run
// these steps:"
// The steps are implemented in WorkletPendingTasks::DecrementCounter().
// TODO(nhiroki): Queue a task instead of executing this here.
pending_tasks->DecrementCounter();
}
void MainThreadWorkletGlobalScope::AddConsoleMessage(
......@@ -95,8 +125,7 @@ void MainThreadWorkletGlobalScope::ExceptionThrown(ErrorEvent* event) {
}
DEFINE_TRACE(MainThreadWorkletGlobalScope) {
visitor->Trace(loader_set_);
visitor->Trace(object_proxy_);
visitor->Trace(loader_map_);
WorkletGlobalScope::Trace(visitor);
ContextClient::Trace(visitor);
}
......
......@@ -11,7 +11,7 @@
#include "core/loader/WorkletScriptLoader.h"
#include "core/workers/WorkletGlobalScope.h"
#include "core/workers/WorkletGlobalScopeProxy.h"
#include "core/workers/WorkletObjectProxy.h"
#include "core/workers/WorkletPendingTasks.h"
namespace blink {
......@@ -31,8 +31,7 @@ class CORE_EXPORT MainThreadWorkletGlobalScope
const KURL&,
const String& user_agent,
PassRefPtr<SecurityOrigin>,
v8::Isolate*,
WorkletObjectProxy*);
v8::Isolate*);
~MainThreadWorkletGlobalScope() override;
bool IsMainThreadWorkletGlobalScope() const final { return true; }
......@@ -42,7 +41,8 @@ class CORE_EXPORT MainThreadWorkletGlobalScope
WorkerThread* GetThread() const final;
// WorkletGlobalScopeProxy
void FetchAndInvokeScript(int32_t request_id, const KURL& script_url) final;
void FetchAndInvokeScript(const KURL& module_url_record,
WorkletPendingTasks*) final;
void EvaluateScript(const ScriptSourceCode&) final;
void TerminateWorkletGlobalScope() final;
......@@ -57,9 +57,8 @@ class CORE_EXPORT MainThreadWorkletGlobalScope
DECLARE_VIRTUAL_TRACE();
private:
HeapHashSet<Member<WorkletScriptLoader>> loader_set_;
Member<WorkletObjectProxy> object_proxy_;
HeapHashMap<Member<WorkletScriptLoader>, Member<WorkletPendingTasks>>
loader_map_;
};
DEFINE_TYPE_CASTS(MainThreadWorkletGlobalScope,
......
......@@ -11,19 +11,6 @@
namespace blink {
namespace {
class WorkletObjectProxyForTest final
: public GarbageCollectedFinalized<WorkletObjectProxyForTest>,
public WorkletObjectProxy {
USING_GARBAGE_COLLECTED_MIXIN(WorkletObjectProxyForTest);
public:
void DidFetchAndInvokeScript(int32_t request_id, bool success) {}
};
} // namespace
class MainThreadWorkletTest : public ::testing::Test {
public:
void SetUp() override {
......@@ -32,8 +19,7 @@ class MainThreadWorkletTest : public ::testing::Test {
security_origin_ = SecurityOrigin::Create(url);
global_scope_ = new MainThreadWorkletGlobalScope(
&page_->GetFrame(), url, "fake user agent", security_origin_.Get(),
ToIsolate(page_->GetFrame().GetDocument()),
new WorkletObjectProxyForTest);
ToIsolate(page_->GetFrame().GetDocument()));
}
void TearDown() override { global_scope_->TerminateWorkletGlobalScope(); }
......
......@@ -34,7 +34,7 @@ class CORE_EXPORT ThreadedWorklet : public Worklet,
virtual bool IsInitialized() const = 0;
// Worklet
ScriptPromise addModule(ScriptState*, const String& url) final;
ScriptPromise addModule(ScriptState*, const String& module_url) final;
// WorkletScriptLoader::Client
void NotifyWorkletScriptLoadingFinished(WorkletScriptLoader*,
......
......@@ -32,7 +32,7 @@ class CORE_EXPORT Worklet : public GarbageCollectedFinalized<Worklet>,
// Worklet.idl
// addModule() imports ES6 module scripts.
virtual ScriptPromise addModule(ScriptState*, const String& url) = 0;
virtual ScriptPromise addModule(ScriptState*, const String& module_url) = 0;
// Returns a proxy to WorkletGlobalScope on the context thread.
virtual WorkletGlobalScopeProxy* GetWorkletGlobalScopeProxy() const = 0;
......
......@@ -7,11 +7,11 @@
#include "core/CoreExport.h"
#include "platform/weborigin/KURL.h"
#include "platform/wtf/text/WTFString.h"
namespace blink {
class ScriptSourceCode;
class WorkletPendingTasks;
// A proxy to talk to the worklet global scope. The global scope may exist in
// the main thread or on a different thread.
......@@ -21,8 +21,8 @@ class CORE_EXPORT WorkletGlobalScopeProxy {
// Runs the "fetch and invoke a worklet script" algorithm:
// https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script
virtual void FetchAndInvokeScript(int32_t request_id,
const KURL& script_url) {}
virtual void FetchAndInvokeScript(const KURL& module_url_record,
WorkletPendingTasks*) {}
// Evaluates the given script source code. This should be called only for
// threaded worklets that still use classic script loading.
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef WorkletObjectProxy_h
#define WorkletObjectProxy_h
#include "core/CoreExport.h"
#include "platform/heap/GarbageCollected.h"
#include "platform/weborigin/KURL.h"
#include "platform/wtf/text/WTFString.h"
namespace blink {
// A proxy to talk to the Worklet object on the main thread from the worklet
// global scope that may exist in the main thread or on a different thread.
class CORE_EXPORT WorkletObjectProxy : public GarbageCollectedMixin {
public:
// Called when the worklet module script is fetched and evaluated. |success|
// is true if the sequence is completed without an error.
virtual void DidFetchAndInvokeScript(int32_t request_id, bool success) = 0;
};
} // namespace blink
#endif // WorkletObjectProxy_h
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/workers/WorkletPendingTasks.h"
#include "core/dom/DOMException.h"
#include "core/dom/ExceptionCode.h"
#include "platform/wtf/WTF.h"
namespace blink {
WorkletPendingTasks::WorkletPendingTasks(int counter,
ScriptPromiseResolver* resolver)
: counter_(counter), resolver_(resolver) {
DCHECK(IsMainThread());
}
void WorkletPendingTasks::Abort() {
DCHECK(IsMainThread());
// Step 3: "If script is null, then queue a task on outsideSettings's
// responsible event loop to run these steps:"
// 1: "If pendingTaskStruct's counter is not -1, then run these steps:"
// 1: "Set pendingTaskStruct's counter to -1."
// 2: "Reject promise with an "AbortError" DOMException."
if (counter_ != -1) {
counter_ = -1;
// TODO(nhiroki): This should be kAbortError.
resolver_->Reject(DOMException::Create(kNetworkError));
}
}
void WorkletPendingTasks::DecrementCounter() {
DCHECK(IsMainThread());
// Step 5: "Queue a task on outsideSettings's responsible event loop to run
// these steps:"
// 1: "If pendingTaskStruct's counter is not -1, then run these steps:"
// 1: "Decrement pendingTaskStruct's counter by 1."
// 2: "If pendingTaskStruct's counter is 0, then resolve promise."
if (counter_ != -1) {
--counter_;
if (counter_ == 0)
resolver_->Resolve();
}
}
} // namespace blink
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef WorkletPendingTasks_h
#define WorkletPendingTasks_h
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "platform/heap/Heap.h"
namespace blink {
// Implementation of the "pending tasks struct":
// https://drafts.css-houdini.org/worklets/#pending-tasks-struct
//
// This also implements a part of the "fetch and invoke a worklet script"
// algorithm:
// https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script
//
// All functions must be accessed on the main thread.
class WorkletPendingTasks final : public GarbageCollected<WorkletPendingTasks> {
public:
WorkletPendingTasks(int counter, ScriptPromiseResolver*);
// Sets |counter_| to -1 and rejects the promise.
void Abort();
// Decrements |counter_| and resolves the promise if the counter becomes 0.
void DecrementCounter();
DEFINE_INLINE_VIRTUAL_TRACE() { visitor->Trace(resolver_); }
private:
// The number of pending tasks. -1 indicates these tasks are aborted and
// |resolver_| already rejected the promise.
int counter_;
Member<ScriptPromiseResolver> resolver_;
};
} // namespace blink
#endif // WorkletPendingTasks_h
......@@ -23,8 +23,7 @@ PaintWorklet::PaintWorklet(LocalFrame* frame)
frame->GetDocument()->Url(),
frame->GetDocument()->UserAgent(),
frame->GetDocument()->GetSecurityOrigin(),
ToIsolate(frame->GetDocument()),
this)) {}
ToIsolate(frame->GetDocument()))) {}
PaintWorklet::~PaintWorklet() {}
......
......@@ -23,12 +23,10 @@ PaintWorkletGlobalScope* PaintWorkletGlobalScope::Create(
const KURL& url,
const String& user_agent,
PassRefPtr<SecurityOrigin> security_origin,
v8::Isolate* isolate,
WorkletObjectProxy* object_proxy) {
v8::Isolate* isolate) {
PaintWorkletGlobalScope* paint_worklet_global_scope =
new PaintWorkletGlobalScope(frame, url, user_agent,
std::move(security_origin), isolate,
object_proxy);
std::move(security_origin), isolate);
paint_worklet_global_scope->ScriptController()->InitializeContextIfNeeded();
MainThreadDebugger::Instance()->ContextCreated(
paint_worklet_global_scope->ScriptController()->GetScriptState(),
......@@ -42,14 +40,12 @@ PaintWorkletGlobalScope::PaintWorkletGlobalScope(
const KURL& url,
const String& user_agent,
PassRefPtr<SecurityOrigin> security_origin,
v8::Isolate* isolate,
WorkletObjectProxy* object_proxy)
v8::Isolate* isolate)
: MainThreadWorkletGlobalScope(frame,
url,
user_agent,
std::move(security_origin),
isolate,
object_proxy) {}
isolate) {}
PaintWorkletGlobalScope::~PaintWorkletGlobalScope() {}
......
......@@ -27,8 +27,7 @@ class MODULES_EXPORT PaintWorkletGlobalScope final
const KURL&,
const String& user_agent,
PassRefPtr<SecurityOrigin>,
v8::Isolate*,
WorkletObjectProxy*);
v8::Isolate*);
~PaintWorkletGlobalScope() override;
void Dispose() final;
......@@ -47,8 +46,7 @@ class MODULES_EXPORT PaintWorkletGlobalScope final
const KURL&,
const String& user_agent,
PassRefPtr<SecurityOrigin>,
v8::Isolate*,
WorkletObjectProxy*);
v8::Isolate*);
typedef HeapHashMap<String, Member<CSSPaintDefinition>> DefinitionMap;
DefinitionMap paint_definitions_;
......
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