blob: 02b0d0062a43a7923724bbdf28c1f40ea08411f5 [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/animation/css_border_image_length_box_interpolation_type.h"
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "third_party/blink/renderer/core/animation/length_interpolation_functions.h"
#include "third_party/blink/renderer/core/animation/side_index.h"
#include "third_party/blink/renderer/core/css/css_identifier_value.h"
#include "third_party/blink/renderer/core/css/css_property_names.h"
#include "third_party/blink/renderer/core/css/css_quad_value.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
namespace blink {
namespace {
enum class SideType {
kNumber,
kAuto,
kLength,
};
SideType GetSideType(const BorderImageLength& side) {
if (side.IsNumber()) {
return SideType::kNumber;
}
if (side.length().IsAuto()) {
return SideType::kAuto;
}
DCHECK(side.length().IsSpecified());
return SideType::kLength;
}
SideType GetSideType(const CSSValue& side) {
if (side.IsPrimitiveValue() && ToCSSPrimitiveValue(side).IsNumber()) {
return SideType::kNumber;
}
if (side.IsIdentifierValue() &&
ToCSSIdentifierValue(side).GetValueID() == CSSValueAuto) {
return SideType::kAuto;
}
return SideType::kLength;
}
struct SideTypes {
explicit SideTypes(const BorderImageLengthBox& box) {
type[kSideTop] = GetSideType(box.Top());
type[kSideRight] = GetSideType(box.Right());
type[kSideBottom] = GetSideType(box.Bottom());
type[kSideLeft] = GetSideType(box.Left());
}
explicit SideTypes(const CSSQuadValue& quad) {
type[kSideTop] = GetSideType(*quad.Top());
type[kSideRight] = GetSideType(*quad.Right());
type[kSideBottom] = GetSideType(*quad.Bottom());
type[kSideLeft] = GetSideType(*quad.Left());
}
bool operator==(const SideTypes& other) const {
for (size_t i = 0; i < kSideIndexCount; i++) {
if (type[i] != other.type[i])
return false;
}
return true;
}
bool operator!=(const SideTypes& other) const { return !(*this == other); }
SideType type[kSideIndexCount];
};
const BorderImageLengthBox& GetBorderImageLengthBox(
const CSSProperty& property,
const ComputedStyle& style) {
switch (property.PropertyID()) {
case CSSPropertyBorderImageOutset:
return style.BorderImageOutset();
case CSSPropertyBorderImageWidth:
return style.BorderImageWidth();
case CSSPropertyWebkitMaskBoxImageOutset:
return style.MaskBoxImageOutset();
case CSSPropertyWebkitMaskBoxImageWidth:
return style.MaskBoxImageWidth();
default:
NOTREACHED();
return GetBorderImageLengthBox(
CSSProperty::Get(CSSPropertyBorderImageOutset),
ComputedStyle::InitialStyle());
}
}
void SetBorderImageLengthBox(const CSSProperty& property,
ComputedStyle& style,
const BorderImageLengthBox& box) {
switch (property.PropertyID()) {
case CSSPropertyBorderImageOutset:
style.SetBorderImageOutset(box);
break;
case CSSPropertyWebkitMaskBoxImageOutset:
style.SetMaskBoxImageOutset(box);
break;
case CSSPropertyBorderImageWidth:
style.SetBorderImageWidth(box);
break;
case CSSPropertyWebkitMaskBoxImageWidth:
style.SetMaskBoxImageWidth(box);
break;
default:
NOTREACHED();
break;
}
}
} // namespace
class CSSBorderImageLengthBoxNonInterpolableValue
: public NonInterpolableValue {
public:
static scoped_refptr<CSSBorderImageLengthBoxNonInterpolableValue> Create(
const SideTypes& side_types,
Vector<scoped_refptr<NonInterpolableValue>>&&
side_non_interpolable_values) {
return base::AdoptRef(new CSSBorderImageLengthBoxNonInterpolableValue(
side_types, std::move(side_non_interpolable_values)));
}
scoped_refptr<CSSBorderImageLengthBoxNonInterpolableValue> Clone() {
return base::AdoptRef(new CSSBorderImageLengthBoxNonInterpolableValue(
side_types_, Vector<scoped_refptr<NonInterpolableValue>>(
side_non_interpolable_values_)));
}
const SideTypes& GetSideTypes() const { return side_types_; }
const Vector<scoped_refptr<NonInterpolableValue>>& SideNonInterpolableValues()
const {
return side_non_interpolable_values_;
}
Vector<scoped_refptr<NonInterpolableValue>>& SideNonInterpolableValues() {
return side_non_interpolable_values_;
}
DECLARE_NON_INTERPOLABLE_VALUE_TYPE();
private:
CSSBorderImageLengthBoxNonInterpolableValue(
const SideTypes& side_types,
Vector<scoped_refptr<NonInterpolableValue>>&&
side_non_interpolable_values)
: side_types_(side_types),
side_non_interpolable_values_(side_non_interpolable_values) {
DCHECK_EQ(side_non_interpolable_values_.size(), kSideIndexCount);
}
const SideTypes side_types_;
Vector<scoped_refptr<NonInterpolableValue>> side_non_interpolable_values_;
};
DEFINE_NON_INTERPOLABLE_VALUE_TYPE(CSSBorderImageLengthBoxNonInterpolableValue);
DEFINE_NON_INTERPOLABLE_VALUE_TYPE_CASTS(
CSSBorderImageLengthBoxNonInterpolableValue);
namespace {
class UnderlyingSideTypesChecker
: public CSSInterpolationType::CSSConversionChecker {
public:
static std::unique_ptr<UnderlyingSideTypesChecker> Create(
const SideTypes& underlying_side_types) {
return base::WrapUnique(
new UnderlyingSideTypesChecker(underlying_side_types));
}
static SideTypes GetUnderlyingSideTypes(
const InterpolationValue& underlying) {
return ToCSSBorderImageLengthBoxNonInterpolableValue(
*underlying.non_interpolable_value)
.GetSideTypes();
}
private:
UnderlyingSideTypesChecker(const SideTypes& underlying_side_types)
: underlying_side_types_(underlying_side_types) {}
bool IsValid(const StyleResolverState&,
const InterpolationValue& underlying) const final {
return underlying_side_types_ == GetUnderlyingSideTypes(underlying);
}
const SideTypes underlying_side_types_;
};
class InheritedSideTypesChecker
: public CSSInterpolationType::CSSConversionChecker {
public:
static std::unique_ptr<InheritedSideTypesChecker> Create(
const CSSProperty& property,
const SideTypes& inherited_side_types) {
return base::WrapUnique(
new InheritedSideTypesChecker(property, inherited_side_types));
}
private:
InheritedSideTypesChecker(const CSSProperty& property,
const SideTypes& inherited_side_types)
: property_(property), inherited_side_types_(inherited_side_types) {}
bool IsValid(const StyleResolverState& state,
const InterpolationValue& underlying) const final {
return inherited_side_types_ ==
SideTypes(GetBorderImageLengthBox(property_, *state.ParentStyle()));
}
const CSSProperty& property_;
const SideTypes inherited_side_types_;
};
InterpolationValue ConvertBorderImageLengthBox(const BorderImageLengthBox& box,
double zoom) {
std::unique_ptr<InterpolableList> list =
InterpolableList::Create(kSideIndexCount);
Vector<scoped_refptr<NonInterpolableValue>> non_interpolable_values(
kSideIndexCount);
const BorderImageLength* sides[kSideIndexCount] = {};
sides[kSideTop] = &box.Top();
sides[kSideRight] = &box.Right();
sides[kSideBottom] = &box.Bottom();
sides[kSideLeft] = &box.Left();
for (wtf_size_t i = 0; i < kSideIndexCount; i++) {
const BorderImageLength& side = *sides[i];
if (side.IsNumber()) {
list->Set(i, InterpolableNumber::Create(side.Number()));
} else if (side.length().IsAuto()) {
list->Set(i, InterpolableList::Create(0));
} else {
InterpolationValue converted_side =
LengthInterpolationFunctions::MaybeConvertLength(side.length(), zoom);
if (!converted_side)
return nullptr;
list->Set(i, std::move(converted_side.interpolable_value));
non_interpolable_values[i] =
std::move(converted_side.non_interpolable_value);
}
}
return InterpolationValue(
std::move(list), CSSBorderImageLengthBoxNonInterpolableValue::Create(
SideTypes(box), std::move(non_interpolable_values)));
}
} // namespace
InterpolationValue
CSSBorderImageLengthBoxInterpolationType::MaybeConvertNeutral(
const InterpolationValue& underlying,
ConversionCheckers& conversion_checkers) const {
SideTypes underlying_side_types =
UnderlyingSideTypesChecker::GetUnderlyingSideTypes(underlying);
conversion_checkers.push_back(
UnderlyingSideTypesChecker::Create(underlying_side_types));
return InterpolationValue(underlying.interpolable_value->CloneAndZero(),
ToCSSBorderImageLengthBoxNonInterpolableValue(
*underlying.non_interpolable_value)
.Clone());
}
InterpolationValue
CSSBorderImageLengthBoxInterpolationType::MaybeConvertInitial(
const StyleResolverState&,
ConversionCheckers&) const {
return ConvertBorderImageLengthBox(
GetBorderImageLengthBox(CssProperty(), ComputedStyle::InitialStyle()), 1);
}
InterpolationValue
CSSBorderImageLengthBoxInterpolationType::MaybeConvertInherit(
const StyleResolverState& state,
ConversionCheckers& conversion_checkers) const {
const BorderImageLengthBox& inherited =
GetBorderImageLengthBox(CssProperty(), *state.ParentStyle());
conversion_checkers.push_back(
InheritedSideTypesChecker::Create(CssProperty(), SideTypes(inherited)));
return ConvertBorderImageLengthBox(inherited,
state.ParentStyle()->EffectiveZoom());
}
InterpolationValue CSSBorderImageLengthBoxInterpolationType::MaybeConvertValue(
const CSSValue& value,
const StyleResolverState*,
ConversionCheckers&) const {
if (!value.IsQuadValue())
return nullptr;
const CSSQuadValue& quad = ToCSSQuadValue(value);
std::unique_ptr<InterpolableList> list =
InterpolableList::Create(kSideIndexCount);
Vector<scoped_refptr<NonInterpolableValue>> non_interpolable_values(
kSideIndexCount);
const CSSValue* sides[kSideIndexCount] = {};
sides[kSideTop] = quad.Top();
sides[kSideRight] = quad.Right();
sides[kSideBottom] = quad.Bottom();
sides[kSideLeft] = quad.Left();
for (wtf_size_t i = 0; i < kSideIndexCount; i++) {
const CSSValue& side = *sides[i];
if (side.IsPrimitiveValue() && ToCSSPrimitiveValue(side).IsNumber()) {
list->Set(i, InterpolableNumber::Create(
ToCSSPrimitiveValue(side).GetDoubleValue()));
} else if (side.IsIdentifierValue() &&
ToCSSIdentifierValue(side).GetValueID() == CSSValueAuto) {
list->Set(i, InterpolableList::Create(0));
} else {
InterpolationValue converted_side =
LengthInterpolationFunctions::MaybeConvertCSSValue(side);
if (!converted_side)
return nullptr;
list->Set(i, std::move(converted_side.interpolable_value));
non_interpolable_values[i] =
std::move(converted_side.non_interpolable_value);
}
}
return InterpolationValue(
std::move(list),
CSSBorderImageLengthBoxNonInterpolableValue::Create(
SideTypes(quad), std::move(non_interpolable_values)));
}
InterpolationValue CSSBorderImageLengthBoxInterpolationType::
MaybeConvertStandardPropertyUnderlyingValue(
const ComputedStyle& style) const {
return ConvertBorderImageLengthBox(
GetBorderImageLengthBox(CssProperty(), style), style.EffectiveZoom());
}
PairwiseInterpolationValue
CSSBorderImageLengthBoxInterpolationType::MaybeMergeSingles(
InterpolationValue&& start,
InterpolationValue&& end) const {
const SideTypes& start_side_types =
ToCSSBorderImageLengthBoxNonInterpolableValue(
*start.non_interpolable_value)
.GetSideTypes();
const SideTypes& end_side_types =
ToCSSBorderImageLengthBoxNonInterpolableValue(*end.non_interpolable_value)
.GetSideTypes();
if (start_side_types != end_side_types)
return nullptr;
return PairwiseInterpolationValue(std::move(start.interpolable_value),
std::move(end.interpolable_value),
std::move(start.non_interpolable_value));
}
void CSSBorderImageLengthBoxInterpolationType::Composite(
UnderlyingValueOwner& underlying_value_owner,
double underlying_fraction,
const InterpolationValue& value,
double interpolation_fraction) const {
const SideTypes& underlying_side_types =
ToCSSBorderImageLengthBoxNonInterpolableValue(
*underlying_value_owner.Value().non_interpolable_value)
.GetSideTypes();
const auto& non_interpolable_value =
ToCSSBorderImageLengthBoxNonInterpolableValue(
*value.non_interpolable_value);
const SideTypes& side_types = non_interpolable_value.GetSideTypes();
if (underlying_side_types != side_types) {
underlying_value_owner.Set(*this, value);
return;
}
InterpolationValue& underlying_value = underlying_value_owner.MutableValue();
InterpolableList& underlying_list =
ToInterpolableList(*underlying_value.interpolable_value);
Vector<scoped_refptr<NonInterpolableValue>>&
underlying_side_non_interpolable_values =
ToCSSBorderImageLengthBoxNonInterpolableValue(
*underlying_value.non_interpolable_value)
.SideNonInterpolableValues();
const InterpolableList& list = ToInterpolableList(*value.interpolable_value);
const Vector<scoped_refptr<NonInterpolableValue>>&
side_non_interpolable_values =
non_interpolable_value.SideNonInterpolableValues();
for (wtf_size_t i = 0; i < kSideIndexCount; i++) {
switch (side_types.type[i]) {
case SideType::kNumber:
underlying_list.GetMutable(i)->ScaleAndAdd(underlying_fraction,
*list.Get(i));
break;
case SideType::kLength:
LengthInterpolationFunctions::Composite(
underlying_list.GetMutable(i),
underlying_side_non_interpolable_values[i], underlying_fraction,
*list.Get(i), side_non_interpolable_values[i].get());
break;
case SideType::kAuto:
break;
default:
NOTREACHED();
break;
}
}
}
void CSSBorderImageLengthBoxInterpolationType::ApplyStandardPropertyValue(
const InterpolableValue& interpolable_value,
const NonInterpolableValue* non_interpolable_value,
StyleResolverState& state) const {
const SideTypes& side_types =
ToCSSBorderImageLengthBoxNonInterpolableValue(non_interpolable_value)
->GetSideTypes();
const Vector<scoped_refptr<NonInterpolableValue>>& non_interpolable_values =
ToCSSBorderImageLengthBoxNonInterpolableValue(non_interpolable_value)
->SideNonInterpolableValues();
const InterpolableList& list = ToInterpolableList(interpolable_value);
const auto& convert_side =
[&side_types, &list, &state,
&non_interpolable_values](wtf_size_t index) -> BorderImageLength {
switch (side_types.type[index]) {
case SideType::kNumber:
return clampTo<double>(ToInterpolableNumber(list.Get(index))->Value(),
0);
case SideType::kAuto:
return Length(kAuto);
case SideType::kLength:
return LengthInterpolationFunctions::CreateLength(
*list.Get(index), non_interpolable_values[index].get(),
state.CssToLengthConversionData(), kValueRangeNonNegative);
default:
NOTREACHED();
return Length(kAuto);
}
};
BorderImageLengthBox box(convert_side(kSideTop), convert_side(kSideRight),
convert_side(kSideBottom), convert_side(kSideLeft));
SetBorderImageLengthBox(CssProperty(), *state.Style(), box);
}
} // namespace blink