blob: 7ceb7a087978c12db5ce33faf354613258826d93 [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 "third_party/blink/renderer/core/css/style_engine.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/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/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
// This test suite verifies that after font changes (e.g., font loaded), we do
// not invalidate the full document's style or layout, but for affected elements
// only.
class FontUpdateInvalidationTest
: private ScopedCSSReducedFontLoadingInvalidationsForTest,
private ScopedCSSReducedFontLoadingLayoutInvalidationsForTest,
public SimTest {
public:
FontUpdateInvalidationTest()
: ScopedCSSReducedFontLoadingInvalidationsForTest(true),
ScopedCSSReducedFontLoadingLayoutInvalidationsForTest(true) {}
protected:
static Vector<char> ReadAhemWoff2() {
return test::ReadFromFile(test::CoreTestDataPath("Ahem.woff2"))
->CopyAs<Vector<char>>();
}
};
TEST_F(FontUpdateInvalidationTest, PartialLayoutInvalidationAfterFontLoading) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest font_resource("https://example.com/Ahem.woff2", "font/woff2");
LoadURL("https://example.com");
main_resource.Write(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;
}
#reference {
font: 25px/1 monospace;
}
</style>
<div><span id=target>0123456789</span></div>
<div><span id=reference>0123456789</div>
)HTML");
// First rendering the page with fallback
Compositor().BeginFrame();
Element* target = GetDocument().getElementById("target");
Element* reference = GetDocument().getElementById("reference");
EXPECT_GT(250, target->OffsetWidth());
EXPECT_GT(250, reference->OffsetWidth());
// Finish font loading, and trigger invalidations.
font_resource.Complete(ReadAhemWoff2());
GetDocument().GetStyleEngine().InvalidateStyleAndLayoutForFontUpdates();
// No element is marked for style recalc, since no computed style is changed.
EXPECT_EQ(kNoStyleChange, target->GetStyleChangeType());
EXPECT_EQ(kNoStyleChange, reference->GetStyleChangeType());
// Only elements that had pending custom fonts are marked for relayout.
EXPECT_TRUE(target->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(reference->GetLayoutObject()->NeedsLayout());
Compositor().BeginFrame();
EXPECT_EQ(250, target->OffsetWidth());
EXPECT_GT(250, reference->OffsetWidth());
main_resource.Finish();
}
TEST_F(FontUpdateInvalidationTest,
PartialLayoutInvalidationAfterFontFaceDeletion) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest font_resource("https://example.com/Ahem.woff2", "font/woff2");
LoadURL("https://example.com");
main_resource.Write(R"HTML(
<!doctype html>
<script>
const face = new FontFace('custom-font',
'url(https://example.com/Ahem.woff2)');
face.load();
document.fonts.add(face);
</script>
<style>
#target {
font: 25px/1 custom-font, monospace;
}
#reference {
font: 25px/1 monospace;
}
</style>
<div><span id=target>0123456789</span></div>
<div><span id=reference>0123456789</div>
)HTML");
// First render the page with the custom font
font_resource.Complete(ReadAhemWoff2());
Compositor().BeginFrame();
Element* target = GetDocument().getElementById("target");
Element* reference = GetDocument().getElementById("reference");
EXPECT_EQ(250, target->OffsetWidth());
EXPECT_GT(250, reference->OffsetWidth());
// Then delete the custom font, and trigger invalidations
main_resource.Write("<script>document.fonts.delete(face);</script>");
GetDocument().GetStyleEngine().InvalidateStyleAndLayoutForFontUpdates();
// No element is marked for style recalc, since no computed style is changed.
EXPECT_EQ(kNoStyleChange, target->GetStyleChangeType());
EXPECT_EQ(kNoStyleChange, reference->GetStyleChangeType());
// Only elements using custom fonts are marked for relayout.
EXPECT_TRUE(target->GetLayoutObject()->NeedsLayout());
EXPECT_FALSE(reference->GetLayoutObject()->NeedsLayout());
Compositor().BeginFrame();
EXPECT_GT(250, target->OffsetWidth());
EXPECT_GT(250, reference->OffsetWidth());
main_resource.Finish();
}
// https://crbug.com/1092411
TEST_F(FontUpdateInvalidationTest, LayoutInvalidationOnModalDialog) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest font_resource("https://example.com/Ahem.woff2", "font/woff2");
LoadURL("https://example.com");
main_resource.Write(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>
<dialog><span id=target>0123456789</span></dialog>
<script>document.querySelector('dialog').showModal();</script>
)HTML");
// First render the page without the custom font
Compositor().BeginFrame();
Element* target = GetDocument().getElementById("target");
EXPECT_GT(250, target->OffsetWidth());
// Then load the font and invalidate layout
font_resource.Complete(ReadAhemWoff2());
GetDocument().GetStyleEngine().InvalidateStyleAndLayoutForFontUpdates();
// <dialog> descendants should be invalidated
EXPECT_EQ(kNoStyleChange, target->GetStyleChangeType());
EXPECT_TRUE(target->GetLayoutObject()->NeedsLayout());
// <dialog> descendants should be re-rendered with the custom font
Compositor().BeginFrame();
EXPECT_EQ(250, target->OffsetWidth());
main_resource.Finish();
}
// https://crbug.com/1101483
TEST_F(FontUpdateInvalidationTest, FallbackBetweenPendingAndLoadedCustomFonts) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest slow_font_resource("https://example.com/nonexist.woff2",
"font/woff2");
SimRequest fast_font_resource("https://example.com/Ahem.woff2", "font/woff2");
LoadURL("https://example.com");
main_resource.Complete(R"HTML(
<!doctype html>
<link rel="preload" href="https://example.com/Ahem.woff2" as="font" crossorigin>
<style>
@font-face {
font-family: slow-font;
src: url(https://example.com/nonexist.woff2) format("woff2");
}
@font-face {
font-family: fast-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
}
#target {
font: 25px/1 slow-font, fast-font, monospace;
}
</style>
<span id=target>0123456789</span>
)HTML");
fast_font_resource.Complete(ReadAhemWoff2());
// While slow-font is pending and fast-font is already available, we should
// use it to render the page.
Compositor().BeginFrame();
Element* target = GetDocument().getElementById("target");
DCHECK_EQ(250, target->OffsetWidth());
slow_font_resource.Finish();
Compositor().BeginFrame();
EXPECT_EQ(250, target->OffsetWidth());
}
// https://crrev.com/1397423004
TEST_F(FontUpdateInvalidationTest, NoRedundantLoadingForSegmentedFont) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest font_resource("https://example.com/font2.woff2", "font/woff2");
LoadURL("https://example.com");
main_resource.Complete(R"HTML(
<!doctype html>
<style>
@font-face {
font-family: custom-font;
/* We intentionally leave it unmocked, so that the test fails if it
* attempts to load font1.woff. */
src: url(https://example.com/font1.woff2) format("woff2");
unicode-range: 0x00-0xFF;
}
@font-face {
font-family: custom-font;
src: url(https://example.com/font2.woff2) format("woff2");
unicode-range: 0x30-0x39; /* '0' to '9' */
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target>0123456789</span>
)HTML");
// Trigger frame to start font loading
Compositor().BeginFrame();
Element* target = GetDocument().getElementById("target");
DCHECK_GT(250, target->OffsetWidth());
font_resource.Complete(ReadAhemWoff2());
Compositor().BeginFrame();
DCHECK_EQ(250, target->OffsetWidth());
// Test finishes without triggering a redundant load of font1.woff.
}
} // namespace blink