1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
|
// 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 GCedCSSStyleValueVector* 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
|