blob: 0e1999187fb8e6d84fd5d47a0baaad312f24239d [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/single_thread_task_runner.h"
#include "services/network/public/mojom/fetch_api.mojom-shared.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_feature.mojom-shared.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/core/dom/document.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/use_counter.h"
#include "third_party/blink/renderer/core/workers/worklet_pending_tasks.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
namespace blink {
Worklet::Worklet(Document* document)
: ContextLifecycleObserver(document),
module_responses_map_(new WorkletModuleResponsesMap) {
DCHECK(IsMainThread());
}
Worklet::~Worklet() {
for (const auto& proxy : proxies_)
proxy->WorkletObjectDestroyed();
DCHECK(!HasPendingTasks());
}
// 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) {
DCHECK(IsMainThread());
if (!GetExecutionContext()) {
return ScriptPromise::RejectWithDOMException(
script_state, DOMException::Create(DOMExceptionCode::kInvalidStateError,
"This frame is already detached"));
}
UseCounter::Count(GetExecutionContext(),
mojom::WebFeature::kWorkletAddModule);
// Step 1: "Let promise be a new promise."
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(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(
DOMException::Create(DOMExceptionCode::kSyntaxError,
"'" + module_url + "' is not a valid URL."));
return promise;
}
WorkletPendingTasks* pending_tasks = new 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(ExecutionContext* execution_context) {
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."
network::mojom::FetchCredentialsMode credentials_mode;
bool result = Request::ParseCredentialsMode(credentials, &credentials_mode);
DCHECK(result);
// Step 7: "Let outsideSettings be the relevant settings object of this."
auto* outside_settings_object =
GetExecutionContext()->CreateFetchClientSettingsObjectSnapshot();
// 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_settings_task_runner, pending_tasks);
}
}
size_t Worklet::SelectGlobalScope() {
DCHECK_EQ(GetNumberOfGlobalScopes(), 1u);
return 0u;
}
void Worklet::Trace(blink::Visitor* visitor) {
visitor->Trace(proxies_);
visitor->Trace(module_responses_map_);
visitor->Trace(pending_tasks_set_);
ScriptWrappable::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
}
} // namespace blink