-
Notifications
You must be signed in to change notification settings - Fork 384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve page experience for fonts in core themes #6674
Changes from 12 commits
0be7466
dfbf616
df8fbc2
5d282ce
1ab2a12
a5e6f6d
c5de528
4e0403f
503aa47
3eda2bc
49c1103
009333b
74ef48b
4c5b586
97f8497
7c6afe7
e02b96a
95c7da2
6c16113
773395e
b3b6579
96b748b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -18,6 +18,7 @@ | |||||||||||||||||||||||||
use AmpProject\Dom\Element; | ||||||||||||||||||||||||||
use AmpProject\Exception\FailedToGetFromRemoteUrl; | ||||||||||||||||||||||||||
use AmpProject\RemoteGetRequest; | ||||||||||||||||||||||||||
use AmpProject\RequestDestination; | ||||||||||||||||||||||||||
use Sabberworm\CSS\RuleSet\DeclarationBlock; | ||||||||||||||||||||||||||
use Sabberworm\CSS\CSSList\CSSList; | ||||||||||||||||||||||||||
use Sabberworm\CSS\Property\Selector; | ||||||||||||||||||||||||||
|
@@ -29,6 +30,7 @@ | |||||||||||||||||||||||||
use Sabberworm\CSS\OutputFormat; | ||||||||||||||||||||||||||
use Sabberworm\CSS\Property\Import; | ||||||||||||||||||||||||||
use Sabberworm\CSS\CSSList\AtRuleBlockList; | ||||||||||||||||||||||||||
use Sabberworm\CSS\Value\CSSFunction; | ||||||||||||||||||||||||||
use Sabberworm\CSS\Value\RuleValueList; | ||||||||||||||||||||||||||
use Sabberworm\CSS\Value\URL; | ||||||||||||||||||||||||||
use Sabberworm\CSS\Value\Value; | ||||||||||||||||||||||||||
|
@@ -139,6 +141,7 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer { | |||||||||||||||||||||||||
* @type bool $skip_tree_shaking Whether tree shaking should be skipped. | ||||||||||||||||||||||||||
* @type bool $allow_excessive_css Whether to allow CSS to exceed the allowed max bytes (without raising validation errors). | ||||||||||||||||||||||||||
* @type bool $transform_important_qualifiers Whether !important rules should be transformed. This also necessarily transform inline style attributes. | ||||||||||||||||||||||||||
* @type string[] $font_face_display_overrides Array of the font family names and the font-display value they should each have. | ||||||||||||||||||||||||||
* } | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
protected $args; | ||||||||||||||||||||||||||
|
@@ -166,6 +169,11 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer { | |||||||||||||||||||||||||
'skip_tree_shaking' => false, | ||||||||||||||||||||||||||
'allow_excessive_css' => false, | ||||||||||||||||||||||||||
'transform_important_qualifiers' => true, | ||||||||||||||||||||||||||
'font_face_display_overrides' => [ | ||||||||||||||||||||||||||
'NonBreakingSpaceOverride' => 'optional', | ||||||||||||||||||||||||||
'Inter var' => 'optional', | ||||||||||||||||||||||||||
'Genericons' => 'auto', | ||||||||||||||||||||||||||
], | ||||||||||||||||||||||||||
]; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
@@ -1655,6 +1663,7 @@ private function get_parsed_stylesheet( $stylesheet, $options = [] ) { | |||||||||||||||||||||||||
'parsed_cache_variant', | ||||||||||||||||||||||||||
'dynamic_element_selectors', | ||||||||||||||||||||||||||
'transform_important_qualifiers', | ||||||||||||||||||||||||||
'font_face_display_overrides', | ||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||
[ | ||||||||||||||||||||||||||
|
@@ -2519,7 +2528,6 @@ private function process_font_face_at_rule( AtRuleSet $ruleset, $options ) { | |||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// Obtain the font-family name to guess the filename. | ||||||||||||||||||||||||||
$font_family = null; | ||||||||||||||||||||||||||
$font_basename = null; | ||||||||||||||||||||||||||
$properties = $ruleset->getRules( 'font-family' ); | ||||||||||||||||||||||||||
if ( isset( $properties[0] ) ) { | ||||||||||||||||||||||||||
|
@@ -2540,8 +2548,10 @@ private function process_font_face_at_rule( AtRuleSet $ruleset, $options ) { | |||||||||||||||||||||||||
$stylesheet_base_url = trailingslashit( $stylesheet_base_url ); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// Define array of font files. | ||||||||||||||||||||||||||
$font_files = []; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// Attempt to transform data: URLs in src properties to be external file URLs. | ||||||||||||||||||||||||||
$converted_count = 0; | ||||||||||||||||||||||||||
foreach ( $src_properties as $src_property ) { | ||||||||||||||||||||||||||
$value = $src_property->getValue(); | ||||||||||||||||||||||||||
if ( ! ( $value instanceof RuleValueList ) ) { | ||||||||||||||||||||||||||
|
@@ -2599,24 +2609,44 @@ private function process_font_face_at_rule( AtRuleSet $ruleset, $options ) { | |||||||||||||||||||||||||
* @var URL[] $source_data_url_objects | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
$source_data_url_objects = []; | ||||||||||||||||||||||||||
foreach ( $sources as $i => $source ) { | ||||||||||||||||||||||||||
if ( $source[0] instanceof URL ) { | ||||||||||||||||||||||||||
$value = $source[0]->getURL()->getString(); | ||||||||||||||||||||||||||
if ( 'data:' === substr( $value, 0, 5 ) ) { | ||||||||||||||||||||||||||
$source_data_url_objects[ $i ] = $source[0]; | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
$source_file_urls[ $i ] = $value; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
foreach ( $sources as $source ) { | ||||||||||||||||||||||||||
if ( count( $source ) !== 2 ) { | ||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
list( $url, $format ) = $source; | ||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||
! $url instanceof URL | ||||||||||||||||||||||||||
|| | ||||||||||||||||||||||||||
! $format instanceof CSSFunction | ||||||||||||||||||||||||||
|| | ||||||||||||||||||||||||||
$format->getName() !== 'format' | ||||||||||||||||||||||||||
|| | ||||||||||||||||||||||||||
count( $format->getArguments() ) !== 1 | ||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
list( $format_value ) = $format->getArguments(); | ||||||||||||||||||||||||||
$format_value = trim( $format_value, '"\'' ); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
$value = $url->getURL()->getString(); | ||||||||||||||||||||||||||
if ( 'data:' === substr( $value, 0, 5 ) ) { | ||||||||||||||||||||||||||
$source_data_url_objects[ $format_value ] = $source[0]; | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
$source_file_urls[] = $value; | ||||||||||||||||||||||||||
$font_files[] = $value; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// Convert data: URLs into regular URLs, assuming there will be a file present (e.g. woff fonts in core themes). | ||||||||||||||||||||||||||
foreach ( $source_data_url_objects as $i => $data_url ) { | ||||||||||||||||||||||||||
foreach ( $source_data_url_objects as $format => $data_url ) { | ||||||||||||||||||||||||||
$mime_type = strtok( substr( $data_url->getURL()->getString(), 5 ), ';' ); | ||||||||||||||||||||||||||
if ( ! $mime_type ) { | ||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||
if ( $mime_type ) { | ||||||||||||||||||||||||||
$extension = preg_replace( ':.+/(.+-)?:', '', $mime_type ); | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
$extension = $format; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
$extension = preg_replace( ':.+/(.+-)?:', '', $mime_type ); | ||||||||||||||||||||||||||
$extension = sanitize_key( $extension ); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
$guessed_urls = []; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
@@ -2648,7 +2678,7 @@ private function process_font_face_at_rule( AtRuleSet $ruleset, $options ) { | |||||||||||||||||||||||||
$path = $this->get_validated_url_file_path( $guessed_url, [ 'woff', 'woff2', 'ttf', 'otf', 'svg' ] ); | ||||||||||||||||||||||||||
if ( ! is_wp_error( $path ) ) { | ||||||||||||||||||||||||||
$data_url->getURL()->setString( $guessed_url ); | ||||||||||||||||||||||||||
$converted_count++; | ||||||||||||||||||||||||||
$font_files[] = $guessed_url; | ||||||||||||||||||||||||||
continue 2; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
@@ -2661,23 +2691,42 @@ private function process_font_face_at_rule( AtRuleSet $ruleset, $options ) { | |||||||||||||||||||||||||
'genericons.woff', | ||||||||||||||||||||||||||
]; | ||||||||||||||||||||||||||
if ( in_array( $font_filename, $bundled_fonts, true ) ) { | ||||||||||||||||||||||||||
$data_url->getURL()->setString( plugin_dir_url( AMP__FILE__ ) . "assets/fonts/$font_filename" ); | ||||||||||||||||||||||||||
$converted_count++; | ||||||||||||||||||||||||||
$font_file = plugin_dir_url( AMP__FILE__ ) . "assets/fonts/$font_filename"; | ||||||||||||||||||||||||||
$data_url->getURL()->setString( $font_file ); | ||||||||||||||||||||||||||
$font_files[] = $font_file; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} // End foreach $source_data_url_objects. | ||||||||||||||||||||||||||
} // End foreach $src_properties. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/* | ||||||||||||||||||||||||||
* If a data: URL has been replaced with an external file URL, then we add a font-display:swap to the @font-face | ||||||||||||||||||||||||||
* rule if one isn't already present. This prevents FO | ||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||
* If no font-display is already present, add font-display:swap since the font is now being loaded externally. | ||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
* Override the 'font-display' property to improve font performance. | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
if ( $converted_count && 0 === count( $ruleset->getRules( 'font-display' ) ) ) { | ||||||||||||||||||||||||||
if ( isset( $font_family ) && in_array( $font_family, array_keys( $this->args['font_face_display_overrides'] ), true ) ) { | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One more condition is missing here it seems, and that is if the first font URL in the list is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Failing test case added in 7c6afe7. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed with e02b96a |
||||||||||||||||||||||||||
$properties = $ruleset->getRules( 'font-display' ); | ||||||||||||||||||||||||||
if ( isset( $properties[0] ) ) { | ||||||||||||||||||||||||||
$ruleset->removeRule( $properties[0] ); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or rather just:
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
$font_display_rule = new Rule( 'font-display' ); | ||||||||||||||||||||||||||
$font_display_rule->setValue( 'swap' ); | ||||||||||||||||||||||||||
$font_display_rule->setValue( $this->args['font_face_display_overrides'][ $font_family ] ); | ||||||||||||||||||||||||||
$ruleset->addRule( $font_display_rule ); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
* If the font-display is auto, block, or swap then we should automatically add the preload link for the first font file. | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
$properties = $ruleset->getRules( 'font-display' ); | ||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||
( | ||||||||||||||||||||||||||
( isset( $properties[0] ) && in_array( $properties[0]->getValue(), [ 'auto', 'block', 'swap' ], true ) ) | ||||||||||||||||||||||||||
|| | ||||||||||||||||||||||||||
( ! isset( $properties[0] ) ) // Defaults to 'auto', hence should be preloaded as well. | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
&& | ||||||||||||||||||||||||||
1 <= count( $font_files ) | ||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||
$this->dom->links->addPreload( $font_files[0], RequestDestination::FONT ); | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good! |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just read Prevent layout shifting and flashes of invisible text (FOIT) by preloading optional fonts which recommends adding preload links to optional fonts as well, although it says:
Nevertheless, adding a preload will introduce network congestion as resources compete for bandwidth. So if the font truly is optional, IMO it seems preferable to not preload it. |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After considering this more, I think
block
is the better value to explicitly say we don't want the font to render anything until it loads.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I read, browsers treat
auto
as “often similar” toblock
, but there seems some uncertainty.