From be0318af9a56efd25b4a371b9acd06ead20a6c03 Mon Sep 17 00:00:00 2001 From: Patrick Hulce Date: Fri, 29 Jun 2018 14:01:33 -0700 Subject: [PATCH] core(fonts): handle CORS cssRules --- .../test/fixtures/perf/cors-fonts.css | 10 +++++ lighthouse-cli/test/fixtures/perf/fonts.html | 8 ++-- lighthouse-cli/test/fixtures/static-server.js | 2 +- .../test/smokehouse/perf/expectations.js | 2 +- lighthouse-core/gather/gatherers/fonts.js | 45 ++++++++++++++----- lighthouse-core/lib/sentry.js | 1 + 6 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 lighthouse-cli/test/fixtures/perf/cors-fonts.css diff --git a/lighthouse-cli/test/fixtures/perf/cors-fonts.css b/lighthouse-cli/test/fixtures/perf/cors-fonts.css new file mode 100644 index 000000000000..7929ba24a8e4 --- /dev/null +++ b/lighthouse-cli/test/fixtures/perf/cors-fonts.css @@ -0,0 +1,10 @@ +@font-face { + font-family: 'Lobster Three'; + font-style: normal; + font-weight: 700; + src: url("http://localhost:10503/perf/lobster-two-v10-latin-700.woff2?cors=true") format('woff2'); +} +.corsfont { + font-family: "Lobster Three", sans-serif; + color: red; +} diff --git a/lighthouse-cli/test/fixtures/perf/fonts.html b/lighthouse-cli/test/fixtures/perf/fonts.html index b4f294fc93a0..259accade822 100644 --- a/lighthouse-cli/test/fixtures/perf/fonts.html +++ b/lighthouse-cli/test/fixtures/perf/fonts.html @@ -6,14 +6,14 @@ font-family: 'Lobster'; font-style: normal; font-weight: 400; - src: local('Lobster'), url('./lobster-v20-latin-regular.eot?#iefix') format('eot'), url('./lobster-v20-latin-regular.woff2') format('woff2'); + src: url('./lobster-v20-latin-regular.woff2') format('woff2'); } @font-face { font-family: 'Lobster Two'; font-style: normal; font-weight: 700; font-display: optional; - src: local("Lobster Two"), url("./lobster-two-v10-latin-700.woff2?delay=4000") format('woff2'); + src: url("./lobster-two-v10-latin-700.woff2?delay=4000") format('woff2'); } .webfont { font-family: Lobster, sans-serif; @@ -25,10 +25,12 @@ font-family: Unknown, sans-serif; } +

Let's load some sweet webfonts...

Let's load some sweet webfonts...

-

Some lovely text that uses the fallback font

+

Some lovely text that uses the fallback font

+

Some lovely text that uses a CORS font

diff --git a/lighthouse-cli/test/fixtures/static-server.js b/lighthouse-cli/test/fixtures/static-server.js index 0a4bb7e39b86..3e507999110c 100644 --- a/lighthouse-cli/test/fixtures/static-server.js +++ b/lighthouse-cli/test/fixtures/static-server.js @@ -60,7 +60,7 @@ function requestHandler(request, response) { } function sendResponse(statusCode, data) { - const headers = {}; + const headers = {'Access-Control-Allow-Origin': '*'}; if (filePath.endsWith('.js')) { headers['Content-Type'] = 'text/javascript'; diff --git a/lighthouse-cli/test/smokehouse/perf/expectations.js b/lighthouse-cli/test/smokehouse/perf/expectations.js index fd579f083102..bdfdc45a04b0 100644 --- a/lighthouse-cli/test/smokehouse/perf/expectations.js +++ b/lighthouse-cli/test/smokehouse/perf/expectations.js @@ -64,7 +64,7 @@ module.exports = [ rawValue: false, details: { items: { - length: 1, + length: 2, }, }, }, diff --git a/lighthouse-core/gather/gatherers/fonts.js b/lighthouse-core/gather/gatherers/fonts.js index 640f7ae79312..4245b6ff3519 100644 --- a/lighthouse-core/gather/gatherers/fonts.js +++ b/lighthouse-core/gather/gatherers/fonts.js @@ -13,7 +13,7 @@ const Sentry = require('../../lib/sentry'); // All the property keys of FontFace where the value is a string and are worth // using for finding font matches (see _findSameFontFamily). /** @typedef {'family'|'style'|'weight'|'stretch'|'unicodeRange'|'variant'|'featureSettings'|'display'} FontFaceStringKeys */ -/** @typedef {{err: {message: string, stack: string}}} FontGatherError */ +/** @typedef {{err: {message: string, stack?: string}}} FontGatherError */ /** @type {Array} */ const fontFaceDescriptors = [ @@ -124,11 +124,24 @@ function getFontFaceFromStylesheets() { return new Promise(resolve => { newNode.addEventListener('load', function onload() { newNode.removeEventListener('load', onload); - resolve(getFontFaceFromStylesheets()); + try { + const stylesheet = Array.from(document.styleSheets).find(sheet => sheet.ownerNode === newNode); + if (stylesheet) { + const cssStylesheet = /** @type {CSSStyleSheet} */ (stylesheet); + resolve(getSheetsFontFaces(cssStylesheet)); + } else { + resolve([{err: {message: 'Could not load stylesheet with CORS'}}]); + } + } catch (err) { + resolve([{err: {message: err.message, stack: err.stack}}]); + } }); newNode.crossOrigin = 'anonymous'; oldNode.parentNode && oldNode.parentNode.insertBefore(newNode, oldNode); oldNode.remove(); + + // Give each stylesheet 1s to load before giving up + setTimeout(() => resolve([{err: {message: 'Could not load stylesheet (timeout)'}}]), 1000); }); } @@ -138,19 +151,28 @@ function getFontFaceFromStylesheets() { const corsDataPromises = []; // Get all loaded stylesheets for (const stylesheet of Array.from(document.styleSheets)) { + const cssStylesheet = /** @type {CSSStyleSheet} */ (stylesheet); + try { - const cssStylesheet = /** @type {CSSStyleSheet} */ (stylesheet); - // Cross-origin stylesheets don't expose cssRules by default. We reload them w/ CORS headers. - if (cssStylesheet.cssRules === null && cssStylesheet.href && cssStylesheet.ownerNode && - // @ts-ignore - crossOrigin exists if ownerNode is an HTMLLinkElement - !cssStylesheet.ownerNode.crossOrigin) { + // cssRules can be null or this access can throw when CORS isn't enabled, throw a matching error message. + if (!cssStylesheet.cssRules) { + throw new Error('Failed to read cssRules'); + } + + data.push(...getSheetsFontFaces(cssStylesheet)); + } catch (err) { + const failedToReadRules = /Failed to read.*cssRules/.test(err.message); + // @ts-ignore - crossOrigin exists if ownerNode is an HTMLLinkElement + const alreadyCORS = !cssStylesheet.ownerNode || !!cssStylesheet.ownerNode.crossOrigin; + + if (failedToReadRules && !alreadyCORS && cssStylesheet.href) { + // Cross-origin stylesheets don't expose cssRules by default. We reload them w/ CORS headers. const ownerLinkEl = /** @type {HTMLLinkElement} */ (cssStylesheet.ownerNode); corsDataPromises.push(loadStylesheetWithCORS(ownerLinkEl)); } else { - data.push(...getSheetsFontFaces(cssStylesheet)); + // Otherwise this is a legit error we should report back to the gatherer. + data.push({err: {message: err.message, stack: err.stack}}); } - } catch (err) { - data.push({err: {message: err.message, stack: err.stack}}); } } // Flatten results @@ -194,7 +216,8 @@ class Fonts extends Gatherer { const dataError = /** @type {FontGatherError} */ (fontOrError); if (dataError.err) { const err = new Error(dataError.err.message); - err.stack = dataError.err.stack; + err.stack = dataError.err.stack || err.stack; + console.log(err) // @ts-ignore TODO(bckenny): Sentry type checking Sentry.captureException(err, {tags: {gatherer: 'Fonts'}, level: 'warning'}); return false; diff --git a/lighthouse-core/lib/sentry.js b/lighthouse-core/lib/sentry.js index 3dea5f90ca5a..bcd66351ca6b 100644 --- a/lighthouse-core/lib/sentry.js +++ b/lighthouse-core/lib/sentry.js @@ -26,6 +26,7 @@ const SAMPLED_ERRORS = [ {pattern: /(IDLE_PERIOD|FMP_TOO_LATE)/, rate: 0.1}, {pattern: /^NO_.*/, rate: 0.1}, // Message based sampling + {pattern: /Could not load stylesheet/, rate: 0.1}, {pattern: /Failed to decode/, rate: 0.1}, {pattern: /All image optimizations failed/, rate: 0.1}, {pattern: /No.*resource with given/, rate: 0.01},