blob: b742b961c38850214f8c58897df7f3bd01e5ae1d [file] [log] [blame]
// 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/modules/csspaint/paint_worklet_proxy_client.h"
#include <memory>
#include <utility>
#include "base/single_thread_task_runner.h"
#include "third_party/blink/renderer/core/css/cssom/cross_thread_color_value.h"
#include "third_party/blink/renderer/core/css/cssom/cross_thread_unit_value.h"
#include "third_party/blink/renderer/core/css/cssom/css_paint_worklet_input.h"
#include "third_party/blink/renderer/core/css/cssom/css_style_value.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/workers/worker_thread.h"
#include "third_party/blink/renderer/modules/csspaint/css_paint_definition.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet.h"
#include "third_party/blink/renderer/platform/graphics/image.h"
#include "third_party/blink/renderer/platform/graphics/paint_worklet_paint_dispatcher.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
namespace blink {
const char PaintWorkletProxyClient::kSupplementName[] =
"PaintWorkletProxyClient";
// static
PaintWorkletProxyClient* PaintWorkletProxyClient::From(WorkerClients* clients) {
return Supplement<WorkerClients>::From<PaintWorkletProxyClient>(clients);
}
// static
PaintWorkletProxyClient* PaintWorkletProxyClient::Create(LocalDOMWindow* window,
int worklet_id) {
WebLocalFrameImpl* local_frame =
WebLocalFrameImpl::FromFrame(window->GetFrame());
PaintWorklet* paint_worklet = PaintWorklet::From(*window);
scoped_refptr<base::SingleThreadTaskRunner> compositor_host_queue;
base::WeakPtr<PaintWorkletPaintDispatcher> compositor_paint_dispatcher =
local_frame->LocalRootFrameWidget()->EnsureCompositorPaintDispatcher(
&compositor_host_queue);
return MakeGarbageCollected<PaintWorkletProxyClient>(
worklet_id, paint_worklet, std::move(compositor_paint_dispatcher),
std::move(compositor_host_queue));
}
PaintWorkletProxyClient::PaintWorkletProxyClient(
int worklet_id,
PaintWorklet* paint_worklet,
base::WeakPtr<PaintWorkletPaintDispatcher> paint_dispatcher,
scoped_refptr<base::SingleThreadTaskRunner> compositor_host_queue)
: Supplement(nullptr),
paint_dispatcher_(std::move(paint_dispatcher)),
compositor_host_queue_(std::move(compositor_host_queue)),
worklet_id_(worklet_id),
state_(RunState::kUninitialized),
main_thread_runner_(Thread::MainThread()->GetTaskRunner()),
paint_worklet_(paint_worklet) {
DCHECK(IsMainThread());
}
void PaintWorkletProxyClient::AddGlobalScope(WorkletGlobalScope* global_scope) {
DCHECK(global_scope);
DCHECK(global_scope->IsContextThread());
if (state_ == RunState::kDisposed)
return;
DCHECK(state_ == RunState::kUninitialized);
global_scopes_.push_back(To<PaintWorkletGlobalScope>(global_scope));
// Wait for all global scopes to be set before registering.
if (global_scopes_.size() < PaintWorklet::kNumGlobalScopesPerThread) {
return;
}
// All the global scopes that share a single PaintWorkletProxyClient run on
// the same thread with the same scheduler. As such we can just grab a task
// runner from the last one to register.
scoped_refptr<base::SingleThreadTaskRunner> global_scope_runner =
global_scope->GetThread()->GetTaskRunner(TaskType::kMiscPlatformAPI);
state_ = RunState::kWorking;
PostCrossThreadTask(
*compositor_host_queue_, FROM_HERE,
CrossThreadBindOnce(
&PaintWorkletPaintDispatcher::RegisterPaintWorkletPainter,
paint_dispatcher_, WrapCrossThreadPersistent(this),
global_scope_runner));
}
void PaintWorkletProxyClient::RegisterCSSPaintDefinition(
const String& name,
CSSPaintDefinition* definition,
ExceptionState& exception_state) {
if (document_definition_map_.Contains(name)) {
DocumentPaintDefinition* document_definition =
document_definition_map_.at(name);
if (!document_definition)
return;
if (!document_definition->RegisterAdditionalPaintDefinition(*definition)) {
document_definition_map_.Set(name, nullptr);
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"A class with name:'" + name +
"' was registered with a different definition.");
return;
}
} else {
auto document_definition = std::make_unique<DocumentPaintDefinition>(
definition->NativeInvalidationProperties(),
definition->CustomInvalidationProperties(),
definition->InputArgumentTypes(),
definition->GetPaintRenderingContext2DSettings()->alpha());
document_definition_map_.insert(name, std::move(document_definition));
}
DocumentPaintDefinition* document_definition =
document_definition_map_.at(name);
// Notify the main thread only once all global scopes have registered the same
// named paint definition (with the same definition as well).
if (document_definition->GetRegisteredDefinitionCount() ==
PaintWorklet::kNumGlobalScopesPerThread) {
const Vector<AtomicString>& custom_properties =
definition->CustomInvalidationProperties();
// Make a deep copy of the |custom_properties| into a Vector<String> so that
// CrossThreadCopier can pass that cross thread boundaries.
Vector<String> passed_custom_properties;
for (const auto& property : custom_properties)
passed_custom_properties.push_back(property.GetString());
PostCrossThreadTask(
*main_thread_runner_, FROM_HERE,
CrossThreadBindOnce(
&PaintWorklet::RegisterMainThreadDocumentPaintDefinition,
paint_worklet_, name, definition->NativeInvalidationProperties(),
std::move(passed_custom_properties),
definition->InputArgumentTypes(),
definition->GetPaintRenderingContext2DSettings()->alpha()));
}
}
void PaintWorkletProxyClient::Dispose() {
if (state_ == RunState::kWorking) {
PostCrossThreadTask(
*compositor_host_queue_, FROM_HERE,
CrossThreadBindOnce(
&PaintWorkletPaintDispatcher::UnregisterPaintWorkletPainter,
paint_dispatcher_, worklet_id_));
}
paint_dispatcher_ = nullptr;
state_ = RunState::kDisposed;
// At worklet scope termination break the reference cycle between
// PaintWorkletGlobalScope and PaintWorkletProxyClient.
global_scopes_.clear();
}
void PaintWorkletProxyClient::Trace(Visitor* visitor) const {
Supplement<WorkerClients>::Trace(visitor);
PaintWorkletPainter::Trace(visitor);
}
sk_sp<PaintRecord> PaintWorkletProxyClient::Paint(
const CompositorPaintWorkletInput* compositor_input,
const CompositorPaintWorkletJob::AnimatedPropertyValues&
animated_property_values) {
// TODO: Can this happen? We don't register till all are here.
if (global_scopes_.IsEmpty())
return sk_make_sp<PaintRecord>();
// PaintWorklets are stateless by spec. There are two ways script might try to
// inject state:
// * From one PaintWorklet to another, in the same frame.
// * Inside the same PaintWorklet, across frames.
//
// To discourage both of these, we randomize selection of the global scope.
// TODO(smcgruer): Once we are passing bundles of PaintWorklets here, we
// should shuffle the bundle randomly and then assign half to the first global
// scope, and half to the rest.
DCHECK_EQ(global_scopes_.size(), PaintWorklet::kNumGlobalScopesPerThread);
PaintWorkletGlobalScope* global_scope = global_scopes_[base::RandInt(
0, (PaintWorklet::kNumGlobalScopesPerThread)-1)];
const CSSPaintWorkletInput* input =
static_cast<const CSSPaintWorkletInput*>(compositor_input);
CSSPaintDefinition* definition =
global_scope->FindDefinition(input->NameCopy());
PaintWorkletStylePropertyMap* style_map =
MakeGarbageCollected<PaintWorkletStylePropertyMap>(input->StyleMapData());
CSSStyleValueVector paint_arguments;
for (const auto& style_value : input->ParsedInputArguments()) {
paint_arguments.push_back(style_value->ToCSSStyleValue());
}
ApplyAnimatedPropertyOverrides(style_map, animated_property_values);
device_pixel_ratio_ = input->DeviceScaleFactor() * input->EffectiveZoom();
sk_sp<PaintRecord> result = definition->Paint(
FloatSize(input->GetSize()), input->EffectiveZoom(), style_map,
&paint_arguments, input->DeviceScaleFactor());
// CSSPaintDefinition::Paint returns nullptr if it fails, but for
// OffThread-PaintWorklet we prefer to insert empty PaintRecords into the
// cache. Do the conversion here.
// TODO(smcgruer): Once OffThread-PaintWorklet launches, we can make
// CSSPaintDefinition::Paint return empty PaintRecords.
if (!result)
result = sk_make_sp<PaintRecord>();
return result;
}
void PaintWorkletProxyClient::ApplyAnimatedPropertyOverrides(
PaintWorkletStylePropertyMap* style_map,
const CompositorPaintWorkletJob::AnimatedPropertyValues&
animated_property_values) {
for (const auto& property_value : animated_property_values) {
DCHECK(property_value.second.has_value());
String property_name(
property_value.first.custom_property_name.value().c_str());
DCHECK(style_map->StyleMapData().Contains(property_name));
CrossThreadStyleValue* old_value =
style_map->StyleMapData().at(property_name);
switch (old_value->GetType()) {
case CrossThreadStyleValue::StyleValueType::kUnitType: {
DCHECK(property_value.second.float_value);
std::unique_ptr<CrossThreadUnitValue> new_value =
std::make_unique<CrossThreadUnitValue>(
property_value.second.float_value.value(),
DynamicTo<CrossThreadUnitValue>(old_value)->GetUnitType());
style_map->StyleMapData().Set(property_name, std::move(new_value));
break;
}
case CrossThreadStyleValue::StyleValueType::kColorType: {
DCHECK(property_value.second.color_value);
SkColor sk_color = property_value.second.color_value.value();
Color color(MakeRGBA(SkColorGetR(sk_color), SkColorGetG(sk_color),
SkColorGetB(sk_color), SkColorGetA(sk_color)));
std::unique_ptr<CrossThreadColorValue> new_value =
std::make_unique<CrossThreadColorValue>(color);
style_map->StyleMapData().Set(property_name, std::move(new_value));
break;
}
default:
NOTREACHED();
break;
}
}
}
void ProvidePaintWorkletProxyClientTo(WorkerClients* clients,
PaintWorkletProxyClient* client) {
clients->ProvideSupplement(client);
}
} // namespace blink