blob: 263cff9ee8aeecf360e1ab9d8890143d1bfadd1f [file] [log] [blame]
// Copyright 2016 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/worklet.h"
#include "base/optional.h"
#include "base/single_thread_task_runner.h"
#include "services/network/public/mojom/fetch_api.mojom-blink.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_worklet_options.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/fetch/request.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/loader/worker_resource_timing_notifier_impl.h"
#include "third_party/blink/renderer/core/workers/worklet_pending_tasks.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
namespace blink {
Worklet::Worklet(LocalDOMWindow& window)
: ExecutionContextLifecycleObserver(&window),
module_responses_map_(MakeGarbageCollected<WorkletModuleResponsesMap>()) {
DCHECK(IsMainThread());
}
Worklet::~Worklet() {
DCHECK(!HasPendingTasks());
}
void Worklet::Dispose() {
for (const auto& proxy : proxies_)
proxy->WorkletObjectDestroyed();
}
// Implementation of the first half of the "addModule(moduleURL, options)"
// algorithm:
// https://drafts.css-houdini.org/worklets/#dom-worklet-addmodule
ScriptPromise Worklet::addModule(ScriptState* script_state,
const String& module_url,
const WorkletOptions* options,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
if (!GetExecutionContext()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"This frame is already detached");
return ScriptPromise();
}
UseCounter::Count(GetExecutionContext(),
mojom::WebFeature::kWorkletAddModule);
// Step 1: "Let promise be a new promise."
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
// Step 2: "Let worklet be the current Worklet."
// |this| is the current Worklet.
// Step 3: "Let moduleURLRecord be the result of parsing the moduleURL
// argument relative to the relevant settings object of this."
KURL module_url_record = GetExecutionContext()->CompleteURL(module_url);
// Step 4: "If moduleURLRecord is failure, then reject promise with a
// "SyntaxError" DOMException and return promise."
if (!module_url_record.IsValid()) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kSyntaxError,
"'" + module_url + "' is not a valid URL."));
return promise;
}
WorkletPendingTasks* pending_tasks =
MakeGarbageCollected<WorkletPendingTasks>(this, resolver);
pending_tasks_set_.insert(pending_tasks);
// Step 5: "Return promise, and then continue running this algorithm in
// parallel."
// |kInternalLoading| is used here because this is a part of script module
// loading.
GetExecutionContext()
->GetTaskRunner(TaskType::kInternalLoading)
->PostTask(FROM_HERE,
WTF::Bind(&Worklet::FetchAndInvokeScript, WrapPersistent(this),
module_url_record, options->credentials(),
WrapPersistent(pending_tasks)));
return promise;
}
void Worklet::ContextDestroyed() {
DCHECK(IsMainThread());
module_responses_map_->Dispose();
for (const auto& proxy : proxies_)
proxy->TerminateWorkletGlobalScope();
}
bool Worklet::HasPendingTasks() const {
return pending_tasks_set_.size() > 0;
}
void Worklet::FinishPendingTasks(WorkletPendingTasks* pending_tasks) {
DCHECK(IsMainThread());
DCHECK(pending_tasks_set_.Contains(pending_tasks));
pending_tasks_set_.erase(pending_tasks);
}
WorkletGlobalScopeProxy* Worklet::FindAvailableGlobalScope() {
DCHECK(IsMainThread());
return proxies_.at(SelectGlobalScope());
}
// Implementation of the second half of the "addModule(moduleURL, options)"
// algorithm:
// https://drafts.css-houdini.org/worklets/#dom-worklet-addmodule
void Worklet::FetchAndInvokeScript(const KURL& module_url_record,
const String& credentials,
WorkletPendingTasks* pending_tasks) {
DCHECK(IsMainThread());
if (!GetExecutionContext())
return;
// Step 6: "Let credentialOptions be the credentials member of options."
base::Optional<network::mojom::CredentialsMode> credentials_mode =
Request::ParseCredentialsMode(credentials);
DCHECK(credentials_mode);
// Step 7: "Let outsideSettings be the relevant settings object of this."
auto* outside_settings_object =
MakeGarbageCollected<FetchClientSettingsObjectSnapshot>(
GetExecutionContext()
->Fetcher()
->GetProperties()
.GetFetchClientSettingsObject());
// Worklets don't support resource timing APIs yet.
auto* outside_resource_timing_notifier =
MakeGarbageCollected<NullWorkerResourceTimingNotifier>();
// Specify TaskType::kInternalLoading because it's commonly used for module
// loading.
scoped_refptr<base::SingleThreadTaskRunner> outside_settings_task_runner =
GetExecutionContext()->GetTaskRunner(TaskType::kInternalLoading);
// Step 8: "Let moduleResponsesMap be worklet's module responses map."
// ModuleResponsesMap() returns moduleResponsesMap.
// Step 9: "Let workletGlobalScopeType be worklet's worklet global scope
// type."
// workletGlobalScopeType is encoded into the class name (e.g., PaintWorklet).
// Step 10: "If the worklet's WorkletGlobalScopes is empty, run the following
// steps:"
// 10.1: "Create a WorkletGlobalScope given workletGlobalScopeType,
// moduleResponsesMap, and outsideSettings."
// 10.2: "Add the WorkletGlobalScope to worklet's WorkletGlobalScopes."
// "Depending on the type of worklet the user agent may create additional
// WorkletGlobalScopes at this time."
while (NeedsToCreateGlobalScope())
proxies_.push_back(CreateGlobalScope());
// Step 11: "Let pendingTaskStruct be a new pending tasks struct with counter
// initialized to the length of worklet's WorkletGlobalScopes."
pending_tasks->InitializeCounter(GetNumberOfGlobalScopes());
// 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."
// moduleResponsesMap is already passed via CreateGlobalScope().
// TODO(nhiroki): Queue a task instead of executing this here.
for (const auto& proxy : proxies_) {
proxy->FetchAndInvokeScript(module_url_record, *credentials_mode,
*outside_settings_object,
*outside_resource_timing_notifier,
outside_settings_task_runner, pending_tasks);
}
}
wtf_size_t Worklet::SelectGlobalScope() {
DCHECK_EQ(GetNumberOfGlobalScopes(), 1u);
return 0u;
}
void Worklet::Trace(Visitor* visitor) const {
visitor->Trace(proxies_);
visitor->Trace(module_responses_map_);
visitor->Trace(pending_tasks_set_);
ScriptWrappable::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
}
} // namespace blink