blob: 4503d3bb65202f9641cf1b19518dc068225c8a30 [file] [log] [blame]
// Copyright 2014 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/animation/string_keyframe.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
#include "third_party/blink/renderer/core/animation/animation_input_helpers.h"
#include "third_party/blink/renderer/core/animation/css/css_animations.h"
#include "third_party/blink/renderer/core/css/css_custom_property_declaration.h"
#include "third_party/blink/renderer/core/css/css_keyframe_shorthand_value.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/style_property_shorthand.h"
#include "third_party/blink/renderer/core/svg/svg_element.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
namespace blink {
namespace {
// Returns handle for the given CSSProperty.
// |value| is required only for custom properties.
PropertyHandle ToPropertyHandle(const CSSProperty& property,
const CSSValue* value) {
if (property.IDEquals(CSSPropertyID::kVariable)) {
return PropertyHandle(To<CSSCustomPropertyDeclaration>(*value).GetName());
} else {
return PropertyHandle(property, false);
}
}
bool IsLogicalProperty(CSSPropertyID property_id) {
const CSSProperty& property = CSSProperty::Get(property_id);
const CSSProperty& resolved_property = property.ResolveDirectionAwareProperty(
TextDirection::kLtr, WritingMode::kHorizontalTb);
return resolved_property.PropertyID() != property_id;
}
MutableCSSPropertyValueSet* CreateCssPropertyValueSet() {
return MakeGarbageCollected<MutableCSSPropertyValueSet>(kHTMLStandardMode);
}
} // namespace
using PropertyResolver = StringKeyframe::PropertyResolver;
StringKeyframe::StringKeyframe(const StringKeyframe& copy_from)
: Keyframe(copy_from.offset_, copy_from.composite_, copy_from.easing_),
input_properties_(copy_from.input_properties_),
presentation_attribute_map_(
copy_from.presentation_attribute_map_->MutableCopy()),
svg_attribute_map_(copy_from.svg_attribute_map_),
has_logical_property_(copy_from.has_logical_property_),
text_direction_(copy_from.text_direction_),
writing_mode_(copy_from.writing_mode_) {
if (copy_from.css_property_map_)
css_property_map_ = copy_from.css_property_map_->MutableCopy();
}
MutableCSSPropertyValueSet::SetResult StringKeyframe::SetCSSPropertyValue(
const AtomicString& custom_property_name,
const String& value,
SecureContextMode secure_context_mode,
StyleSheetContents* style_sheet_contents) {
bool is_animation_tainted = true;
auto* property_map = CreateCssPropertyValueSet();
MutableCSSPropertyValueSet::SetResult result = property_map->SetProperty(
custom_property_name, value, false, secure_context_mode,
style_sheet_contents, is_animation_tainted);
const CSSValue* parsed_value =
property_map->GetPropertyCSSValue(custom_property_name);
if (result.did_parse && parsed_value) {
// Per specification we only keep properties around which are parsable.
input_properties_.Set(PropertyHandle(custom_property_name),
MakeGarbageCollected<PropertyResolver>(
CSSPropertyID::kVariable, *parsed_value));
}
return result;
}
MutableCSSPropertyValueSet::SetResult StringKeyframe::SetCSSPropertyValue(
CSSPropertyID property_id,
const String& value,
SecureContextMode secure_context_mode,
StyleSheetContents* style_sheet_contents) {
DCHECK_NE(property_id, CSSPropertyID::kInvalid);
DCHECK_NE(property_id, CSSPropertyID::kVariable);
const CSSProperty& property = CSSProperty::Get(property_id);
if (CSSAnimations::IsAnimationAffectingProperty(property)) {
bool did_parse = true;
bool did_change = false;
return MutableCSSPropertyValueSet::SetResult{did_parse, did_change};
}
auto* property_value_set = CreateCssPropertyValueSet();
MutableCSSPropertyValueSet::SetResult result =
property_value_set->SetProperty(
property_id, value, false, secure_context_mode, style_sheet_contents);
// TODO(crbug.com/1132078): Add flag to CSSProperty to track if it is for a
// logical style.
bool is_logical = false;
if (property.IsShorthand()) {
// Logical shorthands to not directly map to physical shorthands. Determine
// if the shorthand is for a logical property by checking the first
// longhand.
if (property_value_set->PropertyCount()) {
CSSPropertyValueSet::PropertyReference reference =
property_value_set->PropertyAt(0);
if (IsLogicalProperty(reference.Id()))
is_logical = true;
}
} else {
is_logical = IsLogicalProperty(property_id);
}
if (is_logical)
has_logical_property_ = true;
if (result.did_parse) {
// Per specification we only keep properties around which are parsable.
auto* resolver = MakeGarbageCollected<PropertyResolver>(
property, property_value_set, is_logical);
if (resolver->IsValid()) {
input_properties_.Set(PropertyHandle(property), resolver);
InvalidateCssPropertyMap();
}
}
return result;
}
void StringKeyframe::SetCSSPropertyValue(const CSSProperty& property,
const CSSValue& value) {
CSSPropertyID property_id = property.PropertyID();
DCHECK_NE(property_id, CSSPropertyID::kInvalid);
DCHECK(!CSSAnimations::IsAnimationAffectingProperty(property));
DCHECK(!property.IsShorthand());
DCHECK(!IsLogicalProperty(property_id));
input_properties_.Set(
ToPropertyHandle(property, &value),
MakeGarbageCollected<PropertyResolver>(property_id, value));
InvalidateCssPropertyMap();
}
void StringKeyframe::RemoveCustomCSSProperty(const PropertyHandle& property) {
DCHECK(property.IsCSSCustomProperty());
if (css_property_map_)
css_property_map_->RemoveProperty(property.CustomPropertyName());
input_properties_.erase(property);
}
void StringKeyframe::SetPresentationAttributeValue(
const CSSProperty& property,
const String& value,
SecureContextMode secure_context_mode,
StyleSheetContents* style_sheet_contents) {
DCHECK_NE(property.PropertyID(), CSSPropertyID::kInvalid);
if (!CSSAnimations::IsAnimationAffectingProperty(property)) {
presentation_attribute_map_->SetProperty(property.PropertyID(), value,
false, secure_context_mode,
style_sheet_contents);
}
}
void StringKeyframe::SetSVGAttributeValue(const QualifiedName& attribute_name,
const String& value) {
svg_attribute_map_.Set(&attribute_name, value);
}
PropertyHandleSet StringKeyframe::Properties() const {
// This is not used in time-critical code, so we probably don't need to
// worry about caching this result.
EnsureCssPropertyMap();
PropertyHandleSet properties;
for (unsigned i = 0; i < css_property_map_->PropertyCount(); ++i) {
CSSPropertyValueSet::PropertyReference property_reference =
css_property_map_->PropertyAt(i);
// TODO(crbug.com/980160): Remove access to static Variable instance.
const CSSProperty& property = CSSProperty::Get(property_reference.Id());
DCHECK(!property.IsShorthand())
<< "Web Animations: Encountered unexpanded shorthand CSS property ("
<< static_cast<int>(property.PropertyID()) << ").";
properties.insert(ToPropertyHandle(property, &property_reference.Value()));
}
for (unsigned i = 0; i < presentation_attribute_map_->PropertyCount(); ++i) {
properties.insert(PropertyHandle(
CSSProperty::Get(presentation_attribute_map_->PropertyAt(i).Id()),
true));
}
for (auto* const key : svg_attribute_map_.Keys())
properties.insert(PropertyHandle(*key));
return properties;
}
bool StringKeyframe::HasCssProperty() const {
PropertyHandleSet properties = Properties();
for (const PropertyHandle& property : properties) {
if (property.IsCSSProperty())
return true;
}
return false;
}
void StringKeyframe::AddKeyframePropertiesToV8Object(
V8ObjectBuilder& object_builder,
Element* element) const {
Keyframe::AddKeyframePropertiesToV8Object(object_builder, element);
for (const auto& entry : input_properties_) {
const PropertyHandle& property_handle = entry.key;
const CSSValue* property_value = entry.value->CssValue();
String property_name =
AnimationInputHelpers::PropertyHandleToKeyframeAttribute(
property_handle);
object_builder.Add(property_name, property_value->CssText());
}
// Legacy code path for SVG and Presentation attributes.
//
// TODO(816956): Move these to input_properties_ and remove this. Note that
// this code path is not well tested given that removing it didn't cause any
// test failures.
for (const PropertyHandle& property : Properties()) {
if (property.IsCSSProperty())
continue;
String property_name =
AnimationInputHelpers::PropertyHandleToKeyframeAttribute(property);
String property_value;
if (property.IsPresentationAttribute()) {
const auto& attribute = property.PresentationAttribute();
property_value = PresentationAttributeValue(attribute).CssText();
} else {
DCHECK(property.IsSVGAttribute());
property_value = SvgPropertyValue(property.SvgAttribute());
}
object_builder.Add(property_name, property_value);
}
}
void StringKeyframe::Trace(Visitor* visitor) const {
visitor->Trace(input_properties_);
visitor->Trace(css_property_map_);
visitor->Trace(presentation_attribute_map_);
Keyframe::Trace(visitor);
}
Keyframe* StringKeyframe::Clone() const {
return MakeGarbageCollected<StringKeyframe>(*this);
}
bool StringKeyframe::SetLogicalPropertyResolutionContext(
TextDirection text_direction,
WritingMode writing_mode) {
if (text_direction != text_direction_ || writing_mode != writing_mode_) {
text_direction_ = text_direction;
writing_mode_ = writing_mode;
if (has_logical_property_) {
// force a rebuild of the property map on the next property fetch.
InvalidateCssPropertyMap();
return true;
}
}
return false;
}
void StringKeyframe::EnsureCssPropertyMap() const {
if (css_property_map_)
return;
css_property_map_ =
MakeGarbageCollected<MutableCSSPropertyValueSet>(kHTMLStandardMode);
bool requires_sorting = false;
HeapVector<Member<PropertyResolver>> resolvers;
for (const auto& entry : input_properties_) {
const PropertyHandle& property_handle = entry.key;
if (!property_handle.IsCSSProperty())
continue;
if (property_handle.IsCSSCustomProperty()) {
CSSPropertyName property_name(property_handle.CustomPropertyName());
const CSSValue* value = entry.value->CssValue();
css_property_map_->SetProperty(CSSPropertyValue(property_name, *value));
} else {
PropertyResolver* resolver = entry.value;
if (resolver->IsLogical() || resolver->IsShorthand())
requires_sorting = true;
resolvers.push_back(resolver);
}
}
if (requires_sorting) {
std::stable_sort(resolvers.begin(), resolvers.end(),
PropertyResolver::HasLowerPriority);
}
for (const auto& resolver : resolvers) {
resolver->AppendTo(css_property_map_, text_direction_, writing_mode_);
}
}
Keyframe::PropertySpecificKeyframe*
StringKeyframe::CreatePropertySpecificKeyframe(
const PropertyHandle& property,
EffectModel::CompositeOperation effect_composite,
double offset) const {
EffectModel::CompositeOperation composite =
composite_.value_or(effect_composite);
if (property.IsCSSProperty()) {
return MakeGarbageCollected<CSSPropertySpecificKeyframe>(
offset, &Easing(), &CssPropertyValue(property), composite);
}
if (property.IsPresentationAttribute()) {
return MakeGarbageCollected<CSSPropertySpecificKeyframe>(
offset, &Easing(),
&PresentationAttributeValue(property.PresentationAttribute()),
composite);
}
DCHECK(property.IsSVGAttribute());
return MakeGarbageCollected<SVGPropertySpecificKeyframe>(
offset, &Easing(), SvgPropertyValue(property.SvgAttribute()), composite);
}
bool StringKeyframe::CSSPropertySpecificKeyframe::
PopulateCompositorKeyframeValue(const PropertyHandle& property,
Element& element,
const ComputedStyle& base_style,
const ComputedStyle* parent_style) const {
compositor_keyframe_value_cache_ =
StyleResolver::CreateCompositorKeyframeValueSnapshot(
element, base_style, parent_style, property, value_.Get(), offset_);
return true;
}
bool StringKeyframe::CSSPropertySpecificKeyframe::IsRevert() const {
return value_ && value_->IsRevertValue();
}
Keyframe::PropertySpecificKeyframe*
StringKeyframe::CSSPropertySpecificKeyframe::NeutralKeyframe(
double offset,
scoped_refptr<TimingFunction> easing) const {
return MakeGarbageCollected<CSSPropertySpecificKeyframe>(
offset, std::move(easing), nullptr, EffectModel::kCompositeAdd);
}
void StringKeyframe::CSSPropertySpecificKeyframe::Trace(
Visitor* visitor) const {
visitor->Trace(value_);
visitor->Trace(compositor_keyframe_value_cache_);
Keyframe::PropertySpecificKeyframe::Trace(visitor);
}
Keyframe::PropertySpecificKeyframe*
StringKeyframe::CSSPropertySpecificKeyframe::CloneWithOffset(
double offset) const {
auto* clone = MakeGarbageCollected<CSSPropertySpecificKeyframe>(
offset, easing_, value_.Get(), composite_);
clone->compositor_keyframe_value_cache_ = compositor_keyframe_value_cache_;
return clone;
}
Keyframe::PropertySpecificKeyframe*
SVGPropertySpecificKeyframe::CloneWithOffset(double offset) const {
return MakeGarbageCollected<SVGPropertySpecificKeyframe>(offset, easing_,
value_, composite_);
}
Keyframe::PropertySpecificKeyframe*
SVGPropertySpecificKeyframe::NeutralKeyframe(
double offset,
scoped_refptr<TimingFunction> easing) const {
return MakeGarbageCollected<SVGPropertySpecificKeyframe>(
offset, std::move(easing), String(), EffectModel::kCompositeAdd);
}
// ----- Property Resolver -----
PropertyResolver::PropertyResolver(CSSPropertyID property_id,
const CSSValue& css_value)
: property_id_(property_id), css_value_(css_value) {}
PropertyResolver::PropertyResolver(
const CSSProperty& property,
const MutableCSSPropertyValueSet* property_value_set,
bool is_logical)
: property_id_(property.PropertyID()), is_logical_(is_logical) {
DCHECK_NE(property_id_, CSSPropertyID::kInvalid);
DCHECK_NE(property_id_, CSSPropertyID::kVariable);
if (!property.IsShorthand())
css_value_ = property_value_set->GetPropertyCSSValue(property_id_);
else
css_property_value_set_ = property_value_set->ImmutableCopyIfNeeded();
}
bool PropertyResolver::IsValid() const {
return css_value_ || css_property_value_set_;
}
const CSSValue* PropertyResolver::CssValue() {
DCHECK(IsValid());
if (css_value_)
return css_value_;
// For shorthands create a special wrapper value, |CSSKeyframeShorthandValue|,
// which can be used to correctly serialize it given longhands that are
// present in this set.
css_value_ = MakeGarbageCollected<CSSKeyframeShorthandValue>(
property_id_, css_property_value_set_);
return css_value_;
}
void PropertyResolver::AppendTo(MutableCSSPropertyValueSet* property_value_set,
TextDirection text_direction,
WritingMode writing_mode) {
DCHECK(property_id_ != CSSPropertyID::kInvalid);
DCHECK(property_id_ != CSSPropertyID::kVariable);
if (css_property_value_set_) {
// Shorthand property. Extract longhands from css_property_value_set_.
if (is_logical_) {
// Walk set of properties converting each property name to its
// corresponding physical property.
for (unsigned i = 0; i < css_property_value_set_->PropertyCount(); i++) {
CSSPropertyValueSet::PropertyReference reference =
css_property_value_set_->PropertyAt(i);
SetProperty(property_value_set, reference.Id(), reference.Value(),
text_direction, writing_mode);
}
} else {
property_value_set->MergeAndOverrideOnConflict(css_property_value_set_);
}
} else {
SetProperty(property_value_set, property_id_, *css_value_, text_direction,
writing_mode);
}
}
void PropertyResolver::SetProperty(
MutableCSSPropertyValueSet* property_value_set,
CSSPropertyID property_id,
const CSSValue& value,
TextDirection text_direction,
WritingMode writing_mode) {
const CSSProperty& physical_property =
CSSProperty::Get(property_id)
.ResolveDirectionAwareProperty(text_direction, writing_mode);
property_value_set->SetProperty(physical_property.PropertyID(), value);
}
void PropertyResolver::Trace(Visitor* visitor) const {
visitor->Trace(css_value_);
visitor->Trace(css_property_value_set_);
}
// static
bool PropertyResolver::HasLowerPriority(PropertyResolver* first,
PropertyResolver* second) {
// Longhand properties take precedence over shorthand properties.
if (first->IsShorthand() != second->IsShorthand())
return first->IsShorthand();
// Physical properties take precedence over logical properties.
if (first->IsLogical() != second->IsLogical())
return first->IsLogical();
// Two shorthands with overlapping longhand properties are sorted based
// on the number of longhand properties in their expansions.
if (first->IsShorthand())
return first->ExpansionCount() > second->ExpansionCount();
return false;
}
} // namespace blink