Skip to content
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

new_audit: add preload-fonts audit #11255

Merged
merged 54 commits into from
Aug 25, 2020
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
6c57f87
barebones boiler for new audit
lemcardenas Aug 3, 2020
2a8533b
added boiler test file
lemcardenas Aug 3, 2020
dabd7ba
fleshing out future audit
lemcardenas Aug 6, 2020
e240ff3
Merge branch 'master' into jankless-font-audit
lemcardenas Aug 6, 2020
8297def
more small changes
lemcardenas Aug 6, 2020
5d76ce2
added new audit to default for testing
lemcardenas Aug 6, 2020
11b267b
still experimenting with font-display
lemcardenas Aug 6, 2020
174f7e7
tmp env
lemcardenas Aug 7, 2020
ab5acac
basic working audit
lemcardenas Aug 7, 2020
5c43a17
changes to attempted URLs
lemcardenas Aug 10, 2020
f9da1f0
reverted changes to check for indirect link preloads
lemcardenas Aug 11, 2020
e98473d
cleaned up the audit code
lemcardenas Aug 11, 2020
7aa0cdb
renamed audit
lemcardenas Aug 11, 2020
1c0ccf4
Merge branch 'master' into jankless-font-audit
lemcardenas Aug 11, 2020
1d05617
working basic tests
lemcardenas Aug 12, 2020
bad337a
more tests
lemcardenas Aug 12, 2020
6e5c3f8
more test changes
lemcardenas Aug 12, 2020
90961d2
more tests
lemcardenas Aug 12, 2020
af7cd7a
added title and description
lemcardenas Aug 12, 2020
655e600
reverted changes in uses-rel-preload
lemcardenas Aug 12, 2020
a6f650e
removed tmp env
lemcardenas Aug 12, 2020
27fdcb4
changed variable names
lemcardenas Aug 12, 2020
d400aa1
Merge branch 'master' into jankless-font-audit
lemcardenas Aug 12, 2020
fa4be5d
updated cli index test snap
lemcardenas Aug 12, 2020
d3a3a9a
changed the learn more link
lemcardenas Aug 12, 2020
b940ff4
updated strings
lemcardenas Aug 12, 2020
c189a6a
deleted unfeasible TODO
lemcardenas Aug 12, 2020
52bc2c4
added comment about document.fonts.load
lemcardenas Aug 12, 2020
5016379
added smoke test, refactored audit to use shorthand prop names
lemcardenas Aug 13, 2020
5b2098a
Merge branch 'master' into jankless-font-audit
lemcardenas Aug 13, 2020
dd42bdb
fixed nits
lemcardenas Aug 13, 2020
0983f35
Merge branch 'master' into jankless-font-audit
lemcardenas Aug 13, 2020
0e96a83
Merge remote-tracking branch 'origin/master' into jankless-font-audit
connorjclark Aug 13, 2020
651111b
moved audit to best-practices-ux group
lemcardenas Aug 13, 2020
afe8fb7
fixed default config for new audit group
lemcardenas Aug 13, 2020
d884255
Merge branch 'master' into jankless-font-audit
lemcardenas Aug 13, 2020
8555e05
Merge branch 'jankless-font-audit' of github.com:GoogleChrome/lightho…
lemcardenas Aug 13, 2020
5507406
fixed issue with index-test snap
lemcardenas Aug 13, 2020
e3ffe70
fixed perf test to allow best-practices group for jankless-font
lemcardenas Aug 13, 2020
4846651
updated test since best-practices now has an N/A audit
lemcardenas Aug 13, 2020
116d29a
added comment clarifying jankless-font in smoke perf
lemcardenas Aug 13, 2020
58ffe03
moved clarification comment to config
lemcardenas Aug 14, 2020
be83a44
changed audit name to preload-fonts
lemcardenas Aug 17, 2020
2b1dd00
Merge branch 'master' into jankless-font-audit
lemcardenas Aug 17, 2020
faa65bd
more name changes
lemcardenas Aug 17, 2020
43c5802
changed the link back to preload-optional-fonts
lemcardenas Aug 17, 2020
7e9524d
updated artifacts so that preload-fonts is not N/A
lemcardenas Aug 17, 2020
d7e9974
updated audit description
lemcardenas Aug 17, 2020
e58a1e9
Merge branch 'master' into jankless-font-audit
lemcardenas Aug 17, 2020
859f6fc
fixed nits & removed start/endTime in tests
lemcardenas Aug 18, 2020
4584664
reverted a test name
lemcardenas Aug 18, 2020
dcb8db0
Merge branch 'master' into jankless-font-audit
lemcardenas Aug 18, 2020
788b0d5
removed superfluous comments/code
lemcardenas Aug 25, 2020
d773e20
Merge branch 'master' into jankless-font-audit
lemcardenas Aug 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ Object {
Object {
"path": "image-size-responsive",
},
Object {
"path": "jankless-font",
},
Object {
"path": "deprecations",
},
Expand Down Expand Up @@ -749,6 +752,11 @@ Object {
"id": "image-size-responsive",
"weight": 1,
},
Object {
"group": "best-practices-ux",
"id": "jankless-font",
"weight": 1,
},
Object {
"group": "best-practices-browser-compat",
"id": "doctype",
Expand Down
1 change: 1 addition & 0 deletions lighthouse-cli/test/fixtures/perf/cors-fonts.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
font-family: 'Lobster Three';
font-style: normal;
font-weight: 700;
font-display: optional;
src: url("http://localhost:10503/perf/lobster-two-v10-latin-700.woff2?cors=true") format('woff2');
}
.corsfont {
Expand Down
19 changes: 19 additions & 0 deletions lighthouse-cli/test/fixtures/perf/fonts.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
/* We don't need `local` but keep around for testing robustness of our regex */
src: local("Lobster Two"), url("./lobster-two-v10-latin-700.woff2?delay=4000") format('woff2');
}
@font-face {
font-family: 'Lobster Four';
font-style: normal;
font-weight: 700;
font-display: optional;
/* We don't need `local` but keep around for testing robustness of our regex */
src: local("Lobster Four"), url("./lobster-two-v10-latin-700.woff2?delay=1000") format('woff2');
}
.webfont {
font-family: Lobster, sans-serif;
}
Expand All @@ -26,13 +34,24 @@
.nofont {
font-family: Unknown, sans-serif;
}
.optionalfont {
font-family: Lobster Four, sans-serif;
}
</style>
<link rel="stylesheet" href="http://localhost:10503/perf/cors-fonts.css">
<!-- PASS(jankless-font): not applicable unless font-display: optional -->
<link rel="preload" href="http://localhost:10200/perf/fonts/lobster-v20-latin-regular.woff2" as="font">
<!-- PASS(jankless-font): font-display: optional & preloaded -->
<link rel="preload" href="http://localhost:10200/perf/lobster-two-v10-latin-700.woff2?delay=4000" as="font" crossorigin="anonymous">
<!-- PASS(jankless-font): font-display: optional & attempted preload, we ignore the re-requests from missing crossorigin attribute -->
<link rel="preload" href="http://localhost:10503/perf/lobster-two-v10-latin-700.woff2?cors=true" as="font">
</head>
<body>
<p class="webfont">Let's load some sweet webfonts...</p>
<p><strong class="webfont">Let's load some sweet webfonts...</strong></p>
<p class="nofont">Some lovely text that uses the fallback font</p>
<p class="corsfont">Some lovely text that uses a CORS font</p>
<!-- FAIL(jankless-font): font-display: optional but no preload -->
<p class="optionalfont">Some lovely text that uses an optional font</p>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,21 @@ module.exports = [
'font-display': {
score: 0,
details: {
items: {
length: 2,
},
items: [
{
url: 'http://localhost:10200/perf/lobster-v20-latin-regular.woff2',
},
],
},
},
'jankless-font': {
score: 0,
details: {
items: [
{
url: 'http://localhost:10200/perf/lobster-two-v10-latin-700.woff2?delay=1000',
},
],
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ const perfConfig = {
extends: 'lighthouse:default',
settings: {
throttlingMethod: 'devtools',
onlyCategories: ['performance'],
// jankless-font isn't a performance audit, but can easily leverage the font
// webpages present here, hence the inclusion of 'best-practices'.
onlyCategories: ['performance', 'best-practices'],

// A mixture of under, over, and meeting budget to exercise all paths.
budgets: [{
Expand Down
8 changes: 5 additions & 3 deletions lighthouse-core/audits/font-display.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ class FontDisplay extends Audit {

/**
* @param {LH.Artifacts} artifacts
* @param {RegExp} passingFontDisplayRegex
* @return {{passingURLs: Set<string>, failingURLs: Set<string>}}
*/
static findFontDisplayDeclarations(artifacts) {
static findFontDisplayDeclarations(artifacts, passingFontDisplayRegex) {
/** @type {Set<string>} */
const passingURLs = new Set();
/** @type {Set<string>} */
Expand All @@ -78,7 +79,7 @@ class FontDisplay extends Audit {
// followed either by a semicolon or the end of a block.
const fontDisplayMatch = declaration.match(/font-display\s*:\s*(\w+)\s*(;|\})/);
const rawFontDisplay = (fontDisplayMatch && fontDisplayMatch[1]) || '';
const hasPassingFontDisplay = PASSING_FONT_DISPLAY_REGEX.test(rawFontDisplay);
const hasPassingFontDisplay = passingFontDisplayRegex.test(rawFontDisplay);
const targetURLSet = hasPassingFontDisplay ? passingURLs : failingURLs;

// Finally convert the raw font URLs to the absolute URLs and add them to the set.
Expand Down Expand Up @@ -141,7 +142,8 @@ class FontDisplay extends Audit {
static async audit(artifacts, context) {
const devtoolsLogs = artifacts.devtoolsLogs[this.DEFAULT_PASS];
const networkRecords = await NetworkRecords.request(devtoolsLogs, context);
const {passingURLs, failingURLs} = FontDisplay.findFontDisplayDeclarations(artifacts);
const {passingURLs, failingURLs} =
FontDisplay.findFontDisplayDeclarations(artifacts, PASSING_FONT_DISPLAY_REGEX);
/** @type {Array<string>} */
const warningURLs = [];

Expand Down
98 changes: 98 additions & 0 deletions lighthouse-core/audits/jankless-font.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* @license Copyright 2020 The Lighthouse Authors. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';

/**
* @fileoverview
* Audit that checks whether fonts that use `font-display: optional` were preloaded.
*/

const Audit = require('./audit.js');
const i18n = require('./../lib/i18n/i18n.js');
const FontDisplay = require('./../audits/font-display.js');
const PASSING_FONT_DISPLAY_REGEX = /^(optional)$/;
const NetworkRecords = require('../computed/network-records.js');

const UIStrings = {
/** Title of a Lighthouse audit that provides detail on whether fonts that used `font-display: optional` were preloaded. This descriptive title is shown to users when all fonts that used `font-display: optional` were preloaded. */
title: 'Fonts with `font-display: optional` are preloaded',
/** Title of a Lighthouse audit that provides detail on whether fonts that used `font-display: optional` were preloaded. This descriptive title is shown to users when one or more fonts used `font-display: optional` and were not preloaded. */
failureTitle: 'Fonts with `font-display: optional` are not preloaded',
/** Description of a Lighthouse audit that tells the user why they should preload fonts if they are using `font-display: optional`. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
description: 'Preloading optional fonts can reduce layout shifts and improve CLS. [Learn More](https://web.dev/optimize-cls/#web-fonts-causing-foutfoit)',
};

const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);

class JanklessFontAudit extends Audit {
/**
* @return {LH.Audit.Meta}
*/
static get meta() {
return {
id: 'jankless-font',
title: str_(UIStrings.title),
failureTitle: str_(UIStrings.failureTitle),
description: str_(UIStrings.description),
requiredArtifacts: ['devtoolsLogs', 'URL', 'CSSUsage'],
};
}

/**
* Finds which font URLs were attempted to be preloaded,
* ignoring those that failed to be reused and were requested again.
* Note: document.fonts.load() is a valid way to preload fonts,
* but we are not currently checking for that.
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
* @return {Set<string>}
*/
static getURLsAttemptedToPreload(networkRecords) {
const attemptedURLs = networkRecords
.filter(req => req.resourceType === 'Font')
.filter(req => req.isLinkPreload)
.map(req => req.url);

return new Set(attemptedURLs);
}

/**
* @param {LH.Artifacts} artifacts
* @param {LH.Audit.Context} context
* @return {Promise<LH.Audit.Product>}
*/
static async audit(artifacts, context) {
const devtoolsLog = artifacts.devtoolsLogs[this.DEFAULT_PASS];
const networkRecords = await NetworkRecords.request(devtoolsLog, context);

// Gets the URLs of fonts where font-display: optional.
const optionalFontURLs =
FontDisplay.findFontDisplayDeclarations(artifacts, PASSING_FONT_DISPLAY_REGEX).passingURLs;

// Gets the URLs of fonts attempted to be preloaded.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so if a font is "attempted preload" but preload actually fails, this audit won't say anything. I guess we are relying on use-rel-preload in this edge case? If so, let's make that clear in the audit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah thats right, because if we warned about that here too, we would have duplicate warnings throughout the report so I didn't want to be redundant

in the description for the getURLsAttemptedToPreload I put "Finds which font URLs were attempted to be preloaded, ignoring those that failed to be reused and were requested again.", so should I add this comment to line 72 as well?

const preloadedFontURLs =
JanklessFontAudit.getURLsAttemptedToPreload(networkRecords);

const results = Array.from(optionalFontURLs)
.filter(url => !preloadedFontURLs.has(url))
.map(url => {
return {url};
});

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
{key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
];

return {
score: results.length > 0 ? 0 : 1,
details: Audit.makeTableDetails(headings, results),
notApplicable: optionalFontURLs.size === 0,
};
}
}

module.exports = JanklessFontAudit;
module.exports.UIStrings = UIStrings;
2 changes: 2 additions & 0 deletions lighthouse-core/config/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ const defaultConfig = {
'content-width',
'image-aspect-ratio',
'image-size-responsive',
'jankless-font',
'deprecations',
'mainthread-work-breakdown',
'bootup-time',
Expand Down Expand Up @@ -561,6 +562,7 @@ const defaultConfig = {
{id: 'password-inputs-can-be-pasted-into', weight: 1, group: 'best-practices-ux'},
{id: 'image-aspect-ratio', weight: 1, group: 'best-practices-ux'},
{id: 'image-size-responsive', weight: 1, group: 'best-practices-ux'},
{id: 'jankless-font', weight: 1, group: 'best-practices-ux'},
// Browser Compatibility
{id: 'doctype', weight: 1, group: 'best-practices-browser-compat'},
{id: 'charset', weight: 1, group: 'best-practices-browser-compat'},
Expand Down
9 changes: 9 additions & 0 deletions lighthouse-core/lib/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,15 @@
"lighthouse-core/audits/is-on-https.js | warning": {
"message": "Allowed with warning"
},
"lighthouse-core/audits/jankless-font.js | description": {
"message": "Preloading optional fonts can reduce layout shifts and improve CLS. [Learn More](https://web.dev/optimize-cls/#web-fonts-causing-foutfoit)"
},
"lighthouse-core/audits/jankless-font.js | failureTitle": {
"message": "Fonts with `font-display: optional` are not preloaded"
},
"lighthouse-core/audits/jankless-font.js | title": {
"message": "Fonts with `font-display: optional` are preloaded"
},
"lighthouse-core/audits/large-javascript-libraries.js | columnLibraryName": {
"message": "Library"
},
Expand Down
9 changes: 9 additions & 0 deletions lighthouse-core/lib/i18n/locales/en-XL.json
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,15 @@
"lighthouse-core/audits/is-on-https.js | warning": {
"message": "Âĺl̂óŵéd̂ ẃît́ĥ ẃâŕn̂ín̂ǵ"
},
"lighthouse-core/audits/jankless-font.js | description": {
"message": "P̂ŕêĺôád̂ín̂ǵ ôṕt̂íôńâĺ f̂ón̂t́ŝ ćâń r̂éd̂úĉé l̂áŷóût́ ŝh́îf́t̂ś âńd̂ ím̂ṕr̂óv̂é ĈĹŜ. [Ĺêár̂ń M̂ór̂é](https://web.dev/optimize-cls/#web-fonts-causing-foutfoit)"
},
"lighthouse-core/audits/jankless-font.js | failureTitle": {
"message": "F̂ón̂t́ŝ ẃît́ĥ `font-display: optional` ár̂é n̂ót̂ ṕr̂él̂óâd́êd́"
},
"lighthouse-core/audits/jankless-font.js | title": {
"message": "F̂ón̂t́ŝ ẃît́ĥ `font-display: optional` ár̂é p̂ŕêĺôád̂éd̂"
},
"lighthouse-core/audits/large-javascript-libraries.js | columnLibraryName": {
"message": "L̂íb̂ŕâŕŷ"
},
Expand Down
Loading