blob: c196e974cadff72cf7d2c3c3b2cbf8e3d37dc09b [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// 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.h"
#include "base/rand_util.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/node_rare_data.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/modules/csspaint/css_paint_definition.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet_id_generator.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet_messaging_proxy.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/graphics/paint_generated_image.h"
namespace blink {
const wtf_size_t PaintWorklet::kNumGlobalScopesPerThread = 2u;
const size_t kMaxPaintCountToSwitch = 30u;
// static
PaintWorklet* PaintWorklet::From(LocalDOMWindow& window) {
PaintWorklet* supplement =
Supplement<LocalDOMWindow>::From<PaintWorklet>(window);
if (!supplement && window.GetFrame()) {
supplement = MakeGarbageCollected<PaintWorklet>(window);
ProvideTo(window, supplement);
}
return supplement;
}
PaintWorklet::PaintWorklet(LocalDOMWindow& window)
: Worklet(window),
Supplement<LocalDOMWindow>(window),
pending_generator_registry_(
MakeGarbageCollected<PaintWorkletPendingGeneratorRegistry>()),
worklet_id_(PaintWorkletIdGenerator::NextId()),
is_paint_off_thread_(
RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled() &&
Thread::CompositorThread()) {}
PaintWorklet::~PaintWorklet() = default;
void PaintWorklet::AddPendingGenerator(const String& name,
CSSPaintImageGeneratorImpl* generator) {
pending_generator_registry_->AddPendingGenerator(name, generator);
}
void PaintWorklet::ResetIsPaintOffThreadForTesting() {
is_paint_off_thread_ = RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled();
}
// We start with a random global scope when a new frame starts. Then within this
// frame, we switch to the other global scope after certain amount of paint
// calls (rand(kMaxPaintCountToSwitch)).
// This approach ensures non-deterministic of global scope selecting, and that
// there is a max of one switching within one frame.
wtf_size_t PaintWorklet::SelectGlobalScope() {
size_t current_paint_frame_count =
DomWindow()->GetFrame()->View()->PaintFrameCount();
// Whether a new frame starts or not.
bool frame_changed = current_paint_frame_count != active_frame_count_;
if (frame_changed) {
paints_before_switching_global_scope_ = GetPaintsBeforeSwitching();
active_frame_count_ = current_paint_frame_count;
}
// We switch when |paints_before_switching_global_scope_| is 1 instead of 0
// because the var keeps decrementing and stays at 0.
if (frame_changed || paints_before_switching_global_scope_ == 1)
active_global_scope_ = SelectNewGlobalScope();
if (paints_before_switching_global_scope_ > 0)
paints_before_switching_global_scope_--;
return active_global_scope_;
}
int PaintWorklet::GetPaintsBeforeSwitching() {
// TODO(xidachen): Try not to reset |paints_before_switching_global_scope_|
// every frame. For example, if one frame typically has ~5 paint, then we can
// switch to another global scope after few frames where the accumulated
// number of paint calls during these frames reached the
// |paints_before_switching_global_scope_|.
// TODO(xidachen): Try to set |paints_before_switching_global_scope_|
// according to the actual paints per frame. For example, if we found that
// there are typically ~1000 paints in each frame, we'd want to set the number
// to average at 500.
return base::RandInt(0, kMaxPaintCountToSwitch - 1);
}
wtf_size_t PaintWorklet::SelectNewGlobalScope() {
return static_cast<wtf_size_t>(
base::RandGenerator(kNumGlobalScopesPerThread));
}
scoped_refptr<Image> PaintWorklet::Paint(const String& name,
const ImageResourceObserver& observer,
const gfx::SizeF& container_size,
const CSSStyleValueVector* data) {
if (!document_definition_map_.Contains(name))
return nullptr;
// Check if the existing document definition is valid or not.
DocumentPaintDefinition* document_definition =
document_definition_map_.at(name);
if (!document_definition)
return nullptr;
PaintWorkletGlobalScopeProxy* proxy =
PaintWorkletGlobalScopeProxy::From(FindAvailableGlobalScope());
CSSPaintDefinition* paint_definition = proxy->FindDefinition(name);
if (!paint_definition)
return nullptr;
// TODO(crbug.com/946515): Break dependency on LayoutObject.
const LayoutObject& layout_object =
static_cast<const LayoutObject&>(observer);
float zoom = layout_object.StyleRef().EffectiveZoom();
StylePropertyMapReadOnly* style_map =
MakeGarbageCollected<PrepopulatedComputedStylePropertyMap>(
layout_object.GetDocument(), layout_object.StyleRef(),
paint_definition->NativeInvalidationProperties(),
paint_definition->CustomInvalidationProperties());
// The PaintWorkletGlobalScope is sufficiently isolated that it is safe to
// run during the lifecycle update without concern for it causing
// invalidations to the lifecycle.
ScriptForbiddenScope::AllowUserAgentScript allow_script;
PaintRecord paint_record =
paint_definition->Paint(container_size, zoom, style_map, data);
if (paint_record.empty()) {
return nullptr;
}
return PaintGeneratedImage::Create(std::move(paint_record), container_size);
}
// static
const char PaintWorklet::kSupplementName[] = "PaintWorklet";
void PaintWorklet::Trace(Visitor* visitor) const {
visitor->Trace(pending_generator_registry_);
visitor->Trace(proxy_client_);
Worklet::Trace(visitor);
Supplement<LocalDOMWindow>::Trace(visitor);
}
void PaintWorklet::RegisterCSSPaintDefinition(const String& name,
CSSPaintDefinition* definition,
ExceptionState& exception_state) {
if (document_definition_map_.Contains(name)) {
DocumentPaintDefinition* existing_document_definition =
document_definition_map_.at(name);
if (!existing_document_definition)
return;
if (!existing_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;
}
// Notify the generator ready only when register paint is called the
// second time with the same |name| (i.e. there is already a document
// definition associated with |name|
//
// We are looking for kNumGlobalScopesPerThread number of definitions
// regiserered from RegisterCSSPaintDefinition and one extra definition from
// RegisterMainThreadDocumentPaintDefinition if OffMainThreadCSSPaintEnabled
// is true.
unsigned required_registered_count = is_paint_off_thread_
? kNumGlobalScopesPerThread + 1
: kNumGlobalScopesPerThread;
if (existing_document_definition->GetRegisteredDefinitionCount() ==
required_registered_count)
pending_generator_registry_->NotifyGeneratorReady(name);
} 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));
}
}
void PaintWorklet::RegisterMainThreadDocumentPaintDefinition(
const String& name,
Vector<CSSPropertyID> native_properties,
Vector<String> custom_properties,
Vector<CSSSyntaxDefinition> input_argument_types,
double alpha) {
if (document_definition_map_.Contains(name)) {
DocumentPaintDefinition* document_definition =
document_definition_map_.at(name);
if (!document_definition)
return;
if (!document_definition->RegisterAdditionalPaintDefinition(
native_properties, custom_properties, input_argument_types,
alpha)) {
document_definition_map_.Set(name, nullptr);
return;
}
} else {
// Because this method is called cross-thread, |custom_properties| cannot be
// an AtomicString. Instead, convert to AtomicString now that we are on the
// main thread.
Vector<AtomicString> new_custom_properties;
new_custom_properties.ReserveInitialCapacity(custom_properties.size());
for (const String& property : custom_properties)
new_custom_properties.push_back(AtomicString(property));
auto document_definition = std::make_unique<DocumentPaintDefinition>(
std::move(native_properties), std::move(new_custom_properties),
std::move(input_argument_types), alpha);
document_definition_map_.insert(name, std::move(document_definition));
}
DocumentPaintDefinition* document_definition =
document_definition_map_.at(name);
// We are looking for kNumGlobalScopesPerThread number of definitions
// registered from RegisterCSSPaintDefinition and one extra definition from
// RegisterMainThreadDocumentPaintDefinition
if (document_definition->GetRegisteredDefinitionCount() ==
kNumGlobalScopesPerThread + 1)
pending_generator_registry_->NotifyGeneratorReady(name);
}
bool PaintWorklet::NeedsToCreateGlobalScope() {
wtf_size_t num_scopes_needed = kNumGlobalScopesPerThread;
// If we are running off main thread, we will need twice as many global scopes
if (is_paint_off_thread_)
num_scopes_needed *= 2;
return GetNumberOfGlobalScopes() < num_scopes_needed;
}
WorkletGlobalScopeProxy* PaintWorklet::CreateGlobalScope() {
DCHECK(NeedsToCreateGlobalScope());
// The main thread global scopes must be created first so that they are at the
// front of the vector. This is because SelectNewGlobalScope selects global
// scopes from the beginning of the vector. If this code is changed to put
// the main thread global scopes at the end, then SelectNewGlobalScope must
// also be changed.
if (!is_paint_off_thread_ ||
GetNumberOfGlobalScopes() < kNumGlobalScopesPerThread) {
return MakeGarbageCollected<PaintWorkletGlobalScopeProxy>(
To<LocalDOMWindow>(GetExecutionContext())->GetFrame(),
ModuleResponsesMap(), GetNumberOfGlobalScopes() + 1);
}
if (!proxy_client_) {
proxy_client_ = PaintWorkletProxyClient::Create(
To<LocalDOMWindow>(GetExecutionContext()), worklet_id_);
}
auto* worker_clients = MakeGarbageCollected<WorkerClients>();
ProvidePaintWorkletProxyClientTo(worker_clients, proxy_client_);
PaintWorkletMessagingProxy* proxy =
MakeGarbageCollected<PaintWorkletMessagingProxy>(GetExecutionContext());
proxy->Initialize(worker_clients, ModuleResponsesMap());
return proxy;
}
} // namespace blink