lighthouse 9.5.0-dev.20220904 → 9.5.0-dev.20220907
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/cli-flags.js +17 -0
- package/cli/run.js +2 -2
- package/cli/test/smokehouse/report-assert.js +0 -4
- package/cli/test/smokehouse/smokehouse.js +6 -0
- package/core/audits/byte-efficiency/modern-image-formats.js +5 -5
- package/core/audits/byte-efficiency/offscreen-images.js +2 -2
- package/core/audits/byte-efficiency/uses-long-cache-ttl.js +2 -2
- package/core/audits/byte-efficiency/uses-optimized-images.js +5 -5
- package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +2 -2
- package/core/audits/byte-efficiency/uses-responsive-images.js +2 -2
- package/core/audits/byte-efficiency/uses-text-compression.js +2 -2
- package/core/audits/dobetterweb/uses-http2.js +2 -2
- package/core/audits/font-display.js +7 -6
- package/core/audits/image-aspect-ratio.js +3 -3
- package/core/audits/image-size-responsive.js +3 -3
- package/core/audits/is-on-https.js +2 -2
- package/core/audits/network-requests.js +2 -2
- package/core/audits/seo/canonical.js +2 -2
- package/core/audits/seo/is-crawlable.js +0 -1
- package/core/audits/seo/link-text.js +2 -2
- package/core/audits/seo/plugins.js +0 -1
- package/core/audits/seo/robots-txt.js +0 -1
- package/core/audits/service-worker.js +0 -1
- package/core/audits/unsized-images.js +4 -4
- package/core/audits/uses-rel-preconnect.js +3 -2
- package/core/audits/uses-rel-preload.js +2 -2
- package/core/computed/image-records.js +2 -2
- package/core/computed/resource-summary.js +0 -1
- package/core/fraggle-rock/gather/navigation-runner.js +8 -5
- package/core/fraggle-rock/gather/runner-helpers.js +3 -0
- package/core/fraggle-rock/gather/snapshot-runner.js +3 -2
- package/core/fraggle-rock/gather/timespan-runner.js +3 -2
- package/core/gather/driver/navigation.js +3 -3
- package/core/gather/driver/network-monitor.js +2 -2
- package/core/gather/gather-runner.js +2 -2
- package/core/gather/gatherers/dobetterweb/optimized-images.js +2 -2
- package/core/gather/gatherers/dobetterweb/response-compression.js +2 -2
- package/core/gather/gatherers/link-elements.js +0 -1
- package/core/gather/gatherers/source-maps.js +0 -1
- package/core/index.js +2 -2
- package/core/lib/dependency-graph/simulator/network-analyzer.js +2 -2
- package/core/lib/icons.js +0 -2
- package/core/lib/manifest-parser.js +0 -2
- package/core/lib/network-request.js +7 -7
- package/core/lib/{url-shim.js → url-utils.js} +4 -12
- package/package.json +1 -2
- package/report/test/renderer/performance-category-renderer-test.js +0 -1
- package/report/test/renderer/report-renderer-test.js +0 -1
- package/tsconfig.json +1 -1
- package/types/gatherer.d.ts +2 -0
- package/core/fraggle-rock/replay/stringify-extension.js +0 -104
package/cli/cli-flags.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
/* eslint-disable max-len */
|
|
8
8
|
|
|
9
9
|
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
10
11
|
|
|
11
12
|
import yargs from 'yargs';
|
|
12
13
|
import * as yargsHelpers from 'yargs/helpers';
|
|
@@ -227,6 +228,7 @@ function getYargsParser(manualArgv) {
|
|
|
227
228
|
},
|
|
228
229
|
'output-path': {
|
|
229
230
|
type: 'string',
|
|
231
|
+
coerce: coerceOutputPath,
|
|
230
232
|
describe: `The file path to output the results. Use 'stdout' to write to stdout.
|
|
231
233
|
If using JSON output, default is stdout.
|
|
232
234
|
If using HTML or CSV output, default is a file in the working directory with a name based on the test URL and date.
|
|
@@ -408,6 +410,21 @@ function coerceOutput(values) {
|
|
|
408
410
|
return validValues;
|
|
409
411
|
}
|
|
410
412
|
|
|
413
|
+
/**
|
|
414
|
+
* Verifies outputPath is something we can actually write to.
|
|
415
|
+
* @param {unknown=} value
|
|
416
|
+
* @return {string=}
|
|
417
|
+
*/
|
|
418
|
+
function coerceOutputPath(value) {
|
|
419
|
+
if (value === undefined) return;
|
|
420
|
+
|
|
421
|
+
if (typeof value !== 'string' || !value || !fs.existsSync(path.dirname(value))) {
|
|
422
|
+
throw new Error(`--output-path (${value}) cannot be written to`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return value;
|
|
426
|
+
}
|
|
427
|
+
|
|
411
428
|
/**
|
|
412
429
|
* Verifies value is a string, then coerces type to LH.Locale for convenience. However, don't
|
|
413
430
|
* allowlist specific locales. Why? So we can support the user who requests 'es-MX' (unsupported)
|
package/cli/run.js
CHANGED
|
@@ -19,7 +19,7 @@ import * as Printer from './printer.js';
|
|
|
19
19
|
import lighthouse, {legacyNavigation} from '../core/index.js';
|
|
20
20
|
import {getLhrFilenamePrefix} from '../report/generator/file-namer.js';
|
|
21
21
|
import * as assetSaver from '../core/lib/asset-saver.js';
|
|
22
|
-
import
|
|
22
|
+
import UrlUtils from '../core/lib/url-utils.js';
|
|
23
23
|
|
|
24
24
|
/** @typedef {Error & {code: string, friendlyMessage?: string}} ExitError */
|
|
25
25
|
|
|
@@ -232,7 +232,7 @@ async function runLighthouse(url, flags, config) {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
const shouldGather = flags.gatherMode || flags.gatherMode === flags.auditMode;
|
|
235
|
-
const shouldUseLocalChrome =
|
|
235
|
+
const shouldUseLocalChrome = UrlUtils.isLikeLocalhost(flags.hostname);
|
|
236
236
|
if (shouldGather && shouldUseLocalChrome) {
|
|
237
237
|
launchedChrome = await getDebuggableChrome(flags);
|
|
238
238
|
flags.port = launchedChrome.port;
|
|
@@ -259,10 +259,6 @@ function pruneExpectations(localConsole, lhr, expected, reportOptions) {
|
|
|
259
259
|
* @param {*} obj
|
|
260
260
|
*/
|
|
261
261
|
function failsChromeVersionCheck(obj) {
|
|
262
|
-
// LHR never actually run, so we can't know. Better to not prune
|
|
263
|
-
// things than to fail loudly when accessing lhr.environment.
|
|
264
|
-
if (!lhr.environment) return false;
|
|
265
|
-
|
|
266
262
|
return !chromiumVersionCheck({
|
|
267
263
|
version: getChromeVersionString(),
|
|
268
264
|
min: obj._minChromiumVersion,
|
|
@@ -195,6 +195,12 @@ async function runSmokeTest(smokeTestDefn, testOptions) {
|
|
|
195
195
|
...await lighthouseRunner(requestedUrl, configJson, {isDebug, useLegacyNavigation}),
|
|
196
196
|
networkRequests: takeNetworkRequestUrls ? takeNetworkRequestUrls() : undefined,
|
|
197
197
|
};
|
|
198
|
+
|
|
199
|
+
if (!result.lhr?.audits || !result.artifacts) {
|
|
200
|
+
// Something went really wrong and the runner didn't catch it.
|
|
201
|
+
throw new Error('lighthouse runner returned a bad result. got lhr:\n' +
|
|
202
|
+
JSON.stringify(result.lhr, null, 2));
|
|
203
|
+
}
|
|
198
204
|
} catch (e) {
|
|
199
205
|
// Clear the network requests so that when we retry, we don't see duplicates.
|
|
200
206
|
if (takeNetworkRequestUrls) takeNetworkRequestUrls();
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
12
|
-
import
|
|
12
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
13
13
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
14
14
|
|
|
15
15
|
const UIStrings = {
|
|
@@ -106,7 +106,7 @@ class ModernImageFormats extends ByteEfficiencyAudit {
|
|
|
106
106
|
const imageElement = imageElementsByURL.get(image.url);
|
|
107
107
|
|
|
108
108
|
if (image.failed) {
|
|
109
|
-
warnings.push(`Unable to decode ${
|
|
109
|
+
warnings.push(`Unable to decode ${UrlUtils.getURLDisplayName(image.url)}`);
|
|
110
110
|
continue;
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -123,7 +123,7 @@ class ModernImageFormats extends ByteEfficiencyAudit {
|
|
|
123
123
|
|
|
124
124
|
if (typeof webpSize === 'undefined') {
|
|
125
125
|
if (!imageElement) {
|
|
126
|
-
warnings.push(`Unable to locate resource ${
|
|
126
|
+
warnings.push(`Unable to locate resource ${UrlUtils.getURLDisplayName(image.url)}`);
|
|
127
127
|
continue;
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -152,8 +152,8 @@ class ModernImageFormats extends ByteEfficiencyAudit {
|
|
|
152
152
|
const wastedBytes = image.originalSize - avifSize;
|
|
153
153
|
if (wastedBytes < IGNORE_THRESHOLD_IN_BYTES) continue;
|
|
154
154
|
|
|
155
|
-
const url =
|
|
156
|
-
const isCrossOrigin = !
|
|
155
|
+
const url = UrlUtils.elideDataURI(image.url);
|
|
156
|
+
const isCrossOrigin = !UrlUtils.originsMatch(pageURL, image.url);
|
|
157
157
|
|
|
158
158
|
items.push({
|
|
159
159
|
node: imageElement ? ByteEfficiencyAudit.makeNodeItem(imageElement.node) : undefined,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
13
13
|
import {NetworkRequest} from '../../lib/network-request.js';
|
|
14
14
|
import {Sentry} from '../../lib/sentry.js';
|
|
15
|
-
import
|
|
15
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
16
16
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
17
17
|
import Interactive from '../../computed/metrics/interactive.js';
|
|
18
18
|
import ProcessedTrace from '../../computed/processed-trace.js';
|
|
@@ -87,7 +87,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|
|
87
87
|
// If the image had its loading behavior explicitly controlled already, treat it as passed.
|
|
88
88
|
if (image.loading === 'lazy' || image.loading === 'eager') return null;
|
|
89
89
|
|
|
90
|
-
const url =
|
|
90
|
+
const url = UrlUtils.elideDataURI(image.src);
|
|
91
91
|
const totalPixels = image.displayedWidth * image.displayedHeight;
|
|
92
92
|
const visiblePixels = this.computeVisiblePixels(image.clientRect, viewportDimensions);
|
|
93
93
|
// Treat images with 0 area as if they're offscreen. See https://github.com/GoogleChrome/lighthouse/issues/1914
|
|
@@ -8,7 +8,7 @@ import parseCacheControl from 'parse-cache-control';
|
|
|
8
8
|
|
|
9
9
|
import {Audit} from '../audit.js';
|
|
10
10
|
import {NetworkRequest} from '../../lib/network-request.js';
|
|
11
|
-
import
|
|
11
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
12
12
|
import {linearInterpolation} from '../../lib/statistics.js';
|
|
13
13
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
14
14
|
import NetworkRecords from '../../computed/network-records.js';
|
|
@@ -233,7 +233,7 @@ class CacheHeaders extends Audit {
|
|
|
233
233
|
const cacheHitProbability = CacheHeaders.getCacheHitProbability(cacheLifetimeInSeconds);
|
|
234
234
|
if (cacheHitProbability > IGNORE_THRESHOLD_IN_PERCENT) continue;
|
|
235
235
|
|
|
236
|
-
const url =
|
|
236
|
+
const url = UrlUtils.elideDataURI(record.url);
|
|
237
237
|
const totalBytes = record.transferSize || 0;
|
|
238
238
|
const wastedBytes = (1 - cacheHitProbability) * totalBytes;
|
|
239
239
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
13
|
-
import
|
|
13
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
14
14
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
15
15
|
|
|
16
16
|
const UIStrings = {
|
|
@@ -83,7 +83,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|
|
83
83
|
const imageElement = imageElementsByURL.get(image.url);
|
|
84
84
|
|
|
85
85
|
if (image.failed) {
|
|
86
|
-
warnings.push(`Unable to decode ${
|
|
86
|
+
warnings.push(`Unable to decode ${UrlUtils.getURLDisplayName(image.url)}`);
|
|
87
87
|
continue;
|
|
88
88
|
} else if (/(jpeg|bmp)/.test(image.mimeType) === false) {
|
|
89
89
|
continue;
|
|
@@ -94,7 +94,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|
|
94
94
|
|
|
95
95
|
if (typeof jpegSize === 'undefined') {
|
|
96
96
|
if (!imageElement) {
|
|
97
|
-
warnings.push(`Unable to locate resource ${
|
|
97
|
+
warnings.push(`Unable to locate resource ${UrlUtils.getURLDisplayName(image.url)}`);
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -111,8 +111,8 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {
|
|
|
111
111
|
|
|
112
112
|
if (image.originalSize < jpegSize + IGNORE_THRESHOLD_IN_BYTES) continue;
|
|
113
113
|
|
|
114
|
-
const url =
|
|
115
|
-
const isCrossOrigin = !
|
|
114
|
+
const url = UrlUtils.elideDataURI(image.url);
|
|
115
|
+
const isCrossOrigin = !UrlUtils.originsMatch(pageURL, image.url);
|
|
116
116
|
const jpegSavings = UsesOptimizedImages.computeSavings({...image, jpegSize});
|
|
117
117
|
|
|
118
118
|
items.push({
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import {Audit} from '../audit.js';
|
|
15
15
|
import * as UsesResponsiveImages from './uses-responsive-images.js';
|
|
16
|
-
import
|
|
16
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
17
17
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
18
18
|
|
|
19
19
|
const UIStrings = {
|
|
@@ -71,7 +71,7 @@ class UsesResponsiveImagesSnapshot extends Audit {
|
|
|
71
71
|
|
|
72
72
|
items.push({
|
|
73
73
|
node: Audit.makeNodeItem(image.node),
|
|
74
|
-
url:
|
|
74
|
+
url: UrlUtils.elideDataURI(image.src),
|
|
75
75
|
displayedDimensions: `${displayed.width}x${displayed.height}`,
|
|
76
76
|
actualDimensions: `${actual.width}x${actual.height}`,
|
|
77
77
|
});
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
17
17
|
import {NetworkRequest} from '../../lib/network-request.js';
|
|
18
18
|
import ImageRecords from '../../computed/image-records.js';
|
|
19
|
-
import
|
|
19
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
20
20
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
21
21
|
|
|
22
22
|
const UIStrings = {
|
|
@@ -100,7 +100,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {
|
|
|
100
100
|
const displayed = this.getDisplayedDimensions(image, ViewportDimensions);
|
|
101
101
|
const usedPixels = displayed.width * displayed.height;
|
|
102
102
|
|
|
103
|
-
const url =
|
|
103
|
+
const url = UrlUtils.elideDataURI(image.src);
|
|
104
104
|
const actualPixels = image.naturalWidth * image.naturalHeight;
|
|
105
105
|
const wastedRatio = 1 - (usedPixels / actualPixels);
|
|
106
106
|
const totalBytes = NetworkRequest.getResourceSizeOnNetwork(networkRecord);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
import {ByteEfficiencyAudit} from './byte-efficiency-audit.js';
|
|
13
|
-
import
|
|
13
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
14
14
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
15
15
|
|
|
16
16
|
const UIStrings = {
|
|
@@ -68,7 +68,7 @@ class ResponsesAreCompressed extends ByteEfficiencyAudit {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// remove duplicates
|
|
71
|
-
const url =
|
|
71
|
+
const url = UrlUtils.elideDataURI(record.url);
|
|
72
72
|
const isDuplicate = items.find(item => item.url === url &&
|
|
73
73
|
item.totalBytes === record.resourceSize);
|
|
74
74
|
if (isDuplicate) {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import {Audit} from '../audit.js';
|
|
16
16
|
import ThirdParty from '../../lib/third-party-web.js';
|
|
17
|
-
import
|
|
17
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
18
18
|
import {ByteEfficiencyAudit} from '../byte-efficiency/byte-efficiency-audit.js';
|
|
19
19
|
import Interactive from '../../computed/metrics/lantern-interactive.js';
|
|
20
20
|
import {NetworkRequest} from '../../lib/network-request.js';
|
|
@@ -167,7 +167,7 @@ class UsesHTTP2Audit extends Audit {
|
|
|
167
167
|
const groupedByOrigin = new Map();
|
|
168
168
|
for (const record of networkRecords) {
|
|
169
169
|
if (!UsesHTTP2Audit.isStaticAsset(record)) continue;
|
|
170
|
-
if (
|
|
170
|
+
if (UrlUtils.isLikeLocalhost(record.parsedURL.host)) continue;
|
|
171
171
|
const existing = groupedByOrigin.get(record.parsedURL.securityOrigin) || [];
|
|
172
172
|
existing.push(record);
|
|
173
173
|
groupedByOrigin.set(record.parsedURL.securityOrigin, existing);
|
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {Audit} from './audit.js';
|
|
8
|
-
import
|
|
9
|
-
const PASSING_FONT_DISPLAY_REGEX = /^(block|fallback|optional|swap)$/;
|
|
10
|
-
const CSS_URL_REGEX = /url\((.*?)\)/;
|
|
11
|
-
const CSS_URL_GLOBAL_REGEX = new RegExp(CSS_URL_REGEX, 'g');
|
|
8
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
12
9
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
13
10
|
import {Sentry} from '../lib/sentry.js';
|
|
14
11
|
import NetworkRecords from '../computed/network-records.js';
|
|
15
12
|
|
|
13
|
+
const PASSING_FONT_DISPLAY_REGEX = /^(block|fallback|optional|swap)$/;
|
|
14
|
+
const CSS_URL_REGEX = /url\((.*?)\)/;
|
|
15
|
+
const CSS_URL_GLOBAL_REGEX = new RegExp(CSS_URL_REGEX, 'g');
|
|
16
|
+
|
|
16
17
|
const UIStrings = {
|
|
17
18
|
/** Title of a diagnostic audit that provides detail on if all the text on a webpage was visible while the page was loading its webfonts. This descriptive title is shown to users when the amount is acceptable and no user action is required. */
|
|
18
19
|
title: 'All text remains visible during webfont loads',
|
|
@@ -98,7 +99,7 @@ class FontDisplay extends Audit {
|
|
|
98
99
|
// Convert the relative CSS URL to an absolute URL and add it to the target set.
|
|
99
100
|
for (const relativeURL of relativeURLs) {
|
|
100
101
|
try {
|
|
101
|
-
const relativeRoot =
|
|
102
|
+
const relativeRoot = UrlUtils.isValid(stylesheet.header.sourceURL) ?
|
|
102
103
|
stylesheet.header.sourceURL : artifacts.URL.finalUrl;
|
|
103
104
|
const absoluteURL = new URL(relativeURL, relativeRoot);
|
|
104
105
|
targetURLSet.add(absoluteURL.href);
|
|
@@ -121,7 +122,7 @@ class FontDisplay extends Audit {
|
|
|
121
122
|
/** @type {Map<string, number>} */
|
|
122
123
|
const warningCountByOrigin = new Map();
|
|
123
124
|
for (const warningUrl of warningUrls) {
|
|
124
|
-
const origin =
|
|
125
|
+
const origin = UrlUtils.getOrigin(warningUrl);
|
|
125
126
|
if (!origin) continue;
|
|
126
127
|
|
|
127
128
|
const count = warningCountByOrigin.get(origin) || 0;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
import {Audit} from './audit.js';
|
|
15
|
-
import
|
|
15
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
16
16
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
17
17
|
|
|
18
18
|
const UIStrings = {
|
|
@@ -54,7 +54,7 @@ class ImageAspectRatio extends Audit {
|
|
|
54
54
|
* @return {{url: string, node: LH.Audit.Details.NodeValue, displayedAspectRatio: string, actualAspectRatio: string, doRatiosMatch: boolean}}
|
|
55
55
|
*/
|
|
56
56
|
static computeAspectRatios(image) {
|
|
57
|
-
const url =
|
|
57
|
+
const url = UrlUtils.elideDataURI(image.src);
|
|
58
58
|
const actualAspectRatio = image.naturalDimensions.width / image.naturalDimensions.height;
|
|
59
59
|
const displayedAspectRatio = image.displayedWidth / image.displayedHeight;
|
|
60
60
|
|
|
@@ -89,7 +89,7 @@ class ImageAspectRatio extends Audit {
|
|
|
89
89
|
// - filter all svgs as they have no natural dimensions to audit
|
|
90
90
|
// - filter out images that have falsy naturalWidth or naturalHeight
|
|
91
91
|
return !image.isCss &&
|
|
92
|
-
|
|
92
|
+
UrlUtils.guessMimeType(image.src) !== 'image/svg+xml' &&
|
|
93
93
|
image.naturalDimensions &&
|
|
94
94
|
image.naturalDimensions.height > 5 &&
|
|
95
95
|
image.naturalDimensions.width > 5 &&
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
import {Audit} from './audit.js';
|
|
14
|
-
import
|
|
14
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
15
15
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
16
16
|
|
|
17
17
|
/** @typedef {LH.Artifacts.ImageElement & Required<Pick<LH.Artifacts.ImageElement, 'naturalDimensions'>>} ImageWithNaturalDimensions */
|
|
@@ -96,7 +96,7 @@ function isCandidate(image) {
|
|
|
96
96
|
) {
|
|
97
97
|
return false;
|
|
98
98
|
}
|
|
99
|
-
if (
|
|
99
|
+
if (UrlUtils.guessMimeType(image.src) === 'image/svg+xml') {
|
|
100
100
|
return false;
|
|
101
101
|
}
|
|
102
102
|
if (image.isCss) {
|
|
@@ -147,7 +147,7 @@ function getResult(image, DPR) {
|
|
|
147
147
|
const [expectedWidth, expectedHeight] =
|
|
148
148
|
expectedImageSize(image.displayedWidth, image.displayedHeight, DPR);
|
|
149
149
|
return {
|
|
150
|
-
url:
|
|
150
|
+
url: UrlUtils.elideDataURI(image.src),
|
|
151
151
|
node: Audit.makeNodeItem(image.node),
|
|
152
152
|
displayedSize: `${image.displayedWidth} x ${image.displayedHeight}`,
|
|
153
153
|
actualSize: `${image.naturalDimensions.width} x ${image.naturalDimensions.height}`,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {Audit} from './audit.js';
|
|
8
|
-
import
|
|
8
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
9
9
|
import {NetworkRequest} from '../lib/network-request.js';
|
|
10
10
|
import NetworkRecords from '../computed/network-records.js';
|
|
11
11
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
@@ -75,7 +75,7 @@ class HTTPS extends Audit {
|
|
|
75
75
|
return NetworkRecords.request(devtoolsLogs, context).then(networkRecords => {
|
|
76
76
|
const insecureURLs = networkRecords
|
|
77
77
|
.filter(record => !NetworkRequest.isSecureRequest(record))
|
|
78
|
-
.map(record =>
|
|
78
|
+
.map(record => UrlUtils.elideDataURI(record.url));
|
|
79
79
|
|
|
80
80
|
/** @type {Array<{url: string, resolution?: LH.IcuMessage|string}>} */
|
|
81
81
|
const items = Array.from(new Set(insecureURLs)).map(url => ({url, resolution: undefined}));
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {Audit} from './audit.js';
|
|
8
|
-
import
|
|
8
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
9
9
|
import NetworkRecords from '../computed/network-records.js';
|
|
10
10
|
import MainResource from '../computed/main-resource.js';
|
|
11
11
|
|
|
@@ -62,7 +62,7 @@ class NetworkRequests extends Audit {
|
|
|
62
62
|
undefined;
|
|
63
63
|
|
|
64
64
|
return {
|
|
65
|
-
url:
|
|
65
|
+
url: UrlUtils.elideDataURI(record.url),
|
|
66
66
|
protocol: record.protocol,
|
|
67
67
|
startTime: timeToMs(record.startTime),
|
|
68
68
|
endTime: timeToMs(record.endTime),
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {Audit} from '../audit.js';
|
|
8
|
-
import
|
|
8
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
9
9
|
import MainResource from '../../computed/main-resource.js';
|
|
10
10
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
11
11
|
|
|
@@ -93,7 +93,7 @@ class Canonical extends Audit {
|
|
|
93
93
|
// Links that had an hrefRaw but didn't have a valid href were invalid, flag them
|
|
94
94
|
if (!link.href) invalidCanonicalLink = link;
|
|
95
95
|
// Links that had a valid href but didn't have a valid hrefRaw must have been relatively resolved, flag them
|
|
96
|
-
else if (!
|
|
96
|
+
else if (!UrlUtils.isValid(link.hrefRaw)) relativeCanonicallink = link;
|
|
97
97
|
// Otherwise, it was a valid canonical URL
|
|
98
98
|
else uniqueCanonicalURLs.add(link.href);
|
|
99
99
|
} else if (link.rel === 'alternate') {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {Audit} from '../audit.js';
|
|
8
|
-
import
|
|
8
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
9
9
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
10
10
|
|
|
11
11
|
const BLOCKLIST = new Set([
|
|
@@ -121,7 +121,7 @@ class LinkText extends Audit {
|
|
|
121
121
|
href.startsWith('mailto:') ||
|
|
122
122
|
// This line prevents the audit from flagging anchor links.
|
|
123
123
|
// In this case it is better to use `finalUrl` than `mainDocumentUrl`.
|
|
124
|
-
|
|
124
|
+
UrlUtils.equalWithExcludedFragments(link.href, artifacts.URL.finalUrl)
|
|
125
125
|
) {
|
|
126
126
|
return false;
|
|
127
127
|
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* 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.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import URL from '../lib/url-shim.js';
|
|
8
7
|
import {Audit} from './audit.js';
|
|
9
8
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
10
9
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import {Audit} from './audit.js';
|
|
13
13
|
import * as i18n from './../lib/i18n/i18n.js';
|
|
14
|
-
import
|
|
14
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
15
15
|
|
|
16
16
|
const UIStrings = {
|
|
17
17
|
/** Title of a Lighthouse audit that provides detail on whether all images have explicit width and height. This descriptive title is shown to users when every image has explicit width and height */
|
|
@@ -105,9 +105,9 @@ class UnsizedImages extends Audit {
|
|
|
105
105
|
* @return {boolean}
|
|
106
106
|
*/
|
|
107
107
|
static isNonNetworkSvg(image) {
|
|
108
|
-
const isSvg =
|
|
108
|
+
const isSvg = UrlUtils.guessMimeType(image.src) === 'image/svg+xml';
|
|
109
109
|
const urlScheme = image.src.slice(0, image.src.indexOf(':'));
|
|
110
|
-
const isNonNetwork =
|
|
110
|
+
const isNonNetwork = UrlUtils.isNonNetworkProtocol(urlScheme);
|
|
111
111
|
return isSvg && isNonNetwork;
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -139,7 +139,7 @@ class UnsizedImages extends Audit {
|
|
|
139
139
|
if (isNotDisplayed) continue;
|
|
140
140
|
|
|
141
141
|
unsizedImages.push({
|
|
142
|
-
url:
|
|
142
|
+
url: UrlUtils.elideDataURI(image.src),
|
|
143
143
|
node: Audit.makeNodeItem(image.node),
|
|
144
144
|
});
|
|
145
145
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {Audit} from './audit.js';
|
|
8
8
|
import {ByteEfficiencyAudit} from './byte-efficiency/byte-efficiency-audit.js';
|
|
9
|
-
import
|
|
9
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
10
10
|
import * as i18n from '../lib/i18n/i18n.js';
|
|
11
11
|
import NetworkRecords from '../computed/network-records.js';
|
|
12
12
|
import MainResource from '../computed/main-resource.js';
|
|
@@ -162,7 +162,8 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
const preconnectLinks = artifacts.LinkElements.filter(el => el.rel === 'preconnect');
|
|
165
|
-
const preconnectOrigins =
|
|
165
|
+
const preconnectOrigins =
|
|
166
|
+
new Set(preconnectLinks.map(link => UrlUtils.getOrigin(link.href || '')));
|
|
166
167
|
|
|
167
168
|
/** @type {Array<{url: string, wastedMs: number}>}*/
|
|
168
169
|
let results = [];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 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.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
8
8
|
import {NetworkRequest} from '../lib/network-request.js';
|
|
9
9
|
import {Audit} from './audit.js';
|
|
10
10
|
import {ByteEfficiencyAudit} from './byte-efficiency/byte-efficiency-audit.js';
|
|
@@ -128,7 +128,7 @@ class UsesRelPreloadAudit extends Audit {
|
|
|
128
128
|
// It's not a request for the main frame, it wouldn't get reused even if you did preload it.
|
|
129
129
|
if (request.frameId !== mainResource.frameId) return false;
|
|
130
130
|
// We survived everything else, just check that it's a first party request.
|
|
131
|
-
return
|
|
131
|
+
return UrlUtils.rootDomainsMatch(request.url, mainResource.url);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 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.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
8
8
|
import {makeComputedArtifact} from './computed-artifact.js';
|
|
9
9
|
|
|
10
10
|
class ImageRecords {
|
|
@@ -43,7 +43,7 @@ class ImageRecords {
|
|
|
43
43
|
// Don't change the guessed mime type if no mime type was found.
|
|
44
44
|
imageRecords.push({
|
|
45
45
|
...element,
|
|
46
|
-
mimeType: mimeType ? mimeType :
|
|
46
|
+
mimeType: mimeType ? mimeType : UrlUtils.guessMimeType(element.src),
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
import {makeComputedArtifact} from './computed-artifact.js';
|
|
8
8
|
import NetworkRecords from './network-records.js';
|
|
9
|
-
import URL from '../lib/url-shim.js';
|
|
10
9
|
import {NetworkRequest} from '../lib/network-request.js';
|
|
11
10
|
import {Budget} from '../config/budget.js';
|
|
12
11
|
import {Util} from '../util.cjs';
|
|
@@ -19,7 +19,7 @@ import {initializeConfig} from '../config/config.js';
|
|
|
19
19
|
import {getBaseArtifacts, finalizeArtifacts} from './base-artifacts.js';
|
|
20
20
|
import * as format from '../../../shared/localization/format.js';
|
|
21
21
|
import {LighthouseError} from '../../lib/lh-error.js';
|
|
22
|
-
import
|
|
22
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
23
23
|
import {getPageLoadError} from '../../lib/navigation-error.js';
|
|
24
24
|
import Trace from '../../gather/gatherers/trace.js';
|
|
25
25
|
import DevtoolsLog from '../../gather/gatherers/devtools-log.js';
|
|
@@ -28,6 +28,7 @@ import NetworkRecords from '../../computed/network-records.js';
|
|
|
28
28
|
/**
|
|
29
29
|
* @typedef NavigationContext
|
|
30
30
|
* @property {Driver} driver
|
|
31
|
+
* @property {LH.Puppeteer.Page} page
|
|
31
32
|
* @property {LH.Config.FRConfig} config
|
|
32
33
|
* @property {LH.Config.NavigationDefn} navigation
|
|
33
34
|
* @property {LH.NavigationRequestor} requestor
|
|
@@ -218,6 +219,7 @@ async function _navigation(navigationContext) {
|
|
|
218
219
|
url: initialUrl,
|
|
219
220
|
gatherMode: /** @type {const} */ ('navigation'),
|
|
220
221
|
driver: navigationContext.driver,
|
|
222
|
+
page: navigationContext.page,
|
|
221
223
|
computedCache: navigationContext.computedCache,
|
|
222
224
|
artifactDefinitions: navigationContext.navigation.artifacts,
|
|
223
225
|
artifactState,
|
|
@@ -250,10 +252,10 @@ async function _navigation(navigationContext) {
|
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
/**
|
|
253
|
-
* @param {{driver: Driver, config: LH.Config.FRConfig, requestor: LH.NavigationRequestor; baseArtifacts: LH.FRBaseArtifacts, computedCache: NavigationContext['computedCache']}} args
|
|
255
|
+
* @param {{driver: Driver, page: LH.Puppeteer.Page, config: LH.Config.FRConfig, requestor: LH.NavigationRequestor; baseArtifacts: LH.FRBaseArtifacts, computedCache: NavigationContext['computedCache']}} args
|
|
254
256
|
* @return {Promise<{artifacts: Partial<LH.FRArtifacts & LH.FRBaseArtifacts>}>}
|
|
255
257
|
*/
|
|
256
|
-
async function _navigations({driver, config, requestor, baseArtifacts, computedCache}) {
|
|
258
|
+
async function _navigations({driver, page, config, requestor, baseArtifacts, computedCache}) {
|
|
257
259
|
if (!config.navigations) throw new Error('No navigations configured');
|
|
258
260
|
|
|
259
261
|
/** @type {Partial<LH.FRArtifacts & LH.FRBaseArtifacts>} */
|
|
@@ -264,6 +266,7 @@ async function _navigations({driver, config, requestor, baseArtifacts, computedC
|
|
|
264
266
|
for (const navigation of config.navigations) {
|
|
265
267
|
const navigationContext = {
|
|
266
268
|
driver,
|
|
269
|
+
page,
|
|
267
270
|
navigation,
|
|
268
271
|
requestor,
|
|
269
272
|
config,
|
|
@@ -316,7 +319,7 @@ async function navigationGather(requestor, options) {
|
|
|
316
319
|
const artifacts = await Runner.gather(
|
|
317
320
|
async () => {
|
|
318
321
|
let {page} = options;
|
|
319
|
-
const normalizedRequestor = isCallback ? requestor :
|
|
322
|
+
const normalizedRequestor = isCallback ? requestor : UrlUtils.normalizeUrl(requestor);
|
|
320
323
|
|
|
321
324
|
// For navigation mode, we shouldn't connect to a browser in audit mode,
|
|
322
325
|
// therefore we connect to the browser in the gatherFn callback.
|
|
@@ -333,7 +336,7 @@ async function navigationGather(requestor, options) {
|
|
|
333
336
|
requestor: normalizedRequestor,
|
|
334
337
|
};
|
|
335
338
|
const {baseArtifacts} = await _setup(context);
|
|
336
|
-
const {artifacts} = await _navigations({...context, baseArtifacts, computedCache});
|
|
339
|
+
const {artifacts} = await _navigations({...context, page, baseArtifacts, computedCache});
|
|
337
340
|
await _cleanup(context);
|
|
338
341
|
|
|
339
342
|
return finalizeArtifacts(baseArtifacts, artifacts);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* @typedef CollectPhaseArtifactOptions
|
|
9
9
|
* @property {import('./driver.js').Driver} driver
|
|
10
|
+
* @property {LH.Puppeteer.Page} page
|
|
10
11
|
* @property {Array<LH.Config.AnyArtifactDefn>} artifactDefinitions
|
|
11
12
|
* @property {ArtifactState} artifactState
|
|
12
13
|
* @property {LH.FRBaseArtifacts} baseArtifacts
|
|
@@ -66,6 +67,7 @@ const phaseToPriorPhase = {
|
|
|
66
67
|
async function collectPhaseArtifacts(options) {
|
|
67
68
|
const {
|
|
68
69
|
driver,
|
|
70
|
+
page,
|
|
69
71
|
artifactDefinitions,
|
|
70
72
|
artifactState,
|
|
71
73
|
baseArtifacts,
|
|
@@ -91,6 +93,7 @@ async function collectPhaseArtifacts(options) {
|
|
|
91
93
|
return gatherer[phase]({
|
|
92
94
|
gatherMode,
|
|
93
95
|
driver,
|
|
96
|
+
page,
|
|
94
97
|
baseArtifacts,
|
|
95
98
|
dependencies,
|
|
96
99
|
computedCache,
|
|
@@ -17,11 +17,11 @@ import {getBaseArtifacts, finalizeArtifacts} from './base-artifacts.js';
|
|
|
17
17
|
* @return {Promise<LH.Gatherer.FRGatherResult>}
|
|
18
18
|
*/
|
|
19
19
|
async function snapshotGather(options) {
|
|
20
|
-
const {flags = {}} = options;
|
|
20
|
+
const {page, flags = {}} = options;
|
|
21
21
|
log.setLevel(flags.logLevel || 'error');
|
|
22
22
|
|
|
23
23
|
const {config} = await initializeConfig('snapshot', options.config, flags);
|
|
24
|
-
const driver = new Driver(
|
|
24
|
+
const driver = new Driver(page);
|
|
25
25
|
await driver.connect();
|
|
26
26
|
|
|
27
27
|
/** @type {Map<string, LH.ArbitraryEqualityMap>} */
|
|
@@ -43,6 +43,7 @@ async function snapshotGather(options) {
|
|
|
43
43
|
phase: 'getArtifact',
|
|
44
44
|
gatherMode: 'snapshot',
|
|
45
45
|
driver,
|
|
46
|
+
page,
|
|
46
47
|
baseArtifacts,
|
|
47
48
|
artifactDefinitions,
|
|
48
49
|
artifactState,
|
|
@@ -18,11 +18,11 @@ import {getBaseArtifacts, finalizeArtifacts} from './base-artifacts.js';
|
|
|
18
18
|
* @return {Promise<{endTimespanGather(): Promise<LH.Gatherer.FRGatherResult>}>}
|
|
19
19
|
*/
|
|
20
20
|
async function startTimespanGather(options) {
|
|
21
|
-
const {flags = {}} = options;
|
|
21
|
+
const {page, flags = {}} = options;
|
|
22
22
|
log.setLevel(flags.logLevel || 'error');
|
|
23
23
|
|
|
24
24
|
const {config} = await initializeConfig('timespan', options.config, flags);
|
|
25
|
-
const driver = new Driver(
|
|
25
|
+
const driver = new Driver(page);
|
|
26
26
|
await driver.connect();
|
|
27
27
|
|
|
28
28
|
/** @type {Map<string, LH.ArbitraryEqualityMap>} */
|
|
@@ -34,6 +34,7 @@ async function startTimespanGather(options) {
|
|
|
34
34
|
/** @type {Omit<import('./runner-helpers.js').CollectPhaseArtifactOptions, 'phase'>} */
|
|
35
35
|
const phaseOptions = {
|
|
36
36
|
driver,
|
|
37
|
+
page,
|
|
37
38
|
artifactDefinitions,
|
|
38
39
|
artifactState,
|
|
39
40
|
baseArtifacts,
|
|
@@ -10,7 +10,7 @@ import {NetworkMonitor} from './network-monitor.js';
|
|
|
10
10
|
import {waitForFullyLoaded, waitForFrameNavigated, waitForUserToContinue} from './wait-for-condition.js'; // eslint-disable-line max-len
|
|
11
11
|
import * as constants from '../../config/constants.js';
|
|
12
12
|
import * as i18n from '../../lib/i18n/i18n.js';
|
|
13
|
-
import
|
|
13
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
14
14
|
|
|
15
15
|
const UIStrings = {
|
|
16
16
|
/**
|
|
@@ -130,7 +130,7 @@ async function gotoURL(driver, requestor, options) {
|
|
|
130
130
|
|
|
131
131
|
let requestedUrl = navigationUrls.requestedUrl;
|
|
132
132
|
if (typeof requestor === 'string') {
|
|
133
|
-
if (requestedUrl && !
|
|
133
|
+
if (requestedUrl && !UrlUtils.equalWithExcludedFragments(requestor, requestedUrl)) {
|
|
134
134
|
log.error(
|
|
135
135
|
'Navigation',
|
|
136
136
|
`Provided URL (${requestor}) did not match initial navigation URL (${requestedUrl})`
|
|
@@ -169,7 +169,7 @@ function getNavigationWarnings(navigation) {
|
|
|
169
169
|
|
|
170
170
|
if (navigation.timedOut) warnings.push(str_(UIStrings.warningTimeout));
|
|
171
171
|
|
|
172
|
-
if (!
|
|
172
|
+
if (!UrlUtils.equalWithExcludedFragments(requestedUrl, mainDocumentUrl)) {
|
|
173
173
|
warnings.push(str_(UIStrings.warningRedirected, {
|
|
174
174
|
requested: requestedUrl,
|
|
175
175
|
final: mainDocumentUrl,
|
|
@@ -15,7 +15,7 @@ import log from 'lighthouse-logger';
|
|
|
15
15
|
|
|
16
16
|
import {NetworkRecorder} from '../../lib/network-recorder.js';
|
|
17
17
|
import {NetworkRequest} from '../../lib/network-request.js';
|
|
18
|
-
import
|
|
18
|
+
import UrlUtils from '../../lib/url-utils.js';
|
|
19
19
|
|
|
20
20
|
/** @typedef {import('../../lib/network-recorder.js').NetworkRecorderEventMap} NetworkRecorderEventMap */
|
|
21
21
|
/** @typedef {'network-2-idle'|'network-critical-idle'|'networkidle'|'networkbusy'|'network-critical-busy'|'network-2-busy'} NetworkMonitorEvent_ */
|
|
@@ -214,7 +214,7 @@ class NetworkMonitor extends NetworkMonitorEventEmitter {
|
|
|
214
214
|
/** @type {Array<{time: number, isStart: boolean}>} */
|
|
215
215
|
let timeBoundaries = [];
|
|
216
216
|
requests.forEach(request => {
|
|
217
|
-
if (
|
|
217
|
+
if (UrlUtils.isNonNetworkProtocol(request.protocol)) return;
|
|
218
218
|
if (request.protocol === 'ws' || request.protocol === 'wss') return;
|
|
219
219
|
|
|
220
220
|
// convert the network timestamp to ms
|
|
@@ -21,7 +21,7 @@ import InstallabilityErrors from './gatherers/installability-errors.js';
|
|
|
21
21
|
import NetworkUserAgent from './gatherers/network-user-agent.js';
|
|
22
22
|
import Stacks from './gatherers/stacks.js';
|
|
23
23
|
import {finalizeArtifacts} from '../fraggle-rock/gather/base-artifacts.js';
|
|
24
|
-
import
|
|
24
|
+
import UrlUtils from '../lib/url-utils.js';
|
|
25
25
|
|
|
26
26
|
/** @typedef {import('../gather/driver.js').Driver} Driver */
|
|
27
27
|
/** @typedef {import('../lib/arbitrary-equality-map.js').ArbitraryEqualityMap} ArbitraryEqualityMap */
|
|
@@ -494,7 +494,7 @@ class GatherRunner {
|
|
|
494
494
|
|
|
495
495
|
// Hack for running benchmarkIndex extra times.
|
|
496
496
|
// Add a `bidx=20` query param, eg: https://www.example.com/?bidx=50
|
|
497
|
-
const parsedUrl =
|
|
497
|
+
const parsedUrl = UrlUtils.isValid(options.requestedUrl) && new URL(options.requestedUrl);
|
|
498
498
|
if (options.settings.channel === 'lr' && parsedUrl && parsedUrl.searchParams.has('bidx')) {
|
|
499
499
|
const bidxRunCount = parsedUrl.searchParams.get('bidx') || 0;
|
|
500
500
|
// Add the first bidx into the new set
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import log from 'lighthouse-logger';
|
|
14
14
|
|
|
15
15
|
import FRGatherer from '../../../fraggle-rock/gather/base-gatherer.js';
|
|
16
|
-
import
|
|
16
|
+
import UrlUtils from '../../../lib/url-utils.js';
|
|
17
17
|
import {NetworkRequest} from '../../../lib/network-request.js';
|
|
18
18
|
import {Sentry} from '../../../lib/sentry.js';
|
|
19
19
|
import NetworkRecords from '../../../computed/network-records.js';
|
|
@@ -139,7 +139,7 @@ class OptimizedImages extends FRGatherer {
|
|
|
139
139
|
// want to tank the entire run due to a single image.
|
|
140
140
|
Sentry.captureException(err, {
|
|
141
141
|
tags: {gatherer: 'OptimizedImages'},
|
|
142
|
-
extra: {imageUrl:
|
|
142
|
+
extra: {imageUrl: UrlUtils.elideDataURI(record.url)},
|
|
143
143
|
level: 'warning',
|
|
144
144
|
});
|
|
145
145
|
|
|
@@ -14,7 +14,7 @@ import {Buffer} from 'buffer';
|
|
|
14
14
|
import {gzip} from 'zlib';
|
|
15
15
|
|
|
16
16
|
import FRGatherer from '../../../fraggle-rock/gather/base-gatherer.js';
|
|
17
|
-
import
|
|
17
|
+
import UrlUtils from '../../../lib/url-utils.js';
|
|
18
18
|
import {Sentry} from '../../../lib/sentry.js';
|
|
19
19
|
import {NetworkRequest} from '../../../lib/network-request.js';
|
|
20
20
|
import DevtoolsLog from '../devtools-log.js';
|
|
@@ -122,7 +122,7 @@ class ResponseCompression extends FRGatherer {
|
|
|
122
122
|
}).catch(err => {
|
|
123
123
|
Sentry.captureException(err, {
|
|
124
124
|
tags: {gatherer: 'ResponseCompression'},
|
|
125
|
-
extra: {url:
|
|
125
|
+
extra: {url: UrlUtils.elideDataURI(record.url)},
|
|
126
126
|
level: 'warning',
|
|
127
127
|
});
|
|
128
128
|
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import LinkHeader from 'http-link-header';
|
|
8
8
|
|
|
9
9
|
import FRGatherer from '../../fraggle-rock/gather/base-gatherer.js';
|
|
10
|
-
import URL from '../../lib/url-shim.js';
|
|
11
10
|
import {pageFunctions} from '../../lib/page-functions.js';
|
|
12
11
|
import DevtoolsLog from './devtools-log.js';
|
|
13
12
|
import MainResource from '../../computed/main-resource.js';
|
package/core/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import log from 'lighthouse-logger';
|
|
|
9
9
|
import {Runner} from './runner.js';
|
|
10
10
|
import {CriConnection} from './gather/connections/cri.js';
|
|
11
11
|
import {Config} from './config/config.js';
|
|
12
|
-
import
|
|
12
|
+
import UrlUtils from './lib/url-utils.js';
|
|
13
13
|
import * as fraggleRock from './fraggle-rock/api.js';
|
|
14
14
|
import {Driver} from './gather/driver.js';
|
|
15
15
|
import {initializeConfig} from './fraggle-rock/config/config.js';
|
|
@@ -67,7 +67,7 @@ async function legacyNavigation(url, flags = {}, configJSON, userConnection) {
|
|
|
67
67
|
|
|
68
68
|
// kick off a lighthouse run
|
|
69
69
|
const artifacts = await Runner.gather(() => {
|
|
70
|
-
const requestedUrl =
|
|
70
|
+
const requestedUrl = UrlUtils.normalizeUrl(url);
|
|
71
71
|
return Runner._gatherArtifactsFromBrowser(requestedUrl, options, connection);
|
|
72
72
|
}, options);
|
|
73
73
|
return Runner.audit(artifacts, options);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 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.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import UrlUtils from '../../url-utils.js';
|
|
8
8
|
|
|
9
9
|
const INITIAL_CWD = 14 * 1024;
|
|
10
10
|
|
|
@@ -440,7 +440,7 @@ class NetworkAnalyzer {
|
|
|
440
440
|
// equalWithExcludedFragments is expensive, so check that the resourceUrl starts with the request url first
|
|
441
441
|
return records.find(request =>
|
|
442
442
|
resourceUrl.startsWith(request.url) &&
|
|
443
|
-
|
|
443
|
+
UrlUtils.equalWithExcludedFragments(request.url, resourceUrl)
|
|
444
444
|
);
|
|
445
445
|
}
|
|
446
446
|
|
package/core/lib/icons.js
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
* 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.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import URL from './url-shim.js';
|
|
8
|
-
|
|
9
7
|
/**
|
|
10
8
|
* @param {NonNullable<LH.Artifacts.Manifest['value']>} manifest
|
|
11
9
|
* @return {boolean} Does the manifest have any icons?
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
Trace: ResourceFinish.ts
|
|
53
53
|
*/
|
|
54
54
|
|
|
55
|
-
import
|
|
55
|
+
import UrlUtils from './url-utils.js';
|
|
56
56
|
|
|
57
57
|
// Lightrider X-Header names for timing information.
|
|
58
58
|
// See: _updateTransferSizeForLightrider and _updateTimingsForLightrider.
|
|
@@ -215,7 +215,7 @@ class NetworkRequest {
|
|
|
215
215
|
host: url.hostname,
|
|
216
216
|
securityOrigin: url.origin,
|
|
217
217
|
};
|
|
218
|
-
this.isSecure =
|
|
218
|
+
this.isSecure = UrlUtils.isSecureScheme(this.parsedURL.scheme);
|
|
219
219
|
|
|
220
220
|
this.startTime = data.timestamp;
|
|
221
221
|
|
|
@@ -522,9 +522,9 @@ class NetworkRequest {
|
|
|
522
522
|
*/
|
|
523
523
|
static isNonNetworkRequest(record) {
|
|
524
524
|
// The 'protocol' field in devtools a string more like a `scheme`
|
|
525
|
-
return
|
|
525
|
+
return UrlUtils.isNonNetworkProtocol(record.protocol) ||
|
|
526
526
|
// But `protocol` can fail to be populated if the request fails, so fallback to scheme.
|
|
527
|
-
|
|
527
|
+
UrlUtils.isNonNetworkProtocol(record.parsedURL.scheme);
|
|
528
528
|
}
|
|
529
529
|
|
|
530
530
|
/**
|
|
@@ -535,9 +535,9 @@ class NetworkRequest {
|
|
|
535
535
|
* @return {boolean}
|
|
536
536
|
*/
|
|
537
537
|
static isSecureRequest(record) {
|
|
538
|
-
return
|
|
539
|
-
|
|
540
|
-
|
|
538
|
+
return UrlUtils.isSecureScheme(record.parsedURL.scheme) ||
|
|
539
|
+
UrlUtils.isSecureScheme(record.protocol) ||
|
|
540
|
+
UrlUtils.isLikeLocalhost(record.parsedURL.host) ||
|
|
541
541
|
NetworkRequest.isHstsRequest(record);
|
|
542
542
|
}
|
|
543
543
|
|
|
@@ -4,12 +4,8 @@
|
|
|
4
4
|
* 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.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* URL shim so we keep our code DRY
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
7
|
import {Util} from '../util.cjs';
|
|
12
|
-
import {LighthouseError} from '
|
|
8
|
+
import {LighthouseError} from './lh-error.js';
|
|
13
9
|
|
|
14
10
|
/** @typedef {import('./network-request.js').NetworkRequest} NetworkRequest */
|
|
15
11
|
|
|
@@ -43,8 +39,7 @@ function rewriteChromeInternalUrl(url) {
|
|
|
43
39
|
return url.replace(/^chrome:\/\/chrome\//, 'chrome://');
|
|
44
40
|
}
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
class URLShim extends URL {
|
|
42
|
+
class UrlUtils {
|
|
48
43
|
/**
|
|
49
44
|
* @param {string} url
|
|
50
45
|
* @return {boolean}
|
|
@@ -264,11 +259,8 @@ class URLShim extends URL {
|
|
|
264
259
|
}
|
|
265
260
|
}
|
|
266
261
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
URLShim.INVALID_URL_DEBUG_STRING =
|
|
262
|
+
UrlUtils.INVALID_URL_DEBUG_STRING =
|
|
270
263
|
'Lighthouse was unable to determine the URL of some script executions. ' +
|
|
271
264
|
'It\'s possible a Chrome extension or other eval\'d code is the source.';
|
|
272
265
|
|
|
273
|
-
|
|
274
|
-
export default URLShim;
|
|
266
|
+
export default UrlUtils;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "9.5.0-dev.
|
|
4
|
+
"version": "9.5.0-dev.20220907",
|
|
5
5
|
"description": "Automated auditing, performance metrics, and best practices for the web.",
|
|
6
6
|
"main": "./core/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -98,7 +98,6 @@
|
|
|
98
98
|
"@build-tracker/cli": "^1.0.0-beta.15",
|
|
99
99
|
"@esbuild-kit/esm-loader": "^2.1.1",
|
|
100
100
|
"@jest/fake-timers": "^28.1.0",
|
|
101
|
-
"@puppeteer/replay": "^0.6.1",
|
|
102
101
|
"@rollup/plugin-alias": "^3.1.2",
|
|
103
102
|
"@rollup/plugin-commonjs": "^20.0.0",
|
|
104
103
|
"@rollup/plugin-dynamic-import-vars": "^1.1.1",
|
|
@@ -10,7 +10,6 @@ import jsdom from 'jsdom';
|
|
|
10
10
|
|
|
11
11
|
import {Util} from '../../renderer/util.js';
|
|
12
12
|
import {I18n} from '../../renderer/i18n.js';
|
|
13
|
-
import URL from '../../../core/lib/url-shim.js';
|
|
14
13
|
import {DOM} from '../../renderer/dom.js';
|
|
15
14
|
import {DetailsRenderer} from '../../renderer/details-renderer.js';
|
|
16
15
|
import {PerformanceCategoryRenderer} from '../../renderer/performance-category-renderer.js';
|
|
@@ -10,7 +10,6 @@ import jsdom from 'jsdom';
|
|
|
10
10
|
import jestMock from 'jest-mock';
|
|
11
11
|
|
|
12
12
|
import {Util} from '../../renderer/util.js';
|
|
13
|
-
import URL from '../../../core/lib/url-shim.js';
|
|
14
13
|
import {DOM} from '../../renderer/dom.js';
|
|
15
14
|
import {DetailsRenderer} from '../../renderer/details-renderer.js';
|
|
16
15
|
import {CategoryRenderer} from '../../renderer/category-renderer.js';
|
package/tsconfig.json
CHANGED
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"core/test/lib/tracehouse/task-summary-test.js",
|
|
100
100
|
"core/test/lib/tracehouse/trace-processor-test.js",
|
|
101
101
|
"core/test/lib/traces/metrics-trace-events-test.js",
|
|
102
|
-
"core/test/lib/url-
|
|
102
|
+
"core/test/lib/url-utils-test.js",
|
|
103
103
|
"core/test/network-records-to-devtools-log-test.js",
|
|
104
104
|
"core/test/runner-test.js",
|
|
105
105
|
"core/test/scoring-test.js",
|
package/types/gatherer.d.ts
CHANGED
|
@@ -52,6 +52,8 @@ declare module Gatherer {
|
|
|
52
52
|
gatherMode: GatherMode;
|
|
53
53
|
/** The connection to the page being analyzed. */
|
|
54
54
|
driver: FRTransitionalDriver;
|
|
55
|
+
/** The Puppeteer page handle. Will be undefined in legacy navigation mode. */
|
|
56
|
+
page?: LH.Puppeteer.Page;
|
|
55
57
|
/** The set of base artifacts that are always collected. */
|
|
56
58
|
baseArtifacts: FRBaseArtifacts;
|
|
57
59
|
/** The cached results of computed artifacts. */
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license Copyright 2022 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
-
* 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
|
|
4
|
-
* 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.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as PuppeteerReplay from '@puppeteer/replay';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @param {PuppeteerReplay.Schema.Step} step
|
|
11
|
-
* @return {boolean}
|
|
12
|
-
*/
|
|
13
|
-
function isNavigationStep(step) {
|
|
14
|
-
return Boolean(
|
|
15
|
-
step.type === 'navigate' ||
|
|
16
|
-
step.assertedEvents?.some(event => event.type === 'navigation')
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
class LighthouseStringifyExtension extends PuppeteerReplay.PuppeteerStringifyExtension {
|
|
21
|
-
#isProcessingTimespan = false;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @override
|
|
25
|
-
* @param {PuppeteerReplay.LineWriter} out
|
|
26
|
-
* @param {PuppeteerReplay.Schema.UserFlow} flow
|
|
27
|
-
*/
|
|
28
|
-
async beforeAllSteps(out, flow) {
|
|
29
|
-
out.appendLine(`const fs = require('fs');`);
|
|
30
|
-
|
|
31
|
-
let isMobile = true;
|
|
32
|
-
for (const step of flow.steps) {
|
|
33
|
-
if (step.type !== 'setViewport') continue;
|
|
34
|
-
isMobile = step.isMobile;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
await super.beforeAllSteps(out, flow);
|
|
38
|
-
|
|
39
|
-
const flags = {
|
|
40
|
-
screenEmulation: {
|
|
41
|
-
disabled: true,
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
out.appendLine(`const flags = ${JSON.stringify(flags)}`);
|
|
45
|
-
if (isMobile) {
|
|
46
|
-
out.appendLine(`const config = undefined;`);
|
|
47
|
-
} else {
|
|
48
|
-
// eslint-disable-next-line max-len
|
|
49
|
-
out.appendLine(`const config = (await import('lighthouse/core/config/desktop-config.js')).default;`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
out.appendLine(`const lhApi = await import('lighthouse/core/fraggle-rock/api.js');`);
|
|
53
|
-
// eslint-disable-next-line max-len
|
|
54
|
-
out.appendLine(`const lhFlow = await lhApi.startFlow(page, {name: ${JSON.stringify(flow.title)}, config, flags});`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @override
|
|
59
|
-
* @param {PuppeteerReplay.LineWriter} out
|
|
60
|
-
* @param {PuppeteerReplay.Schema.Step} step
|
|
61
|
-
* @param {PuppeteerReplay.Schema.UserFlow} flow
|
|
62
|
-
*/
|
|
63
|
-
async stringifyStep(out, step, flow) {
|
|
64
|
-
if (step.type === 'setViewport') {
|
|
65
|
-
await super.stringifyStep(out, step, flow);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const isNavigation = isNavigationStep(step);
|
|
70
|
-
|
|
71
|
-
if (isNavigation) {
|
|
72
|
-
if (this.#isProcessingTimespan) {
|
|
73
|
-
out.appendLine(`await lhFlow.endTimespan();`);
|
|
74
|
-
this.#isProcessingTimespan = false;
|
|
75
|
-
}
|
|
76
|
-
out.appendLine(`await lhFlow.startNavigation();`);
|
|
77
|
-
} else if (!this.#isProcessingTimespan) {
|
|
78
|
-
out.appendLine(`await lhFlow.startTimespan();`);
|
|
79
|
-
this.#isProcessingTimespan = true;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
await super.stringifyStep(out, step, flow);
|
|
83
|
-
|
|
84
|
-
if (isNavigation) {
|
|
85
|
-
out.appendLine(`await lhFlow.endNavigation();`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* @override
|
|
91
|
-
* @param {PuppeteerReplay.LineWriter} out
|
|
92
|
-
* @param {PuppeteerReplay.Schema.UserFlow} flow
|
|
93
|
-
*/
|
|
94
|
-
async afterAllSteps(out, flow) {
|
|
95
|
-
if (this.#isProcessingTimespan) {
|
|
96
|
-
out.appendLine(`await lhFlow.endTimespan();`);
|
|
97
|
-
}
|
|
98
|
-
out.appendLine(`const lhFlowReport = await lhFlow.generateReport();`);
|
|
99
|
-
out.appendLine(`fs.writeFileSync(__dirname + '/flow.report.html', lhFlowReport)`);
|
|
100
|
-
await super.afterAllSteps(out, flow);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export default LighthouseStringifyExtension;
|