| // 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/bindings/core/v8/v8_css_style_sheet_init.h" |
| #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_initial_color_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_revert_value.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/cascade_filter.h" |
| #include "third_party/blink/renderer/core/css/resolver/cascade_interpolations.h" |
| #include "third_party/blink/renderer/core/css/resolver/cascade_map.h" |
| #include "third_party/blink/renderer/core/css/resolver/cascade_priority.h" |
| #include "third_party/blink/renderer/core/css/resolver/cascade_resolver.h" |
| #include "third_party/blink/renderer/core/css/resolver/scoped_style_resolver.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/color_scheme_helper.h" |
| #include "third_party/blink/renderer/core/testing/page_test_base.h" |
| #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| |
| namespace blink { |
| |
| using css_test_helpers::ParseDeclarationBlock; |
| using css_test_helpers::RegisterProperty; |
| using Origin = CascadeOrigin; |
| using Priority = CascadePriority; |
| using UnitType = CSSPrimitiveValue::UnitType; |
| |
| class TestCascadeResolver { |
| STACK_ALLOCATED(); |
| |
| public: |
| explicit TestCascadeResolver(uint8_t generation = 0) |
| : resolver_(CascadeFilter(), generation) {} |
| bool InCycle() const { return resolver_.InCycle(); } |
| bool DetectCycle(const CSSProperty& property) { |
| return resolver_.DetectCycle(property); |
| } |
| wtf_size_t CycleDepth() const { return resolver_.cycle_depth_; } |
| void MarkApplied(CascadePriority* priority) { |
| resolver_.MarkApplied(priority); |
| } |
| void MarkUnapplied(CascadePriority* priority) { |
| resolver_.MarkUnapplied(priority); |
| } |
| uint8_t GetGeneration() { return resolver_.generation_; } |
| CascadeResolver& InnerResolver() { return resolver_; } |
| const CSSProperty* CurrentProperty() const { |
| return resolver_.CurrentProperty(); |
| } |
| |
| private: |
| friend class TestCascadeAutoLock; |
| |
| CascadeResolver resolver_; |
| }; |
| |
| class TestCascade { |
| STACK_ALLOCATED(); |
| |
| public: |
| TestCascade(Document& document, Element* target = nullptr) |
| : state_(document, target ? *target : *document.body()), |
| 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); |
| } |
| |
| // Note that because of how MatchResult works, declarations must be added |
| // in "origin order", i.e. UserAgent first, then User, then Author. |
| |
| void Add(String block, |
| CascadeOrigin origin = CascadeOrigin::kAuthor, |
| unsigned link_match_type = CSSSelector::kMatchAll) { |
| CSSParserMode mode = |
| origin == CascadeOrigin::kUserAgent ? kUASheetMode : kHTMLStandardMode; |
| Add(ParseDeclarationBlock(block, mode), origin, link_match_type); |
| } |
| |
| void Add(String name, String value, CascadeOrigin origin = Origin::kAuthor) { |
| Add(name + ":" + value, origin); |
| } |
| |
| void Add(const CSSPropertyValueSet* set, |
| CascadeOrigin origin = CascadeOrigin::kAuthor, |
| unsigned link_match_type = CSSSelector::kMatchAll) { |
| DCHECK_LE(origin, CascadeOrigin::kAuthor) << "Animations not supported"; |
| DCHECK_LE(current_origin_, origin) << "Please add declarations in order"; |
| EnsureAtLeast(origin); |
| cascade_.MutableMatchResult().AddMatchedProperties(set, link_match_type); |
| } |
| |
| void Apply(CascadeFilter filter = CascadeFilter()) { |
| EnsureAtLeast(CascadeOrigin::kAnimation); |
| cascade_.Apply(filter); |
| } |
| |
| void ApplySingle(const CSSProperty& property) { |
| EnsureAtLeast(CascadeOrigin::kAnimation); |
| cascade_.AnalyzeIfNeeded(); |
| TestCascadeResolver resolver(++cascade_.generation_); |
| cascade_.LookupAndApply(property, resolver.InnerResolver()); |
| } |
| |
| void AnalyzeIfNeeded() { cascade_.AnalyzeIfNeeded(); } |
| |
| const CSSValue* Resolve(const CSSProperty& property, |
| const CSSValue& value, |
| CascadeOrigin& origin) { |
| TestCascadeResolver resolver; |
| return cascade_.Resolve(property, value, origin, resolver.InnerResolver()); |
| } |
| |
| std::unique_ptr<CSSBitset> GetImportantSet() { |
| return cascade_.GetImportantSet(); |
| } |
| |
| 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; |
| } |
| |
| CascadePriority GetPriority(String name) { |
| return GetPriority( |
| *CSSPropertyName::From(GetDocument().GetExecutionContext(), name)); |
| } |
| |
| CascadePriority GetPriority(CSSPropertyName name) { |
| CascadePriority* c = cascade_.map_.Find(name); |
| return c ? *c : CascadePriority(); |
| } |
| |
| CascadeOrigin GetOrigin(String name) { return GetPriority(name).GetOrigin(); } |
| |
| void CalculateTransitionUpdate() { |
| CSSAnimations::CalculateTransitionUpdate( |
| state_.AnimationUpdate(), CSSAnimations::PropertyPass::kCustom, |
| &state_.GetElement(), *state_.Style()); |
| CSSAnimations::CalculateTransitionUpdate( |
| state_.AnimationUpdate(), CSSAnimations::PropertyPass::kStandard, |
| &state_.GetElement(), *state_.Style()); |
| AddTransitions(); |
| } |
| |
| void CalculateAnimationUpdate() { |
| CSSAnimations::CalculateAnimationUpdate( |
| state_.AnimationUpdate(), &state_.GetElement(), state_.GetElement(), |
| *state_.Style(), state_.ParentStyle(), |
| &GetDocument().GetStyleResolver()); |
| AddAnimations(); |
| } |
| |
| void Reset() { |
| cascade_.Reset(); |
| current_origin_ = CascadeOrigin::kUserAgent; |
| } |
| |
| bool NeedsMatchResultAnalyze() const { |
| return cascade_.needs_match_result_analyze_; |
| } |
| bool NeedsInterpolationsAnalyze() const { |
| return cascade_.needs_interpolations_analyze_; |
| } |
| bool DependsOnCascadeAffectingProperty() const { |
| return cascade_.depends_on_cascade_affecting_property_; |
| } |
| |
| HeapHashMap<CSSPropertyName, Member<const CSSValue>> GetCascadedValues() |
| const { |
| return cascade_.GetCascadedValues(); |
| } |
| |
| 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); |
| } |
| |
| void FinishOrigin() { |
| switch (current_origin_) { |
| case CascadeOrigin::kUserAgent: |
| cascade_.MutableMatchResult().FinishAddingUARules(); |
| current_origin_ = CascadeOrigin::kUser; |
| break; |
| case CascadeOrigin::kUser: |
| cascade_.MutableMatchResult().FinishAddingUserRules(); |
| current_origin_ = CascadeOrigin::kAuthor; |
| break; |
| case CascadeOrigin::kAuthor: |
| cascade_.MutableMatchResult().FinishAddingAuthorRulesForTreeScope( |
| GetDocument()); |
| current_origin_ = CascadeOrigin::kAnimation; |
| break; |
| case CascadeOrigin::kAnimation: |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void EnsureAtLeast(CascadeOrigin origin) { |
| while (current_origin_ < origin) |
| FinishOrigin(); |
| } |
| |
| void AddAnimations() { |
| const auto& update = state_.AnimationUpdate(); |
| if (update.IsEmpty()) |
| return; |
| cascade_.AddInterpolations( |
| &update.ActiveInterpolationsForCustomAnimations(), |
| CascadeOrigin::kAnimation); |
| cascade_.AddInterpolations( |
| &update.ActiveInterpolationsForStandardAnimations(), |
| CascadeOrigin::kAnimation); |
| } |
| |
| void AddTransitions() { |
| const auto& update = state_.AnimationUpdate(); |
| if (update.IsEmpty()) |
| return; |
| cascade_.AddInterpolations( |
| &update.ActiveInterpolationsForCustomTransitions(), |
| CascadeOrigin::kTransition); |
| cascade_.AddInterpolations( |
| &update.ActiveInterpolationsForStandardTransitions(), |
| CascadeOrigin::kTransition); |
| } |
| |
| CascadeOrigin current_origin_ = CascadeOrigin::kUserAgent; |
| StyleResolverState state_; |
| StyleCascade cascade_; |
| }; |
| |
| class TestCascadeAutoLock { |
| STACK_ALLOCATED(); |
| |
| public: |
| TestCascadeAutoLock(const CSSProperty& property, |
| TestCascadeResolver& resolver) |
| : lock_(property, resolver.resolver_) {} |
| |
| private: |
| CascadeResolver::AutoLock lock_; |
| }; |
| |
| class StyleCascadeTest : public PageTestBase { |
| public: |
| 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(GetDocument().GetFrame()), 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); |
| GetDocument() |
| .GetStyleEngine() |
| .GetDocumentStyleSheetCollection() |
| .AppendActiveStyleSheet(active_sheets[0]); |
| } |
| |
| Element* DocumentElement() const { return GetDocument().documentElement(); } |
| |
| void SetRootFont(String value) { |
| DocumentElement()->SetInlineStyleProperty(CSSPropertyID::kFontSize, value); |
| UpdateAllLifecyclePhasesForTest(); |
| } |
| |
| const MutableCSSPropertyValueSet* AnimationTaintedSet(AtomicString name, |
| String value) { |
| CSSParserMode mode = kHTMLStandardMode; |
| auto* set = MakeGarbageCollected<MutableCSSPropertyValueSet>(mode); |
| set->SetProperty(name, value, /* important */ false, |
| SecureContextMode::kSecureContext, |
| /* context_style_sheet */ nullptr, |
| /* is_animation_tainted */ true); |
| return set; |
| } |
| |
| // 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(); |
| } |
| |
| Document* document_; |
| AtomicString name_; |
| }; |
| |
| CSSPropertyName PropertyName(String name) { |
| return *CSSPropertyName::From(GetDocument().GetExecutionContext(), name); |
| } |
| |
| String CssTextAt( |
| const HeapHashMap<CSSPropertyName, Member<const CSSValue>>& map, |
| String name) { |
| const CSSValue* value = map.at(PropertyName(name)); |
| return value ? value->CssText() : g_null_atom; |
| } |
| }; |
| |
| TEST_F(StyleCascadeTest, ApplySingle) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("width", "1px", CascadeOrigin::kUserAgent); |
| cascade.Add("width", "2px", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("2px", cascade.ComputedValue("width")); |
| } |
| |
| TEST_F(StyleCascadeTest, ApplyImportance) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("width:1px !important", CascadeOrigin::kUserAgent); |
| cascade.Add("width:2px", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("1px", cascade.ComputedValue("width")); |
| } |
| |
| TEST_F(StyleCascadeTest, ApplyAll) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("width:1px", CascadeOrigin::kUserAgent); |
| cascade.Add("height:1px", CascadeOrigin::kUserAgent); |
| cascade.Add("all:initial", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("auto", cascade.ComputedValue("width")); |
| EXPECT_EQ("auto", cascade.ComputedValue("height")); |
| } |
| |
| TEST_F(StyleCascadeTest, ApplyAllImportance) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("opacity:0.5", CascadeOrigin::kUserAgent); |
| cascade.Add("display:block !important", CascadeOrigin::kUserAgent); |
| cascade.Add("all:initial", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("1", cascade.ComputedValue("opacity")); |
| EXPECT_EQ("block", cascade.ComputedValue("display")); |
| } |
| |
| TEST_F(StyleCascadeTest, ApplyAllWithPhysicalLonghands) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("width:1px", CascadeOrigin::kUserAgent); |
| cascade.Add("height:1px !important", CascadeOrigin::kUserAgent); |
| cascade.Add("all:initial", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| EXPECT_EQ("auto", cascade.ComputedValue("width")); |
| EXPECT_EQ("1px", cascade.ComputedValue("height")); |
| } |
| |
| TEST_F(StyleCascadeTest, ApplyCustomProperty) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("--x", " 10px "); |
| cascade.Add("--y", "nope"); |
| cascade.Apply(); |
| |
| EXPECT_EQ(" 10px ", cascade.ComputedValue("--x")); |
| EXPECT_EQ("nope", cascade.ComputedValue("--y")); |
| } |
| |
| TEST_F(StyleCascadeTest, ApplyGenerations) { |
| TestCascade cascade(GetDocument()); |
| |
| cascade.Add("--x:10px"); |
| cascade.Add("width:20px"); |
| cascade.Apply(); |
| EXPECT_EQ("10px", cascade.ComputedValue("--x")); |
| EXPECT_EQ("20px", cascade.ComputedValue("width")); |
| |
| cascade.State().StyleRef().SetWidth(Length::Auto()); |
| cascade.State().StyleRef().SetVariableData("--x", nullptr, true); |
| EXPECT_EQ(g_null_atom, cascade.ComputedValue("--x")); |
| EXPECT_EQ("auto", cascade.ComputedValue("width")); |
| |
| // Apply again |
| cascade.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(); |
| |
| EXPECT_EQ("yes and no", cascade.ComputedValue("--x")); |
| EXPECT_EQ("no", cascade.ComputedValue("--y")); |
| } |
| |
| // Apply --y first. |
| { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("--y", "no"); |
| cascade.Add("--x", "yes and var(--y)"); |
| cascade.Apply(); |
| |
| 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(); |
| |
| 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(); |
| |
| 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(); |
| |
| 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(); |
| |
| 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(); |
| |
| 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(); |
| |
| 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(); |
| |
| 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(); |
| |
| 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"); |
| 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-right", "5px"); |
| 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, PendingSubstitutionInLogicalShorthand) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("margin-inline:var(--x)"); |
| cascade.Add("--x:10px 20px"); |
| cascade.Add("direction:rtl"); |
| cascade.Apply(); |
| |
| EXPECT_EQ("20px", cascade.ComputedValue("margin-left")); |
| EXPECT_EQ("10px", cascade.ComputedValue("margin-right")); |
| } |
| |
| TEST_F(StyleCascadeTest, DetectCycleByName) { |
| TestCascade cascade(GetDocument()); |
| TestCascadeResolver resolver; |
| |
| // Two different CustomProperty instances with the same name: |
| CustomProperty a1("--a", GetDocument()); |
| CustomProperty a2("--a", GetDocument()); |
| |
| { |
| TestCascadeAutoLock lock(a1, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| |
| // This should still be detected as a cycle, even though it's not the same |
| // CustomProperty instance. |
| EXPECT_TRUE(resolver.DetectCycle(a2)); |
| EXPECT_TRUE(resolver.InCycle()); |
| } |
| EXPECT_FALSE(resolver.InCycle()); |
| } |
| |
| TEST_F(StyleCascadeTest, ResolverDetectCycle) { |
| TestCascade cascade(GetDocument()); |
| TestCascadeResolver resolver; |
| |
| CustomProperty a("--a", GetDocument()); |
| CustomProperty b("--b", GetDocument()); |
| CustomProperty c("--c", GetDocument()); |
| |
| { |
| TestCascadeAutoLock lock(a, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| { |
| TestCascadeAutoLock lock(b, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| { |
| TestCascadeAutoLock lock(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()); |
| TestCascadeResolver resolver; |
| |
| CustomProperty a("--a", GetDocument()); |
| CustomProperty b("--b", GetDocument()); |
| CustomProperty c("--c", GetDocument()); |
| CustomProperty x("--x", GetDocument()); |
| |
| { |
| TestCascadeAutoLock lock(a, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| { |
| TestCascadeAutoLock lock(b, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| { |
| TestCascadeAutoLock lock(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()); |
| TestCascadeResolver resolver; |
| |
| CustomProperty a("--a", GetDocument()); |
| |
| { |
| TestCascadeAutoLock lock(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()); |
| TestCascadeResolver resolver; |
| |
| CustomProperty a("--a", GetDocument()); |
| CustomProperty b("--b", GetDocument()); |
| CustomProperty c("--c", GetDocument()); |
| CustomProperty d("--d", GetDocument()); |
| |
| { |
| AutoLock lock(a, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| { |
| AutoLock lock(b, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| { |
| AutoLock lock(c, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| { |
| AutoLock lock(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()); |
| TestCascadeResolver resolver; |
| |
| CustomProperty a("--a", GetDocument()); |
| CustomProperty b("--b", GetDocument()); |
| CustomProperty c("--c", GetDocument()); |
| CustomProperty d("--d", GetDocument()); |
| |
| { |
| AutoLock lock(a, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| { |
| AutoLock lock(b, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| { |
| AutoLock lock(c, resolver); |
| EXPECT_FALSE(resolver.InCycle()); |
| { |
| AutoLock lock(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, ResolverMarkApplied) { |
| TestCascadeResolver resolver(2); |
| |
| CascadePriority priority(CascadeOrigin::kAuthor); |
| EXPECT_EQ(0, priority.GetGeneration()); |
| |
| resolver.MarkApplied(&priority); |
| EXPECT_EQ(2, priority.GetGeneration()); |
| |
| // Mark a second time to verify observation of the same generation. |
| resolver.MarkApplied(&priority); |
| EXPECT_EQ(2, priority.GetGeneration()); |
| } |
| |
| TEST_F(StyleCascadeTest, CurrentProperty) { |
| using AutoLock = TestCascadeAutoLock; |
| |
| TestCascade cascade(GetDocument()); |
| TestCascadeResolver resolver; |
| |
| CustomProperty a("--a", GetDocument()); |
| CustomProperty b("--b", GetDocument()); |
| CustomProperty c("--c", GetDocument()); |
| |
| EXPECT_FALSE(resolver.CurrentProperty()); |
| { |
| AutoLock lock(a, resolver); |
| EXPECT_EQ(&a, resolver.CurrentProperty()); |
| { |
| AutoLock lock(b, resolver); |
| EXPECT_EQ(&b, resolver.CurrentProperty()); |
| { |
| AutoLock lock(c, resolver); |
| EXPECT_EQ(&c, resolver.CurrentProperty()); |
| } |
| EXPECT_EQ(&b, resolver.CurrentProperty()); |
| } |
| EXPECT_EQ(&a, resolver.CurrentProperty()); |
| } |
| EXPECT_FALSE(resolver.CurrentProperty()); |
| } |
| |
| TEST_F(StyleCascadeTest, ResolverMarkUnapplied) { |
| TestCascadeResolver resolver(7); |
| |
| CascadePriority priority(CascadeOrigin::kAuthor); |
| EXPECT_EQ(0, priority.GetGeneration()); |
| |
| resolver.MarkApplied(&priority); |
| EXPECT_EQ(7, priority.GetGeneration()); |
| |
| resolver.MarkUnapplied(&priority); |
| EXPECT_EQ(0, priority.GetGeneration()); |
| |
| // Mark a second time to verify observation of the same generation. |
| resolver.MarkUnapplied(&priority); |
| EXPECT_EQ(0, priority.GetGeneration()); |
| } |
| |
| 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.Reset(); |
| 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.Reset(); |
| 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.Reset(); |
| 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.Reset(); |
| 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.Reset(); |
| 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.Reset(); |
| cascade.Add("--x", "unset"); |
| cascade.Apply(); |
| EXPECT_EQ("foo", cascade.ComputedValue("--x")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertUA) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("display:block", CascadeOrigin::kUserAgent); |
| cascade.Add("display:revert", CascadeOrigin::kUserAgent); |
| |
| cascade.Add("display:block", CascadeOrigin::kUser); |
| cascade.Add("display:revert", CascadeOrigin::kUser); |
| |
| cascade.Add("display:block", CascadeOrigin::kAuthor); |
| cascade.Add("display:revert", CascadeOrigin::kAuthor); |
| |
| cascade.Apply(); |
| |
| EXPECT_EQ("inline", cascade.ComputedValue("display")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertStandardProperty) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("left:10px", CascadeOrigin::kUserAgent); |
| cascade.Add("right:10px", CascadeOrigin::kUserAgent); |
| |
| cascade.Add("right:20px", CascadeOrigin::kUser); |
| cascade.Add("right:revert", CascadeOrigin::kUser); |
| cascade.Add("top:20px", CascadeOrigin::kUser); |
| cascade.Add("bottom:20px", CascadeOrigin::kUser); |
| |
| cascade.Add("bottom:30px", CascadeOrigin::kAuthor); |
| cascade.Add("bottom:revert", CascadeOrigin::kAuthor); |
| cascade.Add("left:30px", CascadeOrigin::kAuthor); |
| cascade.Add("left:revert", CascadeOrigin::kAuthor); |
| cascade.Add("right:revert", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("20px", cascade.ComputedValue("top")); |
| EXPECT_EQ("10px", cascade.ComputedValue("right")); |
| EXPECT_EQ("20px", cascade.ComputedValue("bottom")); |
| EXPECT_EQ("10px", cascade.ComputedValue("left")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertCustomProperty) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("--x:10px", CascadeOrigin::kUser); |
| |
| cascade.Add("--y:fail", CascadeOrigin::kAuthor); |
| |
| cascade.Add("--x:revert", CascadeOrigin::kAuthor); |
| cascade.Add("--y:revert", CascadeOrigin::kAuthor); |
| |
| cascade.Apply(); |
| |
| EXPECT_EQ("10px", cascade.ComputedValue("--x")); |
| EXPECT_FALSE(cascade.ComputedValue("--y")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertChain) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("width:10px", CascadeOrigin::kUserAgent); |
| |
| cascade.Add("width:revert", CascadeOrigin::kUser); |
| cascade.Add("--x:revert", CascadeOrigin::kUser); |
| |
| cascade.Add("width:revert", CascadeOrigin::kAuthor); |
| cascade.Add("--x:revert", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("10px", cascade.ComputedValue("width")); |
| EXPECT_FALSE(cascade.ComputedValue("--x")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertFromAuthorToUA) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("width:10px", CascadeOrigin::kUserAgent); |
| cascade.Add("height:10px", CascadeOrigin::kUserAgent); |
| |
| cascade.Add("width:20px", CascadeOrigin::kAuthor); |
| cascade.Add("height:20px", CascadeOrigin::kAuthor); |
| cascade.Add("width:revert", CascadeOrigin::kAuthor); |
| cascade.Add("height:revert", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("10px", cascade.ComputedValue("width")); |
| EXPECT_EQ("10px", cascade.ComputedValue("height")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertInitialFallback) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("width:20px", CascadeOrigin::kAuthor); |
| cascade.Add("width:revert", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("auto", cascade.ComputedValue("width")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertInheritedFallback) { |
| TestCascade parent(GetDocument()); |
| parent.Add("color", "red"); |
| parent.Apply(); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.InheritFrom(parent.TakeStyle()); |
| EXPECT_EQ("rgb(255, 0, 0)", cascade.ComputedValue("color")); |
| |
| cascade.Add("color:black", CascadeOrigin::kAuthor); |
| cascade.Add("color:revert", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| EXPECT_EQ("rgb(255, 0, 0)", cascade.ComputedValue("color")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertRegistered) { |
| RegisterProperty(GetDocument(), "--x", "<length>", "0px", false); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.Add("--x:20px", CascadeOrigin::kUser); |
| cascade.Add("--x:100px", CascadeOrigin::kAuthor); |
| cascade.Add("--x:revert", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("20px", cascade.ComputedValue("--x")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertRegisteredInitialFallback) { |
| RegisterProperty(GetDocument(), "--x", "<length>", "0px", false); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.Add("--x:20px", CascadeOrigin::kAuthor); |
| cascade.Add("--x:revert", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("0px", cascade.ComputedValue("--x")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertRegisteredInheritedFallback) { |
| RegisterProperty(GetDocument(), "--x", "<length>", "0px", true); |
| |
| TestCascade parent(GetDocument()); |
| parent.Add("--x", "1px"); |
| parent.Apply(); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.InheritFrom(parent.TakeStyle()); |
| EXPECT_EQ("1px", cascade.ComputedValue("--x")); |
| |
| cascade.Add("--x:100px", CascadeOrigin::kAuthor); |
| cascade.Add("--x:revert", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| EXPECT_EQ("1px", cascade.ComputedValue("--x")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertUASurrogate) { |
| TestCascade cascade(GetDocument()); |
| |
| // User-agent: |
| |
| // Only logical: |
| cascade.Add("inline-size:10px", CascadeOrigin::kUserAgent); |
| cascade.Add("min-inline-size:11px", CascadeOrigin::kUserAgent); |
| // Only physical: |
| cascade.Add("height:12px", CascadeOrigin::kUserAgent); |
| cascade.Add("min-height:13px", CascadeOrigin::kUserAgent); |
| // Physical first: |
| cascade.Add("margin-left:14px", CascadeOrigin::kUserAgent); |
| cascade.Add("padding-left:15px", CascadeOrigin::kUserAgent); |
| cascade.Add("margin-inline-start:16px", CascadeOrigin::kUserAgent); |
| cascade.Add("padding-inline-start:17px", CascadeOrigin::kUserAgent); |
| // Logical first: |
| cascade.Add("margin-inline-end:18px", CascadeOrigin::kUserAgent); |
| cascade.Add("padding-inline-end:19px", CascadeOrigin::kUserAgent); |
| cascade.Add("margin-right:20px", CascadeOrigin::kUserAgent); |
| cascade.Add("padding-right:21px", CascadeOrigin::kUserAgent); |
| |
| // Author: |
| |
| cascade.Add("width:100px", CascadeOrigin::kAuthor); |
| cascade.Add("height:101px", CascadeOrigin::kAuthor); |
| cascade.Add("margin:102px", CascadeOrigin::kAuthor); |
| cascade.Add("padding:103px", CascadeOrigin::kAuthor); |
| cascade.Add("min-width:104px", CascadeOrigin::kAuthor); |
| cascade.Add("min-height:105px", CascadeOrigin::kAuthor); |
| // Revert via physical: |
| cascade.Add("width:revert", CascadeOrigin::kAuthor); |
| cascade.Add("height:revert", CascadeOrigin::kAuthor); |
| cascade.Add("margin-left:revert", CascadeOrigin::kAuthor); |
| cascade.Add("margin-right:revert", CascadeOrigin::kAuthor); |
| // Revert via logical: |
| cascade.Add("min-inline-size:revert", CascadeOrigin::kAuthor); |
| cascade.Add("min-block-size:revert", CascadeOrigin::kAuthor); |
| cascade.Add("padding-inline-start:revert", CascadeOrigin::kAuthor); |
| cascade.Add("padding-inline-end:revert", CascadeOrigin::kAuthor); |
| |
| cascade.Apply(); |
| |
| EXPECT_EQ("10px", cascade.ComputedValue("width")); |
| EXPECT_EQ("12px", cascade.ComputedValue("height")); |
| EXPECT_EQ("11px", cascade.ComputedValue("min-width")); |
| EXPECT_EQ("13px", cascade.ComputedValue("min-height")); |
| EXPECT_EQ("102px", cascade.ComputedValue("margin-top")); |
| EXPECT_EQ("20px", cascade.ComputedValue("margin-right")); |
| EXPECT_EQ("102px", cascade.ComputedValue("margin-bottom")); |
| EXPECT_EQ("16px", cascade.ComputedValue("margin-left")); |
| EXPECT_EQ("103px", cascade.ComputedValue("padding-top")); |
| EXPECT_EQ("21px", cascade.ComputedValue("padding-right")); |
| EXPECT_EQ("103px", cascade.ComputedValue("padding-bottom")); |
| EXPECT_EQ("17px", cascade.ComputedValue("padding-left")); |
| |
| EXPECT_EQ("10px", cascade.ComputedValue("inline-size")); |
| EXPECT_EQ("12px", cascade.ComputedValue("block-size")); |
| EXPECT_EQ("11px", cascade.ComputedValue("min-inline-size")); |
| EXPECT_EQ("13px", cascade.ComputedValue("min-block-size")); |
| EXPECT_EQ("102px", cascade.ComputedValue("margin-block-start")); |
| EXPECT_EQ("20px", cascade.ComputedValue("margin-inline-end")); |
| EXPECT_EQ("102px", cascade.ComputedValue("margin-block-end")); |
| EXPECT_EQ("16px", cascade.ComputedValue("margin-inline-start")); |
| EXPECT_EQ("103px", cascade.ComputedValue("padding-block-start")); |
| EXPECT_EQ("21px", cascade.ComputedValue("padding-inline-end")); |
| EXPECT_EQ("103px", cascade.ComputedValue("padding-block-end")); |
| EXPECT_EQ("17px", cascade.ComputedValue("padding-inline-start")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertWithImportantPhysical) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("inline-size:10px", CascadeOrigin::kUserAgent); |
| cascade.Add("block-size:11px", CascadeOrigin::kUserAgent); |
| |
| cascade.Add("width:100px", CascadeOrigin::kAuthor); |
| cascade.Add("height:101px", CascadeOrigin::kAuthor); |
| cascade.Add("width:revert !important", CascadeOrigin::kAuthor); |
| cascade.Add("inline-size:101px", CascadeOrigin::kAuthor); |
| cascade.Add("block-size:102px", CascadeOrigin::kAuthor); |
| cascade.Add("height:revert !important", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("10px", cascade.ComputedValue("width")); |
| EXPECT_EQ("11px", cascade.ComputedValue("height")); |
| EXPECT_EQ("10px", cascade.ComputedValue("inline-size")); |
| EXPECT_EQ("11px", cascade.ComputedValue("block-size")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertWithImportantLogical) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("inline-size:10px", CascadeOrigin::kUserAgent); |
| cascade.Add("block-size:11px", CascadeOrigin::kUserAgent); |
| |
| cascade.Add("inline-size:revert !important", CascadeOrigin::kAuthor); |
| cascade.Add("width:100px", CascadeOrigin::kAuthor); |
| cascade.Add("height:101px", CascadeOrigin::kAuthor); |
| cascade.Add("block-size:revert !important", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("10px", cascade.ComputedValue("width")); |
| EXPECT_EQ("11px", cascade.ComputedValue("height")); |
| EXPECT_EQ("10px", cascade.ComputedValue("inline-size")); |
| EXPECT_EQ("11px", cascade.ComputedValue("block-size")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertSurrogateChain) { |
| TestCascade cascade(GetDocument()); |
| |
| cascade.Add("inline-size:revert", CascadeOrigin::kUserAgent); |
| cascade.Add("block-size:10px", CascadeOrigin::kUserAgent); |
| cascade.Add("min-inline-size:11px", CascadeOrigin::kUserAgent); |
| cascade.Add("min-block-size:12px", CascadeOrigin::kUserAgent); |
| cascade.Add("margin-inline:13px", CascadeOrigin::kUserAgent); |
| cascade.Add("margin-block:14px", CascadeOrigin::kUserAgent); |
| cascade.Add("margin-top:revert", CascadeOrigin::kUserAgent); |
| cascade.Add("margin-left:15px", CascadeOrigin::kUserAgent); |
| cascade.Add("margin-bottom:16px", CascadeOrigin::kUserAgent); |
| cascade.Add("margin-block-end:17px", CascadeOrigin::kUserAgent); |
| |
| cascade.Add("inline-size:101px", CascadeOrigin::kUser); |
| cascade.Add("block-size:102px", CascadeOrigin::kUser); |
| cascade.Add("width:revert", CascadeOrigin::kUser); |
| cascade.Add("height:revert", CascadeOrigin::kUser); |
| cascade.Add("min-inline-size:103px", CascadeOrigin::kUser); |
| cascade.Add("min-block-size:104px", CascadeOrigin::kUser); |
| cascade.Add("margin:105px", CascadeOrigin::kUser); |
| cascade.Add("margin-block-start:revert", CascadeOrigin::kUser); |
| cascade.Add("margin-inline-start:106px", CascadeOrigin::kUser); |
| cascade.Add("margin-block-end:revert", CascadeOrigin::kUser); |
| cascade.Add("margin-right:107px", CascadeOrigin::kUser); |
| |
| cascade.Add("inline-size:revert", CascadeOrigin::kAuthor); |
| cascade.Add("block-size:revert", CascadeOrigin::kAuthor); |
| cascade.Add("min-inline-size:revert", CascadeOrigin::kAuthor); |
| cascade.Add("min-block-size:1001px", CascadeOrigin::kAuthor); |
| cascade.Add("margin:1002px", CascadeOrigin::kAuthor); |
| cascade.Add("margin-top:revert", CascadeOrigin::kAuthor); |
| cascade.Add("margin-left:1003px", CascadeOrigin::kAuthor); |
| cascade.Add("margin-bottom:1004px", CascadeOrigin::kAuthor); |
| cascade.Add("margin-right:1005px", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ("auto", cascade.ComputedValue("width")); |
| EXPECT_EQ("10px", cascade.ComputedValue("height")); |
| EXPECT_EQ("103px", cascade.ComputedValue("min-width")); |
| EXPECT_EQ("1001px", cascade.ComputedValue("min-height")); |
| EXPECT_EQ("0px", cascade.ComputedValue("margin-top")); |
| EXPECT_EQ("1005px", cascade.ComputedValue("margin-right")); |
| EXPECT_EQ("1004px", cascade.ComputedValue("margin-bottom")); |
| EXPECT_EQ("1003px", cascade.ComputedValue("margin-left")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertInKeyframe) { |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { margin-left: 0px; } |
| to { margin-left: revert; } |
| } |
| )HTML"); |
| |
| TestCascade cascade(GetDocument()); |
| |
| cascade.Add("margin-left:100px", CascadeOrigin::kUserAgent); |
| cascade.Add("animation:test linear 1000s -500s"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ("50px", cascade.ComputedValue("margin-left")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertToCustomPropertyInKeyframe) { |
| RegisterProperty(GetDocument(), "--x", "<length>", "0px", false); |
| |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { --x: 0px; } |
| to { --x: revert; } |
| } |
| )HTML"); |
| |
| TestCascade cascade(GetDocument()); |
| |
| cascade.Add("--x:100px", CascadeOrigin::kUser); |
| cascade.Add("--x:1000px", CascadeOrigin::kAuthor); |
| cascade.Add("animation:test linear 1000s -500s"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ("50px", cascade.ComputedValue("--x")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertToCustomPropertyInKeyframeUnset) { |
| RegisterProperty(GetDocument(), "--x", "<length>", "0px", false); |
| RegisterProperty(GetDocument(), "--y", "<length>", "1000px", true); |
| |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { --x: 100px; --y: 100px; } |
| to { --x: revert; --y: revert; } |
| } |
| )HTML"); |
| |
| TestCascade parent(GetDocument()); |
| parent.Add("--y: 0px"); |
| parent.Apply(); |
| EXPECT_EQ("0px", parent.ComputedValue("--y")); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.InheritFrom(parent.TakeStyle()); |
| cascade.Add("--x:10000px", CascadeOrigin::kAuthor); |
| cascade.Add("--y:10000px", CascadeOrigin::kAuthor); |
| cascade.Add("animation:test linear 1000s -500s"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ("50px", cascade.ComputedValue("--x")); |
| EXPECT_EQ("50px", cascade.ComputedValue("--y")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertToCustomPropertyInKeyframeEmptyInherit) { |
| RegisterProperty(GetDocument(), "--x", "<length>", "0px", true); |
| |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { --x: 100px; } |
| to { --x: revert; } |
| } |
| )HTML"); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.Add("--x:10000px", CascadeOrigin::kAuthor); |
| cascade.Add("animation:test linear 1000s -500s"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ("50px", cascade.ComputedValue("--x")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertInKeyframeResponsive) { |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { margin-left: 0px; } |
| to { margin-left: revert; } |
| } |
| )HTML"); |
| |
| TestCascade cascade(GetDocument()); |
| |
| cascade.Add("--x:100px", CascadeOrigin::kUser); |
| cascade.Add("margin-left:var(--x)", CascadeOrigin::kUser); |
| cascade.Add("animation:test linear 1000s -500s"); |
| cascade.Apply(); |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ("50px", cascade.ComputedValue("margin-left")); |
| |
| cascade.Reset(); |
| cascade.Add("--x:100px", CascadeOrigin::kUser); |
| cascade.Add("margin-left:var(--x)", CascadeOrigin::kUser); |
| cascade.Add("animation:test linear 1000s -500s"); |
| cascade.Add("--x:80px", CascadeOrigin::kAuthor); |
| cascade.Apply(); |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ("40px", cascade.ComputedValue("margin-left")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertToCycleInKeyframe) { |
| RegisterProperty(GetDocument(), "--x", "<length>", "0px", false); |
| |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { --x: 100px; } |
| to { --x: revert; } |
| } |
| )HTML"); |
| |
| TestCascade cascade(GetDocument()); |
| |
| cascade.Add("--x:var(--y)", CascadeOrigin::kUser); |
| cascade.Add("--y:var(--x)", CascadeOrigin::kUser); |
| cascade.Add("--x:200px", CascadeOrigin::kAuthor); |
| cascade.Add("animation:test linear 1000s -500s"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ("0px", cascade.ComputedValue("--x")); |
| } |
| |
| TEST_F(StyleCascadeTest, RevertCausesTransition) { |
| TestCascade cascade1(GetDocument()); |
| cascade1.Add("width:200px", CascadeOrigin::kUser); |
| cascade1.Add("width:100px", CascadeOrigin::kAuthor); |
| cascade1.Add("transition: width 1000s steps(2, end)", CascadeOrigin::kAuthor); |
| cascade1.Apply(); |
| |
| GetDocument().body()->SetComputedStyle(cascade1.TakeStyle()); |
| |
| // Now simulate a new style, with new color values. |
| TestCascade cascade2(GetDocument()); |
| cascade2.Add("width:200px", CascadeOrigin::kUser); |
| cascade2.Add("width:100px", CascadeOrigin::kAuthor); |
| cascade2.Add("width:revert", CascadeOrigin::kAuthor); |
| cascade2.Add("transition: width 1000s steps(2, start)", |
| CascadeOrigin::kAuthor); |
| cascade2.Apply(); |
| |
| cascade2.CalculateTransitionUpdate(); |
| cascade2.Apply(); |
| |
| EXPECT_EQ("150px", cascade2.ComputedValue("width")); |
| } |
| |
| TEST_F(StyleCascadeTest, CSSWideKeywordsInFallbacks) { |
| { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("display:var(--u,initial)"); |
| cascade.Add("margin:var(--u,initial)"); |
| cascade.Apply(); |
| } |
| { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("display:var(--u,inherit)"); |
| cascade.Add("margin:var(--u,inherit)"); |
| cascade.Apply(); |
| } |
| { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("display:var(--u,unset)"); |
| cascade.Add("margin:var(--u,unset)"); |
| cascade.Apply(); |
| } |
| { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("display:var(--u,revert)"); |
| cascade.Add("margin:var(--u,revert)"); |
| cascade.Apply(); |
| } |
| |
| // TODO(crbug.com/1105782): Specs and WPT are currently in conflict |
| // regarding the correct behavior here. For now this test just verifies |
| // that we don't crash. |
| } |
| |
| 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", "*", base::nullopt, 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.Reset(); |
| 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.Reset(); |
| 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.Reset(); |
| 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(AnimationTaintedSet("--x", "15px")); |
| 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(AnimationTaintedSet("--x", "15px")); |
| 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.Reset(); |
| cascade.Add(AnimationTaintedSet("--y", "20s")); |
| 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(AnimationTaintedSet("--x", "20s")); |
| 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(AnimationTaintedSet("--x", "20s")); |
| 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")); |
| } |
| |
| TEST_F(StyleCascadeTest, AnimationApplyFilter) { |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { color: white; background-color: white; } |
| to { color: gray; background-color: gray; } |
| } |
| )HTML"); |
| |
| TestCascade cascade(GetDocument()); |
| |
| cascade.Add("animation: test linear 10s -5s"); |
| cascade.Add("color:green"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(CascadeFilter(CSSProperty::kInherited, true)); |
| |
| EXPECT_EQ("rgb(0, 128, 0)", cascade.ComputedValue("color")); |
| EXPECT_EQ("rgb(192, 192, 192)", cascade.ComputedValue("background-color")); |
| } |
| |
| TEST_F(StyleCascadeTest, TransitionApplyFilter) { |
| TestCascade cascade1(GetDocument()); |
| cascade1.Add("background-color: white"); |
| cascade1.Add("color: white"); |
| cascade1.Add("transition: all steps(2, start) 100s"); |
| cascade1.Apply(); |
| |
| // Set the old style on the element, so that the transition |
| // update detects it. |
| GetDocument().body()->SetComputedStyle(cascade1.TakeStyle()); |
| |
| // Now simulate a new style, with new color values. |
| TestCascade cascade2(GetDocument()); |
| cascade2.Add("background-color: gray"); |
| cascade2.Add("color: gray"); |
| cascade2.Add("transition: all steps(2, start) 100s"); |
| cascade2.Apply(); |
| |
| cascade2.CalculateTransitionUpdate(); |
| cascade2.Apply(CascadeFilter(CSSProperty::kInherited, true)); |
| |
| EXPECT_EQ("rgb(128, 128, 128)", cascade2.ComputedValue("color")); |
| EXPECT_EQ("rgb(192, 192, 192)", cascade2.ComputedValue("background-color")); |
| } |
| |
| 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.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetPriority("--x").GetOrigin()); |
| } |
| |
| 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.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetPriority("--x").GetOrigin()); |
| 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(); |
| |
| cascade2.CalculateTransitionUpdate(); |
| cascade2.Apply(); |
| |
| EXPECT_EQ(CascadeOrigin::kTransition, |
| cascade2.GetPriority("--x").GetOrigin()); |
| } |
| |
| 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.CalculateTransitionUpdate(); |
| cascade2.Apply(); |
| |
| EXPECT_EQ(CascadeOrigin::kTransition, cascade2.GetOrigin("--x")); |
| EXPECT_EQ(CascadeOrigin::kTransition, cascade2.GetOrigin("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.Add("--from", "10px"); |
| cascade.Add("--to", "20px"); |
| cascade.Add("--y", "var(--x)"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| 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(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("width")); |
| EXPECT_EQ("15px", cascade.ComputedValue("width")); |
| } |
| |
| TEST_F(StyleCascadeTest, AnimateLogicalProperty) { |
| // We don't support smooth interpolation of css-logical properties yet, |
| // so this test uses a paused animation at t=0. |
| // TODO(crbug.com/865579): Support animations of css-logical properties |
| |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { margin-inline-start: 10px; } |
| to { margin-inline-start: 20px; } |
| } |
| )HTML"); |
| |
| TestCascade cascade(GetDocument()); |
| |
| cascade.Add("margin-left:1000px"); |
| cascade.Add("animation:test 1s linear paused"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-left")); |
| EXPECT_EQ("10px", cascade.ComputedValue("margin-left")); |
| } |
| |
| TEST_F(StyleCascadeTest, AnimateLogicalPropertyWithLookup) { |
| // We don't support smooth interpolation of css-logical properties yet, |
| // so this test uses a paused animation at t=0. |
| // TODO(crbug.com/865579): Support animations of css-logical properties |
| |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { margin-inline-start: 10px; } |
| to { margin-inline-start: 20px; } |
| } |
| )HTML"); |
| |
| TestCascade cascade(GetDocument()); |
| |
| cascade.Add("margin-left:1000px"); |
| cascade.Add("animation:test 1s linear paused"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.ApplySingle(GetCSSPropertyMarginLeft()); |
| |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-left")); |
| EXPECT_EQ("10px", cascade.ComputedValue("margin-left")); |
| } |
| |
| TEST_F(StyleCascadeTest, AuthorImportantWinOverAnimations) { |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { width: 10px; height: 10px; } |
| to { width: 20px; height: 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.Add("width:40px"); |
| cascade.Add("height:40px !important"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("width")); |
| EXPECT_EQ(CascadeOrigin::kAuthor, cascade.GetOrigin("height")); |
| |
| EXPECT_EQ("15px", cascade.ComputedValue("width")); |
| EXPECT_EQ("40px", cascade.ComputedValue("height")); |
| } |
| |
| TEST_F(StyleCascadeTest, TransitionsWinOverAuthorImportant) { |
| // First, simulate an "old style". |
| TestCascade cascade1(GetDocument()); |
| cascade1.Add("width:10px !important"); |
| cascade1.Add("height:10px !important"); |
| cascade1.Add("transition:all 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 width/height. |
| TestCascade cascade2(GetDocument()); |
| cascade2.Add("width:20px !important"); |
| cascade2.Add("height:20px !important"); |
| cascade2.Add("transition:all 1s"); |
| cascade2.Apply(); |
| |
| cascade2.CalculateTransitionUpdate(); |
| cascade2.Apply(); |
| |
| EXPECT_EQ(CascadeOrigin::kTransition, |
| cascade2.GetPriority("width").GetOrigin()); |
| EXPECT_EQ(CascadeOrigin::kTransition, |
| cascade2.GetPriority("height").GetOrigin()); |
| } |
| |
| 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.Add("--x", "2em"); |
| cascade.Add("width", "10em"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| 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.Add("--from", "10px"); |
| cascade.Add("--to", "20px"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| 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(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-top")); |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-right")); |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-bottom")); |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-left")); |
| |
| 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, AnimatedVisitedImportantOverride) { |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { background-color: rgb(100, 100, 100); } |
| to { background-color: rgb(200, 200, 200); } |
| } |
| )HTML"); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.State().Style()->SetInsideLink(EInsideLink::kInsideVisitedLink); |
| |
| cascade.Add(ParseDeclarationBlock("background-color:red !important"), |
| CascadeOrigin::kAuthor, CSSSelector::kMatchVisited); |
| cascade.Add("animation-name:test"); |
| cascade.Add("animation-duration:10s"); |
| cascade.Add("animation-timing-function:linear"); |
| cascade.Add("animation-delay:-5s"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| EXPECT_EQ("rgb(150, 150, 150)", cascade.ComputedValue("background-color")); |
| |
| auto style = cascade.TakeStyle(); |
| |
| style->SetInsideLink(EInsideLink::kInsideVisitedLink); |
| EXPECT_EQ(Color(255, 0, 0), |
| style->VisitedDependentColor(GetCSSPropertyBackgroundColor())); |
| |
| style->SetInsideLink(EInsideLink::kNotInsideLink); |
| EXPECT_EQ(Color(150, 150, 150), |
| style->VisitedDependentColor(GetCSSPropertyBackgroundColor())); |
| } |
| |
| TEST_F(StyleCascadeTest, AnimatedVisitedHighPrio) { |
| AppendSheet(R"HTML( |
| @keyframes test { |
| from { color: rgb(100, 100, 100); } |
| to { color: rgb(200, 200, 200); } |
| } |
| )HTML"); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.Add("color:red"); |
| cascade.Add("animation:test 10s -5s linear"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| EXPECT_EQ("rgb(150, 150, 150)", cascade.ComputedValue("color")); |
| |
| auto style = cascade.TakeStyle(); |
| |
| style->SetInsideLink(EInsideLink::kInsideVisitedLink); |
| EXPECT_EQ(Color(150, 150, 150), |
| style->VisitedDependentColor(GetCSSPropertyColor())); |
| |
| style->SetInsideLink(EInsideLink::kNotInsideLink); |
| EXPECT_EQ(Color(150, 150, 150), |
| style->VisitedDependentColor(GetCSSPropertyColor())); |
| } |
| |
| 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.Add("--from", "10px"); |
| cascade.Add("--to", "20px"); |
| cascade.Apply(); |
| |
| cascade.CalculateAnimationUpdate(); |
| cascade.Apply(); |
| |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-top")); |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-right")); |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-bottom")); |
| EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-left")); |
| |
| 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, ZoomCascadeOrder) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("zoom:200%", CascadeOrigin::kUserAgent); |
| cascade.Add("zoom:normal", CascadeOrigin::kUserAgent); |
| cascade.Apply(); |
| |
| EXPECT_EQ(1.0f, cascade.TakeStyle()->EffectiveZoom()); |
| } |
| |
| TEST_F(StyleCascadeTest, ZoomVsAll) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("zoom:200%", CascadeOrigin::kUserAgent); |
| cascade.Add("all:initial"); |
| cascade.Apply(); |
| |
| EXPECT_EQ(1.0f, cascade.TakeStyle()->EffectiveZoom()); |
| } |
| |
| TEST_F(StyleCascadeTest, ZoomReversedCascadeOrder) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("zoom:normal", CascadeOrigin::kUserAgent); |
| cascade.Add("zoom:200%", CascadeOrigin::kUserAgent); |
| cascade.Apply(); |
| |
| EXPECT_EQ(2.0f, cascade.TakeStyle()->EffectiveZoom()); |
| } |
| |
| TEST_F(StyleCascadeTest, ZoomImportant) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("zoom:200% !important", CascadeOrigin::kUserAgent); |
| cascade.Add("zoom:normal", CascadeOrigin::kAuthor); |
| 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 !important", Origin::kAuthor); |
| 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, RubyPositionCascadeOrder) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("ruby-position", "over"); |
| cascade.Add("-webkit-ruby-position", "after"); |
| cascade.Apply(); |
| |
| EXPECT_EQ("under", cascade.ComputedValue("ruby-position")); |
| EXPECT_EQ("after", cascade.ComputedValue("-webkit-ruby-position")); |
| } |
| |
| TEST_F(StyleCascadeTest, RubyPositionReverseCascadeOrder) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("-webkit-ruby-position", "after"); |
| cascade.Add("ruby-position", "over"); |
| cascade.Apply(); |
| |
| EXPECT_EQ("over", cascade.ComputedValue("ruby-position")); |
| EXPECT_EQ("before", cascade.ComputedValue("-webkit-ruby-position")); |
| } |
| |
| TEST_F(StyleCascadeTest, RubyPositionSurrogateCanCascadeAsOriginal) { |
| // Note: ruby-position is defined as the surrogate, and -webkit-ruby-position |
| // is the original. |
| ASSERT_TRUE(GetCSSPropertyRubyPosition().IsSurrogate()); |
| ASSERT_FALSE(GetCSSPropertyWebkitRubyPosition().IsSurrogate()); |
| |
| const struct { |
| CSSValueID specified; |
| const char* webkit_expected; |
| const char* unprefixed_expected; |
| } tests[] = { |
| {CSSValueID::kBefore, "before", "over"}, |
| {CSSValueID::kAfter, "after", "under"}, |
| {CSSValueID::kOver, "before", "over"}, |
| {CSSValueID::kUnder, "after", "under"}, |
| }; |
| |
| for (const auto& test : tests) { |
| TestCascade cascade(GetDocument()); |
| auto* set = |
| MakeGarbageCollected<MutableCSSPropertyValueSet>(kHTMLStandardMode); |
| set->SetProperty(CSSPropertyID::kWebkitRubyPosition, |
| *CSSIdentifierValue::Create(test.specified)); |
| cascade.Add(set); |
| cascade.Apply(); |
| EXPECT_EQ(test.unprefixed_expected, cascade.ComputedValue("ruby-position")); |
| EXPECT_EQ(test.webkit_expected, |
| cascade.ComputedValue("-webkit-ruby-position")); |
| } |
| } |
| |
| TEST_F(StyleCascadeTest, TextOrientationPriority) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("text-orientation:upright !important"); |
| cascade.Add("-webkit-text-orientation:sideways"); |
| cascade.Apply(); |
| |
| EXPECT_EQ("upright", cascade.ComputedValue("text-orientation")); |
| EXPECT_EQ("upright", cascade.ComputedValue("-webkit-text-orientation")); |
| } |
| |
| TEST_F(StyleCascadeTest, TextOrientationRevert) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("text-orientation:upright", CascadeOrigin::kUserAgent); |
| cascade.Add("-webkit-text-orientation:mixed"); |
| cascade.Add("-webkit-text-orientation:revert"); |
| cascade.Apply(); |
| |
| EXPECT_EQ("upright", cascade.ComputedValue("text-orientation")); |
| EXPECT_EQ("upright", cascade.ComputedValue("-webkit-text-orientation")); |
| } |
| |
| TEST_F(StyleCascadeTest, TextOrientationLegacyKeyword) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("-webkit-text-orientation:vertical-right"); |
| cascade.Apply(); |
| |
| EXPECT_EQ("mixed", cascade.ComputedValue("text-orientation")); |
| EXPECT_EQ("vertical-right", |
| cascade.ComputedValue("-webkit-text-orientation")); |
| } |
| |
| TEST_F(StyleCascadeTest, WebkitBorderImageCascadeOrder) { |
| String gradient1("linear-gradient(rgb(0, 0, 0), rgb(0, 128, 0))"); |
| String gradient2("linear-gradient(rgb(0, 0, 0), rgb(0, 200, 0))"); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.Add("-webkit-border-image", gradient1 + " round 40 / 10px / 20px", |
| Origin::kAuthor); |
| cascade.Add("border-image-source", gradient2, Origin::kAuthor); |
| cascade.Add("border-image-slice", "20", Origin::kAuthor); |
| cascade.Add("border-image-width", "6px", Origin::kAuthor); |
| cascade.Add("border-image-outset", "4px", Origin::kAuthor); |
| cascade.Add("border-image-repeat", "space", Origin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ(gradient2, cascade.ComputedValue("border-image-source")); |
| EXPECT_EQ("20", cascade.ComputedValue("border-image-slice")); |
| EXPECT_EQ("6px", cascade.ComputedValue("border-image-width")); |
| EXPECT_EQ("4px", cascade.ComputedValue("border-image-outset")); |
| EXPECT_EQ("space", cascade.ComputedValue("border-image-repeat")); |
| } |
| |
| TEST_F(StyleCascadeTest, WebkitBorderImageReverseCascadeOrder) { |
| String gradient1("linear-gradient(rgb(0, 0, 0), rgb(0, 128, 0))"); |
| String gradient2("linear-gradient(rgb(0, 0, 0), rgb(0, 200, 0))"); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.Add("border-image-source", gradient2, Origin::kAuthor); |
| cascade.Add("border-image-slice", "20", Origin::kAuthor); |
| cascade.Add("border-image-width", "6px", Origin::kAuthor); |
| cascade.Add("border-image-outset", "4px", Origin::kAuthor); |
| cascade.Add("border-image-repeat", "space", Origin::kAuthor); |
| cascade.Add("-webkit-border-image", gradient1 + " round 40 / 10px / 20px", |
| Origin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ(gradient1, cascade.ComputedValue("border-image-source")); |
| EXPECT_EQ("40 fill", cascade.ComputedValue("border-image-slice")); |
| EXPECT_EQ("10px", cascade.ComputedValue("border-image-width")); |
| EXPECT_EQ("20px", cascade.ComputedValue("border-image-outset")); |
| EXPECT_EQ("round", cascade.ComputedValue("border-image-repeat")); |
| } |
| |
| TEST_F(StyleCascadeTest, WebkitBorderImageMixedOrder) { |
| String gradient1("linear-gradient(rgb(0, 0, 0), rgb(0, 128, 0))"); |
| String gradient2("linear-gradient(rgb(0, 0, 0), rgb(0, 200, 0))"); |
| |
| TestCascade cascade(GetDocument()); |
| cascade.Add("border-image-source", gradient2, Origin::kAuthor); |
| cascade.Add("border-image-width", "6px", Origin::kAuthor); |
| cascade.Add("-webkit-border-image", gradient1 + " round 40 / 10px / 20px", |
| Origin::kAuthor); |
| cascade.Add("border-image-slice", "20", Origin::kAuthor); |
| cascade.Add("border-image-outset", "4px", Origin::kAuthor); |
| cascade.Add("border-image-repeat", "space", Origin::kAuthor); |
| cascade.Apply(); |
| |
| EXPECT_EQ(gradient1, cascade.ComputedValue("border-image-source")); |
| EXPECT_EQ("20", cascade.ComputedValue("border-image-slice")); |
| EXPECT_EQ("10px", cascade.ComputedValue("border-image-width")); |
| EXPECT_EQ("4px", cascade.ComputedValue("border-image-outset")); |
| EXPECT_EQ("space", cascade.ComputedValue("border-image-repeat")); |
| } |
| |
| TEST_F(StyleCascadeTest, InitialDirection) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("margin-inline-start:10px"); |
| cascade.Add("margin-inline-end:20px"); |
| cascade.Apply(); |
| |
| EXPECT_EQ("10px", cascade.ComputedValue("margin-left")); |
| EXPECT_EQ("20px", cascade.ComputedValue("margin-right")); |
| } |
| |
| TEST_F(StyleCascadeTest, NonInitialDirection) { |
| TestCascade cascade(GetDocument()); |
| cascade.Add("margin-inline-start:10px"); |
| cascade.Add("margin-inline-end:20px"); |
| cascade.Add("direction:rtl"); |
| cascade.Apply(); |
| |
| EXPECT_EQ("20px", cascade.ComputedValue("margin-left")); |
| EXPECT_EQ("10px", cascade.ComputedValue("margin-right")); |
| } |
| |