Commit cf7ed613 authored by Nate Chapin's avatar Nate Chapin Committed by Commit Bot

TaskWorklet prototype

In the vein of WorkerTaskQueue, this provides an API for farming work
out to a pool of background threads. However, it is built on top of
the Worklet API, and also supposes addModule() to load and register
tasks.

Classes with a process() function can be created and registered via
the global scope's registerTask() function. taskWorklet.postTask() can
take a |name| string (rather than a function), look up whether a task
was registered with that name, and call the appropriate process().

Bug: 879306
Change-Id: I7f7c23c3482be3641fde6b78d11f3152c437a460
Reviewed-on: https://chromium-review.googlesource.com/c/1278961
Commit-Queue: Nate Chapin <japhet@chromium.org>
Reviewed-by: default avatarHiroki Nakagawa <nhiroki@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601260}
parent 5d3eb569
<body>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script>
promise_test(async () => {
const task = taskWorklet.postTask(i => i, 2);
const task2 = taskWorklet.postTask(i => i, 4);
const task3 = taskWorklet.postTask((i,j) => i+j, task, task2);
const result = await task3.result;
assert_equals(result, 6);
});
</script>
</body>
<body>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script>
promise_test(async t => {
const task = taskWorklet.postTask(i => i, 2);
const task2 = taskWorklet.postTask(i => i, 4);
const task3 = taskWorklet.postTask((i,j) => i+j, task, task2);
task3.cancel();
promise_rejects(t, new Error("Task aborted"), task3.result);
assert_equals(await task2.result, 4);
}, "Cancel the last task in a chain of dependencies");
promise_test(async t => {
const task = taskWorklet.postTask(i => i, 2);
const task2 = taskWorklet.postTask(i => i, 4);
task2.cancel();
const task3 = taskWorklet.postTask((i,j) => i+j, task, task2);
promise_rejects(t, new Error("Task aborted"), task2.result);
promise_rejects(t, new Error("Task aborted"), task3.result);
}, "Canceling a task should cancel its dependents");
</script>
</body>
<body>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script>
promise_test(async t => {
promise_rejects(t, new Error("Invalid task"), taskWorklet.postTask("repeat", 2).result);
});
</script>
</body>
class Repeat {
process(i) { return i; }
}
registerTask("repeat", Repeat);
class Sum {
process(i, j) { return i + j; }
}
registerTask("sum", Sum);
<body>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script>
promise_test(async () => {
await taskWorklet.addModule("resources/sum.js");
const task = taskWorklet.postTask('repeat', 2);
const task2 = taskWorklet.postTask('repeat', 4);
const task3 = taskWorklet.postTask('sum', task, task2);
const result = await task3.result;
assert_equals(result, 6);
});
</script>
</body>
...@@ -7275,6 +7275,13 @@ interface TaskAttributionTiming : PerformanceEntry ...@@ -7275,6 +7275,13 @@ interface TaskAttributionTiming : PerformanceEntry
getter containerType getter containerType
method constructor method constructor
method toJSON method toJSON
interface TaskWorklet : Worklet
attribute @@toStringTag
method constructor
method postTask
interface TaskWorkletGlobalScope : WorkletGlobalScope
attribute @@toStringTag
method constructor
interface Text : CharacterData interface Text : CharacterData
attribute @@toStringTag attribute @@toStringTag
getter assignedSlot getter assignedSlot
...@@ -10683,6 +10690,7 @@ interface webkitURL ...@@ -10683,6 +10690,7 @@ interface webkitURL
getter status getter status
getter statusbar getter statusbar
getter styleMedia getter styleMedia
getter taskWorklet
getter toolbar getter toolbar
getter visualViewport getter visualViewport
getter webgpu getter webgpu
......
...@@ -455,6 +455,8 @@ core_idl_files = ...@@ -455,6 +455,8 @@ core_idl_files =
"typed_arrays/uint8_clamped_array.idl", "typed_arrays/uint8_clamped_array.idl",
"url/url_search_params.idl", "url/url_search_params.idl",
"workers/experimental/task.idl", "workers/experimental/task.idl",
"workers/experimental/task_worklet.idl",
"workers/experimental/task_worklet_global_scope.idl",
"workers/experimental/worker_task_queue.idl", "workers/experimental/worker_task_queue.idl",
"workers/shared_worker.idl", "workers/shared_worker.idl",
"workers/worker.idl", "workers/worker.idl",
...@@ -551,6 +553,7 @@ core_dependency_idl_files = ...@@ -551,6 +553,7 @@ core_dependency_idl_files =
"timing/worker_global_scope_performance.idl", "timing/worker_global_scope_performance.idl",
"url/url_utils_read_only.idl", "url/url_utils_read_only.idl",
"workers/abstract_worker.idl", "workers/abstract_worker.idl",
"workers/experimental/window_task_worklet.idl",
"xml/document_xpath_evaluator.idl", "xml/document_xpath_evaluator.idl",
], ],
"abspath") "abspath")
......
...@@ -33,7 +33,9 @@ class ModuleScriptCreationParams { ...@@ -33,7 +33,9 @@ class ModuleScriptCreationParams {
~ModuleScriptCreationParams() = default; ~ModuleScriptCreationParams() = default;
ModuleScriptCreationParams IsolatedCopy() const { ModuleScriptCreationParams IsolatedCopy() const {
String isolated_source_text = GetSourceText().ToString().IsolatedCopy(); String isolated_source_text =
isolated_source_text_ ? isolated_source_text_.IsolatedCopy()
: GetSourceText().ToString().IsolatedCopy();
return ModuleScriptCreationParams( return ModuleScriptCreationParams(
GetResponseUrl().Copy(), isolated_source_text, GetResponseUrl().Copy(), isolated_source_text,
GetFetchCredentialsMode(), GetAccessControlStatus()); GetFetchCredentialsMode(), GetAccessControlStatus());
......
...@@ -22,10 +22,15 @@ blink_core_sources("workers") { ...@@ -22,10 +22,15 @@ blink_core_sources("workers") {
"execution_context_worker_registry.h", "execution_context_worker_registry.h",
"experimental/task.cc", "experimental/task.cc",
"experimental/task.h", "experimental/task.h",
"experimental/task_worklet.cc",
"experimental/task_worklet.h",
"experimental/task_worklet_global_scope.cc",
"experimental/task_worklet_global_scope.h",
"experimental/thread_pool.cc", "experimental/thread_pool.cc",
"experimental/thread_pool.h", "experimental/thread_pool.h",
"experimental/thread_pool_thread.cc", "experimental/thread_pool_thread.cc",
"experimental/thread_pool_thread.h", "experimental/thread_pool_thread.h",
"experimental/window_task_worklet.h",
"experimental/worker_task_queue.cc", "experimental/worker_task_queue.cc",
"experimental/worker_task_queue.h", "experimental/worker_task_queue.h",
"global_scope_creation_params.cc", "global_scope_creation_params.cc",
......
...@@ -7,4 +7,11 @@ worker_task_queue.{h,cc,idl} exposes two APIs: ...@@ -7,4 +7,11 @@ worker_task_queue.{h,cc,idl} exposes two APIs:
* postFunction - a simple API for posting a task to a worker. * postFunction - a simple API for posting a task to a worker.
* postTask - an API for posting tasks that can specify other tasks as prerequisites and coordinates the transfer of return values of prerequisite tasks to dependent tasks * postTask - an API for posting tasks that can specify other tasks as prerequisites and coordinates the transfer of return values of prerequisite tasks to dependent tasks
task_worklet.{h,cc,idl}, task_worklet_global_scope.{h,cc,idl}, and window_task_worklet.{h,idl} exposes a similar API to
WorkerTaskQueue, built on top of the Worklet API. In addition to implementing postTask() just like WorkerTaskQueue, it
includes addModule() (inherited from Worklet) and a second variant of postTask(). Modules loaded via addModule() can call
registerTask() to specify a name and class with a process() function, and this second variant of postTask() takes a task name
instead of a function. If a task is registered with that given task name, its process() function will be called with the given
parameters.
task.{h,cc,idl} exposes the simple wrapper object returned by postTask and provides the backend that runs tasks on the worker thread (for both postFunction and postTask) and tracks the relationships between tasks (for postTask). task.{h,cc,idl} exposes the simple wrapper object returned by postTask and provides the backend that runs tasks on the worker thread (for both postFunction and postTask) and tracks the relationships between tasks (for postTask).
...@@ -9,28 +9,58 @@ ...@@ -9,28 +9,58 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_function.h" #include "third_party/blink/renderer/bindings/core/v8/v8_function.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_task.h" #include "third_party/blink/renderer/bindings/core/v8/v8_task.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h" #include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/workers/experimental/task_worklet_global_scope.h"
#include "third_party/blink/renderer/core/workers/experimental/thread_pool.h" #include "third_party/blink/renderer/core/workers/experimental/thread_pool.h"
#include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h" #include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h"
#include "third_party/blink/renderer/platform/cross_thread_functional.h" #include "third_party/blink/renderer/platform/cross_thread_functional.h"
namespace blink { namespace blink {
ThreadPoolTask::ThreadPoolTask(ThreadPool* thread_pool, ThreadPoolTask::ThreadPoolTask(ThreadPoolThreadProvider* thread_provider,
v8::Isolate* isolate, v8::Isolate* isolate,
const ScriptValue& function, const ScriptValue& function,
const Vector<ScriptValue>& arguments, const Vector<ScriptValue>& arguments,
TaskType task_type) TaskType task_type)
: ThreadPoolTask(thread_provider,
isolate,
function,
String(),
arguments,
task_type) {}
ThreadPoolTask::ThreadPoolTask(ThreadPoolThreadProvider* thread_provider,
v8::Isolate* isolate,
const String& function_name,
const Vector<ScriptValue>& arguments,
TaskType task_type)
: ThreadPoolTask(thread_provider,
isolate,
ScriptValue(),
function_name,
arguments,
task_type) {}
ThreadPoolTask::ThreadPoolTask(ThreadPoolThreadProvider* thread_provider,
v8::Isolate* isolate,
const ScriptValue& function,
const String& function_name,
const Vector<ScriptValue>& arguments,
TaskType task_type)
: task_type_(task_type), : task_type_(task_type),
self_keep_alive_(base::AdoptRef(this)), self_keep_alive_(base::AdoptRef(this)),
function_name_(function_name.IsolatedCopy()),
arguments_(arguments.size()), arguments_(arguments.size()),
weak_factory_(this) { weak_factory_(this) {
DCHECK(IsMainThread()); DCHECK(IsMainThread());
DCHECK_EQ(!function.IsEmpty(), function_name.IsNull());
DCHECK(task_type_ == TaskType::kUserInteraction || DCHECK(task_type_ == TaskType::kUserInteraction ||
task_type_ == TaskType::kIdleTask); task_type_ == TaskType::kIdleTask);
// TODO(japhet): Handle serialization failures // TODO(japhet): Handle serialization failures
function_ = SerializedScriptValue::SerializeAndSwallowExceptions( if (!function.IsEmpty()) {
isolate, function.V8Value()->ToString(isolate)); function_ = SerializedScriptValue::SerializeAndSwallowExceptions(
isolate, function.V8Value()->ToString(isolate));
}
Vector<ThreadPoolTask*> prerequisites; Vector<ThreadPoolTask*> prerequisites;
Vector<size_t> prerequisites_indices; Vector<size_t> prerequisites_indices;
...@@ -50,7 +80,7 @@ ThreadPoolTask::ThreadPoolTask(ThreadPool* thread_pool, ...@@ -50,7 +80,7 @@ ThreadPoolTask::ThreadPoolTask(ThreadPool* thread_pool,
prerequisites_indices.push_back(i); prerequisites_indices.push_back(i);
} }
worker_thread_ = SelectThread(prerequisites, thread_pool); worker_thread_ = SelectThread(prerequisites, thread_provider);
worker_thread_->IncrementTasksInProgressCount(); worker_thread_->IncrementTasksInProgressCount();
if (prerequisites.IsEmpty()) { if (prerequisites.IsEmpty()) {
...@@ -68,9 +98,9 @@ ThreadPoolTask::ThreadPoolTask(ThreadPool* thread_pool, ...@@ -68,9 +98,9 @@ ThreadPoolTask::ThreadPoolTask(ThreadPool* thread_pool,
// static // static
ThreadPoolThread* ThreadPoolTask::SelectThread( ThreadPoolThread* ThreadPoolTask::SelectThread(
const Vector<ThreadPoolTask*>& prerequisites, const Vector<ThreadPoolTask*>& prerequisites,
ThreadPool* thread_pool) { ThreadPoolThreadProvider* thread_provider) {
DCHECK(IsMainThread()); DCHECK(IsMainThread());
HashCountedSet<WorkerThread*> prerequisite_location_counts; HashCountedSet<ThreadPoolThread*> prerequisite_location_counts;
size_t max_prerequisite_location_count = 0; size_t max_prerequisite_location_count = 0;
ThreadPoolThread* max_prerequisite_thread = nullptr; ThreadPoolThread* max_prerequisite_thread = nullptr;
for (ThreadPoolTask* prerequisite : prerequisites) { for (ThreadPoolTask* prerequisite : prerequisites) {
...@@ -88,7 +118,7 @@ ThreadPoolThread* ThreadPoolTask::SelectThread( ...@@ -88,7 +118,7 @@ ThreadPoolThread* ThreadPoolTask::SelectThread(
} }
} }
return max_prerequisite_thread ? max_prerequisite_thread return max_prerequisite_thread ? max_prerequisite_thread
: thread_pool->GetLeastBusyThread(); : thread_provider->GetLeastBusyThread();
} }
ThreadPoolThread* ThreadPoolTask::GetScheduledThread() { ThreadPoolThread* ThreadPoolTask::GetScheduledThread() {
...@@ -211,9 +241,12 @@ void ThreadPoolTask::StartTaskOnWorkerThread() { ...@@ -211,9 +241,12 @@ void ThreadPoolTask::StartTaskOnWorkerThread() {
return_value = V8String(isolate, "Task aborted"); return_value = V8String(isolate, "Task aborted");
} else { } else {
return_value = RunTaskOnWorkerThread(isolate); return_value = RunTaskOnWorkerThread(isolate);
DCHECK_EQ(return_value.IsEmpty(), block.HasCaught()); if (return_value.IsEmpty()) {
if (block.HasCaught()) if (block.HasCaught())
return_value = block.Exception()->ToString(isolate); return_value = block.Exception()->ToString(isolate);
else
return_value = V8String(isolate, "Invalid task");
}
} }
scoped_refptr<SerializedScriptValue> local_result = scoped_refptr<SerializedScriptValue> local_result =
...@@ -253,13 +286,30 @@ v8::Local<v8::Value> ThreadPoolTask::RunTaskOnWorkerThread( ...@@ -253,13 +286,30 @@ v8::Local<v8::Value> ThreadPoolTask::RunTaskOnWorkerThread(
// No other thread should be touching function_ or arguments_ at this point, // No other thread should be touching function_ or arguments_ at this point,
// so no mutex needed while actually running the task. // so no mutex needed while actually running the task.
v8::Local<v8::Context> context = isolate->GetCurrentContext(); v8::Local<v8::Context> context = isolate->GetCurrentContext();
String core_script = v8::Local<v8::Function> script_function;
"(" + ToCoreString(function_->Deserialize(isolate).As<v8::String>()) + v8::Local<v8::Value> receiver;
")"; if (function_) {
v8::MaybeLocal<v8::Script> script = v8::Script::Compile( String core_script =
isolate->GetCurrentContext(), V8String(isolate, core_script)); "(" + ToCoreString(function_->Deserialize(isolate).As<v8::String>()) +
v8::Local<v8::Function> script_function = ")";
script.ToLocalChecked()->Run(context).ToLocalChecked().As<v8::Function>(); v8::MaybeLocal<v8::Script> script = v8::Script::Compile(
isolate->GetCurrentContext(), V8String(isolate, core_script));
script_function = script.ToLocalChecked()
->Run(context)
.ToLocalChecked()
.As<v8::Function>();
receiver = script_function;
} else if (worker_thread_->IsWorklet()) {
TaskWorkletGlobalScope* task_worklet_global_scope =
static_cast<TaskWorkletGlobalScope*>(worker_thread_->GlobalScope());
script_function = task_worklet_global_scope->GetProcessFunctionForName(
function_name_, isolate);
receiver =
task_worklet_global_scope->GetInstanceForName(function_name_, isolate);
}
if (script_function.IsEmpty())
return v8::Local<v8::Value>();
Vector<v8::Local<v8::Value>> params(arguments_.size()); Vector<v8::Local<v8::Value>> params(arguments_.size());
for (size_t i = 0; i < arguments_.size(); i++) { for (size_t i = 0; i < arguments_.size(); i++) {
...@@ -270,8 +320,8 @@ v8::Local<v8::Value> ThreadPoolTask::RunTaskOnWorkerThread( ...@@ -270,8 +320,8 @@ v8::Local<v8::Value> ThreadPoolTask::RunTaskOnWorkerThread(
params[i] = arguments_[i].v8_value->NewLocal(isolate); params[i] = arguments_[i].v8_value->NewLocal(isolate);
} }
v8::MaybeLocal<v8::Value> ret = script_function->Call( v8::MaybeLocal<v8::Value> ret =
context, script_function, params.size(), params.data()); script_function->Call(context, receiver, params.size(), params.data());
v8::Local<v8::Value> return_value; v8::Local<v8::Value> return_value;
if (!ret.IsEmpty()) { if (!ret.IsEmpty()) {
...@@ -333,12 +383,14 @@ void ThreadPoolTask::Cancel() { ...@@ -333,12 +383,14 @@ void ThreadPoolTask::Cancel() {
void ThreadPoolTask::AdvanceState(State new_state) { void ThreadPoolTask::AdvanceState(State new_state) {
switch (new_state) { switch (new_state) {
case State::kPending: case State::kPending:
NOTREACHED() << "kPending should only be set via initialiation"; NOTREACHED() << "kPending should only be set via initialization";
break; break;
case State::kCancelPending:
case State::kStarted: case State::kStarted:
DCHECK_EQ(State::kPending, state_); DCHECK_EQ(State::kPending, state_);
break; break;
case State::kCancelPending:
DCHECK(state_ == State::kPending || state_ == State::kCancelPending);
break;
case State::kCompleted: case State::kCompleted:
DCHECK_EQ(State::kStarted, state_); DCHECK_EQ(State::kStarted, state_);
break; break;
......
...@@ -6,14 +6,13 @@ ...@@ -6,14 +6,13 @@
#define THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_TASK_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_TASK_H_
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/core/workers/experimental/thread_pool.h" #include "third_party/blink/renderer/core/workers/experimental/thread_pool_thread.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/wtf/ref_counted.h" #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
#include "third_party/blink/renderer/platform/wtf/threading_primitives.h" #include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
namespace blink { namespace blink {
class SerializedScriptValue; class SerializedScriptValue;
class Task;
// Runs |function| with |arguments| on a thread from the given ThreadPool. // Runs |function| with |arguments| on a thread from the given ThreadPool.
// Scans |arguments| for Task objects, and registers those as dependencies, // Scans |arguments| for Task objects, and registers those as dependencies,
...@@ -26,11 +25,16 @@ class Task; ...@@ -26,11 +25,16 @@ class Task;
class ThreadPoolTask final : public RefCounted<ThreadPoolTask> { class ThreadPoolTask final : public RefCounted<ThreadPoolTask> {
public: public:
// Called on main thread // Called on main thread
ThreadPoolTask(ThreadPool*, ThreadPoolTask(ThreadPoolThreadProvider*,
v8::Isolate*, v8::Isolate*,
const ScriptValue& function, const ScriptValue& function,
const Vector<ScriptValue>& arguments, const Vector<ScriptValue>& arguments,
TaskType); TaskType);
ThreadPoolTask(ThreadPoolThreadProvider*,
v8::Isolate*,
const String& function_name,
const Vector<ScriptValue>& arguments,
TaskType);
~ThreadPoolTask(); ~ThreadPoolTask();
// Returns the result of this task, or a promise that will be resolved with // Returns the result of this task, or a promise that will be resolved with
// the result when it completes. // the result when it completes.
...@@ -44,6 +48,13 @@ class ThreadPoolTask final : public RefCounted<ThreadPoolTask> { ...@@ -44,6 +48,13 @@ class ThreadPoolTask final : public RefCounted<ThreadPoolTask> {
private: private:
enum class State { kPending, kStarted, kCancelPending, kCompleted, kFailed }; enum class State { kPending, kStarted, kCancelPending, kCompleted, kFailed };
ThreadPoolTask(ThreadPoolThreadProvider*,
v8::Isolate*,
const ScriptValue& function,
const String& function_name,
const Vector<ScriptValue>& arguments,
TaskType);
void StartTaskOnWorkerThread() LOCKS_EXCLUDED(mutex_); void StartTaskOnWorkerThread() LOCKS_EXCLUDED(mutex_);
v8::Local<v8::Value> RunTaskOnWorkerThread(v8::Isolate*); v8::Local<v8::Value> RunTaskOnWorkerThread(v8::Isolate*);
...@@ -61,7 +72,7 @@ class ThreadPoolTask final : public RefCounted<ThreadPoolTask> { ...@@ -61,7 +72,7 @@ class ThreadPoolTask final : public RefCounted<ThreadPoolTask> {
// Called on main thread // Called on main thread
static ThreadPoolThread* SelectThread( static ThreadPoolThread* SelectThread(
const Vector<ThreadPoolTask*>& prerequisites, const Vector<ThreadPoolTask*>& prerequisites,
ThreadPool*); ThreadPoolThreadProvider*);
ThreadPoolThread* GetScheduledThread() LOCKS_EXCLUDED(mutex_); ThreadPoolThread* GetScheduledThread() LOCKS_EXCLUDED(mutex_);
void RegisterDependencies(const Vector<ThreadPoolTask*>& prerequisites, void RegisterDependencies(const Vector<ThreadPoolTask*>& prerequisites,
const Vector<size_t>& prerequisite_indices) const Vector<size_t>& prerequisite_indices)
...@@ -80,6 +91,7 @@ class ThreadPoolTask final : public RefCounted<ThreadPoolTask> { ...@@ -80,6 +91,7 @@ class ThreadPoolTask final : public RefCounted<ThreadPoolTask> {
// Created in constructor on the main thread, consumed and cleared on // Created in constructor on the main thread, consumed and cleared on
// worker_thread_. Those steps can't overlap, so no mutex_ required. // worker_thread_. Those steps can't overlap, so no mutex_ required.
scoped_refptr<SerializedScriptValue> function_; scoped_refptr<SerializedScriptValue> function_;
const String function_name_;
// Created and populated with non-prerequiste parameters on the main thread. // Created and populated with non-prerequiste parameters on the main thread.
// Each prerequisite writes its return value into arguments_ from its thread. // Each prerequisite writes its return value into arguments_ from its thread.
......
// Copyright 2018 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 "third_party/blink/renderer/core/workers/experimental/task_worklet.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/workers/experimental/thread_pool_thread.h"
#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
#include "third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.h"
#include "third_party/blink/renderer/core/workers/threaded_worklet_object_proxy.h"
#include "third_party/blink/renderer/core/workers/worker_backing_thread.h"
#include "third_party/blink/renderer/core/workers/worker_thread.h"
namespace blink {
class TaskWorkletMessagingProxy final : public ThreadedWorkletMessagingProxy {
public:
TaskWorkletMessagingProxy(ExecutionContext* context)
: ThreadedWorkletMessagingProxy(context) {}
~TaskWorkletMessagingProxy() override = default;
std::unique_ptr<WorkerThread> CreateWorkerThread() override {
return std::make_unique<ThreadPoolThread>(GetExecutionContext(),
WorkletObjectProxy(),
ThreadPoolThread::kWorklet);
}
ThreadPoolThread* GetWorkerThread() const {
return static_cast<ThreadPoolThread*>(
ThreadedMessagingProxyBase::GetWorkerThread());
}
};
const char TaskWorklet::kSupplementName[] = "TaskWorklet";
TaskWorklet* TaskWorklet::From(LocalDOMWindow& window) {
TaskWorklet* task_worklet =
Supplement<LocalDOMWindow>::From<TaskWorklet>(window);
if (!task_worklet) {
task_worklet = new TaskWorklet(window.document());
Supplement<LocalDOMWindow>::ProvideTo(window, task_worklet);
}
return task_worklet;
}
static const size_t kMaxThreadCount = 4;
TaskWorklet::TaskWorklet(Document* document) : Worklet(document) {}
Task* TaskWorklet::postTask(ScriptState* script_state,
const ScriptValue& function,
const Vector<ScriptValue>& arguments) {
DCHECK(function.IsFunction());
// TODO(japhet): Here and below: it's unclear what task type should be used,
// and whether the API should allow it to be configured. Using kIdleTask as a
// placeholder for now.
ThreadPoolTask* thread_pool_task =
new ThreadPoolTask(this, script_state->GetIsolate(), function, arguments,
TaskType::kIdleTask);
return new Task(thread_pool_task);
}
Task* TaskWorklet::postTask(ScriptState* script_state,
const String& function_name,
const Vector<ScriptValue>& arguments) {
ThreadPoolTask* thread_pool_task =
new ThreadPoolTask(this, script_state->GetIsolate(), function_name,
arguments, TaskType::kIdleTask);
return new Task(thread_pool_task);
}
ThreadPoolThread* TaskWorklet::GetLeastBusyThread() {
DCHECK(IsMainThread());
ThreadPoolThread* least_busy_thread = nullptr;
size_t lowest_task_count = std::numeric_limits<std::size_t>::max();
for (auto& proxy : proxies_) {
ThreadPoolThread* current_thread =
static_cast<TaskWorkletMessagingProxy*>(proxy.Get())->GetWorkerThread();
size_t current_task_count = current_thread->GetTasksInProgressCount();
// If there's an idle thread, use it.
if (current_task_count == 0)
return current_thread;
if (current_task_count < lowest_task_count) {
least_busy_thread = current_thread;
lowest_task_count = current_task_count;
}
}
if (proxies_.size() == kMaxThreadCount)
return least_busy_thread;
auto* proxy = static_cast<TaskWorkletMessagingProxy*>(CreateGlobalScope());
proxies_.push_back(proxy);
return proxy->GetWorkerThread();
}
// TODO(japhet): This causes all of the backing threads to be created when
// addModule() is first called. Sort out lazy global scope creation.
// Note that if the function variant of postTask() is called first, global
// scopes will be created lazily; it's only module loading that needs upfront
// global scope creation, presumably because we don't have a way to replay
// module loads from WorkletModuleResponsesMap yet.
bool TaskWorklet::NeedsToCreateGlobalScope() {
return GetNumberOfGlobalScopes() < kMaxThreadCount;
}
WorkletGlobalScopeProxy* TaskWorklet::CreateGlobalScope() {
DCHECK_LT(GetNumberOfGlobalScopes(), kMaxThreadCount);
TaskWorkletMessagingProxy* proxy =
new TaskWorkletMessagingProxy(GetExecutionContext());
proxy->Initialize(WorkerClients::Create(), ModuleResponsesMap(),
WorkerBackingThreadStartupData::CreateDefault());
return proxy;
}
// We select a global scope without this getting called.
size_t TaskWorklet::SelectGlobalScope() {
NOTREACHED();
return 0u;
}
void TaskWorklet::Trace(blink::Visitor* visitor) {
Worklet::Trace(visitor);
Supplement<LocalDOMWindow>::Trace(visitor);
}
} // namespace blink
// Copyright 2018 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 THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_TASK_WORKLET_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_TASK_WORKLET_H_
#include "third_party/blink/renderer/core/workers/experimental/task.h"
#include "third_party/blink/renderer/core/workers/worklet.h"
#include "third_party/blink/renderer/platform/supplementable.h"
namespace blink {
class Document;
class LocalDOMWindow;
class TaskWorklet final : public Worklet,
public Supplement<LocalDOMWindow>,
public ThreadPoolThreadProvider {
DEFINE_WRAPPERTYPEINFO();
USING_GARBAGE_COLLECTED_MIXIN(TaskWorklet);
public:
static const char kSupplementName[];
static TaskWorklet* From(LocalDOMWindow&);
Task* postTask(ScriptState*,
const ScriptValue& task,
const Vector<ScriptValue>& arguments);
Task* postTask(ScriptState*,
const String& function_name,
const Vector<ScriptValue>& arguments);
ThreadPoolThread* GetLeastBusyThread() override;
void Trace(blink::Visitor*) override;
private:
explicit TaskWorklet(Document*);
~TaskWorklet() override = default;
bool NeedsToCreateGlobalScope() final;
WorkletGlobalScopeProxy* CreateGlobalScope() final;
size_t SelectGlobalScope() final;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_TASK_WORKLET_H_
// Copyright 2018 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.
[
RuntimeEnabled=WorkerTaskQueue,
SecureContext
] interface TaskWorklet : Worklet {
[CallWith=ScriptState] Task postTask(CallbackFunctionTreatedAsScriptValue task, any... arguments);
[CallWith=ScriptState] Task postTask(USVString task, any... arguments);
};
// Copyright 2018 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 "third_party/blink/renderer/core/workers/experimental/task_worklet_global_scope.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_object_parser.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
#include "third_party/blink/renderer/core/workers/worker_thread.h"
namespace blink {
class TaskDefinition final : public GarbageCollectedFinalized<TaskDefinition> {
public:
TaskDefinition(v8::Isolate* isolate,
v8::Local<v8::Value> instance,
v8::Local<v8::Function> process)
: instance_(isolate, instance), process_(isolate, process) {}
~TaskDefinition() = default;
v8::Local<v8::Value> InstanceLocal(v8::Isolate* isolate) {
return instance_.NewLocal(isolate);
}
v8::Local<v8::Function> ProcessLocal(v8::Isolate* isolate) {
return process_.NewLocal(isolate);
}
void Trace(blink::Visitor* visitor) {
visitor->Trace(instance_.Cast<v8::Value>());
visitor->Trace(process_.Cast<v8::Value>());
}
private:
// This object keeps the object and process function alive.
// It participates in wrapper tracing as it holds onto V8 wrappers.
TraceWrapperV8Reference<v8::Value> instance_;
TraceWrapperV8Reference<v8::Function> process_;
};
TaskWorkletGlobalScope::TaskWorkletGlobalScope(
std::unique_ptr<GlobalScopeCreationParams> creation_params,
WorkerThread* thread)
: WorkletGlobalScope(std::move(creation_params),
thread->GetWorkerReportingProxy(),
thread) {}
void TaskWorkletGlobalScope::Trace(blink::Visitor* visitor) {
WorkletGlobalScope::Trace(visitor);
visitor->Trace(task_definitions_);
}
void TaskWorkletGlobalScope::registerTask(const String& name,
const ScriptValue& constructor_value,
ExceptionState& exception_state) {
DCHECK(IsContextThread());
if (task_definitions_.Contains(name)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"A class with name:'" + name + "' is already registered.");
return;
}
if (name.IsEmpty()) {
exception_state.ThrowTypeError("The empty string is not a valid name.");
return;
}
v8::Isolate* isolate = ScriptController()->GetScriptState()->GetIsolate();
v8::Local<v8::Context> context = ScriptController()->GetContext();
DCHECK(constructor_value.V8Value()->IsFunction());
v8::Local<v8::Function> constructor =
v8::Local<v8::Function>::Cast(constructor_value.V8Value());
v8::Local<v8::Object> prototype;
if (!V8ObjectParser::ParsePrototype(context, constructor, &prototype,
&exception_state))
return;
v8::Local<v8::Function> process;
if (!V8ObjectParser::ParseFunction(context, prototype, "process", &process,
&exception_state))
return;
v8::Local<v8::Value> instance;
bool did_construct =
V8ScriptRunner::CallAsConstructor(isolate, constructor, this)
.ToLocal(&instance);
if (!did_construct) {
exception_state.ThrowTypeError("Failed to construct TaskProcessor");
return;
}
TaskDefinition* definition = new TaskDefinition(isolate, instance, process);
task_definitions_.Set(name, definition);
}
v8::Local<v8::Value> TaskWorkletGlobalScope::GetInstanceForName(
const String& name,
v8::Isolate* isolate) {
TaskDefinition* definition = task_definitions_.at(name);
return definition ? definition->InstanceLocal(isolate)
: v8::Local<v8::Value>();
}
v8::Local<v8::Function> TaskWorkletGlobalScope::GetProcessFunctionForName(
const String& name,
v8::Isolate* isolate) {
TaskDefinition* definition = task_definitions_.at(name);
return definition ? definition->ProcessLocal(isolate)
: v8::Local<v8::Function>();
}
} // namespace blink
// Copyright 2018 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 THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_TASK_WORKLET_GLOBAL_SCOPE_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_TASK_WORKLET_GLOBAL_SCOPE_H_
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/core/workers/worklet_global_scope.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
namespace blink {
class ExceptionState;
class TaskDefinition;
class TaskWorkletGlobalScope : public WorkletGlobalScope {
DEFINE_WRAPPERTYPEINFO();
public:
TaskWorkletGlobalScope(std::unique_ptr<GlobalScopeCreationParams>,
WorkerThread*);
~TaskWorkletGlobalScope() override = default;
void Trace(blink::Visitor*) override;
void registerTask(const String& name,
const ScriptValue& constructor_value,
ExceptionState&);
v8::Local<v8::Value> GetInstanceForName(const String&, v8::Isolate*);
v8::Local<v8::Function> GetProcessFunctionForName(const String&,
v8::Isolate*);
private:
HeapHashMap<String, TraceWrapperMember<TaskDefinition>> task_definitions_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_TASK_WORKLET_GLOBAL_SCOPE_H_
// Copyright 2018 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.
[
RuntimeEnabled=WorkerTaskQueue,
Global=(Worklet,TaskWorklet)
] interface TaskWorkletGlobalScope : WorkletGlobalScope {
[RaisesException] void registerTask(DOMString name, CallbackFunctionTreatedAsScriptValue taskConstructor);
};
...@@ -46,8 +46,8 @@ class ThreadPoolMessagingProxy final : public ThreadedMessagingProxyBase { ...@@ -46,8 +46,8 @@ class ThreadPoolMessagingProxy final : public ThreadedMessagingProxyBase {
WorkerBackingThreadStartupData::CreateDefault()); WorkerBackingThreadStartupData::CreateDefault());
} }
std::unique_ptr<WorkerThread> CreateWorkerThread() override { std::unique_ptr<WorkerThread> CreateWorkerThread() override {
return std::make_unique<ThreadPoolThread>(GetExecutionContext(), return std::make_unique<ThreadPoolThread>(
*object_proxy_.get()); GetExecutionContext(), *object_proxy_.get(), ThreadPoolThread::kWorker);
} }
ThreadPoolThread* GetWorkerThread() const { ThreadPoolThread* GetWorkerThread() const {
......
...@@ -9,13 +9,13 @@ ...@@ -9,13 +9,13 @@
#include "third_party/blink/renderer/platform/supplementable.h" #include "third_party/blink/renderer/platform/supplementable.h"
namespace blink { namespace blink {
class Document; class Document;
class ThreadPoolMessagingProxy; class ThreadPoolMessagingProxy;
class ThreadPool final : public GarbageCollectedFinalized<ThreadPool>, class ThreadPool final : public GarbageCollectedFinalized<ThreadPool>,
public Supplement<Document>, public Supplement<Document>,
public ContextLifecycleObserver { public ContextLifecycleObserver,
public ThreadPoolThreadProvider {
USING_GARBAGE_COLLECTED_MIXIN(ThreadPool); USING_GARBAGE_COLLECTED_MIXIN(ThreadPool);
EAGERLY_FINALIZE(); EAGERLY_FINALIZE();
...@@ -24,7 +24,7 @@ class ThreadPool final : public GarbageCollectedFinalized<ThreadPool>, ...@@ -24,7 +24,7 @@ class ThreadPool final : public GarbageCollectedFinalized<ThreadPool>,
static ThreadPool* From(Document&); static ThreadPool* From(Document&);
~ThreadPool(); ~ThreadPool();
ThreadPoolThread* GetLeastBusyThread(); ThreadPoolThread* GetLeastBusyThread() override;
void ContextDestroyed(ExecutionContext*) final; void ContextDestroyed(ExecutionContext*) final;
void Trace(blink::Visitor*) final; void Trace(blink::Visitor*) final;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "third_party/blink/renderer/core/workers/experimental/thread_pool_thread.h" #include "third_party/blink/renderer/core/workers/experimental/thread_pool_thread.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/workers/experimental/task_worklet_global_scope.h"
#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h" #include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
#include "third_party/blink/renderer/core/workers/threaded_object_proxy_base.h" #include "third_party/blink/renderer/core/workers/threaded_object_proxy_base.h"
#include "third_party/blink/renderer/core/workers/worker_global_scope.h" #include "third_party/blink/renderer/core/workers/worker_global_scope.h"
...@@ -49,8 +50,9 @@ class ThreadPoolWorkerGlobalScope final : public WorkerGlobalScope { ...@@ -49,8 +50,9 @@ class ThreadPoolWorkerGlobalScope final : public WorkerGlobalScope {
} // anonymous namespace } // anonymous namespace
ThreadPoolThread::ThreadPoolThread(ExecutionContext* parent_execution_context, ThreadPoolThread::ThreadPoolThread(ExecutionContext* parent_execution_context,
ThreadedObjectProxyBase& object_proxy) ThreadedObjectProxyBase& object_proxy,
: WorkerThread(object_proxy) { ThreadBackingPolicy backing_policy)
: WorkerThread(object_proxy), backing_policy_(backing_policy) {
DCHECK(parent_execution_context); DCHECK(parent_execution_context);
worker_backing_thread_ = WorkerBackingThread::Create( worker_backing_thread_ = WorkerBackingThread::Create(
ThreadCreationParams(GetThreadType()) ThreadCreationParams(GetThreadType())
...@@ -59,7 +61,9 @@ ThreadPoolThread::ThreadPoolThread(ExecutionContext* parent_execution_context, ...@@ -59,7 +61,9 @@ ThreadPoolThread::ThreadPoolThread(ExecutionContext* parent_execution_context,
WorkerOrWorkletGlobalScope* ThreadPoolThread::CreateWorkerGlobalScope( WorkerOrWorkletGlobalScope* ThreadPoolThread::CreateWorkerGlobalScope(
std::unique_ptr<GlobalScopeCreationParams> creation_params) { std::unique_ptr<GlobalScopeCreationParams> creation_params) {
return new ThreadPoolWorkerGlobalScope(std::move(creation_params), this); if (backing_policy_ == kWorker)
return new ThreadPoolWorkerGlobalScope(std::move(creation_params), this);
return new TaskWorkletGlobalScope(std::move(creation_params), this);
} }
} // namespace blink } // namespace blink
...@@ -14,7 +14,11 @@ class ThreadedObjectProxyBase; ...@@ -14,7 +14,11 @@ class ThreadedObjectProxyBase;
class ThreadPoolThread final : public WorkerThread { class ThreadPoolThread final : public WorkerThread {
public: public:
ThreadPoolThread(ExecutionContext*, ThreadedObjectProxyBase&); enum ThreadBackingPolicy { kWorker, kWorklet };
ThreadPoolThread(ExecutionContext*,
ThreadedObjectProxyBase&,
ThreadBackingPolicy);
~ThreadPoolThread() override = default; ~ThreadPoolThread() override = default;
void IncrementTasksInProgressCount() { void IncrementTasksInProgressCount() {
...@@ -31,6 +35,8 @@ class ThreadPoolThread final : public WorkerThread { ...@@ -31,6 +35,8 @@ class ThreadPoolThread final : public WorkerThread {
return tasks_in_progress_; return tasks_in_progress_;
} }
bool IsWorklet() const { return backing_policy_ == kWorklet; }
private: private:
WorkerBackingThread& GetWorkerBackingThread() override { WorkerBackingThread& GetWorkerBackingThread() override {
return *worker_backing_thread_; return *worker_backing_thread_;
...@@ -46,6 +52,12 @@ class ThreadPoolThread final : public WorkerThread { ...@@ -46,6 +52,12 @@ class ThreadPoolThread final : public WorkerThread {
} }
std::unique_ptr<WorkerBackingThread> worker_backing_thread_; std::unique_ptr<WorkerBackingThread> worker_backing_thread_;
size_t tasks_in_progress_ = 0; size_t tasks_in_progress_ = 0;
const ThreadBackingPolicy backing_policy_;
};
class ThreadPoolThreadProvider {
public:
virtual ThreadPoolThread* GetLeastBusyThread() = 0;
}; };
} // namespace blink } // namespace blink
......
// Copyright 2018 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 THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_WINDOW_TASK_WORKLET_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_WINDOW_TASK_WORKLET_H_
#include "third_party/blink/renderer/core/workers/experimental/task_worklet.h"
namespace blink {
class WindowTaskWorklet {
STATIC_ONLY(WindowTaskWorklet);
public:
static TaskWorklet* taskWorklet(LocalDOMWindow& window) {
return TaskWorklet::From(window);
}
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_EXPERIMENTAL_WINDOW_TASK_WORKLET_H_
// Copyright 2018 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.
[
RuntimeEnabled=WorkerTaskQueue,
ImplementedAs=WindowTaskWorklet
] partial interface Window {
[SameObject] readonly attribute TaskWorklet taskWorklet;
};
...@@ -35,7 +35,8 @@ ThreadedWorkletMessagingProxy::ThreadedWorkletMessagingProxy( ...@@ -35,7 +35,8 @@ ThreadedWorkletMessagingProxy::ThreadedWorkletMessagingProxy(
void ThreadedWorkletMessagingProxy::Initialize( void ThreadedWorkletMessagingProxy::Initialize(
WorkerClients* worker_clients, WorkerClients* worker_clients,
WorkletModuleResponsesMap* module_responses_map) { WorkletModuleResponsesMap* module_responses_map,
const base::Optional<WorkerBackingThreadStartupData>& thread_startup_data) {
DCHECK(IsMainThread()); DCHECK(IsMainThread());
if (AskedToTerminate()) if (AskedToTerminate())
return; return;
...@@ -71,7 +72,7 @@ void ThreadedWorkletMessagingProxy::Initialize( ...@@ -71,7 +72,7 @@ void ThreadedWorkletMessagingProxy::Initialize(
// Worklets share the pre-initialized backing thread so that we don't have to // Worklets share the pre-initialized backing thread so that we don't have to
// specify the backing thread startup data. // specify the backing thread startup data.
InitializeWorkerThread(std::move(global_scope_creation_params), InitializeWorkerThread(std::move(global_scope_creation_params),
base::nullopt); thread_startup_data);
} }
void ThreadedWorkletMessagingProxy::Trace(blink::Visitor* visitor) { void ThreadedWorkletMessagingProxy::Trace(blink::Visitor* visitor) {
......
...@@ -32,7 +32,10 @@ class CORE_EXPORT ThreadedWorkletMessagingProxy ...@@ -32,7 +32,10 @@ class CORE_EXPORT ThreadedWorkletMessagingProxy
void WorkletObjectDestroyed() final; void WorkletObjectDestroyed() final;
void TerminateWorkletGlobalScope() final; void TerminateWorkletGlobalScope() final;
void Initialize(WorkerClients*, WorkletModuleResponsesMap*); void Initialize(
WorkerClients*,
WorkletModuleResponsesMap*,
const base::Optional<WorkerBackingThreadStartupData>& = base::nullopt);
void Trace(blink::Visitor*) override; void Trace(blink::Visitor*) override;
......
...@@ -64,6 +64,12 @@ class CORE_EXPORT Worklet : public ScriptWrappable, ...@@ -64,6 +64,12 @@ class CORE_EXPORT Worklet : public ScriptWrappable,
return module_responses_map_.Get(); return module_responses_map_.Get();
} }
// "A Worklet has a list of the worklet's WorkletGlobalScopes. Initially this
// list is empty; it is populated when the user agent chooses to create its
// WorkletGlobalScope."
// https://drafts.css-houdini.org/worklets/#worklet-section
HeapVector<Member<WorkletGlobalScopeProxy>> proxies_;
private: private:
virtual void FetchAndInvokeScript(const KURL& module_url_record, virtual void FetchAndInvokeScript(const KURL& module_url_record,
const String& credentials, const String& credentials,
...@@ -80,13 +86,6 @@ class CORE_EXPORT Worklet : public ScriptWrappable, ...@@ -80,13 +86,6 @@ class CORE_EXPORT Worklet : public ScriptWrappable,
// default behavior is to return the global scope at index 0, which is for the // default behavior is to return the global scope at index 0, which is for the
// case where there is only one global scope. // case where there is only one global scope.
virtual size_t SelectGlobalScope(); virtual size_t SelectGlobalScope();
// "A Worklet has a list of the worklet's WorkletGlobalScopes. Initially this
// list is empty; it is populated when the user agent chooses to create its
// WorkletGlobalScope."
// https://drafts.css-houdini.org/worklets/#worklet-section
HeapVector<Member<WorkletGlobalScopeProxy>> proxies_;
// "A Worklet has a module responses map. This is a ordered map of module URLs // "A Worklet has a module responses map. This is a ordered map of module URLs
// to values that are a fetch responses. The map's entries are ordered based // to values that are a fetch responses. The map's entries are ordered based
// on their insertion order. Access to this map should be thread-safe." // on their insertion order. Access to this map should be thread-safe."
......
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