blob: 919e9ae5dd2c62b89e8f460f692eb9cfeb4b693f [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/css/StyleEngine.h"
#include <memory>
#include "bindings/core/v8/V8BindingForCore.h"
#include "core/css/CSSRuleList.h"
#include "core/css/CSSStyleRule.h"
#include "core/css/CSSStyleSheet.h"
#include "core/css/StyleSheetContents.h"
#include "core/css/parser/CSSParserContext.h"
#include "core/dom/Document.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/dom/ShadowRootInit.h"
#include "core/dom/ViewportDescription.h"
#include "core/frame/FrameTestHelpers.h"
#include "core/frame/LocalFrameView.h"
#include "core/html/HTMLElement.h"
#include "core/html/HTMLStyleElement.h"
#include "core/testing/DummyPageHolder.h"
#include "platform/heap/Heap.h"
#include "public/platform/WebFloatRect.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
class StyleEngineTest : public ::testing::Test {
protected:
void SetUp() override;
Document& GetDocument() { return dummy_page_holder_->GetDocument(); }
StyleEngine& GetStyleEngine() { return GetDocument().GetStyleEngine(); }
bool IsDocumentStyleSheetCollectionClean() {
return !GetStyleEngine().ShouldUpdateDocumentStyleSheetCollection();
}
enum RuleSetInvalidation {
kRuleSetInvalidationsScheduled,
kRuleSetInvalidationFullRecalc
};
RuleSetInvalidation ScheduleInvalidationsForRules(TreeScope&,
const String& css_text);
private:
std::unique_ptr<DummyPageHolder> dummy_page_holder_;
};
void StyleEngineTest::SetUp() {
dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
}
StyleEngineTest::RuleSetInvalidation
StyleEngineTest::ScheduleInvalidationsForRules(TreeScope& tree_scope,
const String& css_text) {
StyleSheetContents* sheet =
StyleSheetContents::Create(CSSParserContext::Create(kHTMLStandardMode));
sheet->ParseString(css_text);
HeapHashSet<Member<RuleSet>> rule_sets;
RuleSet& rule_set = sheet->EnsureRuleSet(MediaQueryEvaluator(),
kRuleHasDocumentSecurityOrigin);
rule_set.CompactRulesIfNeeded();
if (rule_set.NeedsFullRecalcForRuleSetInvalidation())
return kRuleSetInvalidationFullRecalc;
rule_sets.insert(&rule_set);
GetStyleEngine().ScheduleInvalidationsForRuleSets(tree_scope, rule_sets);
return kRuleSetInvalidationsScheduled;
}
TEST_F(StyleEngineTest, DocumentDirtyAfterInject) {
StyleSheetContents* parsed_sheet =
StyleSheetContents::Create(CSSParserContext::Create(GetDocument()));
parsed_sheet->ParseString("div {}");
GetStyleEngine().AddUserSheet(parsed_sheet);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_TRUE(IsDocumentStyleSheetCollectionClean());
}
TEST_F(StyleEngineTest, AnalyzedInject) {
GetDocument().body()->SetInnerHTMLFromString(
"<style>"
" #t1 { color: red !important }"
" #t2 { color: black }"
" #t4 { animation-name: dummy-animation }"
"</style>"
"<div id='t1'>Green</div>"
"<div id='t2'>White</div>"
"<div id='t3' style='color: black !important'>White</div>"
"<div id='t4'>I animate!</div>"
"<div></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
Element* t1 = GetDocument().getElementById("t1");
Element* t2 = GetDocument().getElementById("t2");
Element* t3 = GetDocument().getElementById("t3");
ASSERT_TRUE(t1);
ASSERT_TRUE(t2);
ASSERT_TRUE(t3);
ASSERT_TRUE(t1->GetComputedStyle());
ASSERT_TRUE(t2->GetComputedStyle());
ASSERT_TRUE(t3->GetComputedStyle());
EXPECT_EQ(MakeRGB(255, 0, 0),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
EXPECT_EQ(MakeRGB(0, 0, 0),
t2->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
EXPECT_EQ(MakeRGB(0, 0, 0),
t3->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
const unsigned initial_count = GetStyleEngine().StyleForElementCount();
StyleSheetContents* green_parsed_sheet =
StyleSheetContents::Create(CSSParserContext::Create(GetDocument()));
green_parsed_sheet->ParseString(
"#t1 { color: green !important }"
"#t2 { color: white !important }"
"#t3 { color: white }");
WebStyleSheetId green_id =
GetStyleEngine().AddUserSheet(green_parsed_sheet);
EXPECT_EQ(1u, green_id);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(3u, GetStyleEngine().StyleForElementCount() - initial_count);
ASSERT_TRUE(t1->GetComputedStyle());
ASSERT_TRUE(t2->GetComputedStyle());
ASSERT_TRUE(t3->GetComputedStyle());
// Important user rules override both regular and important author rules.
EXPECT_EQ(MakeRGB(0, 128, 0),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
EXPECT_EQ(MakeRGB(255, 255, 255),
t2->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
EXPECT_EQ(MakeRGB(0, 0, 0),
t3->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
StyleSheetContents* blue_parsed_sheet =
StyleSheetContents::Create(CSSParserContext::Create(GetDocument()));
blue_parsed_sheet->ParseString(
"#t1 { color: blue !important }"
"#t2 { color: silver }"
"#t3 { color: silver !important }");
WebStyleSheetId blue_id =
GetStyleEngine().AddUserSheet(blue_parsed_sheet);
EXPECT_EQ(2u, blue_id);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(6u, GetStyleEngine().StyleForElementCount() - initial_count);
ASSERT_TRUE(t1->GetComputedStyle());
ASSERT_TRUE(t2->GetComputedStyle());
ASSERT_TRUE(t3->GetComputedStyle());
// Only important user rules override previously set important user rules.
EXPECT_EQ(MakeRGB(0, 0, 255),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
EXPECT_EQ(MakeRGB(255, 255, 255),
t2->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
// Important user rules override inline author rules.
EXPECT_EQ(MakeRGB(192, 192, 192),
t3->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
GetStyleEngine().RemoveUserSheet(green_id);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(9u, GetStyleEngine().StyleForElementCount() - initial_count);
ASSERT_TRUE(t1->GetComputedStyle());
ASSERT_TRUE(t2->GetComputedStyle());
ASSERT_TRUE(t3->GetComputedStyle());
// Regular user rules do not override author rules.
EXPECT_EQ(MakeRGB(0, 0, 255),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
EXPECT_EQ(MakeRGB(0, 0, 0),
t2->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
EXPECT_EQ(MakeRGB(192, 192, 192),
t3->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
GetStyleEngine().RemoveUserSheet(blue_id);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(12u, GetStyleEngine().StyleForElementCount() - initial_count);
ASSERT_TRUE(t1->GetComputedStyle());
ASSERT_TRUE(t2->GetComputedStyle());
ASSERT_TRUE(t3->GetComputedStyle());
EXPECT_EQ(MakeRGB(255, 0, 0),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
EXPECT_EQ(MakeRGB(0, 0, 0),
t2->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
EXPECT_EQ(MakeRGB(0, 0, 0),
t3->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
// @keyframes rules
Element* t4 = GetDocument().getElementById("t4");
ASSERT_TRUE(t4);
// There's no @keyframes rule named dummy-animation
ASSERT_FALSE(GetStyleEngine().Resolver()->FindKeyframesRule(
t4, AtomicString("dummy-animation")));
StyleSheetContents* keyframes_parsed_sheet =
StyleSheetContents::Create(CSSParserContext::Create(GetDocument()));
keyframes_parsed_sheet->ParseString("@keyframes dummy-animation { from {} }");
WebStyleSheetId keyframes_id =
GetStyleEngine().AddUserSheet(keyframes_parsed_sheet);
GetDocument().View()->UpdateAllLifecyclePhases();
// After injecting the style sheet, a @keyframes rule named dummy-animation
// is found with one keyframe.
StyleRuleKeyframes* keyframes =
GetStyleEngine().Resolver()->FindKeyframesRule(
t4, AtomicString("dummy-animation"));
ASSERT_TRUE(keyframes);
EXPECT_EQ(1u, keyframes->Keyframes().size());
HTMLStyleElement* style_element = HTMLStyleElement::Create(
GetDocument(), false);
style_element->SetInnerHTMLFromString(
"@keyframes dummy-animation { from {} to {} }");
GetDocument().body()->AppendChild(style_element);
GetDocument().View()->UpdateAllLifecyclePhases();
// Author @keyframes rules take precedence; now there are two keyframes (from
// and to).
keyframes = GetStyleEngine().Resolver()->FindKeyframesRule(
t4, AtomicString("dummy-animation"));
ASSERT_TRUE(keyframes);
EXPECT_EQ(2u, keyframes->Keyframes().size());
GetDocument().body()->RemoveChild(style_element);
GetDocument().View()->UpdateAllLifecyclePhases();
keyframes = GetStyleEngine().Resolver()->FindKeyframesRule(
t4, AtomicString("dummy-animation"));
ASSERT_TRUE(keyframes);
EXPECT_EQ(1u, keyframes->Keyframes().size());
GetStyleEngine().RemoveUserSheet(keyframes_id);
GetDocument().View()->UpdateAllLifecyclePhases();
// Injected @keyframes rules are no longer available once removed.
ASSERT_FALSE(GetStyleEngine().Resolver()->FindKeyframesRule(
t4, AtomicString("dummy-animation")));
}
TEST_F(StyleEngineTest, TextToSheetCache) {
HTMLStyleElement* element = HTMLStyleElement::Create(GetDocument(), false);
String sheet_text("div {}");
TextPosition min_pos = TextPosition::MinimumPosition();
StyleEngineContext context;
CSSStyleSheet* sheet1 =
GetStyleEngine().CreateSheet(*element, sheet_text, min_pos, context);
// Check that the first sheet is not using a cached StyleSheetContents.
EXPECT_FALSE(sheet1->Contents()->IsUsedFromTextCache());
CSSStyleSheet* sheet2 =
GetStyleEngine().CreateSheet(*element, sheet_text, min_pos, context);
// Check that the second sheet uses the cached StyleSheetContents for the
// first.
EXPECT_EQ(sheet1->Contents(), sheet2->Contents());
EXPECT_TRUE(sheet2->Contents()->IsUsedFromTextCache());
sheet1 = nullptr;
sheet2 = nullptr;
element = nullptr;
// Garbage collection should clear the weak reference in the
// StyleSheetContents cache.
ThreadState::Current()->CollectAllGarbage();
element = HTMLStyleElement::Create(GetDocument(), false);
sheet1 = GetStyleEngine().CreateSheet(*element, sheet_text, min_pos, context);
// Check that we did not use a cached StyleSheetContents after the garbage
// collection.
EXPECT_FALSE(sheet1->Contents()->IsUsedFromTextCache());
}
TEST_F(StyleEngineTest, RuleSetInvalidationTypeSelectors) {
GetDocument().body()->SetInnerHTMLFromString(
"<div>"
" <span></span>"
" <div></div>"
"</div>"
"<b></b><b></b><b></b><b></b>"
"<i id=i>"
" <i>"
" <b></b>"
" </i>"
"</i>");
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned before_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(kRuleSetInvalidationsScheduled,
ScheduleInvalidationsForRules(GetDocument(),
"span { background: green}"));
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(1u, after_count - before_count);
before_count = after_count;
EXPECT_EQ(kRuleSetInvalidationsScheduled,
ScheduleInvalidationsForRules(GetDocument(),
"body div { background: green}"));
GetDocument().View()->UpdateAllLifecyclePhases();
after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(2u, after_count - before_count);
EXPECT_EQ(kRuleSetInvalidationFullRecalc,
ScheduleInvalidationsForRules(GetDocument(),
"div * { background: green}"));
GetDocument().View()->UpdateAllLifecyclePhases();
before_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(kRuleSetInvalidationsScheduled,
ScheduleInvalidationsForRules(GetDocument(),
"#i b { background: green}"));
GetDocument().View()->UpdateAllLifecyclePhases();
after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(1u, after_count - before_count);
}
TEST_F(StyleEngineTest, RuleSetInvalidationCustomPseudo) {
GetDocument().body()->SetInnerHTMLFromString(
"<style>progress { -webkit-appearance:none }</style>"
"<progress></progress>"
"<div></div><div></div><div></div><div></div><div></div><div></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned before_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(ScheduleInvalidationsForRules(
GetDocument(), "::-webkit-progress-bar { background: green }"),
kRuleSetInvalidationsScheduled);
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(3u, after_count - before_count);
}
TEST_F(StyleEngineTest, RuleSetInvalidationHost) {
GetDocument().body()->SetInnerHTMLFromString(
"<div id=nohost></div><div id=host></div>");
Element* host = GetDocument().getElementById("host");
ASSERT_TRUE(host);
ShadowRootInit init;
init.setMode("open");
ShadowRoot* shadow_root =
host->attachShadow(ToScriptStateForMainWorld(GetDocument().GetFrame()),
init, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(shadow_root);
shadow_root->SetInnerHTMLFromString("<div></div><div></div><div></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned before_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(ScheduleInvalidationsForRules(
*shadow_root, ":host(#nohost), #nohost { background: green}"),
kRuleSetInvalidationsScheduled);
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(0u, after_count - before_count);
before_count = after_count;
EXPECT_EQ(ScheduleInvalidationsForRules(*shadow_root,
":host(#host) { background: green}"),
kRuleSetInvalidationsScheduled);
GetDocument().View()->UpdateAllLifecyclePhases();
after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(1u, after_count - before_count);
EXPECT_EQ(ScheduleInvalidationsForRules(*shadow_root,
":host(div) { background: green}"),
kRuleSetInvalidationsScheduled);
EXPECT_EQ(ScheduleInvalidationsForRules(*shadow_root,
":host(*) { background: green}"),
kRuleSetInvalidationFullRecalc);
EXPECT_EQ(ScheduleInvalidationsForRules(
*shadow_root, ":host(*) :hover { background: green}"),
kRuleSetInvalidationFullRecalc);
}
TEST_F(StyleEngineTest, RuleSetInvalidationSlotted) {
GetDocument().body()->SetInnerHTMLFromString(
"<div id=host>"
" <span slot=other class=s1></span>"
" <span class=s2></span>"
" <span class=s1></span>"
" <span></span>"
"</div>");
Element* host = GetDocument().getElementById("host");
ASSERT_TRUE(host);
ShadowRootInit init;
init.setMode("open");
ShadowRoot* shadow_root =
host->attachShadow(ToScriptStateForMainWorld(GetDocument().GetFrame()),
init, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(shadow_root);
shadow_root->SetInnerHTMLFromString("<slot name=other></slot><slot></slot>");
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned before_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(ScheduleInvalidationsForRules(
*shadow_root, "::slotted(.s1) { background: green}"),
kRuleSetInvalidationsScheduled);
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(4u, after_count - before_count);
before_count = after_count;
EXPECT_EQ(ScheduleInvalidationsForRules(*shadow_root,
"::slotted(*) { background: green}"),
kRuleSetInvalidationsScheduled);
GetDocument().View()->UpdateAllLifecyclePhases();
after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(4u, after_count - before_count);
}
TEST_F(StyleEngineTest, RuleSetInvalidationHostContext) {
GetDocument().body()->SetInnerHTMLFromString("<div id=host></div>");
Element* host = GetDocument().getElementById("host");
ASSERT_TRUE(host);
ShadowRootInit init;
init.setMode("open");
ShadowRoot* shadow_root =
host->attachShadow(ToScriptStateForMainWorld(GetDocument().GetFrame()),
init, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(shadow_root);
shadow_root->SetInnerHTMLFromString(
"<div></div><div class=a></div><div></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned before_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(
ScheduleInvalidationsForRules(
*shadow_root, ":host-context(.nomatch) .a { background: green}"),
kRuleSetInvalidationsScheduled);
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(1u, after_count - before_count);
EXPECT_EQ(ScheduleInvalidationsForRules(
*shadow_root, ":host-context(:hover) { background: green}"),
kRuleSetInvalidationFullRecalc);
EXPECT_EQ(ScheduleInvalidationsForRules(
*shadow_root, ":host-context(#host) { background: green}"),
kRuleSetInvalidationFullRecalc);
}
TEST_F(StyleEngineTest, RuleSetInvalidationV0BoundaryCrossing) {
GetDocument().body()->SetInnerHTMLFromString("<div id=host></div>");
Element* host = GetDocument().getElementById("host");
ASSERT_TRUE(host);
ShadowRootInit init;
init.setMode("open");
ShadowRoot* shadow_root =
host->attachShadow(ToScriptStateForMainWorld(GetDocument().GetFrame()),
init, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(shadow_root);
shadow_root->SetInnerHTMLFromString(
"<div></div><div class=a></div><div></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(ScheduleInvalidationsForRules(
*shadow_root, ".a ::content span { background: green}"),
kRuleSetInvalidationFullRecalc);
if (RuntimeEnabledFeatures::DeepCombinatorInCSSDynamicProfileEnabled()) {
EXPECT_EQ(ScheduleInvalidationsForRules(
*shadow_root, ".a /deep/ span { background: green}"),
kRuleSetInvalidationFullRecalc);
EXPECT_EQ(ScheduleInvalidationsForRules(
*shadow_root, ".a::shadow span { background: green}"),
kRuleSetInvalidationFullRecalc);
}
}
TEST_F(StyleEngineTest, HasViewportDependentMediaQueries) {
GetDocument().body()->SetInnerHTMLFromString(
"<style>div {}</style>"
"<style id='sheet' media='(min-width: 200px)'>"
" div {}"
"</style>");
Element* style_element = GetDocument().getElementById("sheet");
for (unsigned i = 0; i < 10; i++) {
GetDocument().body()->RemoveChild(style_element);
GetDocument().View()->UpdateAllLifecyclePhases();
GetDocument().body()->AppendChild(style_element);
GetDocument().View()->UpdateAllLifecyclePhases();
}
EXPECT_TRUE(
GetDocument().GetStyleEngine().HasViewportDependentMediaQueries());
GetDocument().body()->RemoveChild(style_element);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(
GetDocument().GetStyleEngine().HasViewportDependentMediaQueries());
}
TEST_F(StyleEngineTest, StyleMediaAttributeStyleChange) {
GetDocument().body()->SetInnerHTMLFromString(
"<style id='s1' media='(max-width: 1px)'>#t1 { color: green }</style>"
"<div id='t1'>Green</div><div></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
Element* t1 = GetDocument().getElementById("t1");
ASSERT_TRUE(t1);
ASSERT_TRUE(t1->GetComputedStyle());
EXPECT_EQ(MakeRGB(0, 0, 0),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
unsigned before_count = GetStyleEngine().StyleForElementCount();
Element* s1 = GetDocument().getElementById("s1");
s1->setAttribute(blink::HTMLNames::mediaAttr, "(max-width: 2000px)");
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(1u, after_count - before_count);
ASSERT_TRUE(t1->GetComputedStyle());
EXPECT_EQ(MakeRGB(0, 128, 0),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
}
TEST_F(StyleEngineTest, StyleMediaAttributeNoStyleChange) {
GetDocument().body()->SetInnerHTMLFromString(
"<style id='s1' media='(max-width: 1000px)'>#t1 { color: green }</style>"
"<div id='t1'>Green</div><div></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
Element* t1 = GetDocument().getElementById("t1");
ASSERT_TRUE(t1);
ASSERT_TRUE(t1->GetComputedStyle());
EXPECT_EQ(MakeRGB(0, 128, 0),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
unsigned before_count = GetStyleEngine().StyleForElementCount();
Element* s1 = GetDocument().getElementById("s1");
s1->setAttribute(blink::HTMLNames::mediaAttr, "(max-width: 2000px)");
GetDocument().View()->UpdateAllLifecyclePhases();
unsigned after_count = GetStyleEngine().StyleForElementCount();
EXPECT_EQ(0u, after_count - before_count);
ASSERT_TRUE(t1->GetComputedStyle());
EXPECT_EQ(MakeRGB(0, 128, 0),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
}
TEST_F(StyleEngineTest, ModifyStyleRuleMatchedPropertiesCache) {
// Test that the MatchedPropertiesCache is cleared when a StyleRule is
// modified. The MatchedPropertiesCache caches results based on
// StylePropertySet pointers. When a mutable StylePropertySet is modified,
// the pointer doesn't change, yet the declarations do.
GetDocument().body()->SetInnerHTMLFromString(
"<style id='s1'>#t1 { color: blue }</style>"
"<div id='t1'>Green</div>");
GetDocument().View()->UpdateAllLifecyclePhases();
Element* t1 = GetDocument().getElementById("t1");
ASSERT_TRUE(t1);
ASSERT_TRUE(t1->GetComputedStyle());
EXPECT_EQ(MakeRGB(0, 0, 255),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
CSSStyleSheet* sheet = ToCSSStyleSheet(GetDocument().StyleSheets().item(0));
ASSERT_TRUE(sheet);
ASSERT_TRUE(sheet->cssRules());
CSSStyleRule* style_rule = ToCSSStyleRule(sheet->cssRules()->item(0));
ASSERT_TRUE(style_rule);
ASSERT_TRUE(style_rule->style());
// Modify the StylePropertySet once to make it a mutable set. Subsequent
// modifications will not change the StylePropertySet pointer and cache hash
// value will be the same.
style_rule->style()->setProperty("color", "red", "", ASSERT_NO_EXCEPTION);
GetDocument().View()->UpdateAllLifecyclePhases();
ASSERT_TRUE(t1->GetComputedStyle());
EXPECT_EQ(MakeRGB(255, 0, 0),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
style_rule->style()->setProperty("color", "green", "", ASSERT_NO_EXCEPTION);
GetDocument().View()->UpdateAllLifecyclePhases();
ASSERT_TRUE(t1->GetComputedStyle());
EXPECT_EQ(MakeRGB(0, 128, 0),
t1->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
}
TEST_F(StyleEngineTest, ScheduleInvalidationAfterSubtreeRecalc) {
GetDocument().body()->SetInnerHTMLFromString(
"<style id='s1'>"
" .t1 span { color: green }"
" .t2 span { color: green }"
"</style>"
"<style id='s2'>div { background: lime }</style>"
"<div id='t1'></div>"
"<div id='t2'></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
Element* t1 = GetDocument().getElementById("t1");
Element* t2 = GetDocument().getElementById("t2");
ASSERT_TRUE(t1);
ASSERT_TRUE(t2);
// Sanity test.
t1->setAttribute(blink::HTMLNames::classAttr, "t1");
EXPECT_FALSE(GetDocument().NeedsStyleInvalidation());
EXPECT_TRUE(GetDocument().ChildNeedsStyleInvalidation());
EXPECT_TRUE(t1->NeedsStyleInvalidation());
GetDocument().View()->UpdateAllLifecyclePhases();
// platformColorsChanged() triggers SubtreeStyleChange on document(). If that
// for some reason should change, this test will start failing and the
// SubtreeStyleChange must be set another way.
// Calling setNeedsStyleRecalc() explicitly with an arbitrary reason instead
// requires us to CORE_EXPORT the reason strings.
GetStyleEngine().PlatformColorsChanged();
// Check that no invalidations sets are scheduled when the document node is
// already SubtreeStyleChange.
t2->setAttribute(blink::HTMLNames::classAttr, "t2");
EXPECT_FALSE(GetDocument().NeedsStyleInvalidation());
EXPECT_FALSE(GetDocument().ChildNeedsStyleInvalidation());
GetDocument().View()->UpdateAllLifecyclePhases();
HTMLStyleElement* s2 = ToHTMLStyleElement(GetDocument().getElementById("s2"));
ASSERT_TRUE(s2);
s2->setDisabled(true);
GetStyleEngine().UpdateActiveStyle();
EXPECT_FALSE(GetDocument().ChildNeedsStyleInvalidation());
EXPECT_TRUE(GetDocument().NeedsStyleInvalidation());
GetDocument().View()->UpdateAllLifecyclePhases();
GetStyleEngine().PlatformColorsChanged();
s2->setDisabled(false);
GetStyleEngine().UpdateActiveStyle();
EXPECT_FALSE(GetDocument().ChildNeedsStyleInvalidation());
EXPECT_FALSE(GetDocument().NeedsStyleInvalidation());
GetDocument().View()->UpdateAllLifecyclePhases();
HTMLStyleElement* s1 = ToHTMLStyleElement(GetDocument().getElementById("s1"));
ASSERT_TRUE(s1);
s1->setDisabled(true);
GetStyleEngine().UpdateActiveStyle();
EXPECT_TRUE(GetDocument().ChildNeedsStyleInvalidation());
EXPECT_FALSE(GetDocument().NeedsStyleInvalidation());
EXPECT_TRUE(t1->NeedsStyleInvalidation());
EXPECT_TRUE(t2->NeedsStyleInvalidation());
GetDocument().View()->UpdateAllLifecyclePhases();
GetStyleEngine().PlatformColorsChanged();
s1->setDisabled(false);
GetStyleEngine().UpdateActiveStyle();
EXPECT_FALSE(GetDocument().ChildNeedsStyleInvalidation());
EXPECT_FALSE(GetDocument().NeedsStyleInvalidation());
EXPECT_FALSE(t1->NeedsStyleInvalidation());
EXPECT_FALSE(t2->NeedsStyleInvalidation());
}
TEST_F(StyleEngineTest, NoScheduledRuleSetInvalidationsOnNewShadow) {
GetDocument().body()->SetInnerHTMLFromString("<div id='host'></div>");
Element* host = GetDocument().getElementById("host");
ASSERT_TRUE(host);
GetDocument().View()->UpdateAllLifecyclePhases();
ShadowRootInit init;
init.setMode("open");
ShadowRoot* shadow_root =
host->attachShadow(ToScriptStateForMainWorld(GetDocument().GetFrame()),
init, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(shadow_root);
shadow_root->SetInnerHTMLFromString(
"<style>"
" span { color: green }"
" t1 { color: green }"
"</style>"
"<div id='t1'></div>"
"<span></span>");
GetStyleEngine().UpdateActiveStyle();
EXPECT_FALSE(GetDocument().ChildNeedsStyleInvalidation());
EXPECT_FALSE(GetDocument().NeedsStyleInvalidation());
}
TEST_F(StyleEngineTest, EmptyHttpEquivDefaultStyle) {
GetDocument().body()->SetInnerHTMLFromString(
"<style>div { color:pink }</style><div id=container></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(GetStyleEngine().NeedsActiveStyleUpdate());
Element* container = GetDocument().getElementById("container");
ASSERT_TRUE(container);
container->SetInnerHTMLFromString(
"<meta http-equiv='default-style' content=''>");
EXPECT_FALSE(GetStyleEngine().NeedsActiveStyleUpdate());
container->SetInnerHTMLFromString(
"<meta http-equiv='default-style' content='preferred'>");
EXPECT_TRUE(GetStyleEngine().NeedsActiveStyleUpdate());
}
TEST_F(StyleEngineTest, StyleSheetsForStyleSheetList_Document) {
GetDocument().body()->SetInnerHTMLFromString(
"<style>span { color: green }</style>");
EXPECT_TRUE(GetStyleEngine().NeedsActiveStyleUpdate());
const auto& sheet_list =
GetStyleEngine().StyleSheetsForStyleSheetList(GetDocument());
EXPECT_EQ(1u, sheet_list.size());
EXPECT_TRUE(GetStyleEngine().NeedsActiveStyleUpdate());
GetDocument().body()->SetInnerHTMLFromString(
"<style>span { color: green }</style><style>div { color: pink }</style>");
EXPECT_TRUE(GetStyleEngine().NeedsActiveStyleUpdate());
const auto& second_sheet_list =
GetStyleEngine().StyleSheetsForStyleSheetList(GetDocument());
EXPECT_EQ(2u, second_sheet_list.size());
EXPECT_TRUE(GetStyleEngine().NeedsActiveStyleUpdate());
GetStyleEngine().MarkAllTreeScopesDirty();
GetStyleEngine().StyleSheetsForStyleSheetList(GetDocument());
EXPECT_FALSE(GetStyleEngine().NeedsActiveStyleUpdate());
}
TEST_F(StyleEngineTest, StyleSheetsForStyleSheetList_ShadowRoot) {
GetDocument().body()->SetInnerHTMLFromString("<div id='host'></div>");
Element* host = GetDocument().getElementById("host");
ASSERT_TRUE(host);
GetDocument().View()->UpdateAllLifecyclePhases();
ShadowRootInit init;
init.setMode("open");
ShadowRoot* shadow_root =
host->attachShadow(ToScriptStateForMainWorld(GetDocument().GetFrame()),
init, ASSERT_NO_EXCEPTION);
ASSERT_TRUE(shadow_root);
shadow_root->SetInnerHTMLFromString("<style>span { color: green }</style>");
EXPECT_TRUE(GetStyleEngine().NeedsActiveStyleUpdate());
const auto& sheet_list =
GetStyleEngine().StyleSheetsForStyleSheetList(*shadow_root);
EXPECT_EQ(1u, sheet_list.size());
EXPECT_TRUE(GetStyleEngine().NeedsActiveStyleUpdate());
shadow_root->SetInnerHTMLFromString(
"<style>span { color: green }</style><style>div { color: pink }</style>");
EXPECT_TRUE(GetStyleEngine().NeedsActiveStyleUpdate());
const auto& second_sheet_list =
GetStyleEngine().StyleSheetsForStyleSheetList(*shadow_root);
EXPECT_EQ(2u, second_sheet_list.size());
EXPECT_TRUE(GetStyleEngine().NeedsActiveStyleUpdate());
GetStyleEngine().MarkAllTreeScopesDirty();
GetStyleEngine().StyleSheetsForStyleSheetList(*shadow_root);
EXPECT_FALSE(GetStyleEngine().NeedsActiveStyleUpdate());
}
class StyleEngineClient : public FrameTestHelpers::TestWebViewClient {
public:
StyleEngineClient() : device_scale_factor_(1.f) {}
void ConvertWindowToViewport(WebFloatRect* rect) override {
rect->x *= device_scale_factor_;
rect->y *= device_scale_factor_;
rect->width *= device_scale_factor_;
rect->height *= device_scale_factor_;
}
void set_device_scale_factor(float device_scale_factor) {
device_scale_factor_ = device_scale_factor;
}
private:
float device_scale_factor_;
};
TEST_F(StyleEngineTest, ViewportDescriptionForZoomDSF) {
StyleEngineClient client;
client.set_device_scale_factor(1.f);
FrameTestHelpers::WebViewHelper web_view_helper;
WebViewImpl* web_view_impl =
web_view_helper.Initialize(nullptr, &client, nullptr, nullptr);
web_view_impl->UpdateAllLifecyclePhases();
Document* document =
ToLocalFrame(web_view_impl->GetPage()->MainFrame())->GetDocument();
float min_width =
document->GetViewportDescription().min_width.GetFloatValue();
float max_width =
document->GetViewportDescription().max_width.GetFloatValue();
float min_height =
document->GetViewportDescription().min_height.GetFloatValue();
float max_height =
document->GetViewportDescription().max_height.GetFloatValue();
const float device_scale = 3.5f;
client.set_device_scale_factor(device_scale);
web_view_impl->UpdateAllLifecyclePhases();
EXPECT_FLOAT_EQ(device_scale * min_width,
document->GetViewportDescription().min_width.GetFloatValue());
EXPECT_FLOAT_EQ(device_scale * max_width,
document->GetViewportDescription().max_width.GetFloatValue());
EXPECT_FLOAT_EQ(
device_scale * min_height,
document->GetViewportDescription().min_height.GetFloatValue());
EXPECT_FLOAT_EQ(
device_scale * max_height,
document->GetViewportDescription().max_height.GetFloatValue());
}
} // namespace blink