Commit 3857f5dc authored by Manish Jethani's avatar Manish Jethani Committed by Commit Bot

Support @font-face rules in user style sheets

@font-face rules in author style sheets are passed back to StyleEngine
via ScopedStyleResolver. With crrev.com/c/641294 we started treating
style sheets injected by extensions as user style sheets instead. Since
user style sheets apply to all scopes, they never go via
ScopedStyleResolver but are rather managed by StyleEngine directly.

crrev.com/c/641294 broke extensions using custom fonts because
StyleEngine fails to account for @font-face rules in user style sheets.

Fonts from both user and author style sheets must be maintained in the
same font cache. Even though style sheets can be added in any order,
@font-face rules in author style sheets must appear after those in user
style sheets. In order to achieve this result efficiently, we use a
"dirty" flag and refresh the font cache only once per cycle.

StyleEngine::ApplyRuleSetChanges may be called twice, once for user
style sheets (all scopes) and once for author style sheets (document
scope). If fonts have changed in user style sheets, we simply set the
dirty flag. If fonts have changed in author style sheets, we set the
dirty flag but also then refresh the font cache by first adding all the
@font-face rules from the active user style sheets and then re-adding
all the new author style sheets. This ensures that the font cache is
refreshed only once while the fonts are added in the correct order.

BUG=632009,779048

Change-Id: I58c1070af3ecae925e4afb91cbbb7cb00ee187fd
Reviewed-on: https://chromium-review.googlesource.com/743642
Commit-Queue: nainar <nainar@chromium.org>
Reviewed-by: default avatarnainar <nainar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#513154}
parent ae69109e
...@@ -275,7 +275,7 @@ void StyleEngine::WatchedSelectorsChanged() { ...@@ -275,7 +275,7 @@ void StyleEngine::WatchedSelectorsChanged() {
} }
bool StyleEngine::ShouldUpdateDocumentStyleSheetCollection() const { bool StyleEngine::ShouldUpdateDocumentStyleSheetCollection() const {
return all_tree_scopes_dirty_ || document_scope_dirty_; return all_tree_scopes_dirty_ || document_scope_dirty_ || font_cache_dirty_;
} }
bool StyleEngine::ShouldUpdateShadowTreeStyleSheetCollection() const { bool StyleEngine::ShouldUpdateShadowTreeStyleSheetCollection() const {
...@@ -523,6 +523,20 @@ void StyleEngine::ClearFontCache() { ...@@ -523,6 +523,20 @@ void StyleEngine::ClearFontCache() {
resolver_->InvalidateMatchedPropertiesCache(); resolver_->InvalidateMatchedPropertiesCache();
} }
void StyleEngine::RefreshFontCache() {
DCHECK(IsFontCacheDirty());
ClearFontCache();
// Rebuild the font cache with @font-face rules from user style sheets.
for (unsigned i = 0; i < active_user_style_sheets_.size(); ++i) {
DCHECK(active_user_style_sheets_[i].second);
AddFontFaceRules(*active_user_style_sheets_[i].second);
}
font_cache_dirty_ = false;
}
void StyleEngine::UpdateGenericFontFamilySettings() { void StyleEngine::UpdateGenericFontFamilySettings() {
// FIXME: we should not update generic font family settings when // FIXME: we should not update generic font family settings when
// document is inactive. // document is inactive.
...@@ -1148,6 +1162,13 @@ void StyleEngine::ApplyRuleSetChanges( ...@@ -1148,6 +1162,13 @@ void StyleEngine::ApplyRuleSetChanges(
tree_scope.GetScopedStyleResolver()) tree_scope.GetScopedStyleResolver())
append_all_sheets = scoped_resolver->NeedsAppendAllSheets(); append_all_sheets = scoped_resolver->NeedsAppendAllSheets();
// When the font cache is dirty we have to rebuild it and then add all the
// @font-face rules in the document scope.
if (IsFontCacheDirty()) {
DCHECK(tree_scope.RootNode().IsDocumentNode());
append_all_sheets = true;
}
if (change == kNoActiveSheetsChanged && !append_all_sheets) if (change == kNoActiveSheetsChanged && !append_all_sheets)
return; return;
} }
...@@ -1158,13 +1179,35 @@ void StyleEngine::ApplyRuleSetChanges( ...@@ -1158,13 +1179,35 @@ void StyleEngine::ApplyRuleSetChanges(
unsigned changed_rule_flags = GetRuleSetFlags(changed_rule_sets); unsigned changed_rule_flags = GetRuleSetFlags(changed_rule_sets);
bool fonts_changed = tree_scope.RootNode().IsDocumentNode() && bool fonts_changed = tree_scope.RootNode().IsDocumentNode() &&
(changed_rule_flags & kFontFaceRules); (changed_rule_flags & kFontFaceRules);
bool keyframes_changed = changed_rule_flags & kKeyframesRules;
unsigned append_start_index = 0; unsigned append_start_index = 0;
// We don't need to clear the font cache if new sheets are appended. // We don't need to mark the font cache dirty if new sheets are appended.
if (fonts_changed && change == kActiveSheetsChanged) if (fonts_changed && (invalidation_scope == kInvalidateAllScopes ||
ClearFontCache(); change == kActiveSheetsChanged)) {
MarkFontCacheDirty();
}
if (invalidation_scope == kInvalidateAllScopes) {
if (keyframes_changed) {
if (change == kActiveSheetsChanged)
ClearKeyframeRules();
for (auto it = new_style_sheets.begin();
it != new_style_sheets.end(); it++) {
DCHECK(it->second);
AddKeyframeRules(*it->second);
}
}
}
if (invalidation_scope == kInvalidateCurrentScope) { if (invalidation_scope == kInvalidateCurrentScope) {
if (IsFontCacheDirty()) {
DCHECK(tree_scope.RootNode().IsDocumentNode());
DCHECK(change != kActiveSheetsAppended || append_all_sheets);
RefreshFontCache();
}
// - If all sheets were removed, we remove the ScopedStyleResolver. // - If all sheets were removed, we remove the ScopedStyleResolver.
// - If new sheets were appended to existing ones, start appending after the // - If new sheets were appended to existing ones, start appending after the
// common prefix. // common prefix.
...@@ -1199,20 +1242,8 @@ void StyleEngine::ApplyRuleSetChanges( ...@@ -1199,20 +1242,8 @@ void StyleEngine::ApplyRuleSetChanges(
if (changed_rule_sets.IsEmpty()) if (changed_rule_sets.IsEmpty())
return; return;
if (changed_rule_flags & kKeyframesRules) { if (keyframes_changed)
if (invalidation_scope == kInvalidateAllScopes) {
if (change == kActiveSheetsChanged)
ClearKeyframeRules();
for (auto it = new_style_sheets.begin();
it != new_style_sheets.end(); it++) {
DCHECK(it->second);
AddKeyframeRules(*it->second);
}
}
ScopedStyleResolver::KeyframesRulesAdded(tree_scope); ScopedStyleResolver::KeyframesRulesAdded(tree_scope);
}
Node& invalidation_root = Node& invalidation_root =
ScopedStyleResolver::InvalidationRootForTreeScope(tree_scope); ScopedStyleResolver::InvalidationRootForTreeScope(tree_scope);
...@@ -1340,6 +1371,20 @@ void StyleEngine::CollectMatchingUserRules( ...@@ -1340,6 +1371,20 @@ void StyleEngine::CollectMatchingUserRules(
} }
} }
void StyleEngine::AddFontFaceRules(const RuleSet& rule_set) {
if (!font_selector_)
return;
const HeapVector<Member<StyleRuleFontFace>> font_face_rules =
rule_set.FontFaceRules();
for (auto& font_face_rule : font_face_rules) {
if (FontFace* font_face = FontFace::Create(document_, font_face_rule))
font_selector_->GetFontFaceCache()->Add(font_face_rule, font_face);
}
if (resolver_ && font_face_rules.size())
resolver_->InvalidateMatchedPropertiesCache();
}
void StyleEngine::AddKeyframeRules(const RuleSet& rule_set) { void StyleEngine::AddKeyframeRules(const RuleSet& rule_set) {
const HeapVector<Member<StyleRuleKeyframes>> keyframes_rules = const HeapVector<Member<StyleRuleKeyframes>> keyframes_rules =
rule_set.KeyframesRules(); rule_set.KeyframesRules();
......
...@@ -215,7 +215,6 @@ class CORE_EXPORT StyleEngine final ...@@ -215,7 +215,6 @@ class CORE_EXPORT StyleEngine final
void SetFontSelector(CSSFontSelector*); void SetFontSelector(CSSFontSelector*);
void RemoveFontFaceRules(const HeapVector<Member<const StyleRuleFontFace>>&); void RemoveFontFaceRules(const HeapVector<Member<const StyleRuleFontFace>>&);
void ClearFontCache();
// updateGenericFontFamilySettings is used from WebSettingsImpl. // updateGenericFontFamilySettings is used from WebSettingsImpl.
void UpdateGenericFontFamilySettings(); void UpdateGenericFontFamilySettings();
...@@ -364,8 +363,14 @@ class CORE_EXPORT StyleEngine final ...@@ -364,8 +363,14 @@ class CORE_EXPORT StyleEngine final
const MediaQueryEvaluator& EnsureMediaQueryEvaluator(); const MediaQueryEvaluator& EnsureMediaQueryEvaluator();
void UpdateStyleSheetList(TreeScope&); void UpdateStyleSheetList(TreeScope&);
void ClearFontCache();
void RefreshFontCache();
void MarkFontCacheDirty() { font_cache_dirty_ = true; }
bool IsFontCacheDirty() const { return font_cache_dirty_; }
void ClearKeyframeRules() { keyframes_rule_map_.clear(); } void ClearKeyframeRules() { keyframes_rule_map_.clear(); }
void AddFontFaceRules(const RuleSet&);
void AddKeyframeRules(const RuleSet&); void AddKeyframeRules(const RuleSet&);
void AddKeyframeStyle(StyleRuleKeyframes*); void AddKeyframeStyle(StyleRuleKeyframes*);
...@@ -419,6 +424,7 @@ class CORE_EXPORT StyleEngine final ...@@ -419,6 +424,7 @@ class CORE_EXPORT StyleEngine final
HeapHashSet<Member<Element>> whitespace_reattach_set_; HeapHashSet<Member<Element>> whitespace_reattach_set_;
Member<CSSFontSelector> font_selector_; Member<CSSFontSelector> font_selector_;
bool font_cache_dirty_ = false;
HeapHashMap<AtomicString, WeakMember<StyleSheetContents>> HeapHashMap<AtomicString, WeakMember<StyleSheetContents>>
text_to_sheet_cache_; text_to_sheet_cache_;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <memory> #include <memory>
#include "bindings/core/v8/V8BindingForCore.h" #include "bindings/core/v8/V8BindingForCore.h"
#include "core/css/CSSFontSelector.h"
#include "core/css/CSSRuleList.h" #include "core/css/CSSRuleList.h"
#include "core/css/CSSStyleRule.h" #include "core/css/CSSStyleRule.h"
#include "core/css/CSSStyleSheet.h" #include "core/css/CSSStyleSheet.h"
...@@ -82,14 +83,21 @@ TEST_F(StyleEngineTest, DocumentDirtyAfterInject) { ...@@ -82,14 +83,21 @@ TEST_F(StyleEngineTest, DocumentDirtyAfterInject) {
TEST_F(StyleEngineTest, AnalyzedInject) { TEST_F(StyleEngineTest, AnalyzedInject) {
GetDocument().body()->SetInnerHTMLFromString(R"HTML( GetDocument().body()->SetInnerHTMLFromString(R"HTML(
<style> <style>
@font-face {
font-family: 'Cool Font';
src: local(monospace);
font-weight: bold;
}
#t1 { color: red !important } #t1 { color: red !important }
#t2 { color: black } #t2 { color: black }
#t4 { animation-name: dummy-animation } #t4 { font-family: 'Cool Font'; font-weight: bold; font-style: italic }
#t5 { animation-name: dummy-animation }
</style> </style>
<div id='t1'>Green</div> <div id='t1'>Green</div>
<div id='t2'>White</div> <div id='t2'>White</div>
<div id='t3' style='color: black !important'>White</div> <div id='t3' style='color: black !important'>White</div>
<div id='t4'>I animate!</div> <div id='t4'>I look cool.</div>
<div id='t5'>I animate!</div>
<div></div> <div></div>
)HTML"); )HTML");
GetDocument().View()->UpdateAllLifecyclePhases(); GetDocument().View()->UpdateAllLifecyclePhases();
...@@ -191,14 +199,108 @@ TEST_F(StyleEngineTest, AnalyzedInject) { ...@@ -191,14 +199,108 @@ TEST_F(StyleEngineTest, AnalyzedInject) {
EXPECT_EQ(MakeRGB(0, 0, 0), EXPECT_EQ(MakeRGB(0, 0, 0),
t3->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor)); t3->GetComputedStyle()->VisitedDependentColor(CSSPropertyColor));
// @keyframes rules // @font-face rules
Element* t4 = GetDocument().getElementById("t4"); Element* t4 = GetDocument().getElementById("t4");
ASSERT_TRUE(t4); ASSERT_TRUE(t4);
ASSERT_TRUE(t4->GetComputedStyle());
// There's only one font and it's bold and normal.
EXPECT_EQ(1u, GetStyleEngine().GetFontSelector()->GetFontFaceCache()
->GetNumSegmentedFacesForTesting());
CSSSegmentedFontFace* font_face =
GetStyleEngine().GetFontSelector()->GetFontFaceCache()
->Get(t4->GetComputedStyle()->GetFontDescription(),
AtomicString("Cool Font"));
EXPECT_TRUE(font_face);
FontSelectionCapabilities capabilities =
font_face->GetFontSelectionCapabilities();
ASSERT_EQ(capabilities.weight,
FontSelectionRange({BoldWeightValue(), BoldWeightValue()}));
ASSERT_EQ(capabilities.slope,
FontSelectionRange({NormalSlopeValue(), NormalSlopeValue()}));
StyleSheetContents* font_face_parsed_sheet =
StyleSheetContents::Create(CSSParserContext::Create(GetDocument()));
font_face_parsed_sheet->ParseString(
"@font-face {"
" font-family: 'Cool Font';"
" src: local(monospace);"
" font-weight: bold;"
" font-style: italic;"
"}"
);
WebStyleSheetId font_face_id =
GetStyleEngine().AddUserSheet(font_face_parsed_sheet);
GetDocument().View()->UpdateAllLifecyclePhases();
// After injecting a more specific font, now there are two and the
// bold-italic one is selected.
EXPECT_EQ(2u, GetStyleEngine().GetFontSelector()->GetFontFaceCache()
->GetNumSegmentedFacesForTesting());
font_face = GetStyleEngine().GetFontSelector()->GetFontFaceCache()
->Get(t4->GetComputedStyle()->GetFontDescription(),
AtomicString("Cool Font"));
EXPECT_TRUE(font_face);
capabilities = font_face->GetFontSelectionCapabilities();
ASSERT_EQ(capabilities.weight,
FontSelectionRange({BoldWeightValue(), BoldWeightValue()}));
ASSERT_EQ(capabilities.slope,
FontSelectionRange({ItalicSlopeValue(), ItalicSlopeValue()}));
HTMLStyleElement* style_element = HTMLStyleElement::Create(
GetDocument(), false);
style_element->SetInnerHTMLFromString(
"@font-face {"
" font-family: 'Cool Font';"
" src: local(monospace);"
" font-weight: normal;"
" font-style: italic;"
"}"
);
GetDocument().body()->AppendChild(style_element);
GetDocument().View()->UpdateAllLifecyclePhases();
// Now there are three fonts, but the newest one does not override the older,
// better matching one.
EXPECT_EQ(3u, GetStyleEngine().GetFontSelector()->GetFontFaceCache()
->GetNumSegmentedFacesForTesting());
font_face = GetStyleEngine().GetFontSelector()->GetFontFaceCache()
->Get(t4->GetComputedStyle()->GetFontDescription(),
AtomicString("Cool Font"));
EXPECT_TRUE(font_face);
capabilities = font_face->GetFontSelectionCapabilities();
ASSERT_EQ(capabilities.weight,
FontSelectionRange({BoldWeightValue(), BoldWeightValue()}));
ASSERT_EQ(capabilities.slope,
FontSelectionRange({ItalicSlopeValue(), ItalicSlopeValue()}));
GetStyleEngine().RemoveUserSheet(font_face_id);
GetDocument().View()->UpdateAllLifecyclePhases();
// After removing the injected style sheet we're left with a bold-normal and
// a normal-italic font, and the latter is selected by the matching algorithm
// as font-style trumps font-weight.
EXPECT_EQ(2u, GetStyleEngine().GetFontSelector()->GetFontFaceCache()
->GetNumSegmentedFacesForTesting());
font_face = GetStyleEngine().GetFontSelector()->GetFontFaceCache()
->Get(t4->GetComputedStyle()->GetFontDescription(),
AtomicString("Cool Font"));
EXPECT_TRUE(font_face);
capabilities = font_face->GetFontSelectionCapabilities();
ASSERT_EQ(capabilities.weight,
FontSelectionRange({NormalWeightValue(), NormalWeightValue()}));
ASSERT_EQ(capabilities.slope,
FontSelectionRange({ItalicSlopeValue(), ItalicSlopeValue()}));
// @keyframes rules
Element* t5 = GetDocument().getElementById("t5");
ASSERT_TRUE(t5);
// There's no @keyframes rule named dummy-animation // There's no @keyframes rule named dummy-animation
ASSERT_FALSE(GetStyleEngine().Resolver()->FindKeyframesRule( ASSERT_FALSE(GetStyleEngine().Resolver()->FindKeyframesRule(
t4, AtomicString("dummy-animation"))); t5, AtomicString("dummy-animation")));
StyleSheetContents* keyframes_parsed_sheet = StyleSheetContents* keyframes_parsed_sheet =
StyleSheetContents::Create(CSSParserContext::Create(GetDocument())); StyleSheetContents::Create(CSSParserContext::Create(GetDocument()));
...@@ -211,12 +313,11 @@ TEST_F(StyleEngineTest, AnalyzedInject) { ...@@ -211,12 +313,11 @@ TEST_F(StyleEngineTest, AnalyzedInject) {
// is found with one keyframe. // is found with one keyframe.
StyleRuleKeyframes* keyframes = StyleRuleKeyframes* keyframes =
GetStyleEngine().Resolver()->FindKeyframesRule( GetStyleEngine().Resolver()->FindKeyframesRule(
t4, AtomicString("dummy-animation")); t5, AtomicString("dummy-animation"));
ASSERT_TRUE(keyframes); ASSERT_TRUE(keyframes);
EXPECT_EQ(1u, keyframes->Keyframes().size()); EXPECT_EQ(1u, keyframes->Keyframes().size());
HTMLStyleElement* style_element = HTMLStyleElement::Create( style_element = HTMLStyleElement::Create(GetDocument(), false);
GetDocument(), false);
style_element->SetInnerHTMLFromString( style_element->SetInnerHTMLFromString(
"@keyframes dummy-animation { from {} to {} }"); "@keyframes dummy-animation { from {} to {} }");
GetDocument().body()->AppendChild(style_element); GetDocument().body()->AppendChild(style_element);
...@@ -225,7 +326,7 @@ TEST_F(StyleEngineTest, AnalyzedInject) { ...@@ -225,7 +326,7 @@ TEST_F(StyleEngineTest, AnalyzedInject) {
// Author @keyframes rules take precedence; now there are two keyframes (from // Author @keyframes rules take precedence; now there are two keyframes (from
// and to). // and to).
keyframes = GetStyleEngine().Resolver()->FindKeyframesRule( keyframes = GetStyleEngine().Resolver()->FindKeyframesRule(
t4, AtomicString("dummy-animation")); t5, AtomicString("dummy-animation"));
ASSERT_TRUE(keyframes); ASSERT_TRUE(keyframes);
EXPECT_EQ(2u, keyframes->Keyframes().size()); EXPECT_EQ(2u, keyframes->Keyframes().size());
...@@ -233,7 +334,7 @@ TEST_F(StyleEngineTest, AnalyzedInject) { ...@@ -233,7 +334,7 @@ TEST_F(StyleEngineTest, AnalyzedInject) {
GetDocument().View()->UpdateAllLifecyclePhases(); GetDocument().View()->UpdateAllLifecyclePhases();
keyframes = GetStyleEngine().Resolver()->FindKeyframesRule( keyframes = GetStyleEngine().Resolver()->FindKeyframesRule(
t4, AtomicString("dummy-animation")); t5, AtomicString("dummy-animation"));
ASSERT_TRUE(keyframes); ASSERT_TRUE(keyframes);
EXPECT_EQ(1u, keyframes->Keyframes().size()); EXPECT_EQ(1u, keyframes->Keyframes().size());
...@@ -242,7 +343,7 @@ TEST_F(StyleEngineTest, AnalyzedInject) { ...@@ -242,7 +343,7 @@ TEST_F(StyleEngineTest, AnalyzedInject) {
// Injected @keyframes rules are no longer available once removed. // Injected @keyframes rules are no longer available once removed.
ASSERT_FALSE(GetStyleEngine().Resolver()->FindKeyframesRule( ASSERT_FALSE(GetStyleEngine().Resolver()->FindKeyframesRule(
t4, AtomicString("dummy-animation"))); t5, AtomicString("dummy-animation")));
} }
TEST_F(StyleEngineTest, TextToSheetCache) { TEST_F(StyleEngineTest, TextToSheetCache) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment