Implement font-display loading behaviors
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 5 Oct 2017 19:02:03 +0000 (19:02 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 5 Oct 2017 19:02:03 +0000 (19:02 +0000)
https://bugs.webkit.org/show_bug.cgi?id=175384
<rdar://problem/33813243>

Reviewed by Darin Adler.

Source/WebCore:

The font-display descriptors works off of the following model of font loading:

1. When a font loads, the @font-face enters the first phase, called the "block period." Here,
text using this @font-face is rendered as invisible using a fallback font for metrics. If the
file finishes loading during this period, it is swapped in (visibly).
2. When the first phase is over, the @font-face enters the second phase, called the "swap
period." Here, text using this @font-face is rendered visibly using a fallback font. If the
file finishes loading during this period, it is swapped in.
3. When the second phase is over, the @font-face enters the third phase, called the "failure
period." Here, text using this @font-face is rendered visibly using a fallback font. If the
file finishes loading during this period, it is not swapped in (but it does live in the
network cache for subsequent page loads). This phase lasts forever.

The font-display descriptor changes the duration of these phases. For example, our default
font loading behavior can be achieved by making the first phase 3 seconds long and making the
second phase infinitely long (so the third phase is never reached).

Luckily, our CSSFontFace class already has states which correspond to each phase listed above:
Loading, TimedOut, and Failure. This patch migrates our existing 3-second timer to have logic
to correctly set the timeout duration based on the value of the font-display descriptor and
the current status(). This occurs inside CSSFontFace::setStatus().

This has implications for testing. Previously, our tests for the font loading behavior had a
single boolean that describes whether or not font loads should immediately jump to the "swap
period". Clearly, this is insufficient for testing all aspects of the font-display descriptor.
Instead, this patch deletes this existing infrastructure and instead creates three more fake
values of font-display (achieved in tests by using window.internals). These fake values make
fonts immediately jump into a particular state and stay there forever (so the timeout values
are, for example, [0, infinity, infinity] to test the swap period). This works because
CSSFontFace is smart enough to synchronously move between states that have a 0 timeout, so
there is no race between these timers and font loads.

We also need to test the behavior when a file downloads and when a file hasn't been loaded
yet (and the @font-face is in a particular state). Therefore, this patch adds another bool
which indicates whether the font subsystem should totally ignore font load events. This means
that a font will successfully download (and DOMContentLoaded will be fired, because that
uses the loading subsystem), but the font subsystem will plug its ears and ignore the load.
This means we can test the invisibility of text during the "block period" because DRT will
see that the page load has completed, but the font subsystem will pretend like the font is
still loading and draw invisibly.

Therefore, there are 6 tests: a test to test each of the 3 states an @font-face block may be
in, times 2 for whether or not we are ignoring font loads. These are more comprehensive than
the existing font loading tests which used internals.settings.setWebFontsAlwaysFallBack(),
so I deleted those tests in favor of these new ones.

Tests: fast/text/loading-block-finish.html
       fast/text/loading-block-nofinish.html
       fast/text/loading-failure-finish.html
       fast/text/loading-failure-nofinish.html
       fast/text/loading-swap-finish.html
       fast/text/loading-swap-nofinish.html

* css/CSSFontFace.cpp:
(WebCore::CSSFontFace::setLoadingBehavior):
(WebCore::CSSFontFace::fontLoadEventOccurred): Remove old testing infrastructure.
(WebCore::CSSFontFace::timeoutFired): Previously, the timer was only used for going
from Loading -> TimedOut. Now, we have to ask the status() to figure out which
state transition we should be performing.
(WebCore::CSSFontFace::allSourcesFailed const): A Failed state needs to return true
here, even if some of the sources successfully downloaded.
(WebCore::CSSFontFace::setStatus): The logic to figure out how long to set the timer
for. Also, if the timer value is 0, synchronously recurse to change the status instead
of setting a 0-delay timer.
(WebCore::CSSFontFace::fontLoaded): Remove old testing infrastructure.
(WebCore::CSSFontFace::fontTimeoutIndex const): Implement new testing infrastructure.
(WebCore::CSSFontFace::shouldIgnoreFontLoadCompletions const): Ditto.
(WebCore::CSSFontFace::pump): See comment. Also, we're allowed to be in the Failure
state in more scenarios now, so relax some of our ASSERT()s.
(WebCore::CSSFontFace::font): Ditto.
(WebCore::CSSFontFace::webFontsShouldAlwaysFallBack const): Deleted.
* css/CSSFontFace.h: Migrate to new testing infrastructure.
* css/CSSFontFaceSource.cpp:
(WebCore::CSSFontFaceSource::CSSFontFaceSource): Implement new testing infrastructure.
(WebCore::CSSFontFaceSource::shouldIgnoreFontLoadCompletions const): Ditto.
(WebCore::CSSFontFaceSource::fontLoaded): Ditto.
* css/CSSFontFaceSource.h:
* css/CSSFontSelector.cpp:
(WebCore::CSSFontSelector::beginLoadingFontSoon): Remove old testing infrastructure.
* css/CSSSegmentedFontFace.cpp: It's possible to get different values out of
CSSFontFace::font() in successive calls during the same runloop. FontRanges will
include a raw pointer to one of the values, so all the values need to be kept alive.
* page/Settings.cpp: Migrate to new testing infrastructure.
(WebCore::Settings::Settings):
(WebCore::Settings::setFontTimeoutIndex):
(WebCore::Settings::setShouldIgnoreFontLoadCompletions):
(WebCore::Settings::setWebFontsAlwaysFallBack): Deleted.
* page/Settings.h: Ditto.
(WebCore::Settings::fontTimeoutIndex const):
(WebCore::Settings::shouldIgnoreFontLoadCompletions const):
(WebCore::Settings::webFontsAlwaysFallBack const): Deleted.
* testing/InternalSettings.cpp: Ditto.
(WebCore::InternalSettings::Backup::Backup):
(WebCore::InternalSettings::Backup::restoreTo):
(WebCore::InternalSettings::setFontTimeoutIndex):
(WebCore::InternalSettings::setShouldIgnoreFontLoadCompletions):
(WebCore::InternalSettings::setWebFontsAlwaysFallBack): Deleted.
* testing/InternalSettings.h: Ditto.
* testing/InternalSettings.idl: Ditto.

LayoutTests:

Delete the tests using the old testing infrastructure and replace them
with tests that use the new testing infrastructure.

* fast/text/font-loading-system-fallback-expected.html: Removed.
* fast/text/font-loading-system-fallback.html: Removed.
* fast/text/loading-block-finish-expected.html: Added.
* fast/text/loading-block-finish.html: Added.
* fast/text/loading-block-nofinish-expected.html: Added.
* fast/text/loading-block-nofinish.html: Added.
* fast/text/loading-failure-finish-expected.html: Added.
* fast/text/loading-failure-finish.html: Added.
* fast/text/loading-failure-nofinish-expected.html: Added.
* fast/text/loading-failure-nofinish.html: Added.
* fast/text/loading-swap-finish-expected.html: Added.
* fast/text/loading-swap-finish.html: Added.
* fast/text/loading-swap-nofinish-expected.html: Added.
* fast/text/loading-swap-nofinish.html: Added.
* fast/text/web-font-load-fallback-during-loading-2-expected.html: Removed.
* fast/text/web-font-load-fallback-during-loading-2.html: Removed.
* fast/text/web-font-load-fallback-during-loading-expected.html: Removed.
* fast/text/web-font-load-fallback-during-loading.html: Removed.
* platform/gtk/TestExpectations:
* platform/ios-wk1/TestExpectations:
* platform/win/TestExpectations:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@222926 268f45cc-cd09-0410-ab3c-d52691b4dbfc

36 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/text/font-loading-system-fallback-expected.html [deleted file]
LayoutTests/fast/text/font-loading-system-fallback.html [deleted file]
LayoutTests/fast/text/loading-block-finish-expected.html [new file with mode: 0644]
LayoutTests/fast/text/loading-block-finish.html [new file with mode: 0644]
LayoutTests/fast/text/loading-block-nofinish-expected.html [new file with mode: 0644]
LayoutTests/fast/text/loading-block-nofinish.html [new file with mode: 0644]
LayoutTests/fast/text/loading-failure-finish-expected.html [new file with mode: 0644]
LayoutTests/fast/text/loading-failure-finish.html [new file with mode: 0644]
LayoutTests/fast/text/loading-failure-nofinish-expected.html [new file with mode: 0644]
LayoutTests/fast/text/loading-failure-nofinish.html [new file with mode: 0644]
LayoutTests/fast/text/loading-swap-finish-expected.html [new file with mode: 0644]
LayoutTests/fast/text/loading-swap-finish.html [new file with mode: 0644]
LayoutTests/fast/text/loading-swap-nofinish-expected.html [new file with mode: 0644]
LayoutTests/fast/text/loading-swap-nofinish.html [new file with mode: 0644]
LayoutTests/fast/text/web-font-load-fallback-during-loading-2-expected.html [deleted file]
LayoutTests/fast/text/web-font-load-fallback-during-loading-2.html [deleted file]
LayoutTests/fast/text/web-font-load-fallback-during-loading-expected.html [deleted file]
LayoutTests/fast/text/web-font-load-fallback-during-loading.html [deleted file]
LayoutTests/platform/gtk/TestExpectations
LayoutTests/platform/ios-wk1/TestExpectations
LayoutTests/platform/win/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/css/CSSFontFace.cpp
Source/WebCore/css/CSSFontFace.h
Source/WebCore/css/CSSFontFaceSet.cpp
Source/WebCore/css/CSSFontFaceSource.cpp
Source/WebCore/css/CSSFontFaceSource.h
Source/WebCore/css/CSSFontSelector.cpp
Source/WebCore/css/CSSSegmentedFontFace.cpp
Source/WebCore/page/Settings.cpp
Source/WebCore/page/Settings.h
Source/WebCore/page/Settings.in
Source/WebCore/testing/InternalSettings.cpp
Source/WebCore/testing/InternalSettings.h
Source/WebCore/testing/InternalSettings.idl

index 4eaab3a..c6df84f 100644 (file)
@@ -1,3 +1,36 @@
+2017-10-05  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Implement font-display loading behaviors
+        https://bugs.webkit.org/show_bug.cgi?id=175384
+        <rdar://problem/33813243>
+
+        Reviewed by Darin Adler.
+
+        Delete the tests using the old testing infrastructure and replace them
+        with tests that use the new testing infrastructure.
+
+        * fast/text/font-loading-system-fallback-expected.html: Removed.
+        * fast/text/font-loading-system-fallback.html: Removed.
+        * fast/text/loading-block-finish-expected.html: Added.
+        * fast/text/loading-block-finish.html: Added.
+        * fast/text/loading-block-nofinish-expected.html: Added.
+        * fast/text/loading-block-nofinish.html: Added.
+        * fast/text/loading-failure-finish-expected.html: Added.
+        * fast/text/loading-failure-finish.html: Added.
+        * fast/text/loading-failure-nofinish-expected.html: Added.
+        * fast/text/loading-failure-nofinish.html: Added.
+        * fast/text/loading-swap-finish-expected.html: Added.
+        * fast/text/loading-swap-finish.html: Added.
+        * fast/text/loading-swap-nofinish-expected.html: Added.
+        * fast/text/loading-swap-nofinish.html: Added.
+        * fast/text/web-font-load-fallback-during-loading-2-expected.html: Removed.
+        * fast/text/web-font-load-fallback-during-loading-2.html: Removed.
+        * fast/text/web-font-load-fallback-during-loading-expected.html: Removed.
+        * fast/text/web-font-load-fallback-during-loading.html: Removed.
+        * platform/gtk/TestExpectations:
+        * platform/ios-wk1/TestExpectations:
+        * platform/win/TestExpectations:
+
 2017-10-05  Antoine Quint  <graouts@apple.com>
 
         Layout Test media/modern-media-controls/scrubber-support/scrubber-support-drag.html is flaky.
diff --git a/LayoutTests/fast/text/font-loading-system-fallback-expected.html b/LayoutTests/fast/text/font-loading-system-fallback-expected.html
deleted file mode 100644 (file)
index 5bbefde..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-</head>
-<body>
-This test makes sure that system fallback fonts (occuring when falling off the end of the fallback list) during loading are chosen from the best local font possible. The test passes if the Japanese character below is drawn in a font which matches with Helvetica's design.
-<div style="font: 48px 'Helvetica';">&#x306E;<div style="display: inline-block; width: 1px; height: 200px;"></div></div>
-</body>
-</html>
diff --git a/LayoutTests/fast/text/font-loading-system-fallback.html b/LayoutTests/fast/text/font-loading-system-fallback.html
deleted file mode 100644 (file)
index 45917a9..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<script>
-if (window.internals) {
-    internals.settings.setWebFontsAlwaysFallBack(true);
-    internals.clearMemoryCache();
-    internals.invalidateFontCache();
-}
-</script>
-<style>
-@font-face {
-    font-family: "WebFont";
-    src: url("../../resources/Ahem.ttf") format("truetype");
-}
-</style>
-</head>
-<body>
-This test makes sure that system fallback fonts (occuring when falling off the end of the fallback list) during loading are chosen from the best local font possible. The test passes if the Japanese character below is drawn in a font which matches with Helvetica's design.
-<div style="font: 48px 'WebFont', 'Helvetica';">&#x306E;<div style="display: inline-block; width: 1px; height: 200px;"></div></div>
-</body>
-</html>
diff --git a/LayoutTests/fast/text/loading-block-finish-expected.html b/LayoutTests/fast/text/loading-block-finish-expected.html
new file mode 100644 (file)
index 0000000..a2efaa4
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("../../resources/Ahem.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "block" period after the font finishes loading is rendered as visible. The test passes if you see a black rectangle below.
+<div style="font: 100px 'MyFont';">Hello</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-block-finish.html b/LayoutTests/fast/text/loading-block-finish.html
new file mode 100644 (file)
index 0000000..c7f40c0
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.internals) {
+    internals.settings.setFontLoadTimingOverride("Block");
+    internals.settings.setShouldIgnoreFontLoadCompletions(false);
+}
+</script>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("../../resources/Ahem.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "block" period after the font finishes loading is rendered as visible. The test passes if you see a black rectangle below.
+<div style="font: 100px 'MyFont';">Hello</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-block-nofinish-expected.html b/LayoutTests/fast/text/loading-block-nofinish-expected.html
new file mode 100644 (file)
index 0000000..8d8673c
--- /dev/null
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("../../resources/Ahem.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "block" period while the font is still loading is rendered as invisible. The test passes if there is no text below.
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-block-nofinish.html b/LayoutTests/fast/text/loading-block-nofinish.html
new file mode 100644 (file)
index 0000000..bf605e5
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.internals) {
+    internals.settings.setFontLoadTimingOverride("Block");
+    internals.settings.setShouldIgnoreFontLoadCompletions(true);
+}
+</script>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("../../resources/Ahem.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "block" period while the font is still loading is rendered as invisible. The test passes if there is no text below.
+<div style="font: 100px 'MyFont';">Hello</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-failure-finish-expected.html b/LayoutTests/fast/text/loading-failure-finish-expected.html
new file mode 100644 (file)
index 0000000..5f9569d
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("garbage") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "failure" period after the font finishes loading is rendered with the old font. The test passes if there is text below.
+<div style="font: 100px 'MyFont';">Hello</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-failure-finish.html b/LayoutTests/fast/text/loading-failure-finish.html
new file mode 100644 (file)
index 0000000..894b149
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.internals) {
+    internals.settings.setFontLoadTimingOverride("Failure");
+    internals.settings.setShouldIgnoreFontLoadCompletions(false);
+}
+</script>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("../../resources/Ahem.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "failure" period after the font finishes loading is rendered with the old font. The test passes if there is text below.
+<div style="font: 100px 'MyFont';">Hello</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-failure-nofinish-expected.html b/LayoutTests/fast/text/loading-failure-nofinish-expected.html
new file mode 100644 (file)
index 0000000..5ed1222
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.internals) {
+    internals.settings.setFontTimeoutIndex(7);
+    internals.settings.setShouldIgnoreFontLoadCompletions(true);
+}
+</script>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("garbage") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "failure" period while the font is still loading is rendered as visible. The test passes if there is text below.
+<div style="font: 100px 'MyFont';">Hello</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-failure-nofinish.html b/LayoutTests/fast/text/loading-failure-nofinish.html
new file mode 100644 (file)
index 0000000..eff6b25
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.internals) {
+    internals.settings.setFontLoadTimingOverride("Failure");
+    internals.settings.setShouldIgnoreFontLoadCompletions(true);
+}
+</script>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("../../resources/Ahem.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "failure" period while the font is still loading is rendered as visible. The test passes if there is text below.
+<div style="font: 100px 'MyFont';">Hello</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-swap-finish-expected.html b/LayoutTests/fast/text/loading-swap-finish-expected.html
new file mode 100644 (file)
index 0000000..3f2dbc7
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("../../resources/Ahem.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "swap" period after the font finishes loading is rendered as visible. The test passes if you see a black rectangle below.
+<div style="font: 100px 'MyFont';">Hello</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-swap-finish.html b/LayoutTests/fast/text/loading-swap-finish.html
new file mode 100644 (file)
index 0000000..365ac0b
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.internals) {
+    internals.settings.setFontLoadTimingOverride("Swap");
+    internals.settings.setShouldIgnoreFontLoadCompletions(false);
+}
+</script>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("../../resources/Ahem.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "swap" period after the font finishes loading is rendered as visible. The test passes if you see a black rectangle below.
+<div style="font: 100px 'MyFont';">Hello</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-swap-nofinish-expected.html b/LayoutTests/fast/text/loading-swap-nofinish-expected.html
new file mode 100644 (file)
index 0000000..0b5200d
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("garbage") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "swap" period while the font is still loading is rendered as visible. The test passes if there is text below.
+<div style="font: 100px 'MyFont', 'Helvetica';">Hello<div style="display: inline-block; width: 1px; height: 200px;"></div></div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/loading-swap-nofinish.html b/LayoutTests/fast/text/loading-swap-nofinish.html
new file mode 100644 (file)
index 0000000..51e7f9c
--- /dev/null
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+if (window.internals) {
+    internals.settings.setFontLoadTimingOverride("Swap");
+    internals.settings.setShouldIgnoreFontLoadCompletions(true);
+}
+</script>
+<style>
+@font-face {
+    font-family: "MyFont";
+    src: url("../../resources/Ahem.ttf") format("truetype");
+}
+</style>
+</head>
+<body>
+This test makes sure that text being drawn during the "swap" period while the font is still loading is rendered as visible. The test passes if there is text below.
+<div style="font: 100px 'MyFont', 'Helvetica';">Hello<div style="display: inline-block; width: 1px; height: 200px;"></div></div>
+</body>
+</html>
diff --git a/LayoutTests/fast/text/web-font-load-fallback-during-loading-2-expected.html b/LayoutTests/fast/text/web-font-load-fallback-during-loading-2-expected.html
deleted file mode 100644 (file)
index f357242..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-</head>
-<body>
-This test makes sure that fallback fonts are used during the time when fonts are loading.
-<p><span id="test" style="font-family: 'Helvetica';">This is rendered with Helvetica.</span></p>
-</body>
-</html>
diff --git a/LayoutTests/fast/text/web-font-load-fallback-during-loading-2.html b/LayoutTests/fast/text/web-font-load-fallback-during-loading-2.html
deleted file mode 100644 (file)
index b23c78c..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<script>
-if (window.internals) {
-    internals.settings.setWebFontsAlwaysFallBack(true);
-    internals.clearMemoryCache();
-    internals.invalidateFontCache();
-}
-</script>
-<style>
-@font-face {
-    font-family: "Helvetica";
-    src: url("../../resources/Ahem.ttf") format("truetype");
-}
-</style>
-</head>
-<body>
-This test makes sure that fallback fonts are used during the time when fonts are loading.
-<p><span id="test" style="font-family: 'Helvetica';">This is rendered with Helvetica.</span></p>
-</body>
-</html>
diff --git a/LayoutTests/fast/text/web-font-load-fallback-during-loading-expected.html b/LayoutTests/fast/text/web-font-load-fallback-during-loading-expected.html
deleted file mode 100644 (file)
index 58b5da0..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-</head>
-<body>
-<div style="-webkit-text-size-adjust: none;">
-This test makes sure that fallback fonts are used during the time when fonts are loading.
-<p id="console">PASS: Element is not rendered with American Typewriter.</p>
-<p><span class="test" style="font-family: 'American Typewriter';">This is a test rendering.</span></p>
-<p><span class="test" style="font-family: 'American Typewriter';">PASS: This is rendered with American Typewriter.</span></p>
-</div>
-</body>
-</html>
diff --git a/LayoutTests/fast/text/web-font-load-fallback-during-loading.html b/LayoutTests/fast/text/web-font-load-fallback-during-loading.html
deleted file mode 100644 (file)
index 94b0575..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<script>
-if (window.internals) {
-    internals.settings.setWebFontsAlwaysFallBack(true);
-    internals.clearMemoryCache();
-    internals.invalidateFontCache();
-}
-</script>
-<style>
-@font-face {
-    font-family: "WebFont";
-    src: url("../../resources/Ahem.ttf") format("truetype"), local("American Typewriter");
-}
-@font-face {
-    font-family: "WebFont2";
-    src: local("American Typewriter");
-}
-@font-face {
-    font-family: "WebFont2";
-    src: url("../../resources/Ahem.ttf") format("truetype");
-}
-@font-face {
-    font-family: "WebFont3";
-    src: url("../../resources/Ahem.ttf") format("truetype");
-}
-</style>
-</head>
-<body>
-<div style="-webkit-text-size-adjust: none;">
-This test makes sure that fallback fonts are used during the time when fonts are loading.
-<p id="console">Test NOT complete.</p>
-<p><span id="americanTypewriter" style="font-family: 'American Typewriter';">This is a test rendering.</span></p>
-<p id="container"><span id="notAmericanTypewriter" style="font-family: 'WebFont';">This is a test rendering.</span></p>
-<p><span style="font-family: 'WebFont2';">PASS: This is rendered with American Typewriter.</span></p>
-<!-- Because the primary font is the platform-dependent interstitial font, the baseline position of the line is calculated with this platform-specific font, which means we can't test it with a ref test because we don't know exactly where the text will end up. -->
-<!--p><span style="font-family: 'WebFont3', 'American Typewriter';">PASS: This is rendered with American Typewriter.</span></p-->
-</div>
-<script>
-var console = document.getElementById("console");
-var americanTypewriterWidth = document.getElementById("americanTypewriter").getBoundingClientRect().width;
-var notAmericanTypewriterWidth = document.getElementById("notAmericanTypewriter").getBoundingClientRect().width;
-if (americanTypewriterWidth == notAmericanTypewriterWidth)
-    console.innerText = "FAIL: Element should not be rendered with American Typewriter.";
-else
-    console.innerText = "PASS: Element is not rendered with American Typewriter.";
-document.getElementById("container").style.display = "none";
-</script>
-</body>
-</html>
index ea9bdfc..d98c9ec 100644 (file)
@@ -3227,8 +3227,6 @@ webkit.org/b/171726 media/media-source/media-source-init-segment-duration.html [
 
 webkit.org/b/172270 fast/text/font-interstitial-invisible-width-while-loading.html [ Failure ]
 webkit.org/b/172270 fast/text/web-font-load-invisible-during-loading.html [ Failure ]
-webkit.org/b/172270 fast/text/web-font-load-fallback-during-loading-2.html [ ImageOnlyFailure ]
-webkit.org/b/172270 fast/text/web-font-load-fallback-during-loading.html [ ImageOnlyFailure ]
 
 webkit.org/b/172271 fast/text/softbank-emoji.html [ Failure ]
 webkit.org/b/172271 fast/text/system-font-fallback-emoji.html [ Failure ]
index b42c1e2..1e8ba49 100644 (file)
@@ -1418,7 +1418,6 @@ fast/replaced/table-percent-height.html [ Pass Failure ]
 fast/ruby/ruby-justification-hittest.html [ Pass Failure ]
 fast/shadow-dom/trusted-event-scoped-flags.html [ Pass Failure ]
 fast/sub-pixel/compositing-layers-on-subpixel-position.html [ ImageOnlyFailure Pass ]
-fast/text/web-font-load-fallback-during-loading.html [ ImageOnlyFailure Pass ]
 fast/viewport/ios/width-is-device-width-overflowing-body-overflow-hidden-tall.html [ Pass Failure ]
 fast/viewport/ios/width-is-device-width-overflowing-body-overflow-hidden.html [ Pass Failure ]
 fast/viewport/ios/width-is-device-width-overflowing-no-shrink-to-fit.html [ Pass Failure ]
index 7464aa9..cf7f5b4 100644 (file)
@@ -3555,7 +3555,6 @@ fast/text/multiple-codeunit-vertical-upright.html [ ImageOnlyFailure ]
 fast/text/simple-line-layout-simple-text-but-complex-font-path.html [ ImageOnlyFailure ]
 fast/text/variations/font-face-format.html [ ImageOnlyFailure ]
 fast/text/variations/font-face-format-woff2.html [ ImageOnlyFailure ]
-fast/text/web-font-load-fallback-during-loading.html [ ImageOnlyFailure ]
 imported/blink/scrollbars/avoid-double-scrollbars-when-html-element-is-not-the-renderview.html [ ImageOnlyFailure ]
 imported/w3c/i18n/bidi/bidi-plaintext-011.html [ ImageOnlyFailure ]
 svg/custom/anchor-on-use.svg [ ImageOnlyFailure ]
index 51f02bb..9b85c2b 100644 (file)
@@ -1,3 +1,111 @@
+2017-10-05  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Implement font-display loading behaviors
+        https://bugs.webkit.org/show_bug.cgi?id=175384
+        <rdar://problem/33813243>
+
+        Reviewed by Darin Adler.
+
+        The font-display descriptors works off of the following model of font loading:
+
+        1. When a font loads, the @font-face enters the first phase, called the "block period." Here,
+        text using this @font-face is rendered as invisible using a fallback font for metrics. If the
+        file finishes loading during this period, it is swapped in (visibly).
+        2. When the first phase is over, the @font-face enters the second phase, called the "swap
+        period." Here, text using this @font-face is rendered visibly using a fallback font. If the
+        file finishes loading during this period, it is swapped in.
+        3. When the second phase is over, the @font-face enters the third phase, called the "failure
+        period." Here, text using this @font-face is rendered visibly using a fallback font. If the
+        file finishes loading during this period, it is not swapped in (but it does live in the
+        network cache for subsequent page loads). This phase lasts forever.
+
+        The font-display descriptor changes the duration of these phases. For example, our default
+        font loading behavior can be achieved by making the first phase 3 seconds long and making the
+        second phase infinitely long (so the third phase is never reached).
+
+        Luckily, our CSSFontFace class already has states which correspond to each phase listed above:
+        Loading, TimedOut, and Failure. This patch migrates our existing 3-second timer to have logic
+        to correctly set the timeout duration based on the value of the font-display descriptor and
+        the current status(). This occurs inside CSSFontFace::setStatus().
+
+        This has implications for testing. Previously, our tests for the font loading behavior had a
+        single boolean that describes whether or not font loads should immediately jump to the "swap
+        period". Clearly, this is insufficient for testing all aspects of the font-display descriptor.
+        Instead, this patch deletes this existing infrastructure and instead creates three more fake
+        values of font-display (achieved in tests by using window.internals). These fake values make
+        fonts immediately jump into a particular state and stay there forever (so the timeout values
+        are, for example, [0, infinity, infinity] to test the swap period). This works because
+        CSSFontFace is smart enough to synchronously move between states that have a 0 timeout, so
+        there is no race between these timers and font loads.
+
+        We also need to test the behavior when a file downloads and when a file hasn't been loaded
+        yet (and the @font-face is in a particular state). Therefore, this patch adds another bool
+        which indicates whether the font subsystem should totally ignore font load events. This means
+        that a font will successfully download (and DOMContentLoaded will be fired, because that
+        uses the loading subsystem), but the font subsystem will plug its ears and ignore the load.
+        This means we can test the invisibility of text during the "block period" because DRT will
+        see that the page load has completed, but the font subsystem will pretend like the font is
+        still loading and draw invisibly.
+
+        Therefore, there are 6 tests: a test to test each of the 3 states an @font-face block may be
+        in, times 2 for whether or not we are ignoring font loads. These are more comprehensive than
+        the existing font loading tests which used internals.settings.setWebFontsAlwaysFallBack(),
+        so I deleted those tests in favor of these new ones.
+
+        Tests: fast/text/loading-block-finish.html
+               fast/text/loading-block-nofinish.html
+               fast/text/loading-failure-finish.html
+               fast/text/loading-failure-nofinish.html
+               fast/text/loading-swap-finish.html
+               fast/text/loading-swap-nofinish.html
+
+        * css/CSSFontFace.cpp:
+        (WebCore::CSSFontFace::setLoadingBehavior):
+        (WebCore::CSSFontFace::fontLoadEventOccurred): Remove old testing infrastructure.
+        (WebCore::CSSFontFace::timeoutFired): Previously, the timer was only used for going
+        from Loading -> TimedOut. Now, we have to ask the status() to figure out which
+        state transition we should be performing.
+        (WebCore::CSSFontFace::allSourcesFailed const): A Failed state needs to return true
+        here, even if some of the sources successfully downloaded.
+        (WebCore::CSSFontFace::setStatus): The logic to figure out how long to set the timer
+        for. Also, if the timer value is 0, synchronously recurse to change the status instead
+        of setting a 0-delay timer.
+        (WebCore::CSSFontFace::fontLoaded): Remove old testing infrastructure.
+        (WebCore::CSSFontFace::fontTimeoutIndex const): Implement new testing infrastructure.
+        (WebCore::CSSFontFace::shouldIgnoreFontLoadCompletions const): Ditto.
+        (WebCore::CSSFontFace::pump): See comment. Also, we're allowed to be in the Failure
+        state in more scenarios now, so relax some of our ASSERT()s.
+        (WebCore::CSSFontFace::font): Ditto.
+        (WebCore::CSSFontFace::webFontsShouldAlwaysFallBack const): Deleted.
+        * css/CSSFontFace.h: Migrate to new testing infrastructure.
+        * css/CSSFontFaceSource.cpp:
+        (WebCore::CSSFontFaceSource::CSSFontFaceSource): Implement new testing infrastructure.
+        (WebCore::CSSFontFaceSource::shouldIgnoreFontLoadCompletions const): Ditto.
+        (WebCore::CSSFontFaceSource::fontLoaded): Ditto.
+        * css/CSSFontFaceSource.h:
+        * css/CSSFontSelector.cpp:
+        (WebCore::CSSFontSelector::beginLoadingFontSoon): Remove old testing infrastructure.
+        * css/CSSSegmentedFontFace.cpp: It's possible to get different values out of
+        CSSFontFace::font() in successive calls during the same runloop. FontRanges will 
+        include a raw pointer to one of the values, so all the values need to be kept alive.
+        * page/Settings.cpp: Migrate to new testing infrastructure.
+        (WebCore::Settings::Settings):
+        (WebCore::Settings::setFontTimeoutIndex):
+        (WebCore::Settings::setShouldIgnoreFontLoadCompletions):
+        (WebCore::Settings::setWebFontsAlwaysFallBack): Deleted.
+        * page/Settings.h: Ditto.
+        (WebCore::Settings::fontTimeoutIndex const):
+        (WebCore::Settings::shouldIgnoreFontLoadCompletions const):
+        (WebCore::Settings::webFontsAlwaysFallBack const): Deleted.
+        * testing/InternalSettings.cpp: Ditto.
+        (WebCore::InternalSettings::Backup::Backup):
+        (WebCore::InternalSettings::Backup::restoreTo):
+        (WebCore::InternalSettings::setFontTimeoutIndex):
+        (WebCore::InternalSettings::setShouldIgnoreFontLoadCompletions):
+        (WebCore::InternalSettings::setWebFontsAlwaysFallBack): Deleted.
+        * testing/InternalSettings.h: Ditto.
+        * testing/InternalSettings.idl: Ditto.
+
 2017-10-05  Zalan Bujtas  <zalan@apple.com>
 
         RenderMathMLFenced should not hold a raw pointer to RenderMathMLFencedOperator
index cc053ea..cb27c4d 100644 (file)
@@ -446,7 +446,7 @@ void CSSFontFace::setFeatureSettings(CSSValue& featureSettings)
 
 void CSSFontFace::setLoadingBehavior(CSSValue& loadingBehaviorValue)
 {
-    auto loadingBehavior = static_cast<FontLoadingBehavior>(downcast<CSSPrimitiveValue>(loadingBehaviorValue).valueID());
+    auto loadingBehavior = static_cast<FontLoadingBehavior>(downcast<CSSPrimitiveValue>(loadingBehaviorValue));
 
     if (m_loadingBehavior == loadingBehavior)
         return;
@@ -480,7 +480,7 @@ void CSSFontFace::fontLoadEventOccurred()
     // If the font is already in the cache, CSSFontFaceSource may report it's loaded before it is added here as a source.
     // Let's not pump the state machine until we've got all our sources. font() and load() are smart enough to act correctly
     // when a source is failed or succeeded before we have asked it to load.
-    if (m_sourcesPopulated && !webFontsShouldAlwaysFallBack())
+    if (m_sourcesPopulated)
         pump(ExternalResourceDownloadPolicy::Forbid);
 
     ASSERT(m_fontSelector);
@@ -493,13 +493,30 @@ void CSSFontFace::fontLoadEventOccurred()
 
 void CSSFontFace::timeoutFired()
 {
-    setStatus(Status::TimedOut);
+    switch (status()) {
+    case Status::Loading:
+        setStatus(Status::TimedOut);
+        break;
+    case Status::TimedOut:
+        // Cancelling the network request here could lead to a situation where a site's font never gets
+        // shown as the user navigates around to different pages on the site. This would occur if the
+        // download always takes longer than the timeout (even though the user may spend substantial time
+        // on each page). Therefore, we shouldn't cancel the network request here, but should use the
+        // loading infrastructure's timeout policies instead.
+        setStatus(Status::Failure);
+        break;
+    default:
+        ASSERT_NOT_REACHED();
+        break;
+    }
 
     fontLoadEventOccurred();
 }
 
-bool CSSFontFace::allSourcesFailed() const
+bool CSSFontFace::computeFailureState() const
 {
+    if (status() == Status::Failure)
+        return true;
     for (auto& source : m_sources) {
         if (source->status() != CSSFontFaceSource::Status::Failure)
             return false;
@@ -567,6 +584,40 @@ void CSSFontFace::adoptSource(std::unique_ptr<CSSFontFaceSource>&& source)
     ASSERT(!m_sourcesPopulated);
 }
 
+static Settings::FontLoadTimingOverride fontLoadTimingOverride(CSSFontSelector* fontSelector)
+{
+    auto overrideValue = Settings::FontLoadTimingOverride::None;
+    if (fontSelector && fontSelector->document())
+        overrideValue = fontSelector->document()->settings().fontLoadTimingOverride();
+    return overrideValue;
+}
+
+auto CSSFontFace::fontLoadTiming() const -> FontLoadTiming
+{
+    switch (fontLoadTimingOverride(m_fontSelector.get())) {
+    case Settings::FontLoadTimingOverride::None:
+        switch (m_loadingBehavior) {
+        case FontLoadingBehavior::Auto:
+        case FontLoadingBehavior::Block:
+            return { 3_s, Seconds::infinity() };
+        case FontLoadingBehavior::Swap:
+            return { 0_s, Seconds::infinity() };
+        case FontLoadingBehavior::Fallback:
+            return { 0.1_s, 3_s };
+        case FontLoadingBehavior::Optional:
+            return { 0.1_s, 0_s };
+        }
+    case Settings::FontLoadTimingOverride::Block:
+        return { Seconds::infinity(), 0_s };
+    case Settings::FontLoadTimingOverride::Swap:
+        return { 0_s, Seconds::infinity() };
+    case Settings::FontLoadTimingOverride::Failure:
+        return { 0_s, 0_s };
+    }
+    ASSERT_NOT_REACHED();
+    return { 3_s, Seconds::infinity() };
+}
+
 void CSSFontFace::setStatus(Status newStatus)
 {
     switch (newStatus) {
@@ -587,49 +638,86 @@ void CSSFontFace::setStatus(Status newStatus)
         break;
     }
 
-    bool webFontsShouldAlwaysFallBack = this->webFontsShouldAlwaysFallBack();
-    if (!webFontsShouldAlwaysFallBack) {
-        if (newStatus == Status::Loading) {
-            Seconds timeUntilInterstitialFontIsDrawnVisibly = 3_s;
-            m_timeoutTimer.startOneShot(timeUntilInterstitialFontIsDrawnVisibly);
-        } else if (newStatus == Status::Success || newStatus == Status::Failure)
-            m_timeoutTimer.stop();
-    }
-
     iterateClients(m_clients, [&](Client& client) {
         client.fontStateChanged(*this, m_status, newStatus);
     });
 
     m_status = newStatus;
 
-    if (newStatus == Status::Loading && webFontsShouldAlwaysFallBack)
-        timeoutFired();
+    Seconds blockPeriodTimeout;
+    Seconds swapPeriodTimeout;
+    auto timeouts = fontLoadTiming();
+    blockPeriodTimeout = timeouts.blockPeriod;
+    swapPeriodTimeout = timeouts.swapPeriod;
+
+    // Transfer across 0-delay timers synchronously. Layouts/script may
+    // take arbitrarily long time, and we shouldn't be in a 0-duration
+    // state for an arbitrarily long time. Also it's necessary for
+    // testing so we don't have a race with the font load.
+    switch (newStatus) {
+    case Status::Pending:
+        ASSERT_NOT_REACHED();
+        break;
+    case Status::Loading:
+        if (blockPeriodTimeout == 0_s)
+            setStatus(Status::TimedOut);
+        else if (isfinite(blockPeriodTimeout.value()))
+            m_timeoutTimer.startOneShot(blockPeriodTimeout);
+        break;
+    case Status::TimedOut:
+        if (swapPeriodTimeout == 0_s)
+            setStatus(Status::Failure);
+        else if (isfinite(swapPeriodTimeout.value()))
+            m_timeoutTimer.startOneShot(swapPeriodTimeout);
+        break;
+    case Status::Success:
+    case Status::Failure:
+        m_timeoutTimer.stop();
+        break;
+    }
 }
 
 void CSSFontFace::fontLoaded(CSSFontFaceSource&)
 {
-    ASSERT(!webFontsShouldAlwaysFallBack());
-
     fontLoadEventOccurred();
 }
 
-bool CSSFontFace::webFontsShouldAlwaysFallBack() const
+bool CSSFontFace::shouldIgnoreFontLoadCompletions() const
 {
-    return m_fontSelector && m_fontSelector->document() && m_fontSelector->document()->settings().webFontsAlwaysFallBack();
+    if (m_fontSelector && m_fontSelector->document())
+        return m_fontSelector->document()->settings().shouldIgnoreFontLoadCompletions();
+    return false;
 }
 
 size_t CSSFontFace::pump(ExternalResourceDownloadPolicy policy)
 {
+    if (status() == Status::Failure)
+        return 0;
+
     size_t i;
     for (i = 0; i < m_sources.size(); ++i) {
         auto& source = m_sources[i];
 
         if (source->status() == CSSFontFaceSource::Status::Pending) {
             ASSERT(m_status == Status::Pending || m_status == Status::Loading || m_status == Status::TimedOut);
+            // This is a little tricky. After calling CSSFontFace::font(Forbid), a font must never fail later in
+            // this turn of the runloop because the return value of CSSFontFace::font() shouldn't get nulled out
+            // from under an existing FontRanges object. Remote fonts are all downloaded asynchronously, so this
+            // isn't a problem for them because CSSFontFace::font() will always return the interstitial font.
+            // However, local fonts may synchronously fail when you call load() on them. Therefore, we have to call
+            // load() here in order to guarantee that, if the font synchronously fails, it happens now during the
+            // first call to CSSFontFace::font() and the FontRanges object sees a consistent view of the
+            // CSSFontFace. This means we eagerly create some internal font objects when they may not be needed,
+            // but it seems that this behavior is a requirement of the design of FontRanges. FIXME: Perhaps rethink
+            // this design.
             if (policy == ExternalResourceDownloadPolicy::Allow || !source->requiresExternalResource()) {
                 if (m_status == Status::Pending)
                     setStatus(Status::Loading);
                 source->load(m_fontSelector.get());
+            } else if (fontLoadTimingOverride(m_fontSelector.get()) != Settings::FontLoadTimingOverride::None && m_status == Status::Pending) {
+                // Similar to above, if a test that has set fontLoadTimingOverride() needs to fail, this needs to happen
+                // eagerly so the FontRanges sees a consistent view of the CSSFontFace.
+                setStatus(Status::Loading);
             }
         }
 
@@ -638,12 +726,12 @@ size_t CSSFontFace::pump(ExternalResourceDownloadPolicy policy)
             ASSERT(policy == ExternalResourceDownloadPolicy::Forbid);
             return i;
         case CSSFontFaceSource::Status::Loading:
-            ASSERT(m_status == Status::Pending || m_status == Status::Loading || m_status == Status::TimedOut);
+            ASSERT(m_status == Status::Pending || m_status == Status::Loading || m_status == Status::TimedOut || m_status == Status::Failure);
             if (m_status == Status::Pending)
                 setStatus(Status::Loading);
             return i;
         case CSSFontFaceSource::Status::Success:
-            ASSERT(m_status == Status::Pending || m_status == Status::Loading || m_status == Status::TimedOut || m_status == Status::Success);
+            ASSERT(m_status == Status::Pending || m_status == Status::Loading || m_status == Status::TimedOut || m_status == Status::Success || m_status == Status::Failure);
             if (m_status == Status::Pending)
                 setStatus(Status::Loading);
             if (m_status == Status::Loading || m_status == Status::TimedOut)
@@ -669,7 +757,7 @@ void CSSFontFace::load()
 
 RefPtr<Font> CSSFontFace::font(const FontDescription& fontDescription, bool syntheticBold, bool syntheticItalic, ExternalResourceDownloadPolicy policy)
 {
-    if (allSourcesFailed())
+    if (computeFailureState())
         return nullptr;
 
     // Our status is derived from the first non-failed source. However, this source may
@@ -677,6 +765,10 @@ RefPtr<Font> CSSFontFace::font(const FontDescription& fontDescription, bool synt
     // of the sources to try to find a font to use. These subsequent tries should not affect
     // our own state, though.
     size_t startIndex = pump(policy);
+
+    if (computeFailureState())
+        return nullptr;
+
     for (size_t i = startIndex; i < m_sources.size(); ++i) {
         auto& source = m_sources[i];
         if (source->status() == CSSFontFaceSource::Status::Pending && (policy == ExternalResourceDownloadPolicy::Allow || !source->requiresExternalResource()))
@@ -685,8 +777,8 @@ RefPtr<Font> CSSFontFace::font(const FontDescription& fontDescription, bool synt
         switch (source->status()) {
         case CSSFontFaceSource::Status::Pending:
         case CSSFontFaceSource::Status::Loading: {
-            Font::Visibility visibility = status() == Status::TimedOut ? Font::Visibility::Visible : Font::Visibility::Invisible;
-            return Font::create(FontCache::singleton().lastResortFallbackFont(fontDescription)->platformData(), Font::Origin::Remote, Font::Interstitial::Yes, visibility);
+            Font::Visibility visibility = status() == Status::TimedOut || status() == Status::Failure ? Font::Visibility::Visible : Font::Visibility::Invisible;
+            return Font::create(FontCache::singleton().lastResortFallbackFont(fontDescription)->platformData(), Font::Origin::Local, Font::Interstitial::Yes, visibility);
         }
         case CSSFontFaceSource::Status::Success:
             if (RefPtr<Font> result = source->font(fontDescription, syntheticBold, syntheticItalic, m_featureSettings, m_variantSettings, m_fontSelectionCapabilities))
index d927949..230eac5 100644 (file)
@@ -101,7 +101,7 @@ public:
     void addClient(Client&);
     void removeClient(Client&);
 
-    bool allSourcesFailed() const;
+    bool computeFailureState() const;
 
     void adoptSource(std::unique_ptr<CSSFontFaceSource>&&);
     void sourcesPopulated() { m_sourcesPopulated = true; }
@@ -149,7 +149,12 @@ public:
     void setWrapper(FontFace&);
     FontFace* existingWrapper() { return m_wrapper.get(); }
 
-    bool webFontsShouldAlwaysFallBack() const;
+    struct FontLoadTiming {
+        Seconds blockPeriod;
+        Seconds swapPeriod;
+    };
+    FontLoadTiming fontLoadTiming() const;
+    bool shouldIgnoreFontLoadCompletions() const;
 
     bool purgeable() const;
 
index c80a25c..812cb65 100644 (file)
@@ -114,7 +114,7 @@ void CSSFontFaceSet::ensureLocalFontFacesForFamilyRegistered(const String& famil
         face->setFamilies(familyList.get());
         face->setFontSelectionCapabilities(item);
         face->adoptSource(std::make_unique<CSSFontFaceSource>(face.get(), familyName));
-        ASSERT(!face->allSourcesFailed());
+        ASSERT(!face->computeFailureState());
         faces.append(WTFMove(face));
     }
     m_locallyInstalledFacesLookupTable.add(familyName, WTFMove(faces));
index 52aac75..2431d36 100644 (file)
@@ -90,10 +90,12 @@ CSSFontFaceSource::CSSFontFaceSource(CSSFontFace& owner, const String& familyNam
 
     if (status() == Status::Pending && m_font && m_font->isLoaded()) {
         setStatus(Status::Loading);
-        if (m_font && m_font->errorOccurred())
-            setStatus(Status::Failure);
-        else
-            setStatus(Status::Success);
+        if (!shouldIgnoreFontLoadCompletions()) {
+            if (m_font && m_font->errorOccurred())
+                setStatus(Status::Failure);
+            else
+                setStatus(Status::Success);
+        }
     }
 }
 
@@ -103,10 +105,18 @@ CSSFontFaceSource::~CSSFontFaceSource()
         m_font->removeClient(*this);
 }
 
+bool CSSFontFaceSource::shouldIgnoreFontLoadCompletions() const
+{
+    return m_face.shouldIgnoreFontLoadCompletions();
+}
+
 void CSSFontFaceSource::fontLoaded(CachedFont& loadedFont)
 {
     ASSERT_UNUSED(loadedFont, &loadedFont == m_font.get());
 
+    if (shouldIgnoreFontLoadCompletions())
+        return;
+
     Ref<CSSFontFace> protectedFace(m_face);
 
     // If the font is in the cache, this will be synchronously called from CachedFont::addClient().
@@ -118,9 +128,6 @@ void CSSFontFaceSource::fontLoaded(CachedFont& loadedFont)
         return;
     }
 
-    if (m_face.webFontsShouldAlwaysFallBack())
-        return;
-
     if (m_font->errorOccurred() || !m_font->ensureCustomFontData(m_familyNameOrURI))
         setStatus(Status::Failure);
     else
index 7af010a..5c287bc 100644 (file)
@@ -76,6 +76,8 @@ public:
 #endif
 
 private:
+    bool shouldIgnoreFontLoadCompletions() const;
+
     void fontLoaded(CachedFont&) override;
 
     void setStatus(Status);
index 4a05e18..e7c7aeb 100644 (file)
@@ -202,7 +202,7 @@ void CSSFontSelector::addFontFaceRule(StyleRuleFontFace& fontFaceRule, bool isIn
         fontFace->setLoadingBehavior(*loadingBehavior);
 
     CSSFontFace::appendSources(fontFace, srcList, m_document, isInitiatingElementInUserAgentShadowTree);
-    if (fontFace->allSourcesFailed())
+    if (fontFace->computeFailureState())
         return;
 
     if (RefPtr<CSSFontFace> existingFace = m_cssFontFaceSet->lookUpByCSSConnection(fontFaceRule)) {
@@ -334,13 +334,12 @@ void CSSFontSelector::beginLoadingFontSoon(CachedFont& font)
     if (!m_document)
         return;
 
-    if (!m_document->settings().webFontsAlwaysFallBack()) {
-        m_fontsToBeginLoading.append(&font);
-        // Increment the request count now, in order to prevent didFinishLoad from being dispatched
-        // after this font has been requested but before it began loading. Balanced by
-        // decrementRequestCount() in beginLoadTimerFired() and in clearDocument().
-        m_document->cachedResourceLoader().incrementRequestCount(font);
-    }
+    m_fontsToBeginLoading.append(&font);
+    // Increment the request count now, in order to prevent didFinishLoad from being dispatched
+    // after this font has been requested but before it began loading. Balanced by
+    // decrementRequestCount() in beginLoadTimerFired() and in clearDocument().
+    m_document->cachedResourceLoader().incrementRequestCount(font);
+
     m_beginLoadingTimer.startOneShot(0_s);
 }
 
index b603cfa..dda20fd 100644 (file)
@@ -115,7 +115,7 @@ FontRanges CSSSegmentedFontFace::fontRanges(const FontDescription& fontDescripti
 
     if (addResult.isNewEntry) {
         for (auto& face : m_fontFaces) {
-            if (face->allSourcesFailed())
+            if (face->computeFailureState())
                 continue;
 
             auto selectionCapabilities = face->fontSelectionCapabilities();
index 67b7fb2..e6d7495 100644 (file)
@@ -243,7 +243,6 @@ Settings::Settings(Page* page)
     , m_hiddenPageDOMTimerThrottlingEnabled(false)
     , m_hiddenPageCSSAnimationSuspensionEnabled(false)
     , m_fontFallbackPrefersPictographs(false)
-    , m_webFontsAlwaysFallBack(false)
     , m_forcePendingWebGLPolicy(false)
 {
     // A Frame may not have been created yet, so we initialize the AtomicString
@@ -734,14 +733,6 @@ void Settings::setFontFallbackPrefersPictographs(bool preferPictographs)
         m_page->setNeedsRecalcStyleInAllFrames();
 }
 
-void Settings::setWebFontsAlwaysFallBack(bool enable)
-{
-    if (m_webFontsAlwaysFallBack == enable)
-        return;
-
-    m_webFontsAlwaysFallBack = enable;
-}
-
 void Settings::setLowPowerVideoAudioBufferSizeEnabled(bool flag)
 {
     gLowPowerVideoAudioBufferSizeEnabled = flag;
index f60096d..4b5998c 100644 (file)
@@ -104,6 +104,8 @@ public:
 
     void pageDestroyed() { m_page = nullptr; }
 
+    enum class FontLoadTimingOverride { None, Block, Swap, Failure };
+
     enum class ForcedAccessibilityValue { System, On, Off };
     static const Settings::ForcedAccessibilityValue defaultForcedColorsAreInvertedAccessibilityValue = ForcedAccessibilityValue::System;
     static const Settings::ForcedAccessibilityValue defaultForcedDisplayIsMonochromeAccessibilityValue = ForcedAccessibilityValue::System;
@@ -271,9 +273,6 @@ public:
     WEBCORE_EXPORT void setFontFallbackPrefersPictographs(bool);
     bool fontFallbackPrefersPictographs() const { return m_fontFallbackPrefersPictographs; }
 
-    WEBCORE_EXPORT void setWebFontsAlwaysFallBack(bool);
-    bool webFontsAlwaysFallBack() const { return m_webFontsAlwaysFallBack; }
-
     static bool lowPowerVideoAudioBufferSizeEnabled() { return gLowPowerVideoAudioBufferSizeEnabled; }
     WEBCORE_EXPORT static void setLowPowerVideoAudioBufferSizeEnabled(bool);
 
@@ -385,7 +384,6 @@ private:
     bool m_hiddenPageDOMTimerThrottlingEnabled : 1;
     bool m_hiddenPageCSSAnimationSuspensionEnabled : 1;
     bool m_fontFallbackPrefersPictographs : 1;
-    bool m_webFontsAlwaysFallBack : 1;
 
     bool m_forcePendingWebGLPolicy : 1;
 
index 90bf197..19526ef 100644 (file)
@@ -303,5 +303,8 @@ allowMediaContentTypesRequiringHardwareSupportAsFallback initial=false
 
 paymentRequestEnabled initial=false, conditional=PAYMENT_REQUEST
 
+fontLoadTimingOverride type=FontLoadTimingOverride, initial=FontLoadTimingOverride::None
+shouldIgnoreFontLoadCompletions initial=false
+
 storageAccessAPIEnabled initial=false
 
index 5321fe7..1468e20 100644 (file)
@@ -77,7 +77,7 @@ InternalSettings::Backup::Backup(Settings& settings)
     , m_youTubeFlashPluginReplacementEnabled(settings.youTubeFlashPluginReplacementEnabled())
     , m_shouldConvertPositionStyleOnCopy(settings.shouldConvertPositionStyleOnCopy())
     , m_fontFallbackPrefersPictographs(settings.fontFallbackPrefersPictographs())
-    , m_webFontsAlwaysFallBack(settings.webFontsAlwaysFallBack())
+    , m_shouldIgnoreFontLoadCompletions(settings.shouldIgnoreFontLoadCompletions())
     , m_backgroundShouldExtendBeyondPage(settings.backgroundShouldExtendBeyondPage())
     , m_storageBlockingPolicy(settings.storageBlockingPolicy())
     , m_scrollingTreeIncludesFrames(settings.scrollingTreeIncludesFrames())
@@ -98,6 +98,7 @@ InternalSettings::Backup::Backup(Settings& settings)
     , m_forcedColorsAreInvertedAccessibilityValue(settings.forcedColorsAreInvertedAccessibilityValue())
     , m_forcedDisplayIsMonochromeAccessibilityValue(settings.forcedDisplayIsMonochromeAccessibilityValue())
     , m_forcedPrefersReducedMotionAccessibilityValue(settings.forcedPrefersReducedMotionAccessibilityValue())
+    , m_fontLoadTimingOverride(settings.fontLoadTimingOverride())
     , m_frameFlattening(settings.frameFlattening())
 #if ENABLE(INDEXED_DATABASE_IN_WORKERS)
     , m_indexedDBWorkersEnabled(RuntimeEnabledFeatures::sharedFeatures().indexedDBWorkersEnabled())
@@ -171,7 +172,7 @@ void InternalSettings::Backup::restoreTo(Settings& settings)
     settings.setAutoscrollForDragAndDropEnabled(m_autoscrollForDragAndDropEnabled);
     settings.setShouldConvertPositionStyleOnCopy(m_shouldConvertPositionStyleOnCopy);
     settings.setFontFallbackPrefersPictographs(m_fontFallbackPrefersPictographs);
-    settings.setWebFontsAlwaysFallBack(m_webFontsAlwaysFallBack);
+    settings.setShouldIgnoreFontLoadCompletions(m_shouldIgnoreFontLoadCompletions);
     settings.setBackgroundShouldExtendBeyondPage(m_backgroundShouldExtendBeyondPage);
     settings.setStorageBlockingPolicy(m_storageBlockingPolicy);
     settings.setScrollingTreeIncludesFrames(m_scrollingTreeIncludesFrames);
@@ -191,6 +192,7 @@ void InternalSettings::Backup::restoreTo(Settings& settings)
     settings.setForcedColorsAreInvertedAccessibilityValue(m_forcedColorsAreInvertedAccessibilityValue);
     settings.setForcedDisplayIsMonochromeAccessibilityValue(m_forcedDisplayIsMonochromeAccessibilityValue);
     settings.setForcedPrefersReducedMotionAccessibilityValue(m_forcedPrefersReducedMotionAccessibilityValue);
+    settings.setFontLoadTimingOverride(m_fontLoadTimingOverride);
     Settings::setAllowsAnySSLCertificate(false);
     RenderTheme::singleton().setShouldMockBoldSystemFontForAccessibility(m_shouldMockBoldSystemFontForAccessibility);
     FontCache::singleton().setShouldMockBoldSystemFontForAccessibility(m_shouldMockBoldSystemFontForAccessibility);
@@ -612,11 +614,31 @@ ExceptionOr<void> InternalSettings::setFontFallbackPrefersPictographs(bool prefe
     return { };
 }
 
-ExceptionOr<void> InternalSettings::setWebFontsAlwaysFallBack(bool enable)
+ExceptionOr<void> InternalSettings::setFontLoadTimingOverride(const FontLoadTimingOverride& fontLoadTimingOverride)
 {
     if (!m_page)
         return Exception { InvalidAccessError };
-    settings().setWebFontsAlwaysFallBack(enable);
+    auto policy = Settings::FontLoadTimingOverride::None;
+    switch (fontLoadTimingOverride) {
+    case FontLoadTimingOverride::Block:
+        policy = Settings::FontLoadTimingOverride::Block;
+        break;
+    case FontLoadTimingOverride::Swap:
+        policy = Settings::FontLoadTimingOverride::Swap;
+        break;
+    case FontLoadTimingOverride::Failure:
+        policy = Settings::FontLoadTimingOverride::Failure;
+        break;
+    }
+    settings().setFontLoadTimingOverride(policy);
+    return { };
+}
+
+ExceptionOr<void> InternalSettings::setShouldIgnoreFontLoadCompletions(bool ignore)
+{
+    if (!m_page)
+        return Exception { InvalidAccessError };
+    settings().setShouldIgnoreFontLoadCompletions(ignore);
     return { };
 }
 
index 201023d..8b491b7 100644 (file)
@@ -81,7 +81,9 @@ public:
     ExceptionOr<void> setUseLegacyBackgroundSizeShorthandBehavior(bool);
     ExceptionOr<void> setAutoscrollForDragAndDropEnabled(bool);
     ExceptionOr<void> setFontFallbackPrefersPictographs(bool);
-    ExceptionOr<void> setWebFontsAlwaysFallBack(bool);
+    enum class FontLoadTimingOverride { Block, Swap, Failure };
+    ExceptionOr<void> setFontLoadTimingOverride(const FontLoadTimingOverride&);
+    ExceptionOr<void> setShouldIgnoreFontLoadCompletions(bool);
     ExceptionOr<void> setQuickTimePluginReplacementEnabled(bool);
     ExceptionOr<void> setYouTubeFlashPluginReplacementEnabled(bool);
     ExceptionOr<void> setBackgroundShouldExtendBeyondPage(bool);
@@ -170,7 +172,7 @@ private:
         bool m_youTubeFlashPluginReplacementEnabled;
         bool m_shouldConvertPositionStyleOnCopy;
         bool m_fontFallbackPrefersPictographs;
-        bool m_webFontsAlwaysFallBack;
+        bool m_shouldIgnoreFontLoadCompletions;
         bool m_backgroundShouldExtendBeyondPage;
         SecurityOrigin::StorageBlockingPolicy m_storageBlockingPolicy;
         bool m_scrollingTreeIncludesFrames;
@@ -192,6 +194,7 @@ private:
         Settings::ForcedAccessibilityValue m_forcedColorsAreInvertedAccessibilityValue;
         Settings::ForcedAccessibilityValue m_forcedDisplayIsMonochromeAccessibilityValue;
         Settings::ForcedAccessibilityValue m_forcedPrefersReducedMotionAccessibilityValue;
+        Settings::FontLoadTimingOverride m_fontLoadTimingOverride;
         FrameFlattening m_frameFlattening;
 
         // Runtime enabled settings.
index 057d464..11bb9c7 100644 (file)
@@ -26,6 +26,7 @@
 
 enum ForcedAccessibilityValue { "system", "on", "off" };
 enum FrameFlatteningValue { "Disabled", "EnabledForNonFullScreenIFrames", "FullyEnabled" };
+enum FontLoadTimingOverride { "Block", "Swap", "Failure" };
 
 [
     NoInterfaceObject,
@@ -43,7 +44,8 @@ enum FrameFlatteningValue { "Disabled", "EnabledForNonFullScreenIFrames", "Fully
     [MayThrowException] void setFantasyFontFamily(DOMString family, DOMString script);
     [MayThrowException] void setPictographFontFamily(DOMString family, DOMString script);
     [MayThrowException] void setFontFallbackPrefersPictographs(boolean preferPictographs);
-    [MayThrowException] void setWebFontsAlwaysFallBack(boolean enable);
+    [MayThrowException] void setFontLoadTimingOverride(FontLoadTimingOverride override);
+    [MayThrowException] void setShouldIgnoreFontLoadCompletions(boolean ignore);
 
     [MayThrowException] void setTextAutosizingEnabled(boolean enabled);
     [MayThrowException] void setTextAutosizingWindowSizeOverride(long width, long height);