blob: bac65b23a27b768363e8d1dd2794c960ce4434e2 [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/css/css_paint_value.h"
#include "base/metrics/histogram_macros.h"
#include "third_party/blink/renderer/core/css/css_custom_ident_value.h"
#include "third_party/blink/renderer/core/css/css_paint_image_generator.h"
#include "third_party/blink/renderer/core/css/css_syntax_definition.h"
#include "third_party/blink/renderer/core/css/cssom/css_paint_worklet_input.h"
#include "third_party/blink/renderer/core/css/cssom/paint_worklet_deferred_image.h"
#include "third_party/blink/renderer/core/css/cssom/style_value_factory.h"
#include "third_party/blink/renderer/core/css/properties/computed_style_utils.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/platform/graphics/image.h"
#include "third_party/blink/renderer/platform/graphics/platform_paint_worklet_layer_painter.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
CSSPaintValue::CSSPaintValue(CSSCustomIdentValue* name,
bool threaded_compositing_enabled)
: CSSImageGeneratorValue(kPaintClass),
name_(name),
paint_image_generator_observer_(MakeGarbageCollected<Observer>(this)),
off_thread_paint_state_(
(!threaded_compositing_enabled ||
!RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled())
? OffThreadPaintState::kMainThread
: OffThreadPaintState::kUnknown) {}
CSSPaintValue::CSSPaintValue(CSSCustomIdentValue* name)
: CSSPaintValue(name, Thread::CompositorThread()) {}
CSSPaintValue::CSSPaintValue(
CSSCustomIdentValue* name,
Vector<scoped_refptr<CSSVariableData>>& variable_data)
: CSSPaintValue(name) {
argument_variable_data_.swap(variable_data);
}
CSSPaintValue::~CSSPaintValue() = default;
String CSSPaintValue::CustomCSSText() const {
StringBuilder result;
result.Append("paint(");
result.Append(name_->CustomCSSText());
for (const auto& variable_data : argument_variable_data_) {
result.Append(", ");
result.Append(variable_data.get()->TokenRange().Serialize());
}
result.Append(')');
return result.ToString();
}
String CSSPaintValue::GetName() const {
return name_->Value();
}
const Vector<CSSPropertyID>* CSSPaintValue::NativeInvalidationProperties(
const Document& document) const {
const CSSPaintImageGenerator* generator = generators_.at(&document);
if (!generator)
return nullptr;
return &generator->NativeInvalidationProperties();
}
const Vector<AtomicString>* CSSPaintValue::CustomInvalidationProperties(
const Document& document) const {
const CSSPaintImageGenerator* generator = generators_.at(&document);
if (!generator)
return nullptr;
return &generator->CustomInvalidationProperties();
}
bool CSSPaintValue::IsUsingCustomProperty(
const AtomicString& custom_property_name,
const Document& document) const {
const CSSPaintImageGenerator* generator = generators_.at(&document);
if (!generator || !generator->IsImageGeneratorReady())
return false;
return generator->CustomInvalidationProperties().Contains(
custom_property_name);
}
CSSPaintImageGenerator& CSSPaintValue::EnsureGenerator(
const Document& document) {
auto& generator = generators_.insert(&document, nullptr).stored_value->value;
if (!generator) {
generator = CSSPaintImageGenerator::Create(GetName(), document,
paint_image_generator_observer_);
}
return *generator;
}
scoped_refptr<Image> CSSPaintValue::GetImage(
const ImageResourceObserver& client,
const Document& document,
const ComputedStyle& style,
const FloatSize& target_size) {
// https://crbug.com/835589: early exit when paint target is associated with
// a link.
if (style.InsideLink() != EInsideLink::kNotInsideLink)
return nullptr;
CSSPaintImageGenerator& generator = EnsureGenerator(document);
// If the generator isn't ready yet, we have nothing to paint. Our
// |paint_image_generator_observer_| will cause us to be called again once the
// generator is ready.
if (!generator.IsImageGeneratorReady())
return nullptr;
if (!ParseInputArguments(document))
return nullptr;
// TODO(crbug.com/946515): Break dependency on LayoutObject.
const LayoutObject& layout_object = static_cast<const LayoutObject&>(client);
// TODO(crbug.com/716231): Remove this hack once zoom_for_dsf is enabled on
// all platforms (currently not enabled on Mac).
float device_scale_factor = 1;
if (layout_object.GetFrame() && layout_object.GetFrame()->GetPage()) {
// The value of DeviceScaleFactorDeprecated would be 1 on a platform where
// zoom_for_dsf is enabled, even if we run chrome with
// --force-device-scale-factor with a value that is not 1.
device_scale_factor =
layout_object.GetFrame()->GetPage()->DeviceScaleFactorDeprecated();
}
// For Off-Thread PaintWorklet, we just collect the necessary inputs together
// and defer the actual JavaScript call until much later (during cc Raster).
//
// Generating print-previews happens entirely on the main thread, so we have
// to fall-back to main in that case.
if (off_thread_paint_state_ != OffThreadPaintState::kMainThread &&
!document.Printing()) {
// It is not necessary for a LayoutObject to always have RareData which
// contains the ElementId. If this |layout_object| doesn't have an
// ElementId, then create one for it.
layout_object.GetMutableForPainting().EnsureId();
const Vector<CSSPropertyID>& native_properties =
generator.NativeInvalidationProperties();
const Vector<AtomicString>& custom_properties =
generator.CustomInvalidationProperties();
float zoom = layout_object.StyleRef().EffectiveZoom();
CompositorPaintWorkletInput::PropertyKeys input_property_keys;
auto style_data = PaintWorkletStylePropertyMap::BuildCrossThreadData(
document, layout_object.UniqueId(), style, native_properties,
custom_properties, input_property_keys);
if (off_thread_paint_state_ == OffThreadPaintState::kUnknown) {
UMA_HISTOGRAM_BOOLEAN("Blink.CSSPaintValue.PaintOffThread",
style_data.has_value());
}
off_thread_paint_state_ = style_data.has_value()
? OffThreadPaintState::kOffThread
: OffThreadPaintState::kMainThread;
if (off_thread_paint_state_ == OffThreadPaintState::kOffThread) {
Vector<std::unique_ptr<CrossThreadStyleValue>>
cross_thread_input_arguments;
BuildInputArgumentValues(cross_thread_input_arguments);
scoped_refptr<CSSPaintWorkletInput> input =
base::MakeRefCounted<CSSPaintWorkletInput>(
GetName(), target_size, zoom, device_scale_factor,
generator.WorkletId(), std::move(style_data.value()),
std::move(cross_thread_input_arguments),
std::move(input_property_keys));
return PaintWorkletDeferredImage::Create(std::move(input), target_size);
}
}
return generator.Paint(client, target_size, parsed_input_arguments_,
device_scale_factor);
}
void CSSPaintValue::BuildInputArgumentValues(
Vector<std::unique_ptr<CrossThreadStyleValue>>&
cross_thread_input_arguments) {
if (!parsed_input_arguments_)
return;
for (const auto& style_value : *parsed_input_arguments_) {
std::unique_ptr<CrossThreadStyleValue> cross_thread_style =
ComputedStyleUtils::CrossThreadStyleValueFromCSSStyleValue(style_value);
cross_thread_input_arguments.push_back(std::move(cross_thread_style));
}
}
bool CSSPaintValue::ParseInputArguments(const Document& document) {
if (input_arguments_invalid_)
return false;
if (parsed_input_arguments_ ||
!RuntimeEnabledFeatures::CSSPaintAPIArgumentsEnabled())
return true;
DCHECK(generators_.at(&document)->IsImageGeneratorReady());
const Vector<CSSSyntaxDefinition>& input_argument_types =
generators_.at(&document)->InputArgumentTypes();
if (argument_variable_data_.size() != input_argument_types.size()) {
input_arguments_invalid_ = true;
return false;
}
parsed_input_arguments_ = MakeGarbageCollected<CSSStyleValueVector>();
for (wtf_size_t i = 0; i < argument_variable_data_.size(); ++i) {
// If we are parsing a paint() function, we must be a secure context.
DCHECK_EQ(SecureContextMode::kSecureContext,
document.GetExecutionContext()->GetSecureContextMode());
const CSSValue* parsed_value = argument_variable_data_[i]->ParseForSyntax(
input_argument_types[i], SecureContextMode::kSecureContext);
if (!parsed_value) {
input_arguments_invalid_ = true;
parsed_input_arguments_ = nullptr;
return false;
}
parsed_input_arguments_->AppendVector(
StyleValueFactory::CssValueToStyleValueVector(*parsed_value));
}
return true;
}
void CSSPaintValue::Observer::PaintImageGeneratorReady() {
owner_value_->PaintImageGeneratorReady();
}
void CSSPaintValue::PaintImageGeneratorReady() {
for (const ImageResourceObserver* client : Clients().Keys()) {
// TODO(ikilpatrick): We shouldn't be casting like this or mutate the layout
// tree from a const pointer.
const_cast<ImageResourceObserver*>(client)->ImageChanged(
static_cast<WrappedImagePtr>(this),
ImageResourceObserver::CanDeferInvalidation::kNo);
}
}
bool CSSPaintValue::KnownToBeOpaque(const Document& document,
const ComputedStyle&) const {
const CSSPaintImageGenerator* generator = generators_.at(&document);
return generator && !generator->HasAlpha();
}
bool CSSPaintValue::Equals(const CSSPaintValue& other) const {
return GetName() == other.GetName() &&
CustomCSSText() == other.CustomCSSText();
}
void CSSPaintValue::TraceAfterDispatch(blink::Visitor* visitor) const {
visitor->Trace(name_);
visitor->Trace(generators_);
visitor->Trace(paint_image_generator_observer_);
visitor->Trace(parsed_input_arguments_);
CSSImageGeneratorValue::TraceAfterDispatch(visitor);
}
} // namespace blink