blob: 18ffd73e86caab0fe2155f78ad89cde168adf404 [file] [log] [blame]
// Copyright 2020 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 "base/test/scoped_feature_list.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/core/css/font_face_set_document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
class FontDisplayAutoLCPAlignTestBase : public SimTest {
public:
void SetUp() override {
std::map<std::string, std::string> parameters;
parameters["intervention-mode"] = intervention_mode_;
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kAlignFontDisplayAutoTimeoutWithLCPGoal, parameters);
SimTest::SetUp();
}
static Vector<char> ReadAhemWoff2() {
return test::ReadFromFile(test::CoreTestDataPath("Ahem.woff2"))
->CopyAs<Vector<char>>();
}
static Vector<char> ReadMaterialIconsWoff2() {
return test::ReadFromFile(
test::CoreTestDataPath("MaterialIcons-Regular.woff2"))
->CopyAs<Vector<char>>();
}
protected:
Element* GetTarget() { return GetDocument().getElementById("target"); }
const Font& GetFont(const Element* element) {
return element->GetLayoutObject()->Style()->GetFont();
}
const Font& GetTargetFont() { return GetFont(GetTarget()); }
std::string intervention_mode_;
base::test::ScopedFeatureList scoped_feature_list_;
};
class FontDisplayAutoLCPAlignFailureModeTest
: public FontDisplayAutoLCPAlignTestBase {
public:
void SetUp() override {
intervention_mode_ = "failure";
FontDisplayAutoLCPAlignTestBase::SetUp();
}
};
TEST_F(FontDisplayAutoLCPAlignFailureModeTest, FontFinishesBeforeLCPLimit) {
SimRequest main_resource("https://example.com", "text/html");
SimSubresourceRequest 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");
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target style="position:relative">0123456789</span>
)HTML");
// The first frame is rendered with invisible fallback, as the web font is
// still loading, and is in the block display period.
Compositor().BeginFrame();
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_TRUE(GetTargetFont().ShouldSkipDrawing());
font_resource.Complete(ReadAhemWoff2());
// The next frame is rendered with the web font.
Compositor().BeginFrame();
EXPECT_EQ(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
TEST_F(FontDisplayAutoLCPAlignFailureModeTest, FontFinishesAfterLCPLimit) {
SimRequest main_resource("https://example.com", "text/html");
SimSubresourceRequest 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");
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target style="position:relative">0123456789</span>
)HTML");
// The first frame is rendered with invisible fallback, as the web font is
// still loading, and is in the block display period.
Compositor().BeginFrame();
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_TRUE(GetTargetFont().ShouldSkipDrawing());
// Wait until we reach the LCP limit, and the relevant timeout fires.
test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(
features::kAlignFontDisplayAutoTimeoutWithLCPGoalTimeoutParam.Get()));
// After reaching the LCP limit, the web font should enter the failure
// display period. We should render visible fallback for it.
Compositor().BeginFrame();
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
font_resource.Complete(ReadAhemWoff2());
// We shouldn't use the web font even if it loads now. It's already in the
// failure display period.
Compositor().BeginFrame();
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
TEST_F(FontDisplayAutoLCPAlignFailureModeTest, FontFaceAddedAfterLCPLimit) {
SimRequest main_resource("https://example.com", "text/html");
SimSubresourceRequest font_resource("https://example.com/Ahem.woff2",
"font/woff2");
LoadURL("https://example.com");
main_resource.Write("<!doctype html>");
// Wait until we reach the LCP limit, and the relevant timeout fires.
test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(
features::kAlignFontDisplayAutoTimeoutWithLCPGoalTimeoutParam.Get()));
main_resource.Complete(R"HTML(
<style>
@font-face {
font-family: custom-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target style="position:relative">0123456789</span>
)HTML");
font_resource.Complete(ReadAhemWoff2());
// Since the font face is added after the LCP limit and is not in the memory
// cache, we'll treated as already in the failure period to prevent any
// latency or layout shifting. We should rendering visible fallback for it.
Compositor().BeginFrame();
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
TEST_F(FontDisplayAutoLCPAlignFailureModeTest,
FontFaceInMemoryCacheAddedAfterLCPLimit) {
SimRequest main_resource("https://example.com", "text/html");
SimSubresourceRequest font_resource("https://example.com/Ahem.woff2",
"font/woff2");
LoadURL("https://example.com");
main_resource.Write(R"HTML(
<!doctype html>
<link rel="preload" as="font" type="font/woff2"
href="https://example.com/Ahem.woff2" crossorigin>
)HTML");
font_resource.Complete(ReadAhemWoff2());
// Wait until we reach the LCP limit, and the relevant timeout fires.
test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(
features::kAlignFontDisplayAutoTimeoutWithLCPGoalTimeoutParam.Get()));
main_resource.Complete(R"HTML(
<style>
@font-face {
font-family: custom-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target style="position:relative">0123456789</span>
)HTML");
// The font face is added after the LCP limit, but it's already preloaded and
// available from the memory cache. We'll render with it as it's immediate
// available.
Compositor().BeginFrame();
EXPECT_EQ(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
// https://crbug.com/1065508
TEST_F(FontDisplayAutoLCPAlignFailureModeTest,
TimeoutFiredAfterDocumentShutdown) {
SimRequest main_resource("https://example.com/", "text/html");
SimSubresourceRequest 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");
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target style="position:relative">0123456789</span>
)HTML");
font_resource.Complete();
SimRequest next_page_resource("https://example2.com/", "text/html");
LoadURL("https://example2.com/");
// Wait until we reach the LCP limit, and the timeout for the previous
// document fires. Shouldn't crash here.
test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(
features::kAlignFontDisplayAutoTimeoutWithLCPGoalTimeoutParam.Get()));
next_page_resource.Finish();
}
TEST_F(FontDisplayAutoLCPAlignFailureModeTest, IconAndNonIconFonts) {
SimRequest main_resource("https://example.com", "text/html");
SimSubresourceRequest icon_font_resource(
"https://example.com/MaterialIcons-Regular.woff2", "font/woff2");
SimSubresourceRequest 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
: public FontDisplayAutoLCPAlignTestBase {
public:
void SetUp() override {
intervention_mode_ = "swap";
FontDisplayAutoLCPAlignTestBase::SetUp();
}
};
TEST_F(FontDisplayAutoLCPAlignSwapModeTest, FontFinishesBeforeLCPLimit) {
SimRequest main_resource("https://example.com", "text/html");
SimSubresourceRequest 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");
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target style="position:relative">0123456789</span>
)HTML");
// The first frame is rendered with invisible fallback, as the web font is
// still loading, and is in the block display period.
Compositor().BeginFrame();
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_TRUE(GetTargetFont().ShouldSkipDrawing());
font_resource.Complete(ReadAhemWoff2());
// The next frame is rendered with the web font.
Compositor().BeginFrame();
EXPECT_EQ(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
TEST_F(FontDisplayAutoLCPAlignSwapModeTest, FontFinishesAfterLCPLimit) {
SimRequest main_resource("https://example.com", "text/html");
SimSubresourceRequest 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");
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target style="position:relative">0123456789</span>
)HTML");
// The first frame is rendered with invisible fallback, as the web font is
// still loading, and is in the block display period.
Compositor().BeginFrame();
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_TRUE(GetTargetFont().ShouldSkipDrawing());
// Wait until we reach the LCP limit, and the relevant timeout fires.
test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(
features::kAlignFontDisplayAutoTimeoutWithLCPGoalTimeoutParam.Get()));
// After reaching the LCP limit, the web font should enter the swap
// display period. We should render visible fallback for it.
Compositor().BeginFrame();
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
font_resource.Complete(ReadAhemWoff2());
// The web font swaps in after finishing loading.
Compositor().BeginFrame();
EXPECT_EQ(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
TEST_F(FontDisplayAutoLCPAlignSwapModeTest, FontFaceAddedAfterLCPLimit) {
SimRequest main_resource("https://example.com", "text/html");
SimSubresourceRequest font_resource("https://example.com/Ahem.woff2",
"font/woff2");
LoadURL("https://example.com");
main_resource.Write("<!doctype html>");
// Wait until we reach the LCP limit, and the relevant timeout fires.
test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(
features::kAlignFontDisplayAutoTimeoutWithLCPGoalTimeoutParam.Get()));
main_resource.Complete(R"HTML(
<style>
@font-face {
font-family: custom-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target style="position:relative">0123456789</span>
)HTML");
font_resource.Complete(ReadAhemWoff2());
// The web font swaps in after finishing loading.
Compositor().BeginFrame();
EXPECT_EQ(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
TEST_F(FontDisplayAutoLCPAlignSwapModeTest,
FontFaceInMemoryCacheAddedAfterLCPLimit) {
SimRequest main_resource("https://example.com", "text/html");
SimSubresourceRequest font_resource("https://example.com/Ahem.woff2",
"font/woff2");
LoadURL("https://example.com");
main_resource.Write(R"HTML(
<!doctype html>
<link rel="preload" as="font" type="font/woff2"
href="https://example.com/Ahem.woff2" crossorigin>
)HTML");
font_resource.Complete(ReadAhemWoff2());
// Wait until we reach the LCP limit, and the relevant timeout fires.
test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(
features::kAlignFontDisplayAutoTimeoutWithLCPGoalTimeoutParam.Get()));
main_resource.Complete(R"HTML(
<style>
@font-face {
font-family: custom-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target style="position:relative">0123456789</span>
)HTML");
// The font face is added after the LCP limit, but it's already preloaded and
// available from the memory cache. We'll render with it as it's immediate
// available.
Compositor().BeginFrame();
EXPECT_EQ(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
} // namespace blink