| // 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 |