blob: 5d9a62dbed04339e96b00323e13c6e486cb81276 [file] [log] [blame]
// Copyright 2019 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/resolver/style_cascade.h"
#include <vector>
#include "third_party/blink/renderer/core/animation/css/css_animations.h"
#include "third_party/blink/renderer/core/css/active_style_sheets.h"
#include "third_party/blink/renderer/core/css/css_custom_property_declaration.h"
#include "third_party/blink/renderer/core/css/css_pending_interpolation_value.h"
#include "third_party/blink/renderer/core/css/css_pending_substitution_value.h"
#include "third_party/blink/renderer/core/css/css_primitive_value.h"
#include "third_party/blink/renderer/core/css/css_style_sheet_init.h"
#include "third_party/blink/renderer/core/css/css_test_helpers.h"
#include "third_party/blink/renderer/core/css/css_variable_reference_value.h"
#include "third_party/blink/renderer/core/css/document_style_environment_variables.h"
#include "third_party/blink/renderer/core/css/media_query_evaluator.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_local_context.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h"
#include "third_party/blink/renderer/core/css/parser/css_property_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
#include "third_party/blink/renderer/core/css/parser/css_variable_parser.h"
#include "third_party/blink/renderer/core/css/properties/css_property_instances.h"
#include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
#include "third_party/blink/renderer/core/css/properties/longhands/custom_property.h"
#include "third_party/blink/renderer/core/css/property_registry.h"
#include "third_party/blink/renderer/core/css/resolver/scoped_style_resolver.h"
#include "third_party/blink/renderer/core/css/resolver/style_animator.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/css/style_sheet_contents.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style_property_shorthand.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
using namespace css_test_helpers;
using namespace cssvalue;
using Origin = StyleCascade::Origin;
using Priority = StyleCascade::Priority;
using UnitType = CSSPrimitiveValue::UnitType;
enum class AnimationTainted { kYes, kNo };
class TestCascade {
STACK_ALLOCATED();
public:
TestCascade(Document& document, Element* target = nullptr)
: state_(document, target ? *target : *document.body(), nullptr),
cascade_(InitState(state_)) {}
scoped_refptr<ComputedStyle> TakeStyle() { return state_.TakeStyle(); }
StyleResolverState& State() { return state_; }
StyleCascade& InnerCascade() { return cascade_; }
void InheritFrom(scoped_refptr<ComputedStyle> parent) {
state_.SetParentStyle(parent);
state_.StyleRef().InheritFrom(*parent);
}
void Add(String name,
String value,
Priority priority = Origin::kAuthor,
AnimationTainted animation_tainted = AnimationTainted::kNo) {
return Add(*CSSPropertyName::From(name), value, priority,
animation_tainted);
}
void Add(const CSSPropertyName& name,
String value,
Priority priority = Origin::kAuthor,
AnimationTainted animation_tainted = AnimationTainted::kNo) {
HeapVector<CSSPropertyValue, 256> values =
ParseValues(name, value, animation_tainted);
for (const CSSPropertyValue& v : values)
Add(v.Name(), v.Value(), priority);
}
void Add(String name,
const CSSValue* value,
Priority priority = Origin::kAuthor) {
Add(*CSSPropertyName::From(name), value, priority);
}
void Add(const CSSPropertyName& name,
const CSSValue* value,
Priority priority = Origin::kAuthor) {
DCHECK(CSSPropertyRef(name, GetDocument()).GetProperty().IsLonghand());
cascade_.Add(name, value, priority);
}
void Apply(const CSSPropertyName& name) { cascade_.Apply(name); }
void Apply(String name) { Apply(*CSSPropertyName::From(name)); }
void Apply() { cascade_.Apply(); }
void Apply(StyleCascade::Animator& animator) { cascade_.Apply(animator); }
HeapVector<CSSPropertyValue, 256> ParseValues(
const CSSPropertyName& name,
String value,
AnimationTainted animation_tainted) {
CSSTokenizer tokenizer(value);
auto tokens = tokenizer.TokenizeToEOF();
auto* context = MakeGarbageCollected<CSSParserContext>(GetDocument());
context->SetMode(kUASheetMode); // Allows -internal variables.
HeapVector<CSSPropertyValue, 256> parsed_properties;
bool is_animation_tainted = animation_tainted == AnimationTainted::kYes;
if (name.Id() == CSSPropertyID::kVariable) {
// TODO(andruud): Make CSSPropertyParser::ParseValue handle custom props.
const CSSValue* decl = CSSVariableParser::ParseDeclarationValue(
name.ToAtomicString(), tokens, is_animation_tainted, *context);
DCHECK(decl);
parsed_properties.emplace_back(GetCSSPropertyVariable(), *decl);
return parsed_properties;
}
const bool important = false;
bool ok = CSSPropertyParser::ParseValue(name.Id(), important, tokens,
context, parsed_properties,
StyleRule::RuleType::kStyle);
DCHECK(ok);
return parsed_properties;
}
String ComputedValue(String name) const {
CSSPropertyRef ref(name, GetDocument());
DCHECK(ref.IsValid());
const LayoutObject* layout_object = nullptr;
bool allow_visited_style = false;
const CSSValue* value = ref.GetProperty().CSSValueFromComputedStyle(
*state_.Style(), layout_object, allow_visited_style);
return value ? value->CssText() : g_null_atom;
}
bool HasValue(String name, const CSSValue* value) {
return cascade_.HasValue(*CSSPropertyName::From(name), value);
}
const CSSValue* GetCSSValue(String name) {
return cascade_.GetValue(*CSSPropertyName::From(name));
}
const String GetValue(String name) {
const CSSValue* value = GetCSSValue(name);
// Per spec, CSSPendingSubstitutionValue serializes as an empty string,
// but for testing purposes it's nice to see the actual value.
if (const auto* v = DynamicTo<CSSPendingSubstitutionValue>(value))
return v->ShorthandValue()->CssText();
if (DynamicTo<CSSPendingInterpolationValue>(value))
return "<interpolation>";
return value ? value->CssText() : g_null_atom;
}
CSSAnimationUpdate& CalculateTransitionUpdate() {
CSSAnimations::CalculateTransitionUpdate(
state_.AnimationUpdate(), CSSAnimations::PropertyPass::kCustom,
&state_.GetElement(), *state_.Style());
CSSAnimations::CalculateTransitionUpdate(
state_.AnimationUpdate(), CSSAnimations::PropertyPass::kStandard,
&state_.GetElement(), *state_.Style());
return state_.AnimationUpdate();
}
CSSAnimationUpdate& CalculateAnimationUpdate() {
CSSAnimations::CalculateAnimationUpdate(
state_.AnimationUpdate(), &state_.GetElement(), state_.GetElement(),
*state_.Style(), state_.ParentStyle(),
&GetDocument().EnsureStyleResolver());
return state_.AnimationUpdate();
}
void AddAnimations() {
auto& update = CalculateAnimationUpdate();
using Type = CSSPendingInterpolationValue::Type;
for (const auto& entry : update.ActiveInterpolationsForCustomAnimations()) {
auto name = entry.key.GetCSSPropertyName();
auto* v = CSSPendingInterpolationValue::Create(Type::kCSSProperty);
Add(name.ToAtomicString(), v);
}
for (const auto& entry :
update.ActiveInterpolationsForStandardAnimations()) {
auto name = entry.key.GetCSSPropertyName();
auto* v = CSSPendingInterpolationValue::Create(Type::kCSSProperty);
Add(name.ToAtomicString(), v);
}
}
void AddTransitions() {
auto& update = CalculateTransitionUpdate();
using Type = CSSPendingInterpolationValue::Type;
for (const auto& entry :
update.ActiveInterpolationsForCustomTransitions()) {
auto name = entry.key.GetCSSPropertyName();
auto* v = CSSPendingInterpolationValue::Create(Type::kCSSProperty);
Add(name.ToAtomicString(), v);
}
for (const auto& entry :
update.ActiveInterpolationsForStandardTransitions()) {
auto name = entry.key.GetCSSPropertyName();
auto* v = CSSPendingInterpolationValue::Create(Type::kCSSProperty);
Add(name.ToAtomicString(), v);
}
}
private:
Document& GetDocument() const { return state_.GetDocument(); }
Element* Body() const { return GetDocument().body(); }
static StyleResolverState& InitState(StyleResolverState& state) {
state.SetStyle(InitialStyle(state.GetDocument()));
state.SetParentStyle(InitialStyle(state.GetDocument()));
return state;
}
static scoped_refptr<ComputedStyle> InitialStyle(Document& document) {
return StyleResolver::InitialStyleForElement(document);
}
StyleResolverState state_;
StyleCascade cascade_;
};
class TestCascadeResolver {
STACK_ALLOCATED();
public:
TestCascadeResolver(Document& document, StyleAnimator& animator)
: document_(&document), resolver_(animator) {}
bool InCycle() const { return resolver_.InCycle(); }
bool DetectCycle(String name) {
CSSPropertyRef ref(name, *document_);
DCHECK(ref.IsValid());
const CSSProperty& property = ref.GetProperty();
return resolver_.DetectCycle(property);
}
wtf_size_t CycleDepth() const { return resolver_.cycle_depth_; }
private:
friend class TestCascadeAutoLock;
Member<Document> document_;
StyleCascade::Resolver resolver_;
};
class TestCascadeAutoLock {
STACK_ALLOCATED();
public:
TestCascadeAutoLock(const CSSPropertyName& name,
TestCascadeResolver& resolver)
: lock_(name, resolver.resolver_) {}
private:
StyleCascade::AutoLock lock_;
};
class StyleCascadeTest : public PageTestBase {
public:
void SetUp() override {
RuntimeEnabledFeatures::SetCSSCascadeEnabled(true);
PageTestBase::SetUp();
}
void TearDown() override {
PageTestBase::TearDown();
RuntimeEnabledFeatures::SetCSSCascadeEnabled(false);
}
CSSStyleSheet* CreateSheet(const String& css_text) {
auto* init = MakeGarbageCollected<CSSStyleSheetInit>();
DummyExceptionStateForTesting exception_state;
CSSStyleSheet* sheet =
CSSStyleSheet::Create(GetDocument(), init, exception_state);
sheet->replaceSync(css_text, exception_state);
sheet->Contents()->EnsureRuleSet(MediaQueryEvaluator(),
kRuleHasNoSpecialState);
return sheet;
}
void AppendSheet(const String& css_text) {
CSSStyleSheet* sheet = CreateSheet(css_text);
ASSERT_TRUE(sheet);
Element* body = GetDocument().body();
ASSERT_TRUE(body->IsInTreeScope());
TreeScope& tree_scope = body->GetTreeScope();
ScopedStyleResolver& scoped_resolver =
tree_scope.EnsureScopedStyleResolver();
ActiveStyleSheetVector active_sheets;
active_sheets.push_back(
std::make_pair(sheet, &sheet->Contents()->GetRuleSet()));
scoped_resolver.AppendActiveStyleSheets(0, active_sheets);
}
Element* DocumentElement() const { return GetDocument().documentElement(); }
void SetRootFont(String value) {
DocumentElement()->SetInlineStyleProperty(CSSPropertyID::kFontSize, value);
UpdateAllLifecyclePhasesForTest();
}
Priority AuthorPriority(uint16_t tree_order, uint16_t cascade_order) {
return Priority(Origin::kAuthor, tree_order)
.WithCascadeOrder(cascade_order);
}
Priority ImportantAuthorPriority(uint16_t tree_order,
uint16_t cascade_order) {
return Priority(Origin::kImportantAuthor, tree_order)
.WithCascadeOrder(cascade_order);
}
// Temporarily create a CSS Environment Variable.
// https://drafts.csswg.org/css-env-1/
class AutoEnv {
STACK_ALLOCATED();
public:
AutoEnv(PageTestBase& test, AtomicString name, String value)
: document_(&test.GetDocument()), name_(name) {
EnsureEnvironmentVariables().SetVariable(name, value);
}
~AutoEnv() { EnsureEnvironmentVariables().RemoveVariable(name_); }
private:
DocumentStyleEnvironmentVariables& EnsureEnvironmentVariables() {
return document_->GetStyleEngine().EnsureEnvironmentVariables();
}
Member<Document> document_;
AtomicString name_;
};
};
TEST_F(StyleCascadeTest, OriginImportance) {
EXPECT_EQ(Origin::kImportantUserAgent,
Priority(Origin::kUserAgent).AddImportance().GetOrigin());
EXPECT_EQ(Origin::kImportantUser,
Priority(Origin::kUser).AddImportance().GetOrigin());
EXPECT_EQ(Origin::kImportantAuthor,
Priority(Origin::kAuthor).AddImportance().GetOrigin());
}
TEST_F(StyleCascadeTest, PriorityOrigin) {
std::vector<Priority> priorities = {
Origin::kTransition, Origin::kImportantUserAgent,
Origin::kImportantUser, Origin::kImportantAuthor,
Origin::kAnimation, Origin::kAuthor,
Origin::kUser, Origin::kUserAgent,
Origin::kNone};
for (size_t i = 0; i < priorities.size(); ++i) {
for (size_t j = i; j < priorities.size(); ++j)
EXPECT_GE(priorities[i], priorities[j]);
}
EXPECT_FALSE(Priority(Origin::kUser) >= Priority(Origin::kAuthor));
}
TEST_F(StyleCascadeTest, PriorityHasOrigin) {
EXPECT_TRUE(Priority(Origin::kTransition).HasOrigin());
EXPECT_TRUE(Priority(Origin::kAuthor).HasOrigin());
EXPECT_FALSE(Priority(Origin::kNone).HasOrigin());
}
TEST_F(StyleCascadeTest, PriorityTreeOrder) {
Origin origin = Origin::kAuthor;
EXPECT_GE(Priority(origin, 0), Priority(origin, 1));
EXPECT_GE(Priority(origin, 6), Priority(origin, 7));
EXPECT_GE(Priority(origin, 42), Priority(origin, 42));
EXPECT_FALSE(Priority(origin, 8) >= Priority(origin, 1));
}
TEST_F(StyleCascadeTest, PriorityTreeOrderImportant) {
Origin origin = Origin::kImportantAuthor;
EXPECT_GE(Priority(origin, 1), Priority(origin, 0));
EXPECT_GE(Priority(origin, 7), Priority(origin, 6));
EXPECT_GE(Priority(origin, 42), Priority(origin, 42));
EXPECT_FALSE(Priority(origin, 1) >= Priority(origin, 8));
}
TEST_F(StyleCascadeTest, PriorityTreeOrderDifferentOrigin) {
// Tree order does not matter if the origin is different.
Origin author = Origin::kAuthor;
Origin transition = Origin::kTransition;
EXPECT_GE(Priority(transition, 42), Priority(author, 1));
EXPECT_GE(Priority(transition, 1), Priority(author, 1));
}
TEST_F(StyleCascadeTest, PriorityCascadeOrder) {
// AuthorPriority(tree_order, cascade_order)
EXPECT_GE(AuthorPriority(0, 0), AuthorPriority(0, 0));
EXPECT_GE(AuthorPriority(0, 1), AuthorPriority(0, 1));
EXPECT_GE(AuthorPriority(0, 1), AuthorPriority(0, 0));
EXPECT_GE(AuthorPriority(0, 2), AuthorPriority(0, 1));
EXPECT_GE(AuthorPriority(0, 0xFFFF), AuthorPriority(0, 0xFFFE));
EXPECT_FALSE(AuthorPriority(0, 2) >= AuthorPriority(0, 3));
}
TEST_F(StyleCascadeTest, PriorityCascadeOrderAndTreeOrder) {
// AuthorPriority(tree_order, cascade_order)
EXPECT_GE(AuthorPriority(0, 0), AuthorPriority(1, 0));
EXPECT_GE(AuthorPriority(0, 1), AuthorPriority(1, 1));
EXPECT_GE(AuthorPriority(0, 1), AuthorPriority(1, 3));
EXPECT_GE(AuthorPriority(0, 2), AuthorPriority(1, 0xFFFE));
}
TEST_F(StyleCascadeTest, PriorityCascadeOrderAndOrigin) {
// [Important]AuthorPriority(tree_order, cascade_order)
EXPECT_GE(ImportantAuthorPriority(0, 0), AuthorPriority(0, 0));
EXPECT_GE(ImportantAuthorPriority(0, 1), AuthorPriority(0, 1));
EXPECT_GE(ImportantAuthorPriority(0, 1), AuthorPriority(0, 3));
EXPECT_GE(ImportantAuthorPriority(0, 2), AuthorPriority(0, 0xFFFE));
}
TEST_F(StyleCascadeTest, ApplySingle) {
TestCascade cascade(GetDocument());
cascade.Add("width", "2px", Origin::kAuthor);
cascade.Add("width", "1px", Origin::kUser);
cascade.Apply("width");
EXPECT_EQ("2px", cascade.ComputedValue("width"));
}
TEST_F(StyleCascadeTest, ApplyCustomProperty) {
TestCascade cascade(GetDocument());
cascade.Add("--x", " 10px ");
cascade.Add("--y", "nope");
cascade.Apply("--x");
cascade.Apply("--y");
EXPECT_EQ(" 10px ", cascade.ComputedValue("--x"));
EXPECT_EQ("nope", cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, Copy) {
StyleResolverState state(GetDocument(), *GetDocument().body(), nullptr);
TestCascade cascade(GetDocument());
cascade.Add("--x", "10px");
cascade.Add("width", "20px");
// Take snapshot of the cascade, pointing to the same StyleResolverState.
StyleCascade snapshot(cascade.InnerCascade());
cascade.Add("--x", "0px");
cascade.Add("width", "1px");
cascade.Apply();
EXPECT_EQ("0px", cascade.ComputedValue("--x"));
EXPECT_EQ("1px", cascade.ComputedValue("width"));
snapshot.Apply();
EXPECT_EQ("10px", cascade.ComputedValue("--x"));
EXPECT_EQ("20px", cascade.ComputedValue("width"));
}
TEST_F(StyleCascadeTest, ApplyCustomPropertyVar) {
// Apply --x first.
{
TestCascade cascade(GetDocument());
cascade.Add("--x", "yes and var(--y)");
cascade.Add("--y", "no");
cascade.Apply("--x");
cascade.Apply("--y");
EXPECT_EQ("yes and no", cascade.ComputedValue("--x"));
EXPECT_EQ("no", cascade.ComputedValue("--y"));
}
// Apply --y first.
{
TestCascade cascade(GetDocument());
cascade.Add("--x", "yes and var(--y)");
cascade.Add("--y", "no");
cascade.Apply("--y");
cascade.Apply("--x");
EXPECT_EQ("yes and no", cascade.ComputedValue("--x"));
EXPECT_EQ("no", cascade.ComputedValue("--y"));
}
}
TEST_F(StyleCascadeTest, InvalidVarReferenceCauseInvalidVariable) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "nope var(--y)");
cascade.Apply("--x");
EXPECT_EQ(g_null_atom, cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, ApplyCustomPropertyFallback) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "yes and var(--y,no)");
cascade.Apply("--x");
EXPECT_EQ("yes and no", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, RegisteredPropertyFallback) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("--x", "var(--y,10px)");
cascade.Apply();
EXPECT_EQ("10px", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, RegisteredPropertyFallbackValidation) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("--x", "10px");
cascade.Add("--y", "var(--x,red)"); // Fallback must be valid <length>.
cascade.Add("--z", "var(--y,pass)");
cascade.Apply();
EXPECT_EQ("pass", cascade.ComputedValue("--z"));
}
TEST_F(StyleCascadeTest, VarInFallback) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "one var(--z,two var(--y))");
cascade.Add("--y", "three");
cascade.Apply("--x");
EXPECT_EQ("one two three", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, VarReferenceInNormalProperty) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "10px");
cascade.Add("width", "var(--x)");
cascade.Apply("width");
EXPECT_EQ("10px", cascade.ComputedValue("width"));
}
TEST_F(StyleCascadeTest, MultipleVarRefs) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "var(--y) bar var(--y)");
cascade.Add("--y", "foo");
cascade.Apply("--x");
EXPECT_EQ("foo bar foo", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, RegisteredPropertyComputedValue) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("--x", "1in");
cascade.Apply("--x");
EXPECT_EQ("96px", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, RegisteredPropertySyntaxErrorCausesInitial) {
RegisterProperty(GetDocument(), "--x", "<length>", "10px", false);
TestCascade cascade(GetDocument());
cascade.Add("--x", "#fefefe");
cascade.Add("--y", "var(--x)");
cascade.Apply("--x");
cascade.Apply("--y");
EXPECT_EQ("10px", cascade.ComputedValue("--x"));
EXPECT_EQ("10px", cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, RegisteredPropertySubstitution) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("--x", "1in");
cascade.Add("--y", "var(--x)");
cascade.Apply("--y");
EXPECT_EQ("96px", cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, RegisteredPropertyChain) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
RegisterProperty(GetDocument(), "--z", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("--x", "1in");
cascade.Add("--y", "var(--x)");
cascade.Add("--z", "calc(var(--y) + 1in)");
cascade.Apply();
EXPECT_EQ("96px", cascade.ComputedValue("--x"));
EXPECT_EQ("96px", cascade.ComputedValue("--y"));
EXPECT_EQ("192px", cascade.ComputedValue("--z"));
}
TEST_F(StyleCascadeTest, BasicShorthand) {
TestCascade cascade(GetDocument());
cascade.Add("margin", "1px 2px 3px 4px");
cascade.Apply();
EXPECT_EQ("1px", cascade.ComputedValue("margin-top"));
EXPECT_EQ("2px", cascade.ComputedValue("margin-right"));
EXPECT_EQ("3px", cascade.ComputedValue("margin-bottom"));
EXPECT_EQ("4px", cascade.ComputedValue("margin-left"));
}
TEST_F(StyleCascadeTest, BasicVarShorthand) {
TestCascade cascade(GetDocument());
cascade.Add("margin", "1px var(--x) 3px 4px");
cascade.Add("--x", "2px");
cascade.Apply();
EXPECT_EQ("1px", cascade.ComputedValue("margin-top"));
EXPECT_EQ("2px", cascade.ComputedValue("margin-right"));
EXPECT_EQ("3px", cascade.ComputedValue("margin-bottom"));
EXPECT_EQ("4px", cascade.ComputedValue("margin-left"));
}
TEST_F(StyleCascadeTest, ApplyingPendingSubstitutionFirst) {
TestCascade cascade(GetDocument());
cascade.Add("margin", "1px var(--x) 3px 4px");
cascade.Add("--x", "2px");
cascade.Add("margin-right", "5px");
// Apply one of the pending substitution values first. This should not
// overwrite margin-right's 5px.
cascade.Apply("margin-left");
cascade.Apply();
EXPECT_EQ("1px", cascade.ComputedValue("margin-top"));
EXPECT_EQ("5px", cascade.ComputedValue("margin-right"));
EXPECT_EQ("3px", cascade.ComputedValue("margin-bottom"));
EXPECT_EQ("4px", cascade.ComputedValue("margin-left"));
}
TEST_F(StyleCascadeTest, ApplyingPendingSubstitutionLast) {
TestCascade cascade(GetDocument());
cascade.Add("margin", "1px var(--x) 3px 4px");
cascade.Add("--x", "2px");
cascade.Add("margin-right", "5px");
// Apply margin-right before the others. Applying the pending substitution
// afterwards should not overwrite margin-right's 5px.
cascade.Apply("margin-right");
cascade.Apply();
EXPECT_EQ("1px", cascade.ComputedValue("margin-top"));
EXPECT_EQ("5px", cascade.ComputedValue("margin-right"));
EXPECT_EQ("3px", cascade.ComputedValue("margin-bottom"));
EXPECT_EQ("4px", cascade.ComputedValue("margin-left"));
}
TEST_F(StyleCascadeTest, ApplyingPendingSubstitutionModifiesCascade) {
TestCascade cascade(GetDocument());
cascade.Add("margin", "1px var(--x) 3px 4px");
cascade.Add("--x", "2px");
cascade.Add("margin-right", "5px");
// We expect the pending substitution value for all the shorthands,
// except margin-right.
EXPECT_EQ("1px var(--x) 3px 4px", cascade.GetValue("margin-top"));
EXPECT_EQ("5px", cascade.GetValue("margin-right"));
EXPECT_EQ("1px var(--x) 3px 4px", cascade.GetValue("margin-bottom"));
EXPECT_EQ("1px var(--x) 3px 4px", cascade.GetValue("margin-left"));
// Apply a pending substitution value should modify the cascade for other
// longhands with the same pending substitution value.
cascade.Apply("margin-left");
EXPECT_EQ("1px", cascade.GetValue("margin-top"));
EXPECT_EQ("5px", cascade.GetValue("margin-right"));
EXPECT_EQ("3px", cascade.GetValue("margin-bottom"));
EXPECT_FALSE(cascade.GetValue("margin-left"));
}
TEST_F(StyleCascadeTest, ResolverDetectCycle) {
TestCascade cascade(GetDocument());
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
TestCascadeResolver resolver(GetDocument(), animator);
{
TestCascadeAutoLock lock(CSSPropertyName("--a"), resolver);
EXPECT_FALSE(resolver.InCycle());
{
TestCascadeAutoLock lock(CSSPropertyName("--b"), resolver);
EXPECT_FALSE(resolver.InCycle());
{
TestCascadeAutoLock lock(CSSPropertyName("--c"), resolver);
EXPECT_FALSE(resolver.InCycle());
EXPECT_TRUE(resolver.DetectCycle("--a"));
EXPECT_TRUE(resolver.InCycle());
}
EXPECT_TRUE(resolver.InCycle());
}
EXPECT_TRUE(resolver.InCycle());
}
EXPECT_FALSE(resolver.InCycle());
}
TEST_F(StyleCascadeTest, ResolverDetectNoCycle) {
TestCascade cascade(GetDocument());
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
TestCascadeResolver resolver(GetDocument(), animator);
{
TestCascadeAutoLock lock(CSSPropertyName("--a"), resolver);
EXPECT_FALSE(resolver.InCycle());
{
TestCascadeAutoLock lock(CSSPropertyName("--b"), resolver);
EXPECT_FALSE(resolver.InCycle());
{
TestCascadeAutoLock lock(CSSPropertyName("--c"), resolver);
EXPECT_FALSE(resolver.InCycle());
EXPECT_FALSE(resolver.DetectCycle("--x"));
EXPECT_FALSE(resolver.InCycle());
}
EXPECT_FALSE(resolver.InCycle());
}
EXPECT_FALSE(resolver.InCycle());
}
EXPECT_FALSE(resolver.InCycle());
}
TEST_F(StyleCascadeTest, ResolverDetectCycleSelf) {
TestCascade cascade(GetDocument());
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
TestCascadeResolver resolver(GetDocument(), animator);
{
TestCascadeAutoLock lock(CSSPropertyName("--a"), resolver);
EXPECT_FALSE(resolver.InCycle());
EXPECT_TRUE(resolver.DetectCycle("--a"));
EXPECT_TRUE(resolver.InCycle());
}
EXPECT_FALSE(resolver.InCycle());
}
TEST_F(StyleCascadeTest, ResolverDetectMultiCycle) {
using AutoLock = TestCascadeAutoLock;
TestCascade cascade(GetDocument());
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
TestCascadeResolver resolver(GetDocument(), animator);
{
AutoLock lock(CSSPropertyName("--a"), resolver);
EXPECT_FALSE(resolver.InCycle());
{
AutoLock lock(CSSPropertyName("--b"), resolver);
EXPECT_FALSE(resolver.InCycle());
{
AutoLock lock(CSSPropertyName("--c"), resolver);
EXPECT_FALSE(resolver.InCycle());
{
AutoLock lock(CSSPropertyName("--d"), resolver);
EXPECT_FALSE(resolver.InCycle());
// Cycle 1 (big cycle):
EXPECT_TRUE(resolver.DetectCycle("--b"));
EXPECT_TRUE(resolver.InCycle());
EXPECT_EQ(1u, resolver.CycleDepth());
// Cycle 2 (small cycle):
EXPECT_TRUE(resolver.DetectCycle("--c"));
EXPECT_TRUE(resolver.InCycle());
EXPECT_EQ(1u, resolver.CycleDepth());
}
}
EXPECT_TRUE(resolver.InCycle());
}
EXPECT_FALSE(resolver.InCycle());
}
EXPECT_FALSE(resolver.InCycle());
}
TEST_F(StyleCascadeTest, ResolverDetectMultiCycleReverse) {
using AutoLock = TestCascadeAutoLock;
TestCascade cascade(GetDocument());
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
TestCascadeResolver resolver(GetDocument(), animator);
{
AutoLock lock(CSSPropertyName("--a"), resolver);
EXPECT_FALSE(resolver.InCycle());
{
AutoLock lock(CSSPropertyName("--b"), resolver);
EXPECT_FALSE(resolver.InCycle());
{
AutoLock lock(CSSPropertyName("--c"), resolver);
EXPECT_FALSE(resolver.InCycle());
{
AutoLock lock(CSSPropertyName("--d"), resolver);
EXPECT_FALSE(resolver.InCycle());
// Cycle 1 (small cycle):
EXPECT_TRUE(resolver.DetectCycle("--c"));
EXPECT_TRUE(resolver.InCycle());
EXPECT_EQ(2u, resolver.CycleDepth());
// Cycle 2 (big cycle):
EXPECT_TRUE(resolver.DetectCycle("--b"));
EXPECT_TRUE(resolver.InCycle());
EXPECT_EQ(1u, resolver.CycleDepth());
}
}
EXPECT_TRUE(resolver.InCycle());
}
EXPECT_FALSE(resolver.InCycle());
}
EXPECT_FALSE(resolver.InCycle());
}
TEST_F(StyleCascadeTest, BasicCycle) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "foo");
cascade.Add("--b", "bar");
cascade.Apply();
EXPECT_EQ("foo", cascade.ComputedValue("--a"));
EXPECT_EQ("bar", cascade.ComputedValue("--b"));
cascade.Add("--a", "var(--b)");
cascade.Add("--b", "var(--a)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
}
TEST_F(StyleCascadeTest, SelfCycle) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "foo");
cascade.Apply();
EXPECT_EQ("foo", cascade.ComputedValue("--a"));
cascade.Add("--a", "var(--a)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
}
TEST_F(StyleCascadeTest, SelfCycleInFallback) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "var(--x, var(--a))");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
}
TEST_F(StyleCascadeTest, SelfCycleInUnusedFallback) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "var(--b, var(--a))");
cascade.Add("--b", "10px");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_EQ("10px", cascade.ComputedValue("--b"));
}
TEST_F(StyleCascadeTest, LongCycle) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "var(--b)");
cascade.Add("--b", "var(--c)");
cascade.Add("--c", "var(--d)");
cascade.Add("--d", "var(--e)");
cascade.Add("--e", "var(--a)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_FALSE(cascade.ComputedValue("--c"));
EXPECT_FALSE(cascade.ComputedValue("--d"));
EXPECT_FALSE(cascade.ComputedValue("--e"));
}
TEST_F(StyleCascadeTest, PartialCycle) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "var(--b)");
cascade.Add("--b", "var(--a)");
cascade.Add("--c", "bar var(--d) var(--a)");
cascade.Add("--d", "foo");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_FALSE(cascade.ComputedValue("--c"));
EXPECT_EQ("foo", cascade.ComputedValue("--d"));
}
TEST_F(StyleCascadeTest, VarCycleViaFallback) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "var(--b)");
cascade.Add("--b", "var(--x, var(--a))");
cascade.Add("--c", "var(--a)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_FALSE(cascade.ComputedValue("--c"));
}
TEST_F(StyleCascadeTest, FallbackTriggeredByCycle) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "var(--b)");
cascade.Add("--b", "var(--a)");
cascade.Add("--c", "var(--a,foo)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_EQ("foo", cascade.ComputedValue("--c"));
}
TEST_F(StyleCascadeTest, RegisteredCycle) {
RegisterProperty(GetDocument(), "--a", "<length>", "0px", false);
RegisterProperty(GetDocument(), "--b", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("--a", "var(--b)");
cascade.Add("--b", "var(--a)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
}
TEST_F(StyleCascadeTest, PartiallyRegisteredCycle) {
RegisterProperty(GetDocument(), "--a", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("--a", "var(--b)");
cascade.Add("--b", "var(--a)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
}
TEST_F(StyleCascadeTest, FallbackTriggeredByRegisteredCycle) {
RegisterProperty(GetDocument(), "--a", "<length>", "0px", false);
RegisterProperty(GetDocument(), "--b", "<length>", "0px", false);
TestCascade cascade(GetDocument());
// Cycle:
cascade.Add("--a", "var(--b)");
cascade.Add("--b", "var(--a)");
// References to cycle:
cascade.Add("--c", "var(--a,1px)");
cascade.Add("--d", "var(--b,2px)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_EQ("1px", cascade.ComputedValue("--c"));
EXPECT_EQ("2px", cascade.ComputedValue("--d"));
}
TEST_F(StyleCascadeTest, CycleStillInvalidWithFallback) {
TestCascade cascade(GetDocument());
// Cycle:
cascade.Add("--a", "var(--b,red)");
cascade.Add("--b", "var(--a,red)");
// References to cycle:
cascade.Add("--c", "var(--a,green)");
cascade.Add("--d", "var(--b,green)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_EQ("green", cascade.ComputedValue("--c"));
EXPECT_EQ("green", cascade.ComputedValue("--d"));
}
TEST_F(StyleCascadeTest, CycleInFallbackStillInvalid) {
TestCascade cascade(GetDocument());
// Cycle:
cascade.Add("--a", "var(--b,red)");
cascade.Add("--b", "var(--x,var(--a))");
// References to cycle:
cascade.Add("--c", "var(--a,green)");
cascade.Add("--d", "var(--b,green)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_EQ("green", cascade.ComputedValue("--c"));
EXPECT_EQ("green", cascade.ComputedValue("--d"));
}
TEST_F(StyleCascadeTest, CycleMultiple) {
TestCascade cascade(GetDocument());
// Cycle:
cascade.Add("--a", "var(--c, red)");
cascade.Add("--b", "var(--c, red)");
cascade.Add("--c", "var(--a, blue) var(--b, blue)");
// References to cycle:
cascade.Add("--d", "var(--a,green)");
cascade.Add("--e", "var(--b,green)");
cascade.Add("--f", "var(--c,green)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_FALSE(cascade.ComputedValue("--c"));
EXPECT_EQ("green", cascade.ComputedValue("--d"));
EXPECT_EQ("green", cascade.ComputedValue("--e"));
EXPECT_EQ("green", cascade.ComputedValue("--f"));
}
TEST_F(StyleCascadeTest, CycleMultipleFallback) {
TestCascade cascade(GetDocument());
// Cycle:
cascade.Add("--a", "var(--b, red)");
cascade.Add("--b", "var(--a, var(--c, red))");
cascade.Add("--c", "var(--b, red)");
// References to cycle:
cascade.Add("--d", "var(--a,green)");
cascade.Add("--e", "var(--b,green)");
cascade.Add("--f", "var(--c,green)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_FALSE(cascade.ComputedValue("--c"));
EXPECT_EQ("green", cascade.ComputedValue("--d"));
EXPECT_EQ("green", cascade.ComputedValue("--e"));
EXPECT_EQ("green", cascade.ComputedValue("--f"));
}
TEST_F(StyleCascadeTest, CycleMultipleUnusedFallback) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "red");
// Cycle:
cascade.Add("--b", "var(--c, red)");
cascade.Add("--c", "var(--a, var(--b, red) var(--d, red))");
cascade.Add("--d", "var(--c, red)");
// References to cycle:
cascade.Add("--e", "var(--b,green)");
cascade.Add("--f", "var(--c,green)");
cascade.Add("--g", "var(--d,green)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_FALSE(cascade.ComputedValue("--c"));
EXPECT_FALSE(cascade.ComputedValue("--d"));
EXPECT_EQ("green", cascade.ComputedValue("--e"));
EXPECT_EQ("green", cascade.ComputedValue("--f"));
EXPECT_EQ("green", cascade.ComputedValue("--g"));
}
TEST_F(StyleCascadeTest, CycleReferencedFromStandardProperty) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "var(--b)");
cascade.Add("--b", "var(--a)");
cascade.Add("color", "var(--a,green)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_EQ("rgb(0, 128, 0)", cascade.ComputedValue("color"));
}
TEST_F(StyleCascadeTest, CycleReferencedFromShorthand) {
TestCascade cascade(GetDocument());
cascade.Add("--a", "var(--b)");
cascade.Add("--b", "var(--a)");
cascade.Add("background", "var(--a,green)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--a"));
EXPECT_FALSE(cascade.ComputedValue("--b"));
EXPECT_EQ("rgb(0, 128, 0)", cascade.ComputedValue("background-color"));
}
TEST_F(StyleCascadeTest, EmUnit) {
TestCascade cascade(GetDocument());
cascade.Add("font-size", "10px");
cascade.Add("width", "10em");
cascade.Apply();
EXPECT_EQ("100px", cascade.ComputedValue("width"));
}
TEST_F(StyleCascadeTest, EmUnitCustomProperty) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("font-size", "10px");
cascade.Add("--x", "10em");
cascade.Apply();
EXPECT_EQ("100px", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, EmUnitNonCycle) {
TestCascade parent(GetDocument());
parent.Add("font-size", "10px");
parent.Apply();
TestCascade cascade(GetDocument());
cascade.InheritFrom(parent.TakeStyle());
cascade.Add("font-size", "var(--x)");
cascade.Add("--x", "10em");
cascade.Apply();
// Note: Only registered properties can have cycles with font-size.
EXPECT_EQ("100px", cascade.ComputedValue("font-size"));
}
TEST_F(StyleCascadeTest, EmUnitCycle) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("font-size", "var(--x)");
cascade.Add("--x", "10em");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, SubstitutingEmCycles) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("font-size", "var(--x)");
cascade.Add("--x", "10em");
cascade.Add("--y", "var(--x)");
cascade.Add("--z", "var(--x,1px)");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--y"));
EXPECT_EQ("1px", cascade.ComputedValue("--z"));
}
TEST_F(StyleCascadeTest, RemUnit) {
SetRootFont("10px");
UpdateAllLifecyclePhasesForTest();
TestCascade cascade(GetDocument());
cascade.Add("width", "10rem");
cascade.Apply();
EXPECT_EQ("100px", cascade.ComputedValue("width"));
}
TEST_F(StyleCascadeTest, RemUnitCustomProperty) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
SetRootFont("10px");
UpdateAllLifecyclePhasesForTest();
TestCascade cascade(GetDocument());
cascade.Add("--x", "10rem");
cascade.Apply();
EXPECT_EQ("100px", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, RemUnitInFontSize) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
SetRootFont("10px");
UpdateAllLifecyclePhasesForTest();
TestCascade cascade(GetDocument());
cascade.Add("font-size", "1rem");
cascade.Add("--x", "10rem");
cascade.Apply();
EXPECT_EQ("100px", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, RemUnitInRootFontSizeCycle) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade(GetDocument(), DocumentElement());
cascade.Add("font-size", "var(--x)");
cascade.Add("--x", "1rem");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, RemUnitInRootFontSizeNonCycle) {
TestCascade cascade(GetDocument(), DocumentElement());
cascade.Add("font-size", "initial");
cascade.Apply();
String expected = cascade.ComputedValue("font-size");
cascade.Add("font-size", "var(--x)");
cascade.Add("--x", "1rem");
cascade.Apply();
// Note: Only registered properties can have cycles with font-size.
EXPECT_EQ("1rem", cascade.ComputedValue("--x"));
EXPECT_EQ(expected, cascade.ComputedValue("font-size"));
}
TEST_F(StyleCascadeTest, Initial) {
TestCascade parent(GetDocument());
parent.Add("--x", "foo");
parent.Apply();
TestCascade cascade(GetDocument());
cascade.InheritFrom(parent.TakeStyle());
cascade.Add("--y", "foo");
cascade.Apply();
EXPECT_EQ("foo", cascade.ComputedValue("--x"));
EXPECT_EQ("foo", cascade.ComputedValue("--y"));
cascade.Add("--x", "initial");
cascade.Add("--y", "initial");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--x"));
EXPECT_FALSE(cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, Inherit) {
TestCascade parent(GetDocument());
parent.Add("--x", "foo");
parent.Apply();
TestCascade cascade(GetDocument());
cascade.InheritFrom(parent.TakeStyle());
EXPECT_EQ("foo", cascade.ComputedValue("--x"));
cascade.Add("--x", "bar");
cascade.Apply();
EXPECT_EQ("bar", cascade.ComputedValue("--x"));
cascade.Add("--x", "inherit");
cascade.Apply();
EXPECT_EQ("foo", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, Unset) {
TestCascade parent(GetDocument());
parent.Add("--x", "foo");
parent.Apply();
TestCascade cascade(GetDocument());
cascade.InheritFrom(parent.TakeStyle());
EXPECT_EQ("foo", cascade.ComputedValue("--x"));
cascade.Add("--x", "bar");
cascade.Apply();
EXPECT_EQ("bar", cascade.ComputedValue("--x"));
cascade.Add("--x", "unset");
cascade.Apply();
EXPECT_EQ("foo", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, RegisteredInitial) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Apply();
EXPECT_EQ("0px", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, SubstituteRegisteredImplicitInitialValue) {
RegisterProperty(GetDocument(), "--x", "<length>", "13px", false);
TestCascade cascade(GetDocument());
cascade.Add("--y", " var(--x) ");
cascade.Apply();
EXPECT_EQ("13px", cascade.ComputedValue("--x"));
EXPECT_EQ(" 13px ", cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, SubstituteRegisteredUniversal) {
RegisterProperty(GetDocument(), "--x", "*", "foo", false);
TestCascade cascade(GetDocument());
cascade.Add("--x", "bar");
cascade.Add("--y", "var(--x)");
cascade.Apply();
EXPECT_EQ("bar", cascade.ComputedValue("--x"));
EXPECT_EQ("bar", cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, SubstituteRegisteredUniversalInvalid) {
RegisterProperty(GetDocument(), "--x", "*", g_null_atom, false);
TestCascade cascade(GetDocument());
cascade.Add("--y", " var(--x) ");
cascade.Apply();
EXPECT_FALSE(cascade.ComputedValue("--x"));
EXPECT_FALSE(cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, SubstituteRegisteredUniversalInitial) {
RegisterProperty(GetDocument(), "--x", "*", "foo", false);
TestCascade cascade(GetDocument());
cascade.Add("--y", " var(--x) ");
cascade.Apply();
EXPECT_EQ("foo", cascade.ComputedValue("--x"));
EXPECT_EQ(" foo ", cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, RegisteredExplicitInitial) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("--x", "10px");
cascade.Apply();
EXPECT_EQ("10px", cascade.ComputedValue("--x"));
cascade.Add("--x", "initial");
cascade.Add("--y", "var(--x)");
cascade.Apply();
EXPECT_EQ("0px", cascade.ComputedValue("--x"));
EXPECT_EQ("0px", cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, RegisteredExplicitInherit) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade parent(GetDocument());
parent.Add("--x", "15px");
parent.Apply();
EXPECT_EQ("15px", parent.ComputedValue("--x"));
TestCascade cascade(GetDocument());
cascade.InheritFrom(parent.TakeStyle());
cascade.Apply();
EXPECT_EQ("0px", cascade.ComputedValue("--x")); // Note: inherit==false
cascade.Add("--x", "inherit");
cascade.Add("--y", "var(--x)");
cascade.Apply();
EXPECT_EQ("15px", cascade.ComputedValue("--x"));
EXPECT_EQ("15px", cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, RegisteredExplicitUnset) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
RegisterProperty(GetDocument(), "--y", "<length>", "0px", true);
TestCascade parent(GetDocument());
parent.Add("--x", "15px");
parent.Add("--y", "15px");
parent.Apply();
EXPECT_EQ("15px", parent.ComputedValue("--x"));
EXPECT_EQ("15px", parent.ComputedValue("--y"));
TestCascade cascade(GetDocument());
cascade.InheritFrom(parent.TakeStyle());
cascade.Add("--x", "2px");
cascade.Add("--y", "2px");
cascade.Apply();
EXPECT_EQ("2px", cascade.ComputedValue("--x"));
EXPECT_EQ("2px", cascade.ComputedValue("--y"));
cascade.Add("--x", "unset");
cascade.Add("--y", "unset");
cascade.Add("--z", "var(--x) var(--y)");
cascade.Apply();
EXPECT_EQ("0px", cascade.ComputedValue("--x"));
EXPECT_EQ("15px", cascade.ComputedValue("--y"));
EXPECT_EQ("0px 15px", cascade.ComputedValue("--z"));
}
TEST_F(StyleCascadeTest, SubstituteAnimationTaintedInCustomProperty) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "15px", Origin::kAuthor, AnimationTainted::kYes);
cascade.Add("--y", "var(--x)");
cascade.Apply();
EXPECT_EQ("15px", cascade.ComputedValue("--x"));
EXPECT_EQ("15px", cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, SubstituteAnimationTaintedInStandardProperty) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "15px", Origin::kAuthor, AnimationTainted::kYes);
cascade.Add("width", "var(--x)");
cascade.Apply();
EXPECT_EQ("15px", cascade.ComputedValue("--x"));
EXPECT_EQ("15px", cascade.ComputedValue("width"));
}
TEST_F(StyleCascadeTest, SubstituteAnimationTaintedInAnimationProperty) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "20s");
cascade.Add("animation-duration", "var(--x)");
cascade.Apply();
EXPECT_EQ("20s", cascade.ComputedValue("--x"));
EXPECT_EQ("20s", cascade.ComputedValue("animation-duration"));
cascade.Add("--y", "20s", Origin::kAuthor, AnimationTainted::kYes);
cascade.Add("animation-duration", "var(--y)");
cascade.Apply();
EXPECT_EQ("20s", cascade.ComputedValue("--y"));
EXPECT_EQ("0s", cascade.ComputedValue("animation-duration"));
}
TEST_F(StyleCascadeTest, IndirectlyAnimationTainted) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "20s", Origin::kAuthor, AnimationTainted::kYes);
cascade.Add("--y", "var(--x)");
cascade.Add("animation-duration", "var(--y)");
cascade.Apply();
EXPECT_EQ("20s", cascade.ComputedValue("--x"));
EXPECT_EQ("20s", cascade.ComputedValue("--y"));
EXPECT_EQ("0s", cascade.ComputedValue("animation-duration"));
}
TEST_F(StyleCascadeTest, AnimationTaintedFallback) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "20s", Origin::kAuthor, AnimationTainted::kYes);
cascade.Add("animation-duration", "var(--x,1s)");
cascade.Apply();
EXPECT_EQ("20s", cascade.ComputedValue("--x"));
EXPECT_EQ("1s", cascade.ComputedValue("animation-duration"));
}
TEST_F(StyleCascadeTest, EnvMissingNestedVar) {
TestCascade cascade(GetDocument());
cascade.Add("--x", "rgb(0, 0, 0)");
cascade.Add("background-color", "env(missing, var(--x))");
cascade.Apply();
EXPECT_EQ("rgb(0, 0, 0)", cascade.ComputedValue("--x"));
EXPECT_EQ("rgb(0, 0, 0)", cascade.ComputedValue("background-color"));
}
TEST_F(StyleCascadeTest, EnvMissingNestedVarFallback) {
TestCascade cascade(GetDocument());
cascade.Add("background-color", "env(missing, var(--missing, blue))");
cascade.Apply();
EXPECT_EQ("rgb(0, 0, 255)", cascade.ComputedValue("background-color"));
}
TEST_F(StyleCascadeTest, EnvMissingFallback) {
TestCascade cascade(GetDocument());
cascade.Add("background-color", "env(missing, blue)");
cascade.Apply();
EXPECT_EQ("rgb(0, 0, 255)", cascade.ComputedValue("background-color"));
}
TEST_F(StyleCascadeTest, ValidEnv) {
AutoEnv env(*this, "test", "red");
TestCascade cascade(GetDocument());
cascade.Add("background-color", "env(test, blue)");
cascade.Apply();
EXPECT_EQ("rgb(255, 0, 0)", cascade.ComputedValue("background-color"));
}
TEST_F(StyleCascadeTest, ValidEnvFallback) {
AutoEnv env(*this, "test", "red");
TestCascade cascade(GetDocument());
cascade.Add("background-color", "env(test, blue)");
cascade.Apply();
EXPECT_EQ("rgb(255, 0, 0)", cascade.ComputedValue("background-color"));
}
TEST_F(StyleCascadeTest, ValidEnvInUnusedFallback) {
AutoEnv env(*this, "test", "red");
TestCascade cascade(GetDocument());
cascade.Add("--x", "rgb(0, 0, 0)");
cascade.Add("background-color", "var(--x, env(test))");
cascade.Apply();
EXPECT_EQ("rgb(0, 0, 0)", cascade.ComputedValue("--x"));
EXPECT_EQ("rgb(0, 0, 0)", cascade.ComputedValue("background-color"));
}
TEST_F(StyleCascadeTest, ValidEnvInUsedFallback) {
AutoEnv env(*this, "test", "red");
TestCascade cascade(GetDocument());
cascade.Add("background-color", "var(--missing, env(test))");
cascade.Apply();
EXPECT_EQ("rgb(255, 0, 0)", cascade.ComputedValue("background-color"));
}
// An Animator that just records the name of all the properties
// applied.
class RecordingAnimator : public StyleCascade::Animator {
public:
void Apply(const CSSProperty& property,
const cssvalue::CSSPendingInterpolationValue&,
StyleCascade::Resolver& resolver) override {
record.push_back(property.GetCSSPropertyName());
}
Vector<CSSPropertyName> record;
};
TEST_F(StyleCascadeTest, AnimatorCalledByPendingInterpolationValue) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
RecordingAnimator animator;
using Type = CSSPendingInterpolationValue::Type;
TestCascade cascade(GetDocument());
cascade.Add("--x", CSSPendingInterpolationValue::Create(Type::kCSSProperty));
cascade.Add("--y", CSSPendingInterpolationValue::Create(Type::kCSSProperty));
cascade.Apply(animator);
EXPECT_TRUE(animator.record.Contains(*CSSPropertyName::From("--x")));
EXPECT_TRUE(animator.record.Contains(*CSSPropertyName::From("--y")));
}
TEST_F(StyleCascadeTest, PendingKeyframeAnimation) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
AppendSheet(R"HTML(
@keyframes test {
from { --x: 10px; }
to { --x: 20px; }
}
)HTML");
TestCascade cascade(GetDocument());
cascade.Add("animation-name", "test");
cascade.Add("animation-duration", "1s");
cascade.Apply();
cascade.AddAnimations();
EXPECT_EQ("<interpolation>", cascade.GetValue("--x"));
}
TEST_F(StyleCascadeTest, PendingKeyframeAnimationApply) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
AppendSheet(R"HTML(
@keyframes test {
from { --x: 10px; }
to { --x: 20px; }
}
)HTML");
TestCascade cascade(GetDocument());
cascade.Add("animation-name", "test");
cascade.Add("animation-duration", "10s");
cascade.Add("animation-timing-function", "linear");
cascade.Add("animation-delay", "-5s");
cascade.Apply();
cascade.AddAnimations();
EXPECT_EQ("<interpolation>", cascade.GetValue("--x"));
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
cascade.Apply(animator);
EXPECT_EQ("15px", cascade.ComputedValue("--x"));
}
TEST_F(StyleCascadeTest, TransitionCausesInterpolationValue) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
// First, simulate an "old style".
TestCascade cascade1(GetDocument());
cascade1.Add("--x", "10px");
cascade1.Add("transition", "--x 1s");
cascade1.Apply();
// Set the old style on the element, so that the animation
// update detects it.
GetDocument().body()->SetComputedStyle(cascade1.TakeStyle());
// Now simulate a new style, with a new value for --x.
TestCascade cascade2(GetDocument());
cascade2.Add("--x", "20px");
cascade2.Add("transition", "--x 1s");
cascade2.Apply();
// Detects transitions, and adds CSSPendingInterpolationValues
// to the cascade, as appropriate.
cascade2.AddTransitions();
EXPECT_EQ("<interpolation>", cascade2.GetValue("--x"));
}
TEST_F(StyleCascadeTest, TransitionDetectedForChangedFontSize) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
TestCascade cascade1(GetDocument());
cascade1.Add("font-size", "10px");
cascade1.Add("--x", "10em");
cascade1.Add("width", "10em");
cascade1.Add("height", "10px");
cascade1.Add("transition", "--x 1s, width 1s");
cascade1.Apply();
GetDocument().body()->SetComputedStyle(cascade1.TakeStyle());
TestCascade cascade2(GetDocument());
cascade2.Add("font-size", "20px");
cascade2.Add("--x", "10em");
cascade2.Add("width", "10em");
cascade2.Add("height", "10px");
cascade2.Add("transition", "--x 1s, width 1s");
cascade2.Apply();
cascade2.AddTransitions();
EXPECT_EQ("<interpolation>", cascade2.GetValue("--x"));
EXPECT_EQ("<interpolation>", cascade2.GetValue("width"));
EXPECT_EQ("10px", cascade2.ComputedValue("height"));
}
TEST_F(StyleCascadeTest, AnimatingVarReferences) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
AppendSheet(R"HTML(
@keyframes test {
from { --x: var(--from); }
to { --x: var(--to); }
}
)HTML");
TestCascade cascade(GetDocument());
cascade.Add("animation-name", "test");
cascade.Add("animation-duration", "10s");
cascade.Add("animation-timing-function", "linear");
cascade.Add("animation-delay", "-5s");
cascade.Apply();
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
cascade.AddAnimations();
cascade.Add("--from", "10px");
cascade.Add("--to", "20px");
cascade.Add("--y", "var(--x)");
cascade.Apply(animator);
EXPECT_EQ("15px", cascade.ComputedValue("--x"));
EXPECT_EQ("15px", cascade.ComputedValue("--y"));
}
TEST_F(StyleCascadeTest, AnimateStandardProperty) {
AppendSheet(R"HTML(
@keyframes test {
from { width: 10px; }
to { width: 20px; }
}
)HTML");
TestCascade cascade(GetDocument());
cascade.Add("animation-name", "test");
cascade.Add("animation-duration", "10s");
cascade.Add("animation-timing-function", "linear");
cascade.Add("animation-delay", "-5s");
cascade.Apply();
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
cascade.AddAnimations();
EXPECT_EQ("<interpolation>", cascade.GetValue("width"));
cascade.Apply(animator);
EXPECT_EQ("15px", cascade.ComputedValue("width"));
}
TEST_F(StyleCascadeTest, EmRespondsToAnimatedFontSize) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
AppendSheet(R"HTML(
@keyframes test {
from { font-size: 10px; }
to { font-size: 20px; }
}
)HTML");
TestCascade cascade(GetDocument());
cascade.Add("animation-name", "test");
cascade.Add("animation-duration", "10s");
cascade.Add("animation-timing-function", "linear");
cascade.Add("animation-delay", "-5s");
cascade.Apply();
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
cascade.AddAnimations();
cascade.Add("--x", "2em");
cascade.Add("width", "10em");
cascade.Apply(animator);
EXPECT_EQ("30px", cascade.ComputedValue("--x"));
EXPECT_EQ("150px", cascade.ComputedValue("width"));
}
TEST_F(StyleCascadeTest, AnimateStandardPropertyWithVar) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
AppendSheet(R"HTML(
@keyframes test {
from { width: var(--from); }
to { width: var(--to); }
}
)HTML");
TestCascade cascade(GetDocument());
cascade.Add("animation-name", "test");
cascade.Add("animation-duration", "10s");
cascade.Add("animation-timing-function", "linear");
cascade.Add("animation-delay", "-5s");
cascade.Apply();
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
cascade.AddAnimations();
cascade.Add("--from", "10px");
cascade.Add("--to", "20px");
cascade.Apply(animator);
EXPECT_EQ("15px", cascade.ComputedValue("width"));
}
TEST_F(StyleCascadeTest, AnimateStandardShorthand) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
AppendSheet(R"HTML(
@keyframes test {
from { margin: 10px; }
to { margin: 20px; }
}
)HTML");
TestCascade cascade(GetDocument());
cascade.Add("animation-name", "test");
cascade.Add("animation-duration", "10s");
cascade.Add("animation-timing-function", "linear");
cascade.Add("animation-delay", "-5s");
cascade.Apply();
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
cascade.AddAnimations();
EXPECT_EQ("<interpolation>", cascade.GetValue("margin-top"));
EXPECT_EQ("<interpolation>", cascade.GetValue("margin-right"));
EXPECT_EQ("<interpolation>", cascade.GetValue("margin-bottom"));
EXPECT_EQ("<interpolation>", cascade.GetValue("margin-left"));
cascade.Apply(animator);
EXPECT_EQ("15px", cascade.ComputedValue("margin-top"));
EXPECT_EQ("15px", cascade.ComputedValue("margin-right"));
EXPECT_EQ("15px", cascade.ComputedValue("margin-bottom"));
EXPECT_EQ("15px", cascade.ComputedValue("margin-left"));
}
TEST_F(StyleCascadeTest, AnimatePendingSubstitutionValue) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
AppendSheet(R"HTML(
@keyframes test {
from { margin: var(--from); }
to { margin: var(--to); }
}
)HTML");
TestCascade cascade(GetDocument());
cascade.Add("animation-name", "test");
cascade.Add("animation-duration", "10s");
cascade.Add("animation-timing-function", "linear");
cascade.Add("animation-delay", "-5s");
cascade.Apply();
StyleAnimator animator(cascade.State(), cascade.InnerCascade());
cascade.AddAnimations();
cascade.Add("--from", "10px");
cascade.Add("--to", "20px");
EXPECT_EQ("<interpolation>", cascade.GetValue("margin-top"));
EXPECT_EQ("<interpolation>", cascade.GetValue("margin-right"));
EXPECT_EQ("<interpolation>", cascade.GetValue("margin-bottom"));
EXPECT_EQ("<interpolation>", cascade.GetValue("margin-left"));
cascade.Apply(animator);
EXPECT_EQ("15px", cascade.ComputedValue("margin-top"));
EXPECT_EQ("15px", cascade.ComputedValue("margin-right"));
EXPECT_EQ("15px", cascade.ComputedValue("margin-bottom"));
EXPECT_EQ("15px", cascade.ComputedValue("margin-left"));
}
TEST_F(StyleCascadeTest, ForeignObjectZoomVsEffectiveZoom) {
GetDocument().body()->SetInnerHTMLFromString(R"HTML(
<svg>
<foreignObject id='foreign'></foreignObject>
</svg>
)HTML");
UpdateAllLifecyclePhasesForTest();
Element* foreign_object = GetDocument().getElementById("foreign");
ASSERT_TRUE(foreign_object);
TestCascade cascade(GetDocument(), foreign_object);
cascade.Add("zoom", "200%");
// TODO(andruud): Can't use CSSPropertyName to get -internal properties
// yet.
cascade.Add(CSSPropertyName(CSSPropertyID::kInternalEffectiveZoom),
"initial");
cascade.Apply();
// If both zoom and -internal-zoom exists in the cascade,
// -internal-effective-zoom should win.
EXPECT_EQ(1.0f, cascade.TakeStyle()->EffectiveZoom());
}
TEST_F(StyleCascadeTest, ZoomCascadeOrder) {
CSSPropertyName effective_zoom(CSSPropertyID::kInternalEffectiveZoom);
TestCascade cascade(GetDocument());
cascade.Add("zoom", "200%");
cascade.Add(effective_zoom, "initial");
cascade.Apply();
EXPECT_EQ(1.0f, cascade.TakeStyle()->EffectiveZoom());
}
TEST_F(StyleCascadeTest, ZoomReversedCascadeOrder) {
CSSPropertyName effective_zoom(CSSPropertyID::kInternalEffectiveZoom);
TestCascade cascade(GetDocument());
cascade.Add(effective_zoom, "initial");
cascade.Add("zoom", "200%");
cascade.Apply();
EXPECT_EQ(2.0f, cascade.TakeStyle()->EffectiveZoom());
}
TEST_F(StyleCascadeTest, ZoomPriority) {
CSSPropertyName effective_zoom(CSSPropertyID::kInternalEffectiveZoom);
TestCascade cascade(GetDocument());
cascade.Add("zoom", "200%", Origin::kImportantAuthor);
cascade.Add(effective_zoom, "initial");
cascade.Apply();
EXPECT_EQ(2.0f, cascade.TakeStyle()->EffectiveZoom());
}
TEST_F(StyleCascadeTest, WritingModeCascadeOrder) {
TestCascade cascade(GetDocument());
cascade.Add("writing-mode", "vertical-lr");
cascade.Add("-webkit-writing-mode", "vertical-rl");
cascade.Apply();
EXPECT_EQ("vertical-rl", cascade.ComputedValue("writing-mode"));
EXPECT_EQ("vertical-rl", cascade.ComputedValue("-webkit-writing-mode"));
}
TEST_F(StyleCascadeTest, WritingModeReversedCascadeOrder) {
TestCascade cascade(GetDocument());
cascade.Add("-webkit-writing-mode", "vertical-rl");
cascade.Add("writing-mode", "vertical-lr");
cascade.Apply();
EXPECT_EQ("vertical-lr", cascade.ComputedValue("writing-mode"));
EXPECT_EQ("vertical-lr", cascade.ComputedValue("-webkit-writing-mode"));
}
TEST_F(StyleCascadeTest, WritingModePriority) {
TestCascade cascade(GetDocument());
cascade.Add("writing-mode", "vertical-lr", Origin::kImportantAuthor);
cascade.Add("-webkit-writing-mode", "vertical-rl", Origin::kAuthor);
cascade.Apply();
EXPECT_EQ("vertical-lr", cascade.ComputedValue("writing-mode"));
EXPECT_EQ("vertical-lr", cascade.ComputedValue("-webkit-writing-mode"));
}
TEST_F(StyleCascadeTest, MarkReferenced) {
RegisterProperty(GetDocument(), "--x", "<length>", "0px", false);
RegisterProperty(GetDocument(), "--y", "<length>", "0px", false);
TestCascade cascade(GetDocument());
cascade.Add("width", "var(--x)");
cascade.Apply();
const auto* registry = GetDocument().GetPropertyRegistry();
ASSERT_TRUE(registry);
EXPECT_TRUE(registry->WasReferenced("--x"));
EXPECT_FALSE(registry->WasReferenced("--y"));
}
TEST_F(StyleCascadeTest, InternalVisitedColorLonghand) {
CSSPropertyName visited_color(CSSPropertyID::kInternalVisitedColor);
TestCascade cascade(GetDocument());
cascade.Add(visited_color, "red");
cascade.Add("color", "green");
cascade.Apply();
cascade.State().Style()->SetInsideLink(EInsideLink::kInsideVisitedLink);
EXPECT_EQ("rgb(0, 128, 0)", cascade.ComputedValue("color"));
Color red(255, 0, 0);
const CSSProperty& color = GetCSSPropertyColor();
EXPECT_EQ(red, cascade.TakeStyle()->VisitedDependentColor(color));
}
TEST_F(StyleCascadeTest, VarInInternalVisitedShorthand) {
CSSPropertyName visited_outline_color(
CSSPropertyID::kInternalVisitedOutlineColor);
TestCascade cascade(GetDocument());
cascade.Add("--x", "green");
cascade.Add("outline", "medium solid var(--x)");
// Copy pending substitution value from outline-color to
// -internal-visited-outline-color, approximating StyleResolver's behavior
// for :visited declarations.
const CSSValue* pending_substitution = cascade.GetCSSValue("outline-color");
ASSERT_TRUE(pending_substitution);
cascade.Add(visited_outline_color, pending_substitution);
cascade.Add("outline-color", "red");
// Apply "outline-color" manually first, to ensure that
// -internal-visited-outline-color is applied afterwards.
cascade.Apply("outline-color");
// When applying -internal-visited-outline-color, it should not modify
// outline-color.
cascade.Apply();
cascade.State().Style()->SetInsideLink(EInsideLink::kInsideVisitedLink);
EXPECT_EQ("rgb(255, 0, 0)", cascade.ComputedValue("outline-color"));
Color green(0, 128, 0);
const CSSProperty& outline_color = GetCSSPropertyOutlineColor();
EXPECT_EQ(green, cascade.TakeStyle()->VisitedDependentColor(outline_color));
}
} // namespace blink