Commit 63f81424 authored by Xiaocheng Hu's avatar Xiaocheng Hu Committed by Commit Bot

Exclude icon fonts from AlignFontDisplayAutoTimeoutWithLCPGoal intervention

In previous study, we found out that:
- The "failure" mode of this intervention breaks icon fonts massively
- Icon fonts generally define almost all of their glyphs in Private Use
  Area, while non-icon fonts don't use PUA that much

Hence, this patch adds a heuristic to decide whether a font is an icon
font by checking its PUA usage, and use that as a hint to decide whether
AlignFontDisplayAutoTimeoutWithLCPGoal should move a font into the
failure display period. As a result:
- Non-icon fonts that don't finish loading fast enough after navigation
  will end up with fallback
- Icon fonts will have the same behavior as before

Bug: 1065508
Change-Id: I54e52e714a74c17b647d8b072d2f634660e56627
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2261898Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Commit-Queue: Xiaocheng Hu <xiaochengh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#782702}
parent 3de25fd8
...@@ -30,13 +30,21 @@ class FontDisplayAutoLCPAlignTestBase : public SimTest { ...@@ -30,13 +30,21 @@ class FontDisplayAutoLCPAlignTestBase : public SimTest {
->CopyAs<Vector<char>>(); ->CopyAs<Vector<char>>();
} }
static Vector<char> ReadMaterialIconsWoff2() {
return test::ReadFromFile(
test::CoreTestDataPath("MaterialIcons-Regular.woff2"))
->CopyAs<Vector<char>>();
}
protected: protected:
Element* GetTarget() { return GetDocument().getElementById("target"); } Element* GetTarget() { return GetDocument().getElementById("target"); }
const Font& GetTargetFont() { const Font& GetFont(const Element* element) {
return GetTarget()->GetLayoutObject()->Style()->GetFont(); return element->GetLayoutObject()->Style()->GetFont();
} }
const Font& GetTargetFont() { return GetFont(GetTarget()); }
std::string intervention_mode_; std::string intervention_mode_;
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
}; };
...@@ -234,6 +242,70 @@ TEST_F(FontDisplayAutoLCPAlignFailureModeTest, ...@@ -234,6 +242,70 @@ TEST_F(FontDisplayAutoLCPAlignFailureModeTest,
next_page_resource.Finish(); next_page_resource.Finish();
} }
TEST_F(FontDisplayAutoLCPAlignFailureModeTest, IconAndNonIconFonts) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest icon_font_resource(
"https://example.com/MaterialIcons-Regular.woff2", "font/woff2");
SimRequest non_icon_font_resource("https://example.com/Ahem.woff2",
"font/woff2");
LoadURL("https://example.com");
main_resource.Complete(R"HTML(
<!doctype html>
<style>
@font-face {
font-family: custom-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
}
@font-face {
font-family: icon-font;
font-style: normal;
font-weight: 400;
src: url(https://example.com/MaterialIcons-Regular.woff2) format("woff2");
}
#non-icon-text {
font: 25px/1 custom-font, monospace;
}
#icon-text {
font-family: icon-font;
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
}
</style>
<div><span id=icon-text>face</span></div>
<div><span id=non-icon-text>0123456789</span></div>
)HTML");
Element* icon_text = GetDocument().getElementById("icon-text");
Element* non_icon_text = GetDocument().getElementById("non-icon-text");
// The first frame is rendered with invisible fallback, as the web fonts are
// still loading, and are in the block display period.
Compositor().BeginFrame();
EXPECT_NE(24, icon_text->OffsetWidth());
EXPECT_TRUE(GetFont(icon_text).ShouldSkipDrawing());
EXPECT_GT(250, non_icon_text->OffsetWidth());
EXPECT_TRUE(GetFont(non_icon_text).ShouldSkipDrawing());
// Wait until we reach the LCP limit, and the relevant timeout fires.
test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(
features::kAlignFontDisplayAutoTimeoutWithLCPGoalTimeoutParam.Get()));
icon_font_resource.Complete(ReadMaterialIconsWoff2());
non_icon_font_resource.Complete(ReadAhemWoff2());
// After reaching the LCP limit, the non-icon web font should reach the
// failure period, while the icon font should be used.
Compositor().BeginFrame();
EXPECT_EQ(24, icon_text->OffsetWidth());
EXPECT_FALSE(GetFont(icon_text).ShouldSkipDrawing());
EXPECT_GT(250, non_icon_text->OffsetWidth());
EXPECT_FALSE(GetFont(non_icon_text).ShouldSkipDrawing());
}
class FontDisplayAutoLCPAlignSwapModeTest class FontDisplayAutoLCPAlignSwapModeTest
: public FontDisplayAutoLCPAlignTestBase { : public FontDisplayAutoLCPAlignTestBase {
public: public:
......
...@@ -56,9 +56,11 @@ RemoteFontFaceSource::ComputeFontDisplayAutoPeriod() const { ...@@ -56,9 +56,11 @@ RemoteFontFaceSource::ComputeFontDisplayAutoPeriod() const {
using Mode = features::AlignFontDisplayAutoTimeoutWithLCPGoalMode; using Mode = features::AlignFontDisplayAutoTimeoutWithLCPGoalMode;
Mode mode = Mode mode =
features::kAlignFontDisplayAutoTimeoutWithLCPGoalModeParam.Get(); features::kAlignFontDisplayAutoTimeoutWithLCPGoalModeParam.Get();
if (mode == Mode::kToFailurePeriod) if (mode == Mode::kToSwapPeriod)
return kSwapPeriod;
DCHECK_EQ(Mode::kToFailurePeriod, mode);
if (custom_font_data_ && !custom_font_data_->MayBeIconFont())
return kFailurePeriod; return kFailurePeriod;
DCHECK_EQ(Mode::kToSwapPeriod, mode);
return kSwapPeriod; return kSwapPeriod;
} }
......
...@@ -182,4 +182,37 @@ bool FontCustomPlatformData::SupportsFormat(const String& format) { ...@@ -182,4 +182,37 @@ bool FontCustomPlatformData::SupportsFormat(const String& format) {
EqualIgnoringASCIICase(format, "woff2-variations"); EqualIgnoringASCIICase(format, "woff2-variations");
} }
bool FontCustomPlatformData::MayBeIconFont() const {
if (!may_be_icon_font_computed_) {
// We observed that many icon fonts define almost all of their glyphs in the
// Unicode Private Use Area, while non-icon fonts rarely use PUA. We use
// this as a heuristic to determine if a font is an icon font.
// We first obtain the list of glyphs mapped from PUA codepoint range:
// https://unicode.org/charts/PDF/UE000.pdf
const SkUnichar pua_start = 0xE000;
const SkUnichar pua_end = 0xF900;
Vector<SkUnichar> pua_codepoints(pua_end - pua_start);
for (wtf_size_t i = 0; i < pua_codepoints.size(); ++i)
pua_codepoints[i] = pua_start + i;
Vector<SkGlyphID> glyphs(pua_codepoints.size());
base_typeface_->unicharsToGlyphs(pua_codepoints.data(),
pua_codepoints.size(), glyphs.data());
// Deduplicate and exclude glyph ID 0 (which means undefined glyph)
std::sort(glyphs.begin(), glyphs.end());
glyphs.erase(std::unique(glyphs.begin(), glyphs.end()), glyphs.end());
if (!glyphs[0])
glyphs.EraseAt(0);
// We use the heuristic that if most of the define glyphs are in PUA, then
// the font may be an icon font.
wtf_size_t pua_glyph_count = glyphs.size();
wtf_size_t total_glyphs = base_typeface_->countGlyphs();
may_be_icon_font_ = pua_glyph_count * 2 > total_glyphs;
}
return may_be_icon_font_;
}
} // namespace blink } // namespace blink
...@@ -74,11 +74,16 @@ class PLATFORM_EXPORT FontCustomPlatformData ...@@ -74,11 +74,16 @@ class PLATFORM_EXPORT FontCustomPlatformData
size_t DataSize() const { return data_size_; } size_t DataSize() const { return data_size_; }
static bool SupportsFormat(const String&); static bool SupportsFormat(const String&);
bool MayBeIconFont() const;
private: private:
FontCustomPlatformData(sk_sp<SkTypeface>, size_t data_size); FontCustomPlatformData(sk_sp<SkTypeface>, size_t data_size);
sk_sp<SkTypeface> base_typeface_; sk_sp<SkTypeface> base_typeface_;
size_t data_size_; size_t data_size_;
mutable bool may_be_icon_font_computed_ = false;
mutable bool may_be_icon_font_ = false;
DISALLOW_COPY_AND_ASSIGN(FontCustomPlatformData); DISALLOW_COPY_AND_ASSIGN(FontCustomPlatformData);
}; };
......
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