diff --git a/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html b/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html
index 32e75993292a..8089053387c8 100644
--- a/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html
+++ b/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html
@@ -149,6 +149,13 @@
Do better web tester page
Hi there!
+
+
+
diff --git a/lighthouse-cli/test/smokehouse/dbw-config.js b/lighthouse-cli/test/smokehouse/dbw-config.js
index 7226e3f3e7f5..c4f87d8356db 100644
--- a/lighthouse-cli/test/smokehouse/dbw-config.js
+++ b/lighthouse-cli/test/smokehouse/dbw-config.js
@@ -19,6 +19,7 @@ module.exports = {
'render-blocking-resources',
'errors-in-console',
'efficient-animated-content',
+ 'apple-touch-icon', // pull in apple touch icon to test `LinkElements`
],
},
};
diff --git a/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js b/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js
index c6a86c2d975e..a64801be3577 100644
--- a/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js
+++ b/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js
@@ -16,6 +16,107 @@ module.exports = [
}, {
id: 'wordpress',
}],
+ LinkElements: [
+ {
+ rel: 'stylesheet',
+ href: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=100',
+ hrefRaw: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=100',
+ hreflang: '',
+ as: '',
+ crossOrigin: null,
+ source: 'head',
+ },
+ {
+ rel: 'stylesheet',
+ href: 'http://localhost:10200/dobetterweb/unknown404.css?delay=200',
+ hrefRaw: 'http://localhost:10200/dobetterweb/unknown404.css?delay=200',
+ hreflang: '',
+ as: '',
+ crossOrigin: null,
+ source: 'head',
+ },
+ {
+ rel: 'stylesheet',
+ href: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200',
+ hrefRaw: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200',
+ hreflang: '',
+ as: '',
+ crossOrigin: null,
+ source: 'head',
+ },
+ {
+ rel: 'stylesheet',
+ href: 'http://localhost:10200/dobetterweb/dbw_disabled.css?delay=200&isdisabled',
+ hrefRaw: 'http://localhost:10200/dobetterweb/dbw_disabled.css?delay=200&isdisabled',
+ hreflang: '',
+ as: '',
+ crossOrigin: null,
+ source: 'head',
+ },
+ {
+ rel: 'import',
+ href: 'http://localhost:10200/dobetterweb/dbw_partial_a.html?delay=200',
+ hrefRaw: 'http://localhost:10200/dobetterweb/dbw_partial_a.html?delay=200',
+ hreflang: '',
+ as: '',
+ crossOrigin: null,
+ source: 'head',
+ },
+ {
+ rel: 'import',
+ href: 'http://localhost:10200/dobetterweb/dbw_partial_b.html?delay=200&isasync',
+ hrefRaw: 'http://localhost:10200/dobetterweb/dbw_partial_b.html?delay=200&isasync',
+ hreflang: '',
+ as: '',
+ crossOrigin: null,
+ source: 'head',
+ },
+ {
+ rel: 'stylesheet',
+ href: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&capped',
+ hrefRaw: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&capped',
+ hreflang: '',
+ as: '',
+ crossOrigin: null,
+ source: 'head',
+ },
+ {
+ rel: 'stylesheet',
+ href: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true',
+ hrefRaw: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true',
+ hreflang: '',
+ as: 'style',
+ crossOrigin: null,
+ source: 'head',
+ },
+ {
+ rel: 'stylesheet',
+ href: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true',
+ hrefRaw: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true',
+ hreflang: '',
+ as: '',
+ crossOrigin: null,
+ source: 'head',
+ },
+ {
+ rel: 'alternate stylesheet',
+ href: 'http://localhost:10200/dobetterweb/empty.css',
+ hrefRaw: 'http://localhost:10200/dobetterweb/empty.css',
+ hreflang: '',
+ as: '',
+ crossOrigin: null,
+ source: 'head',
+ },
+ {
+ rel: 'stylesheet',
+ href: 'http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200',
+ hrefRaw: 'http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200',
+ hreflang: '',
+ as: '',
+ crossOrigin: null,
+ source: 'head',
+ },
+ ],
TagsBlockingFirstPaint: [
{
tag: {
@@ -261,11 +362,11 @@ module.exports = [
},
'dom-size': {
score: 1,
- numericValue: 137,
+ numericValue: 141,
details: {
items: [
- {statistic: 'Total DOM Elements', value: '137'},
- {statistic: 'Maximum DOM Depth', value: '3'},
+ {statistic: 'Total DOM Elements', value: '141'},
+ {statistic: 'Maximum DOM Depth', value: '4'},
{
statistic: 'Maximum Child Elements',
value: '100',
diff --git a/lighthouse-core/gather/gatherers/link-elements.js b/lighthouse-core/gather/gatherers/link-elements.js
index 156dad46c254..a6961ed8b1b6 100644
--- a/lighthouse-core/gather/gatherers/link-elements.js
+++ b/lighthouse-core/gather/gatherers/link-elements.js
@@ -9,8 +9,9 @@ const Gatherer = require('./gatherer.js');
const URL = require('../../lib/url-shim.js').URL;
const NetworkAnalyzer = require('../../lib/dependency-graph/simulator/network-analyzer.js');
const LinkHeader = require('http-link-header');
-const getElementsInDocumentString = require('../../lib/page-functions.js')
- .getElementsInDocumentString; // eslint-disable-line max-len
+const {getElementsInDocumentString} = require('../../lib/page-functions.js');
+
+/* globals HTMLLinkElement */
/**
* @fileoverview
@@ -42,6 +43,36 @@ function getCrossoriginFromHeader(value) {
return null;
}
+/**
+ * @return {LH.Artifacts['LinkElements']}
+ */
+/* istanbul ignore next */
+function getLinkElementsInDOM() {
+ /** @type {Array} */
+ // @ts-ignore - getElementsInDocument put into scope via stringification
+ const browserElements = getElementsInDocument('link'); // eslint-disable-line no-undef
+ /** @type {LH.Artifacts['LinkElements']} */
+ const linkElements = [];
+
+ for (const link of browserElements) {
+ // We're only interested in actual LinkElements, not `` tagName elements inside SVGs.
+ // https://github.com/GoogleChrome/lighthouse/issues/9764
+ if (!(link instanceof HTMLLinkElement)) continue;
+
+ linkElements.push({
+ rel: link.rel,
+ href: link.href,
+ hrefRaw: link.href,
+ hreflang: link.hreflang,
+ as: link.as,
+ crossOrigin: link.crossOrigin,
+ source: link.closest('head') ? 'head' : 'body',
+ });
+ }
+
+ return linkElements;
+}
+
class LinkElements extends Gatherer {
/**
* @param {LH.Gatherer.PassContext} passContext
@@ -52,18 +83,9 @@ class LinkElements extends Gatherer {
// the values like access from JavaScript does.
return passContext.driver.evaluateAsync(`(() => {
${getElementsInDocumentString};
+ ${getLinkElementsInDOM};
- return getElementsInDocument('link').map(link => {
- return {
- rel: link.rel,
- href: link.href,
- hrefRaw: link.href,
- hreflang: link.hreflang,
- as: link.as,
- crossOrigin: link.crossOrigin,
- source: link.closest('head') ? 'head' : 'body',
- };
- });
+ return getLinkElementsInDOM();
})()`, {useIsolation: true});
}
diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts
index 50b188531672..cf188d1aeba8 100644
--- a/types/artifacts.d.ts
+++ b/types/artifacts.d.ts
@@ -217,7 +217,7 @@ declare global {
/** The `as` attribute of the link */
as: string
/** The `crossOrigin` attribute of the link */
- crossOrigin: 'anonymous'|'use-credentials'|null
+ crossOrigin: string | null
/** Where the link was found, either in the DOM or in the headers of the main document */
source: 'head'|'body'|'headers'
}