lighthouse 10.0.2 → 10.1.0-dev.20230323
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/bin.js +1 -1
- package/cli/run.js +1 -1
- package/cli/test/smokehouse/config/exclusions.js +0 -2
- package/cli/test/smokehouse/lighthouse-runners/bundle.js +1 -1
- package/core/audits/audit.d.ts +6 -0
- package/core/audits/audit.js +16 -4
- package/core/audits/bootup-time.js +2 -3
- package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +4 -0
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +8 -2
- package/core/audits/byte-efficiency/legacy-javascript.d.ts +5 -0
- package/core/audits/byte-efficiency/legacy-javascript.js +42 -16
- package/core/audits/byte-efficiency/polyfill-graph-data.json +50 -50
- package/core/audits/byte-efficiency/total-byte-weight.js +1 -1
- package/core/audits/byte-efficiency/unminified-javascript.js +2 -1
- package/core/audits/byte-efficiency/uses-long-cache-ttl.js +2 -2
- package/core/audits/byte-efficiency/uses-responsive-images-snapshot.js +4 -0
- package/core/audits/dobetterweb/doctype.js +3 -2
- package/core/audits/long-tasks.js +2 -1
- package/core/audits/mainthread-work-breakdown.js +2 -1
- package/core/audits/network-rtt.js +1 -1
- package/core/audits/network-server-latency.js +2 -1
- package/core/audits/performance-budget.js +1 -1
- package/core/audits/prioritize-lcp-image.js +1 -1
- package/core/audits/seo/tap-targets.js +1 -1
- package/core/audits/third-party-summary.js +4 -1
- package/core/audits/timing-budget.js +2 -1
- package/core/audits/uses-rel-preconnect.js +1 -1
- package/core/audits/uses-rel-preload.js +1 -1
- package/core/audits/valid-source-maps.js +1 -1
- package/core/audits/work-during-interaction.js +1 -1
- package/core/computed/metrics/cumulative-layout-shift.d.ts +3 -3
- package/core/computed/metrics/cumulative-layout-shift.js +21 -9
- package/core/computed/unused-css.js +8 -7
- package/core/config/lr-mobile-config.js +0 -11
- package/core/gather/driver/prepare.js +15 -0
- package/core/gather/gatherers/image-elements.js +1 -0
- package/core/gather/gatherers/link-elements.js +2 -0
- package/core/lib/asset-saver.js +2 -12
- package/core/lib/i18n/i18n.d.ts +1 -1
- package/core/lib/i18n/i18n.js +1 -1
- package/core/lib/lighthouse-compatibility.js +17 -0
- package/core/lib/network-request.js +1 -1
- package/core/lib/page-functions.d.ts +17 -5
- package/core/lib/page-functions.js +41 -22
- package/core/lib/tracehouse/trace-processor.d.ts +8 -0
- package/core/lib/tracehouse/trace-processor.js +27 -6
- package/core/lib/url-utils.js +1 -1
- package/core/runner.js +1 -0
- package/dist/report/bundle.esm.js +258 -45
- package/dist/report/flow.js +18 -18
- package/dist/report/standalone.js +17 -17
- package/flow-report/src/i18n/i18n.d.ts +6 -0
- package/flow-report/src/summary/category.tsx +0 -1
- package/package.json +6 -7
- package/report/assets/styles.css +89 -5
- package/report/renderer/components.js +1 -1
- package/report/renderer/details-renderer.d.ts +26 -6
- package/report/renderer/details-renderer.js +144 -21
- package/report/renderer/performance-category-renderer.js +3 -3
- package/report/renderer/report-renderer.js +1 -0
- package/report/renderer/report-ui-features.js +18 -9
- package/report/renderer/report-utils.d.ts +13 -4
- package/report/renderer/report-utils.js +43 -11
- package/shared/localization/locales/ar-XB.json +1 -1
- package/shared/localization/locales/ar.json +1 -1
- package/shared/localization/locales/bg.json +1 -1
- package/shared/localization/locales/ca.json +1 -1
- package/shared/localization/locales/cs.json +1 -1
- package/shared/localization/locales/da.json +1 -1
- package/shared/localization/locales/de.json +1 -1
- package/shared/localization/locales/el.json +1 -1
- package/shared/localization/locales/en-GB.json +1 -1
- package/shared/localization/locales/en-US.json +11 -2
- package/shared/localization/locales/en-XA.json +1 -1
- package/shared/localization/locales/en-XL.json +11 -2
- package/shared/localization/locales/es-419.json +1 -1
- package/shared/localization/locales/es.json +1 -1
- package/shared/localization/locales/fi.json +1 -1
- package/shared/localization/locales/fil.json +1 -1
- package/shared/localization/locales/fr.json +1 -1
- package/shared/localization/locales/he.json +1 -1
- package/shared/localization/locales/hi.json +1 -1
- package/shared/localization/locales/hr.json +1 -1
- package/shared/localization/locales/hu.json +1 -1
- package/shared/localization/locales/id.json +1 -1
- package/shared/localization/locales/it.json +1 -1
- package/shared/localization/locales/ja.json +1 -1
- package/shared/localization/locales/ko.json +1 -1
- package/shared/localization/locales/lt.json +1 -1
- package/shared/localization/locales/lv.json +1 -1
- package/shared/localization/locales/nl.json +1 -1
- package/shared/localization/locales/no.json +1 -1
- package/shared/localization/locales/pl.json +1 -1
- package/shared/localization/locales/pt-PT.json +1 -1
- package/shared/localization/locales/pt.json +1 -1
- package/shared/localization/locales/ro.json +1 -1
- package/shared/localization/locales/ru.json +1 -1
- package/shared/localization/locales/sk.json +1 -1
- package/shared/localization/locales/sl.json +1 -1
- package/shared/localization/locales/sr-Latn.json +1 -1
- package/shared/localization/locales/sr.json +1 -1
- package/shared/localization/locales/sv.json +1 -1
- package/shared/localization/locales/ta.json +1 -1
- package/shared/localization/locales/te.json +1 -1
- package/shared/localization/locales/th.json +1 -1
- package/shared/localization/locales/tr.json +1 -1
- package/shared/localization/locales/uk.json +1 -1
- package/shared/localization/locales/vi.json +1 -1
- package/shared/localization/locales/zh-HK.json +1 -1
- package/shared/localization/locales/zh-TW.json +1 -1
- package/shared/localization/locales/zh.json +1 -1
- package/shared/tsconfig.json +1 -1
- package/shared/util.d.ts +6 -0
- package/shared/util.js +33 -0
- package/types/artifacts.d.ts +6 -1
- package/types/internal/node.d.ts +15 -2
- package/types/lhr/audit-details.d.ts +35 -11
|
@@ -319,7 +319,7 @@ class PrioritizeLcpImage extends Audit {
|
|
|
319
319
|
{key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)},
|
|
320
320
|
];
|
|
321
321
|
const details = Audit.makeOpportunityDetails(headings, results,
|
|
322
|
-
{overallSavingsMs: wastedMs});
|
|
322
|
+
{overallSavingsMs: wastedMs, sortedBy: ['wastedMs']});
|
|
323
323
|
|
|
324
324
|
// If LCP element was an image and had valid network records (regardless of
|
|
325
325
|
// if it should be preloaded), it will be found first in the `initiatorPath`.
|
|
@@ -28,7 +28,7 @@ const UIStrings = {
|
|
|
28
28
|
/** Descriptive title of a Lighthouse audit that provides detail on whether tap targets (like buttons and links) on a page are big enough so they can easily be tapped on a mobile device. This descriptive title is shown when tap targets are not easy to tap on. */
|
|
29
29
|
failureTitle: 'Tap targets are not sized appropriately',
|
|
30
30
|
/** Description of a Lighthouse audit that tells the user why buttons and links need to be big enough and what 'big enough' means. This is displayed after a user expands the section to see more. No character length limits. The last sentence starting with 'Learn' becomes link text to additional documentation. */
|
|
31
|
-
description: 'Interactive elements like buttons and links should be large enough (48x48px),
|
|
31
|
+
description: 'Interactive elements like buttons and links should be large enough (48x48px), or have enough space around them, to be easy enough to tap without overlapping onto other elements. [Learn more about tap targets](https://developer.chrome.com/docs/lighthouse/seo/tap-targets/).',
|
|
32
32
|
/** Label of a table column that identifies tap targets (like buttons and links) that have failed the audit and aren't easy to tap on. */
|
|
33
33
|
tapTargetHeader: 'Tap Target',
|
|
34
34
|
/** Label of a table column that identifies a tap target (like a link or button) that overlaps with another tap target. */
|
|
@@ -243,12 +243,15 @@ class ThirdPartySummary extends Audit {
|
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
const details = Audit.makeTableDetails(headings, results,
|
|
247
|
+
{...overallSummary, isEntityGrouped: true});
|
|
248
|
+
|
|
246
249
|
return {
|
|
247
250
|
score: Number(overallSummary.wastedMs <= PASS_THRESHOLD_IN_MS),
|
|
248
251
|
displayValue: str_(UIStrings.displayValue, {
|
|
249
252
|
timeInMs: overallSummary.wastedMs,
|
|
250
253
|
}),
|
|
251
|
-
details
|
|
254
|
+
details,
|
|
252
255
|
};
|
|
253
256
|
}
|
|
254
257
|
}
|
|
@@ -164,7 +164,8 @@ class TimingBudget extends Audit {
|
|
|
164
164
|
];
|
|
165
165
|
|
|
166
166
|
return {
|
|
167
|
-
details: Audit.makeTableDetails(headers, this.tableItems(budget, summary)
|
|
167
|
+
details: Audit.makeTableDetails(headers, this.tableItems(budget, summary),
|
|
168
|
+
{sortedBy: ['overBudget']}),
|
|
168
169
|
score: 1,
|
|
169
170
|
};
|
|
170
171
|
}
|
|
@@ -234,7 +234,7 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
234
234
|
];
|
|
235
235
|
|
|
236
236
|
const details = Audit.makeOpportunityDetails(headings, results,
|
|
237
|
-
{overallSavingsMs: maxWasted});
|
|
237
|
+
{overallSavingsMs: maxWasted, sortedBy: ['wastedMs']});
|
|
238
238
|
|
|
239
239
|
return {
|
|
240
240
|
score: ByteEfficiencyAudit.scoreForWastedMs(maxWasted),
|
|
@@ -240,7 +240,7 @@ class UsesRelPreloadAudit extends Audit {
|
|
|
240
240
|
{key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)},
|
|
241
241
|
];
|
|
242
242
|
const details = Audit.makeOpportunityDetails(headings, results,
|
|
243
|
-
{overallSavingsMs: wastedMs});
|
|
243
|
+
{overallSavingsMs: wastedMs, sortedBy: ['wastedMs']});
|
|
244
244
|
|
|
245
245
|
return {
|
|
246
246
|
score: ByteEfficiencyAudit.scoreForWastedMs(wastedMs),
|
|
@@ -19,7 +19,7 @@ const UIStrings = {
|
|
|
19
19
|
description: 'Source maps translate minified code to the original source code. This helps ' +
|
|
20
20
|
'developers debug in production. In addition, Lighthouse is able to provide further ' +
|
|
21
21
|
'insights. Consider deploying source maps to take advantage of these benefits. ' +
|
|
22
|
-
'[Learn more about source maps](https://
|
|
22
|
+
'[Learn more about source maps](https://developer.chrome.com/docs/devtools/javascript/source-maps/).',
|
|
23
23
|
/** Label for a column in a data table. Entries will be URLs to JavaScript source maps. */
|
|
24
24
|
columnMapURL: 'Map URL',
|
|
25
25
|
/** Label for a possible error message indicating that a source map for a large, first-party JavaScript script is missing. */
|
|
@@ -13,17 +13,16 @@ declare const CumulativeLayoutShiftComputed: typeof CumulativeLayoutShift & {
|
|
|
13
13
|
totalCumulativeLayoutShift: number;
|
|
14
14
|
}>;
|
|
15
15
|
};
|
|
16
|
-
/** @typedef {{ts: number, isMainFrame: boolean, weightedScore: number}} LayoutShiftEvent */
|
|
17
16
|
declare class CumulativeLayoutShift {
|
|
18
17
|
/**
|
|
19
18
|
* Returns all LayoutShift events that had no recent input.
|
|
20
19
|
* Only a `weightedScore` per event is returned. For non-main-frame events, this is
|
|
21
20
|
* the only score that matters. For main-frame events, `weighted_score_delta === score`.
|
|
22
21
|
* @see https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/layout/layout_shift_tracker.cc;l=492-495;drc=de3b3a8a8839269c6b44403fa38a13a1ed12fed5
|
|
23
|
-
* @param {LH.
|
|
22
|
+
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
24
23
|
* @return {Array<LayoutShiftEvent>}
|
|
25
24
|
*/
|
|
26
|
-
static getLayoutShiftEvents(
|
|
25
|
+
static getLayoutShiftEvents(processedTrace: LH.Artifacts.ProcessedTrace): Array<LayoutShiftEvent>;
|
|
27
26
|
/**
|
|
28
27
|
* Calculates cumulative layout shifts per cluster (session) of LayoutShift
|
|
29
28
|
* events -- where a new cluster is created when there's a gap of more than
|
|
@@ -50,4 +49,5 @@ declare class CumulativeLayoutShift {
|
|
|
50
49
|
totalCumulativeLayoutShift: number;
|
|
51
50
|
}>;
|
|
52
51
|
}
|
|
52
|
+
import { ProcessedTrace } from "../processed-trace.js";
|
|
53
53
|
//# sourceMappingURL=cumulative-layout-shift.d.ts.map
|
|
@@ -9,16 +9,18 @@ import {ProcessedTrace} from '../processed-trace.js';
|
|
|
9
9
|
|
|
10
10
|
/** @typedef {{ts: number, isMainFrame: boolean, weightedScore: number}} LayoutShiftEvent */
|
|
11
11
|
|
|
12
|
+
const RECENT_INPUT_WINDOW = 500;
|
|
13
|
+
|
|
12
14
|
class CumulativeLayoutShift {
|
|
13
15
|
/**
|
|
14
16
|
* Returns all LayoutShift events that had no recent input.
|
|
15
17
|
* Only a `weightedScore` per event is returned. For non-main-frame events, this is
|
|
16
18
|
* the only score that matters. For main-frame events, `weighted_score_delta === score`.
|
|
17
19
|
* @see https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/layout/layout_shift_tracker.cc;l=492-495;drc=de3b3a8a8839269c6b44403fa38a13a1ed12fed5
|
|
18
|
-
* @param {LH.
|
|
20
|
+
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
19
21
|
* @return {Array<LayoutShiftEvent>}
|
|
20
22
|
*/
|
|
21
|
-
static getLayoutShiftEvents(
|
|
23
|
+
static getLayoutShiftEvents(processedTrace) {
|
|
22
24
|
const layoutShiftEvents = [];
|
|
23
25
|
|
|
24
26
|
// Chromium will set `had_recent_input` if there was recent user input, which
|
|
@@ -26,9 +28,20 @@ class CumulativeLayoutShift {
|
|
|
26
28
|
// Lighthouse changes the emulation size. This results in the first few shift
|
|
27
29
|
// events having `had_recent_input` set, so ignore it for those events.
|
|
28
30
|
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1094974.
|
|
29
|
-
let
|
|
31
|
+
let mustRespectHadRecentInput = false;
|
|
32
|
+
|
|
33
|
+
// Even if emulation was applied before navigating, Chrome will issue a viewport
|
|
34
|
+
// change event after a navigation starts which is treated as an interaction when
|
|
35
|
+
// deciding the `had_recent_input` flag. Anything within 500ms of this event should
|
|
36
|
+
// always be counted for CLS regardless of the `had_recent_input` flag.
|
|
37
|
+
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1302667
|
|
38
|
+
let viewportChangeTs = processedTrace.timestamps.timeOrigin;
|
|
39
|
+
const firstViewportEvent = processedTrace.frameEvents.find(event => event.name === 'viewport');
|
|
40
|
+
if (firstViewportEvent) {
|
|
41
|
+
viewportChangeTs = firstViewportEvent.ts;
|
|
42
|
+
}
|
|
30
43
|
|
|
31
|
-
for (const event of
|
|
44
|
+
for (const event of processedTrace.frameTreeEvents) {
|
|
32
45
|
if (event.name !== 'LayoutShift' ||
|
|
33
46
|
!event.args.data ||
|
|
34
47
|
event.args.data.is_main_frame === undefined) {
|
|
@@ -42,11 +55,10 @@ class CumulativeLayoutShift {
|
|
|
42
55
|
}
|
|
43
56
|
|
|
44
57
|
if (event.args.data.had_recent_input) {
|
|
45
|
-
|
|
46
|
-
if (
|
|
58
|
+
const timing = (event.ts - viewportChangeTs) / 1000;
|
|
59
|
+
if (timing > RECENT_INPUT_WINDOW || mustRespectHadRecentInput) continue;
|
|
47
60
|
} else {
|
|
48
|
-
|
|
49
|
-
ignoreHadRecentInput = false;
|
|
61
|
+
mustRespectHadRecentInput = true;
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
layoutShiftEvents.push({
|
|
@@ -106,7 +118,7 @@ class CumulativeLayoutShift {
|
|
|
106
118
|
const processedTrace = await ProcessedTrace.request(trace, context);
|
|
107
119
|
|
|
108
120
|
const allFrameShiftEvents =
|
|
109
|
-
CumulativeLayoutShift.getLayoutShiftEvents(processedTrace
|
|
121
|
+
CumulativeLayoutShift.getLayoutShiftEvents(processedTrace);
|
|
110
122
|
const mainFrameShiftEvents = allFrameShiftEvents.filter(e => e.isMainFrame);
|
|
111
123
|
|
|
112
124
|
// The original Cumulative Layout Shift metric, the sum of all main-frame shift events.
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import {makeComputedArtifact} from './computed-artifact.js';
|
|
8
8
|
import {ByteEfficiencyAudit} from '../audits/byte-efficiency/byte-efficiency-audit.js';
|
|
9
9
|
import {NetworkRecords} from './network-records.js';
|
|
10
|
+
import {Util} from '../../shared/util.js';
|
|
10
11
|
|
|
11
12
|
const PREVIEW_LENGTH = 100;
|
|
12
13
|
|
|
@@ -87,8 +88,7 @@ class UnusedCSS {
|
|
|
87
88
|
* @return {string}
|
|
88
89
|
*/
|
|
89
90
|
static determineContentPreview(content) {
|
|
90
|
-
let preview = (content || '')
|
|
91
|
-
.slice(0, PREVIEW_LENGTH * 5)
|
|
91
|
+
let preview = Util.truncate(content || '', PREVIEW_LENGTH * 5, '')
|
|
92
92
|
.replace(/( {2,}|\t)+/g, ' ') // remove leading indentation if present
|
|
93
93
|
.replace(/\n\s+}/g, '\n}') // completely remove indentation of closing braces
|
|
94
94
|
.trim(); // trim the leading whitespace
|
|
@@ -101,16 +101,17 @@ class UnusedCSS {
|
|
|
101
101
|
firstRuleStart > firstRuleEnd ||
|
|
102
102
|
firstRuleStart > PREVIEW_LENGTH) {
|
|
103
103
|
// We couldn't determine the first rule-set or it's not within the preview
|
|
104
|
-
preview =
|
|
104
|
+
preview = Util.truncate(preview, PREVIEW_LENGTH);
|
|
105
105
|
} else if (firstRuleEnd < PREVIEW_LENGTH) {
|
|
106
106
|
// The entire first rule-set fits within the preview
|
|
107
|
-
preview = preview.slice(0, firstRuleEnd + 1) + '
|
|
107
|
+
preview = preview.slice(0, firstRuleEnd + 1) + ' …';
|
|
108
108
|
} else {
|
|
109
109
|
// The first rule-set doesn't fit within the preview, just show as many as we can
|
|
110
|
-
const
|
|
110
|
+
const truncated = Util.truncate(preview, PREVIEW_LENGTH, '');
|
|
111
|
+
const lastSemicolonIndex = truncated.lastIndexOf(';');
|
|
111
112
|
preview = lastSemicolonIndex < firstRuleStart ?
|
|
112
|
-
|
|
113
|
-
preview.slice(0, lastSemicolonIndex + 1) + '
|
|
113
|
+
truncated + '… } …' :
|
|
114
|
+
preview.slice(0, lastSemicolonIndex + 1) + ' … } …';
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
|
|
@@ -17,17 +17,6 @@ const config = {
|
|
|
17
17
|
'bf-cache',
|
|
18
18
|
],
|
|
19
19
|
},
|
|
20
|
-
audits: [
|
|
21
|
-
'metrics/first-contentful-paint-3g',
|
|
22
|
-
],
|
|
23
|
-
categories: {
|
|
24
|
-
// TODO(bckenny): type extended Config where e.g. category.title isn't required
|
|
25
|
-
performance: /** @type {LH.Config.CategoryJson} */({
|
|
26
|
-
auditRefs: [
|
|
27
|
-
{id: 'first-contentful-paint-3g', weight: 0},
|
|
28
|
-
],
|
|
29
|
-
}),
|
|
30
|
-
},
|
|
31
20
|
};
|
|
32
21
|
|
|
33
22
|
export default config;
|
|
@@ -167,10 +167,23 @@ async function prepareTargetForTimespanMode(driver, settings) {
|
|
|
167
167
|
disableThrottling: false,
|
|
168
168
|
blockedUrlPatterns: undefined,
|
|
169
169
|
});
|
|
170
|
+
await warmUpIntlSegmenter(driver);
|
|
170
171
|
|
|
171
172
|
log.timeEnd(status);
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Ensure the `Intl.Segmenter` created in `pageFunctions.truncate` is cached by v8 before
|
|
177
|
+
* recording the trace begins.
|
|
178
|
+
*
|
|
179
|
+
* @param {LH.Gatherer.FRTransitionalDriver} driver
|
|
180
|
+
*/
|
|
181
|
+
async function warmUpIntlSegmenter(driver) {
|
|
182
|
+
await driver.executionContext.evaluate(pageFunctions.truncate, {
|
|
183
|
+
args: ['aaa', 2],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
/**
|
|
175
188
|
* Prepares a target to be analyzed in navigation mode by enabling protocol domains, emulation, and new document
|
|
176
189
|
* handlers for global APIs or error handling.
|
|
@@ -197,6 +210,8 @@ async function prepareTargetForNavigationMode(driver, settings) {
|
|
|
197
210
|
await shimRequestIdleCallbackOnNewDocument(driver, settings);
|
|
198
211
|
}
|
|
199
212
|
|
|
213
|
+
await warmUpIntlSegmenter(driver);
|
|
214
|
+
|
|
200
215
|
log.timeEnd(status);
|
|
201
216
|
}
|
|
202
217
|
|
|
@@ -84,6 +84,7 @@ function getHTMLImages(allElements) {
|
|
|
84
84
|
isPicture,
|
|
85
85
|
loading: element.loading,
|
|
86
86
|
isInShadowDOM: element.getRootNode() instanceof ShadowRoot,
|
|
87
|
+
fetchPriority: element.fetchPriority,
|
|
87
88
|
// @ts-expect-error - getNodeDetails put into scope via stringification
|
|
88
89
|
node: getNodeDetails(element),
|
|
89
90
|
};
|
|
@@ -70,6 +70,7 @@ function getLinkElementsInDOM() {
|
|
|
70
70
|
crossOrigin: link.crossOrigin,
|
|
71
71
|
hrefRaw,
|
|
72
72
|
source,
|
|
73
|
+
fetchPriority: link.fetchPriority,
|
|
73
74
|
// @ts-expect-error - put into scope via stringification
|
|
74
75
|
node: getNodeDetails(link),
|
|
75
76
|
});
|
|
@@ -134,6 +135,7 @@ class LinkElements extends FRGatherer {
|
|
|
134
135
|
as: link.as || '',
|
|
135
136
|
crossOrigin: getCrossoriginFromHeader(link.crossorigin),
|
|
136
137
|
source: 'headers',
|
|
138
|
+
fetchPriority: link.fetchpriority,
|
|
137
139
|
node: null,
|
|
138
140
|
});
|
|
139
141
|
}
|
package/core/lib/asset-saver.js
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import stream from 'stream';
|
|
10
|
-
import util from 'util';
|
|
11
10
|
|
|
12
11
|
import log from 'lighthouse-logger';
|
|
13
12
|
|
|
@@ -18,15 +17,6 @@ import {NetworkAnalysis} from '../computed/network-analysis.js';
|
|
|
18
17
|
import {LoadSimulator} from '../computed/load-simulator.js';
|
|
19
18
|
import {LighthouseError} from '../lib/lh-error.js';
|
|
20
19
|
|
|
21
|
-
const {promisify} = util;
|
|
22
|
-
|
|
23
|
-
// Rollup does not support `promisfy` or `stream.pipeline`. Bundled files
|
|
24
|
-
// don't need anything in this file except for `stringifyReplacer`, so a check for
|
|
25
|
-
// truthiness before using is enough.
|
|
26
|
-
// TODO: Can remove promisify(pipeline) in Node 15.
|
|
27
|
-
// https://nodejs.org/api/stream.html#streams-promises-api
|
|
28
|
-
const pipeline = promisify && promisify(stream.pipeline);
|
|
29
|
-
|
|
30
20
|
const optionsFilename = 'options.json';
|
|
31
21
|
const artifactsFilename = 'artifacts.json';
|
|
32
22
|
const traceSuffix = '.trace.json';
|
|
@@ -349,7 +339,7 @@ async function saveTrace(traceData, traceFilename) {
|
|
|
349
339
|
const traceIter = traceJsonGenerator(traceData);
|
|
350
340
|
const writeStream = fs.createWriteStream(traceFilename);
|
|
351
341
|
|
|
352
|
-
return pipeline(traceIter, writeStream);
|
|
342
|
+
return stream.promises.pipeline(traceIter, writeStream);
|
|
353
343
|
}
|
|
354
344
|
|
|
355
345
|
/**
|
|
@@ -361,7 +351,7 @@ async function saveTrace(traceData, traceFilename) {
|
|
|
361
351
|
function saveDevtoolsLog(devtoolsLog, devtoolLogFilename) {
|
|
362
352
|
const writeStream = fs.createWriteStream(devtoolLogFilename);
|
|
363
353
|
|
|
364
|
-
return pipeline(function* () {
|
|
354
|
+
return stream.promises.pipeline(function* () {
|
|
365
355
|
yield* arrayOfObjectsJsonGenerator(devtoolsLog);
|
|
366
356
|
yield '\n';
|
|
367
357
|
}, writeStream);
|
package/core/lib/i18n/i18n.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export namespace UIStrings {
|
|
|
57
57
|
* If `locale` isn't provided or one could not be found, DEFAULT_LOCALE is returned.
|
|
58
58
|
*
|
|
59
59
|
* By default any of the locales Lighthouse has strings for can be returned, but this
|
|
60
|
-
* can be
|
|
60
|
+
* can be overridden with `possibleLocales`, useful e.g. when Lighthouse is bundled and
|
|
61
61
|
* only DEFAULT_LOCALE is available, but `possibleLocales` can be used to select a
|
|
62
62
|
* locale available to be downloaded on demand.
|
|
63
63
|
* @param {string|string[]=} locales
|
package/core/lib/i18n/i18n.js
CHANGED
|
@@ -123,7 +123,7 @@ const UIStrings = {
|
|
|
123
123
|
* If `locale` isn't provided or one could not be found, DEFAULT_LOCALE is returned.
|
|
124
124
|
*
|
|
125
125
|
* By default any of the locales Lighthouse has strings for can be returned, but this
|
|
126
|
-
* can be
|
|
126
|
+
* can be overridden with `possibleLocales`, useful e.g. when Lighthouse is bundled and
|
|
127
127
|
* only DEFAULT_LOCALE is available, but `possibleLocales` can be used to select a
|
|
128
128
|
* locale available to be downloaded on demand.
|
|
129
129
|
* @param {string|string[]=} locales
|
|
@@ -81,6 +81,23 @@ function upgradeLhrForCompatibility(lhr) {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// In 10.0, third-party-summary deprecated entity: LinkValue and switched to entity name string
|
|
85
|
+
if (audit.id === 'third-party-summary') {
|
|
86
|
+
if (audit.details.type === 'opportunity' || audit.details.type === 'table') {
|
|
87
|
+
const {headings, items} = audit.details;
|
|
88
|
+
if (headings[0].valueType === 'link') {
|
|
89
|
+
// Apply upgrade only if we are dealing with an older version (valueType=link marker).
|
|
90
|
+
headings[0].valueType = 'text';
|
|
91
|
+
for (const item of items) {
|
|
92
|
+
if (typeof item.entity === 'object' && item.entity.type === 'link') {
|
|
93
|
+
item.entity = item.entity.text;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
audit.details.isEntityGrouped = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
84
101
|
// TODO: convert printf-style displayValue.
|
|
85
102
|
// Added: #5099, v3
|
|
86
103
|
// Removed: #6767, v4
|
|
@@ -225,7 +225,7 @@ class NetworkRequest {
|
|
|
225
225
|
this.isSecure = UrlUtils.isSecureScheme(this.parsedURL.scheme);
|
|
226
226
|
|
|
227
227
|
this.rendererStartTime = data.timestamp * 1000;
|
|
228
|
-
// Expected to be
|
|
228
|
+
// Expected to be overridden with better value in `_recomputeTimesWithResourceTiming`.
|
|
229
229
|
this.networkRequestTime = this.rendererStartTime;
|
|
230
230
|
|
|
231
231
|
this.requestMethod = data.request.method;
|
|
@@ -10,16 +10,12 @@ export namespace pageFunctions {
|
|
|
10
10
|
export { isPositionFixed };
|
|
11
11
|
export { wrapRequestIdleCallback };
|
|
12
12
|
export { getBoundingClientRect };
|
|
13
|
+
export { truncate };
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* `typed-query-selector`'s CSS selector parser.
|
|
16
17
|
*/
|
|
17
18
|
export type ParseSelector<T extends string> = import('typed-query-selector/parser').ParseSelector<T>;
|
|
18
|
-
/**
|
|
19
|
-
* @license Copyright 2018 The Lighthouse Authors. All Rights Reserved.
|
|
20
|
-
* 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
|
|
21
|
-
* 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.
|
|
22
|
-
*/
|
|
23
19
|
/**
|
|
24
20
|
* @fileoverview
|
|
25
21
|
* Helper functions that are passed by `toString()` by Driver to be evaluated in target page.
|
|
@@ -65,6 +61,9 @@ declare function getElementsInDocument<T extends string>(selector: T): import("t
|
|
|
65
61
|
* @return {string}
|
|
66
62
|
*/
|
|
67
63
|
declare function getOuterHTMLSnippet(element: Element | ShadowRoot, ignoreAttrs?: Array<string> | undefined, snippetCharacterLimit?: number): string;
|
|
64
|
+
declare namespace getOuterHTMLSnippet {
|
|
65
|
+
function toString(): string;
|
|
66
|
+
}
|
|
68
67
|
/**
|
|
69
68
|
* Computes a memory/CPU performance benchmark index to determine rough device class.
|
|
70
69
|
* @see https://github.com/GoogleChrome/lighthouse/issues/9085
|
|
@@ -125,6 +124,9 @@ declare function getNodeSelector(element: Element): string;
|
|
|
125
124
|
* @return {string | null}
|
|
126
125
|
*/
|
|
127
126
|
declare function getNodeLabel(element: Element): string | null;
|
|
127
|
+
declare namespace getNodeLabel {
|
|
128
|
+
function toString(): string;
|
|
129
|
+
}
|
|
128
130
|
/**
|
|
129
131
|
* This function checks if an element or an ancestor of an element is `position:fixed`.
|
|
130
132
|
* In addition we ensure that the element is capable of behaving as a `position:fixed`
|
|
@@ -145,5 +147,15 @@ declare function wrapRequestIdleCallback(cpuSlowdownMultiplier: number): void;
|
|
|
145
147
|
* @return {LH.Artifacts.Rect}
|
|
146
148
|
*/
|
|
147
149
|
declare function getBoundingClientRect(element: Element): LH.Artifacts.Rect;
|
|
150
|
+
/**
|
|
151
|
+
*
|
|
152
|
+
* @param {string} string
|
|
153
|
+
* @param {number} characterLimit
|
|
154
|
+
* @return {string}
|
|
155
|
+
*/
|
|
156
|
+
declare function truncate(string: string, characterLimit: number): string;
|
|
157
|
+
declare namespace truncate {
|
|
158
|
+
function toString(): string;
|
|
159
|
+
}
|
|
148
160
|
export {};
|
|
149
161
|
//# sourceMappingURL=page-functions.d.ts.map
|
|
@@ -4,6 +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
|
+
import {Util} from '../../shared/util.js';
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* @fileoverview
|
|
9
11
|
* Helper functions that are passed by `toString()` by Driver to be evaluated in target page.
|
|
@@ -132,10 +134,9 @@ function getOuterHTMLSnippet(element, ignoreAttrs = [], snippetCharacterLimit =
|
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
// Elide attribute value if too long.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
137
|
+
const truncatedString = truncate(attributeValue, ATTRIBUTE_CHAR_LIMIT);
|
|
138
|
+
if (truncatedString !== attributeValue) dirty = true;
|
|
139
|
+
attributeValue = truncatedString;
|
|
139
140
|
|
|
140
141
|
if (dirty) {
|
|
141
142
|
// Style attributes can be blocked by the CSP if they are set via `setAttribute`.
|
|
@@ -372,22 +373,6 @@ function isPositionFixed(element) {
|
|
|
372
373
|
* @return {string | null}
|
|
373
374
|
*/
|
|
374
375
|
function getNodeLabel(element) {
|
|
375
|
-
// Inline so that audits that import getNodeLabel don't
|
|
376
|
-
// also need to import truncate
|
|
377
|
-
/**
|
|
378
|
-
* @param {string} str
|
|
379
|
-
* @param {number} maxLength
|
|
380
|
-
* @return {string}
|
|
381
|
-
*/
|
|
382
|
-
function truncate(str, maxLength) {
|
|
383
|
-
if (str.length <= maxLength) {
|
|
384
|
-
return str;
|
|
385
|
-
}
|
|
386
|
-
// Take advantage of string iterator multi-byte character awareness.
|
|
387
|
-
// Regular `.slice` will ignore unicode character boundaries and lead to malformed text.
|
|
388
|
-
return Array.from(str).slice(0, maxLength - 1).join('') + '…';
|
|
389
|
-
}
|
|
390
|
-
|
|
391
376
|
const tagName = element.tagName.toLowerCase();
|
|
392
377
|
// html and body content is too broad to be useful, since they contain all page content
|
|
393
378
|
if (tagName !== 'html' && tagName !== 'body') {
|
|
@@ -518,14 +503,47 @@ function getNodeDetails(element) {
|
|
|
518
503
|
return details;
|
|
519
504
|
}
|
|
520
505
|
|
|
506
|
+
/**
|
|
507
|
+
*
|
|
508
|
+
* @param {string} string
|
|
509
|
+
* @param {number} characterLimit
|
|
510
|
+
* @return {string}
|
|
511
|
+
*/
|
|
512
|
+
function truncate(string, characterLimit) {
|
|
513
|
+
return Util.truncate(string, characterLimit);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** @type {string} */
|
|
517
|
+
const truncateRawString = truncate.toString();
|
|
518
|
+
truncate.toString = () => `function truncate(string, characterLimit) {
|
|
519
|
+
const Util = { ${Util.truncate} };
|
|
520
|
+
return (${truncateRawString})(string, characterLimit);
|
|
521
|
+
}`;
|
|
522
|
+
|
|
523
|
+
/** @type {string} */
|
|
524
|
+
const getNodeLabelRawString = getNodeLabel.toString();
|
|
525
|
+
getNodeLabel.toString = () => `function getNodeLabel(element) {
|
|
526
|
+
${truncate};
|
|
527
|
+
return (${getNodeLabelRawString})(element);
|
|
528
|
+
}`;
|
|
529
|
+
|
|
530
|
+
/** @type {string} */
|
|
531
|
+
const getOuterHTMLSnippetRawString = getOuterHTMLSnippet.toString();
|
|
532
|
+
// eslint-disable-next-line max-len
|
|
533
|
+
getOuterHTMLSnippet.toString = () => `function getOuterHTMLSnippet(element, ignoreAttrs = [], snippetCharacterLimit = 500) {
|
|
534
|
+
${truncate};
|
|
535
|
+
return (${getOuterHTMLSnippetRawString})(element, ignoreAttrs, snippetCharacterLimit);
|
|
536
|
+
}`;
|
|
537
|
+
|
|
521
538
|
/** @type {string} */
|
|
522
539
|
const getNodeDetailsRawString = getNodeDetails.toString();
|
|
523
540
|
getNodeDetails.toString = () => `function getNodeDetails(element) {
|
|
541
|
+
${truncate};
|
|
524
542
|
${getNodePath};
|
|
525
543
|
${getNodeSelector};
|
|
526
544
|
${getBoundingClientRect};
|
|
527
|
-
${
|
|
528
|
-
${
|
|
545
|
+
${getOuterHTMLSnippetRawString};
|
|
546
|
+
${getNodeLabelRawString};
|
|
529
547
|
return (${getNodeDetailsRawString})(element);
|
|
530
548
|
}`;
|
|
531
549
|
|
|
@@ -541,4 +559,5 @@ export const pageFunctions = {
|
|
|
541
559
|
isPositionFixed,
|
|
542
560
|
wrapRequestIdleCallback,
|
|
543
561
|
getBoundingClientRect,
|
|
562
|
+
truncate,
|
|
544
563
|
};
|
|
@@ -179,6 +179,14 @@ export class TraceProcessor {
|
|
|
179
179
|
* @return {evt is LCPCandidateEvent}
|
|
180
180
|
*/
|
|
181
181
|
static isLCPCandidateEvent(evt: LH.TraceEvent): evt is LCPCandidateEvent;
|
|
182
|
+
/**
|
|
183
|
+
* The associated frame ID is set in different locations for different trace events.
|
|
184
|
+
* This function checks all known locations for the frame ID and returns `undefined` if it's not found.
|
|
185
|
+
*
|
|
186
|
+
* @param {LH.TraceEvent} evt
|
|
187
|
+
* @return {string|undefined}
|
|
188
|
+
*/
|
|
189
|
+
static getFrameId(evt: LH.TraceEvent): string | undefined;
|
|
182
190
|
/**
|
|
183
191
|
* Returns the maximum LCP event across all frames in `events`.
|
|
184
192
|
* Sets `invalidated` flag if LCP of every frame is invalidated.
|
|
@@ -506,14 +506,22 @@ class TraceProcessor {
|
|
|
506
506
|
const pidToTid = new Map();
|
|
507
507
|
|
|
508
508
|
for (const pid of new Set(mainFramePids)) {
|
|
509
|
-
|
|
510
|
-
const threadNameEvt = keyEvents.find(e =>
|
|
509
|
+
const threadEvents = keyEvents.filter(e =>
|
|
511
510
|
e.cat === '__metadata' &&
|
|
512
511
|
e.pid === pid &&
|
|
513
512
|
e.ph === 'M' &&
|
|
514
|
-
e.name === 'thread_name'
|
|
515
|
-
e.args.name === 'CrRendererMain'
|
|
513
|
+
e.name === 'thread_name'
|
|
516
514
|
);
|
|
515
|
+
|
|
516
|
+
// While renderer tids are generally predictable, we'll doublecheck it
|
|
517
|
+
let threadNameEvt = threadEvents.find(e => e.args.name === 'CrRendererMain');
|
|
518
|
+
|
|
519
|
+
// `CrRendererMain` can be missing if chrome is launched with the `--single-process` flag.
|
|
520
|
+
// In this case, page tasks will be run in the browser thread.
|
|
521
|
+
if (!threadNameEvt) {
|
|
522
|
+
threadNameEvt = threadEvents.find(e => e.args.name === 'CrBrowserMain');
|
|
523
|
+
}
|
|
524
|
+
|
|
517
525
|
const tid = threadNameEvt?.tid;
|
|
518
526
|
|
|
519
527
|
if (!tid) {
|
|
@@ -559,6 +567,19 @@ class TraceProcessor {
|
|
|
559
567
|
);
|
|
560
568
|
}
|
|
561
569
|
|
|
570
|
+
/**
|
|
571
|
+
* The associated frame ID is set in different locations for different trace events.
|
|
572
|
+
* This function checks all known locations for the frame ID and returns `undefined` if it's not found.
|
|
573
|
+
*
|
|
574
|
+
* @param {LH.TraceEvent} evt
|
|
575
|
+
* @return {string|undefined}
|
|
576
|
+
*/
|
|
577
|
+
static getFrameId(evt) {
|
|
578
|
+
return evt.args?.data?.frame ||
|
|
579
|
+
evt.args.data?.frameID ||
|
|
580
|
+
evt.args.frame;
|
|
581
|
+
}
|
|
582
|
+
|
|
562
583
|
/**
|
|
563
584
|
* Returns the maximum LCP event across all frames in `events`.
|
|
564
585
|
* Sets `invalidated` flag if LCP of every frame is invalidated.
|
|
@@ -704,13 +725,13 @@ class TraceProcessor {
|
|
|
704
725
|
// Filter to just events matching the main frame ID, just to make sure.
|
|
705
726
|
/** @param {LH.TraceEvent} e */
|
|
706
727
|
function associatedToMainFrame(e) {
|
|
707
|
-
const frameId =
|
|
728
|
+
const frameId = TraceProcessor.getFrameId(e);
|
|
708
729
|
return frameId === mainFrameInfo.frameId;
|
|
709
730
|
}
|
|
710
731
|
|
|
711
732
|
/** @param {LH.TraceEvent} e */
|
|
712
733
|
function associatedToAllFrames(e) {
|
|
713
|
-
const frameId =
|
|
734
|
+
const frameId = TraceProcessor.getFrameId(e);
|
|
714
735
|
return frameId ? inspectedTreeFrameIds.includes(frameId) : false;
|
|
715
736
|
}
|
|
716
737
|
const frameEvents = keyEvents.filter(e => associatedToMainFrame(e));
|
package/core/lib/url-utils.js
CHANGED
|
@@ -138,7 +138,7 @@ class UrlUtils {
|
|
|
138
138
|
static elideDataURI(url) {
|
|
139
139
|
try {
|
|
140
140
|
const parsed = new URL(url);
|
|
141
|
-
return parsed.protocol === 'data:' ?
|
|
141
|
+
return parsed.protocol === 'data:' ? Util.truncate(url, 100) : url;
|
|
142
142
|
} catch (e) {
|
|
143
143
|
return url;
|
|
144
144
|
}
|
package/core/runner.js
CHANGED
|
@@ -170,6 +170,7 @@ class Runner {
|
|
|
170
170
|
// Reduce payload size in LHR JSON by omitting whats falsy.
|
|
171
171
|
if (entity === classifiedEntities.firstParty) shortEntity.isFirstParty = true;
|
|
172
172
|
if (entity.isUnrecognized) shortEntity.isUnrecognized = true;
|
|
173
|
+
if (entity.category) shortEntity.category = entity.category;
|
|
173
174
|
entities.push(shortEntity);
|
|
174
175
|
}
|
|
175
176
|
|