lighthouse 12.8.2-dev.20251006 → 12.8.2-dev.20251008
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/core/audits/insights/dom-size-insight.js +5 -1
- package/core/audits/insights/font-display-insight.js +3 -1
- package/core/audits/insights/insight-audit.d.ts +4 -2
- package/core/audits/insights/insight-audit.js +22 -3
- package/core/audits/predictive-perf.js +2 -2
- package/core/audits/redirects.js +0 -1
- package/core/audits/seo/crawlable-anchors.js +2 -3
- package/core/computed/metrics/lcp-breakdown.d.ts +10 -5
- package/core/computed/metrics/lcp-breakdown.js +50 -23
- package/core/computed/metrics/time-to-first-byte.js +33 -10
- package/core/computed/metrics/timing-summary.js +3 -2
- package/core/config/default-config.js +0 -12
- package/core/gather/gatherers/anchor-elements.js +8 -24
- package/core/gather/gatherers/inspector-issues.js +1 -28
- package/core/gather/gatherers/trace-elements.d.ts +0 -9
- package/core/gather/gatherers/trace-elements.js +0 -36
- package/core/lib/network-request.d.ts +0 -7
- package/core/lib/network-request.js +0 -16
- package/package.json +2 -4
- package/shared/localization/locales/ar-XB.json +20 -38
- package/shared/localization/locales/ar.json +20 -38
- package/shared/localization/locales/bg.json +9 -27
- package/shared/localization/locales/ca.json +9 -27
- package/shared/localization/locales/cs.json +9 -27
- package/shared/localization/locales/da.json +9 -27
- package/shared/localization/locales/de.json +9 -27
- package/shared/localization/locales/el.json +9 -27
- package/shared/localization/locales/en-GB.json +9 -27
- package/shared/localization/locales/en-US.json +18 -45
- package/shared/localization/locales/en-XA.json +0 -27
- package/shared/localization/locales/en-XL.json +18 -45
- package/shared/localization/locales/es-419.json +9 -27
- package/shared/localization/locales/es.json +9 -27
- package/shared/localization/locales/fi.json +9 -27
- package/shared/localization/locales/fil.json +9 -27
- package/shared/localization/locales/fr.json +9 -27
- package/shared/localization/locales/he.json +31 -49
- package/shared/localization/locales/hi.json +9 -27
- package/shared/localization/locales/hr.json +9 -27
- package/shared/localization/locales/hu.json +9 -27
- package/shared/localization/locales/id.json +9 -27
- package/shared/localization/locales/it.json +9 -27
- package/shared/localization/locales/ja.json +9 -27
- package/shared/localization/locales/ko.json +10 -28
- package/shared/localization/locales/lt.json +9 -27
- package/shared/localization/locales/lv.json +10 -28
- package/shared/localization/locales/nl.json +9 -27
- package/shared/localization/locales/no.json +9 -27
- package/shared/localization/locales/pl.json +9 -27
- package/shared/localization/locales/pt-PT.json +9 -27
- package/shared/localization/locales/pt.json +9 -27
- package/shared/localization/locales/ro.json +10 -28
- package/shared/localization/locales/ru.json +9 -27
- package/shared/localization/locales/sk.json +9 -27
- package/shared/localization/locales/sl.json +9 -27
- package/shared/localization/locales/sr-Latn.json +9 -27
- package/shared/localization/locales/sr.json +9 -27
- package/shared/localization/locales/sv.json +9 -27
- package/shared/localization/locales/ta.json +9 -27
- package/shared/localization/locales/te.json +10 -28
- package/shared/localization/locales/th.json +9 -27
- package/shared/localization/locales/tr.json +9 -27
- package/shared/localization/locales/uk.json +9 -27
- package/shared/localization/locales/vi.json +9 -27
- package/shared/localization/locales/zh-HK.json +9 -27
- package/shared/localization/locales/zh-TW.json +10 -28
- package/shared/localization/locales/zh.json +9 -27
- package/tsconfig.json +0 -3
- package/types/artifacts.d.ts +4 -43
- package/core/audits/byte-efficiency/offscreen-images.d.ts +0 -63
- package/core/audits/byte-efficiency/offscreen-images.js +0 -240
- package/core/audits/dobetterweb/no-document-write.d.ts +0 -16
- package/core/audits/dobetterweb/no-document-write.js +0 -86
- package/core/audits/dobetterweb/uses-passive-event-listeners.d.ts +0 -16
- package/core/audits/dobetterweb/uses-passive-event-listeners.js +0 -69
- package/core/audits/metrics/first-meaningful-paint.d.ts +0 -12
- package/core/audits/metrics/first-meaningful-paint.js +0 -47
- package/core/computed/critical-request-chains.d.ts +0 -42
- package/core/computed/critical-request-chains.js +0 -143
- package/core/computed/viewport-meta.d.ts +0 -37
- package/core/computed/viewport-meta.js +0 -71
- package/core/gather/gatherers/cache-contents.d.ts +0 -11
- package/core/gather/gatherers/cache-contents.js +0 -56
- package/core/gather/gatherers/dobetterweb/domstats.d.ts +0 -10
- package/core/gather/gatherers/dobetterweb/domstats.js +0 -102
- package/core/gather/gatherers/dobetterweb/optimized-images.d.ts +0 -48
- package/core/gather/gatherers/dobetterweb/optimized-images.js +0 -169
- package/core/gather/gatherers/dobetterweb/response-compression.d.ts +0 -23
- package/core/gather/gatherers/dobetterweb/response-compression.js +0 -136
- package/types/internal/metaviewport-parser.d.ts +0 -13
- package/types/internal/parse-cache-control.d.ts +0 -20
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2016 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as Lantern from '../lib/lantern/lantern.js';
|
|
8
|
-
import {makeComputedArtifact} from './computed-artifact.js';
|
|
9
|
-
import {NetworkRequest} from '../lib/network-request.js';
|
|
10
|
-
import {MainResource} from './main-resource.js';
|
|
11
|
-
import {PageDependencyGraph} from './page-dependency-graph.js';
|
|
12
|
-
|
|
13
|
-
class CriticalRequestChains {
|
|
14
|
-
/**
|
|
15
|
-
* For now, we use network priorities as a proxy for "render-blocking"/critical-ness.
|
|
16
|
-
* It's imperfect, but there is not a higher-fidelity signal available yet.
|
|
17
|
-
* @see https://docs.google.com/document/d/1bCDuq9H1ih9iNjgzyAL0gpwNFiEP4TZS-YLRp_RuMlc
|
|
18
|
-
* @param {Lantern.Types.NetworkRequest} request
|
|
19
|
-
* @param {Lantern.Types.NetworkRequest} mainResource
|
|
20
|
-
* @return {boolean}
|
|
21
|
-
*/
|
|
22
|
-
static isCritical(request, mainResource) {
|
|
23
|
-
if (!mainResource) {
|
|
24
|
-
throw new Error('mainResource not provided');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// The main resource is always critical.
|
|
28
|
-
if (request.requestId === mainResource.requestId) return true;
|
|
29
|
-
|
|
30
|
-
// Treat any preloaded resource as non-critical
|
|
31
|
-
if (request.isLinkPreload) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Whenever a request is a redirect, we don't know if it's critical until we resolve the final
|
|
36
|
-
// destination. At that point we can assign all the properties (priority, resourceType) of the
|
|
37
|
-
// final request back to the redirect(s) that led to it.
|
|
38
|
-
// See https://github.com/GoogleChrome/lighthouse/pull/6704
|
|
39
|
-
while (request.redirectDestination) {
|
|
40
|
-
request = request.redirectDestination;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Iframes are considered High Priority but they are not render blocking
|
|
44
|
-
const isIframe = request.resourceType === NetworkRequest.TYPES.Document &&
|
|
45
|
-
request.frameId !== mainResource.frameId;
|
|
46
|
-
// XHRs are fetched at High priority, but we exclude them, as they are unlikely to be critical
|
|
47
|
-
// Images are also non-critical.
|
|
48
|
-
// Treat any missed images, primarily favicons, as non-critical resources
|
|
49
|
-
/** @type {Array<LH.Crdp.Network.ResourceType>} */
|
|
50
|
-
const nonCriticalResourceTypes = [
|
|
51
|
-
NetworkRequest.TYPES.Image,
|
|
52
|
-
NetworkRequest.TYPES.XHR,
|
|
53
|
-
NetworkRequest.TYPES.Fetch,
|
|
54
|
-
NetworkRequest.TYPES.EventSource,
|
|
55
|
-
];
|
|
56
|
-
if (nonCriticalResourceTypes.includes(request.resourceType || 'Other') ||
|
|
57
|
-
isIframe ||
|
|
58
|
-
request.mimeType && request.mimeType.startsWith('image/')) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Requests that have no initiatorRequest are typically ambiguous late-load assets.
|
|
63
|
-
// Even on the off chance they were important, we don't have any parent to display for them.
|
|
64
|
-
if (!request.initiatorRequest) return false;
|
|
65
|
-
|
|
66
|
-
return ['VeryHigh', 'High', 'Medium'].includes(request.priority);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Create a tree of critical requests.
|
|
71
|
-
* @param {LH.Artifacts.NetworkRequest} mainResource
|
|
72
|
-
* @param {LH.Gatherer.Simulation.GraphNode} graph
|
|
73
|
-
* @return {LH.Artifacts.CriticalRequestNode}
|
|
74
|
-
*/
|
|
75
|
-
static extractChainsFromGraph(mainResource, graph) {
|
|
76
|
-
/** @type {LH.Artifacts.CriticalRequestNode} */
|
|
77
|
-
const rootNode = {};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* @param {LH.Artifacts.NetworkRequest[]} path
|
|
81
|
-
*/
|
|
82
|
-
function addChain(path) {
|
|
83
|
-
let currentNode = rootNode;
|
|
84
|
-
|
|
85
|
-
for (const record of path) {
|
|
86
|
-
if (!currentNode[record.requestId]) {
|
|
87
|
-
currentNode[record.requestId] = {
|
|
88
|
-
request: record,
|
|
89
|
-
children: {},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
currentNode = currentNode[record.requestId].children;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// By default `traverse` will discover nodes in BFS-order regardless of dependencies, but
|
|
98
|
-
// here we need traversal in a topological sort order. We'll visit a node only when its
|
|
99
|
-
// dependencies have been met.
|
|
100
|
-
const seenNodes = new Set();
|
|
101
|
-
/** @param {LH.Gatherer.Simulation.GraphNode} node */
|
|
102
|
-
function getNextNodes(node) {
|
|
103
|
-
return node.getDependents().filter(n => n.getDependencies().every(d => seenNodes.has(d)));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
graph.traverse((node, traversalPath) => {
|
|
107
|
-
seenNodes.add(node);
|
|
108
|
-
if (node.type !== 'network') return;
|
|
109
|
-
if (!CriticalRequestChains.isCritical(node.request, mainResource)) return;
|
|
110
|
-
|
|
111
|
-
const networkPath = traversalPath
|
|
112
|
-
.filter(n => n.type === 'network')
|
|
113
|
-
.reverse()
|
|
114
|
-
.map(node => node.rawRequest);
|
|
115
|
-
|
|
116
|
-
// Ignore if some ancestor is not a critical request.
|
|
117
|
-
if (networkPath.some(r => !CriticalRequestChains.isCritical(r, mainResource))) return;
|
|
118
|
-
|
|
119
|
-
// Ignore non-network things (like data urls).
|
|
120
|
-
if (NetworkRequest.isNonNetworkRequest(node.request)) return;
|
|
121
|
-
|
|
122
|
-
addChain(networkPath);
|
|
123
|
-
}, getNextNodes);
|
|
124
|
-
|
|
125
|
-
return rootNode;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* @param {{URL: LH.Artifacts['URL'], SourceMaps: LH.Artifacts['SourceMaps'], devtoolsLog: LH.DevtoolsLog, trace: LH.Trace, settings: LH.Audit.Context['settings']}} data
|
|
130
|
-
* @param {LH.Artifacts.ComputedContext} context
|
|
131
|
-
* @return {Promise<LH.Artifacts.CriticalRequestNode>}
|
|
132
|
-
*/
|
|
133
|
-
static async compute_(data, context) {
|
|
134
|
-
const mainResource = await MainResource.request(data, context);
|
|
135
|
-
const graph = await PageDependencyGraph.request({...data, fromTrace: false}, context);
|
|
136
|
-
|
|
137
|
-
return CriticalRequestChains.extractChainsFromGraph(mainResource, graph);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const CriticalRequestChainsComputed = makeComputedArtifact(CriticalRequestChains,
|
|
142
|
-
['URL', 'SourceMaps', 'devtoolsLog', 'trace', 'settings']);
|
|
143
|
-
export {CriticalRequestChainsComputed as CriticalRequestChains};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export { ViewportMetaComputed as ViewportMeta };
|
|
2
|
-
export type ViewportMetaResult = {
|
|
3
|
-
/**
|
|
4
|
-
* Whether the page has any viewport tag.
|
|
5
|
-
*/
|
|
6
|
-
hasViewportTag: boolean;
|
|
7
|
-
/**
|
|
8
|
-
* Whether the viewport tag is optimized for mobile screens.
|
|
9
|
-
*/
|
|
10
|
-
isMobileOptimized: boolean;
|
|
11
|
-
/**
|
|
12
|
-
* Warnings if the parser encountered invalid content in the viewport tag.
|
|
13
|
-
*/
|
|
14
|
-
parserWarnings: Array<string>;
|
|
15
|
-
/**
|
|
16
|
-
* The `content` attribute value, if a viewport was defined.
|
|
17
|
-
*/
|
|
18
|
-
rawContentString: string | undefined;
|
|
19
|
-
};
|
|
20
|
-
declare const ViewportMetaComputed: typeof ViewportMeta & {
|
|
21
|
-
request: (dependencies: {
|
|
22
|
-
name?: string;
|
|
23
|
-
content?: string;
|
|
24
|
-
property?: string;
|
|
25
|
-
httpEquiv?: string;
|
|
26
|
-
charset?: string;
|
|
27
|
-
node: import("../index.js").Artifacts.NodeDetails;
|
|
28
|
-
}[], context: LH.Artifacts.ComputedContext) => Promise<ViewportMetaResult>;
|
|
29
|
-
};
|
|
30
|
-
declare class ViewportMeta {
|
|
31
|
-
/**
|
|
32
|
-
* @param {LH.GathererArtifacts['MetaElements']} MetaElements
|
|
33
|
-
* @return {Promise<ViewportMetaResult>}
|
|
34
|
-
*/
|
|
35
|
-
static compute_(MetaElements: LH.GathererArtifacts["MetaElements"]): Promise<ViewportMetaResult>;
|
|
36
|
-
}
|
|
37
|
-
//# sourceMappingURL=viewport-meta.d.ts.map
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2019 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import Parser from 'metaviewport-parser';
|
|
8
|
-
|
|
9
|
-
import {makeComputedArtifact} from './computed-artifact.js';
|
|
10
|
-
|
|
11
|
-
class ViewportMeta {
|
|
12
|
-
/**
|
|
13
|
-
* @param {LH.GathererArtifacts['MetaElements']} MetaElements
|
|
14
|
-
* @return {Promise<ViewportMetaResult>}
|
|
15
|
-
*/
|
|
16
|
-
static async compute_(MetaElements) {
|
|
17
|
-
const viewportMeta = MetaElements.find(meta => meta.name === 'viewport');
|
|
18
|
-
|
|
19
|
-
if (!viewportMeta) {
|
|
20
|
-
return {
|
|
21
|
-
hasViewportTag: false,
|
|
22
|
-
isMobileOptimized: false,
|
|
23
|
-
parserWarnings: [],
|
|
24
|
-
rawContentString: undefined,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const warnings = [];
|
|
29
|
-
const rawContentString = viewportMeta.content || '';
|
|
30
|
-
const parsedProps = Parser.parseMetaViewPortContent(rawContentString);
|
|
31
|
-
|
|
32
|
-
if (Object.keys(parsedProps.unknownProperties).length) {
|
|
33
|
-
warnings.push(`Invalid properties found: ${JSON.stringify(parsedProps.unknownProperties)}`);
|
|
34
|
-
}
|
|
35
|
-
if (Object.keys(parsedProps.invalidValues).length) {
|
|
36
|
-
warnings.push(`Invalid values found: ${JSON.stringify(parsedProps.invalidValues)}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const viewportProps = parsedProps.validProperties;
|
|
40
|
-
const initialScale = Number(viewportProps['initial-scale']);
|
|
41
|
-
|
|
42
|
-
if (!isNaN(initialScale) && initialScale < 1) {
|
|
43
|
-
return {
|
|
44
|
-
hasViewportTag: true,
|
|
45
|
-
isMobileOptimized: false,
|
|
46
|
-
parserWarnings: warnings,
|
|
47
|
-
rawContentString,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const isMobileOptimized = Boolean(viewportProps.width || initialScale);
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
hasViewportTag: true,
|
|
55
|
-
isMobileOptimized,
|
|
56
|
-
parserWarnings: warnings,
|
|
57
|
-
rawContentString,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const ViewportMetaComputed = makeComputedArtifact(ViewportMeta, null);
|
|
63
|
-
export {ViewportMetaComputed as ViewportMeta};
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @typedef {object} ViewportMetaResult
|
|
67
|
-
* @property {boolean} hasViewportTag Whether the page has any viewport tag.
|
|
68
|
-
* @property {boolean} isMobileOptimized Whether the viewport tag is optimized for mobile screens.
|
|
69
|
-
* @property {Array<string>} parserWarnings Warnings if the parser encountered invalid content in the viewport tag.
|
|
70
|
-
* @property {string|undefined} rawContentString The `content` attribute value, if a viewport was defined.
|
|
71
|
-
*/
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export default CacheContents;
|
|
2
|
-
declare class CacheContents extends BaseGatherer {
|
|
3
|
-
/**
|
|
4
|
-
* Creates an array of cached URLs.
|
|
5
|
-
* @param {LH.Gatherer.Context} passContext
|
|
6
|
-
* @return {Promise<LH.Artifacts['CacheContents']>}
|
|
7
|
-
*/
|
|
8
|
-
getArtifact(passContext: LH.Gatherer.Context): Promise<LH.Artifacts["CacheContents"]>;
|
|
9
|
-
}
|
|
10
|
-
import BaseGatherer from '../base-gatherer.js';
|
|
11
|
-
//# sourceMappingURL=cache-contents.d.ts.map
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2016 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import BaseGatherer from '../base-gatherer.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @return {Promise<Array<string>>}
|
|
11
|
-
*/
|
|
12
|
-
/* c8 ignore start */
|
|
13
|
-
function getCacheContents() {
|
|
14
|
-
// Get every cache by name.
|
|
15
|
-
return caches.keys()
|
|
16
|
-
|
|
17
|
-
// Open each one.
|
|
18
|
-
.then(cacheNames => Promise.all(cacheNames.map(cacheName => caches.open(cacheName))))
|
|
19
|
-
|
|
20
|
-
.then(caches => {
|
|
21
|
-
/** @type {Array<string>} */
|
|
22
|
-
const requests = [];
|
|
23
|
-
|
|
24
|
-
// Take each cache and get any requests is contains, and bounce each one down to its URL.
|
|
25
|
-
return Promise.all(caches.map(cache => {
|
|
26
|
-
return cache.keys()
|
|
27
|
-
.then(reqs => {
|
|
28
|
-
requests.push(...reqs.map(r => r.url));
|
|
29
|
-
});
|
|
30
|
-
})).then(_ => {
|
|
31
|
-
return requests;
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
/* c8 ignore stop */
|
|
36
|
-
|
|
37
|
-
class CacheContents extends BaseGatherer {
|
|
38
|
-
/** @type {LH.Gatherer.GathererMeta} */
|
|
39
|
-
meta = {
|
|
40
|
-
supportedModes: ['snapshot', 'navigation'],
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Creates an array of cached URLs.
|
|
45
|
-
* @param {LH.Gatherer.Context} passContext
|
|
46
|
-
* @return {Promise<LH.Artifacts['CacheContents']>}
|
|
47
|
-
*/
|
|
48
|
-
async getArtifact(passContext) {
|
|
49
|
-
const driver = passContext.driver;
|
|
50
|
-
|
|
51
|
-
const cacheUrls = await driver.executionContext.evaluate(getCacheContents, {args: []});
|
|
52
|
-
return cacheUrls;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default CacheContents;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export default DOMStats;
|
|
2
|
-
declare class DOMStats extends BaseGatherer {
|
|
3
|
-
/**
|
|
4
|
-
* @param {LH.Gatherer.Context} passContext
|
|
5
|
-
* @return {Promise<LH.Artifacts['DOMStats']>}
|
|
6
|
-
*/
|
|
7
|
-
getArtifact(passContext: LH.Gatherer.Context): Promise<LH.Artifacts["DOMStats"]>;
|
|
8
|
-
}
|
|
9
|
-
import BaseGatherer from '../../base-gatherer.js';
|
|
10
|
-
//# sourceMappingURL=domstats.d.ts.map
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2017 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @fileoverview Gathers stats about the max height and width of the DOM tree
|
|
9
|
-
* and total number of elements used on the page.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/* global getNodeDetails */
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import BaseGatherer from '../../base-gatherer.js';
|
|
16
|
-
import {pageFunctions} from '../../../lib/page-functions.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Calculates the maximum tree depth of the DOM.
|
|
20
|
-
* @param {HTMLElement} element Root of the tree to look in.
|
|
21
|
-
* @param {boolean=} deep True to include shadow roots. Defaults to true.
|
|
22
|
-
* @return {LH.Artifacts.DOMStats}
|
|
23
|
-
*/
|
|
24
|
-
/* c8 ignore start */
|
|
25
|
-
function getDOMStats(element = document.body, deep = true) {
|
|
26
|
-
let deepestElement = null;
|
|
27
|
-
let maxDepth = -1;
|
|
28
|
-
let maxWidth = -1;
|
|
29
|
-
let numElements = 0;
|
|
30
|
-
let parentWithMostChildren = null;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @param {Element|ShadowRoot} element
|
|
34
|
-
* @param {number} depth
|
|
35
|
-
*/
|
|
36
|
-
const _calcDOMWidthAndHeight = function(element, depth = 1) {
|
|
37
|
-
if (depth > maxDepth) {
|
|
38
|
-
deepestElement = element;
|
|
39
|
-
maxDepth = depth;
|
|
40
|
-
}
|
|
41
|
-
if (element.children.length > maxWidth) {
|
|
42
|
-
parentWithMostChildren = element;
|
|
43
|
-
maxWidth = element.children.length;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
let child = element.firstElementChild;
|
|
47
|
-
while (child) {
|
|
48
|
-
_calcDOMWidthAndHeight(child, depth + 1);
|
|
49
|
-
// If element has shadow dom, traverse into that tree.
|
|
50
|
-
if (deep && child.shadowRoot) {
|
|
51
|
-
_calcDOMWidthAndHeight(child.shadowRoot, depth + 1);
|
|
52
|
-
}
|
|
53
|
-
child = child.nextElementSibling;
|
|
54
|
-
numElements++;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return {maxDepth, maxWidth, numElements};
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const result = _calcDOMWidthAndHeight(element);
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
depth: {
|
|
64
|
-
max: result.maxDepth,
|
|
65
|
-
// @ts-expect-error - getNodeDetails put into scope via stringification
|
|
66
|
-
...getNodeDetails(deepestElement),
|
|
67
|
-
},
|
|
68
|
-
width: {
|
|
69
|
-
max: result.maxWidth,
|
|
70
|
-
// @ts-expect-error - getNodeDetails put into scope via stringification
|
|
71
|
-
...getNodeDetails(parentWithMostChildren),
|
|
72
|
-
},
|
|
73
|
-
totalBodyElements: result.numElements,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
/* c8 ignore stop */
|
|
77
|
-
|
|
78
|
-
class DOMStats extends BaseGatherer {
|
|
79
|
-
/** @type {LH.Gatherer.GathererMeta} */
|
|
80
|
-
meta = {
|
|
81
|
-
supportedModes: ['snapshot', 'navigation'],
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @param {LH.Gatherer.Context} passContext
|
|
86
|
-
* @return {Promise<LH.Artifacts['DOMStats']>}
|
|
87
|
-
*/
|
|
88
|
-
async getArtifact(passContext) {
|
|
89
|
-
const driver = passContext.driver;
|
|
90
|
-
|
|
91
|
-
await driver.defaultSession.sendCommand('DOM.enable');
|
|
92
|
-
const results = await driver.executionContext.evaluate(getDOMStats, {
|
|
93
|
-
args: [],
|
|
94
|
-
useIsolation: true,
|
|
95
|
-
deps: [pageFunctions.getNodeDetails],
|
|
96
|
-
});
|
|
97
|
-
await driver.defaultSession.sendCommand('DOM.disable');
|
|
98
|
-
return results;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export default DOMStats;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export default OptimizedImages;
|
|
2
|
-
export type SimplifiedNetworkRecord = {
|
|
3
|
-
requestId: string;
|
|
4
|
-
url: string;
|
|
5
|
-
mimeType: string;
|
|
6
|
-
resourceSize: number;
|
|
7
|
-
};
|
|
8
|
-
/** @typedef {{requestId: string, url: string, mimeType: string, resourceSize: number}} SimplifiedNetworkRecord */
|
|
9
|
-
declare class OptimizedImages extends BaseGatherer {
|
|
10
|
-
/**
|
|
11
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
12
|
-
* @return {Array<SimplifiedNetworkRecord>}
|
|
13
|
-
*/
|
|
14
|
-
static filterImageRequests(networkRecords: Array<LH.Artifacts.NetworkRequest>): Array<SimplifiedNetworkRecord>;
|
|
15
|
-
/** @type {LH.Gatherer.GathererMeta<'DevtoolsLog'>} */
|
|
16
|
-
meta: LH.Gatherer.GathererMeta<"DevtoolsLog">;
|
|
17
|
-
_encodingStartAt: number;
|
|
18
|
-
/**
|
|
19
|
-
* @param {LH.Gatherer.ProtocolSession} session
|
|
20
|
-
* @param {string} requestId
|
|
21
|
-
* @param {'jpeg'|'webp'} encoding Either webp or jpeg.
|
|
22
|
-
* @return {Promise<LH.Crdp.Audits.GetEncodedResponseResponse>}
|
|
23
|
-
*/
|
|
24
|
-
_getEncodedResponse(session: LH.Gatherer.ProtocolSession, requestId: string, encoding: "jpeg" | "webp"): Promise<LH.Crdp.Audits.GetEncodedResponseResponse>;
|
|
25
|
-
/**
|
|
26
|
-
* @param {LH.Gatherer.ProtocolSession} session
|
|
27
|
-
* @param {SimplifiedNetworkRecord} networkRecord
|
|
28
|
-
* @return {Promise<{originalSize: number, jpegSize?: number, webpSize?: number}>}
|
|
29
|
-
*/
|
|
30
|
-
calculateImageStats(session: LH.Gatherer.ProtocolSession, networkRecord: SimplifiedNetworkRecord): Promise<{
|
|
31
|
-
originalSize: number;
|
|
32
|
-
jpegSize?: number;
|
|
33
|
-
webpSize?: number;
|
|
34
|
-
}>;
|
|
35
|
-
/**
|
|
36
|
-
* @param {LH.Gatherer.ProtocolSession} session
|
|
37
|
-
* @param {Array<SimplifiedNetworkRecord>} imageRecords
|
|
38
|
-
* @return {Promise<LH.Artifacts['OptimizedImages']>}
|
|
39
|
-
*/
|
|
40
|
-
computeOptimizedImages(session: LH.Gatherer.ProtocolSession, imageRecords: Array<SimplifiedNetworkRecord>): Promise<LH.Artifacts["OptimizedImages"]>;
|
|
41
|
-
/**
|
|
42
|
-
* @param {LH.Gatherer.Context<'DevtoolsLog'>} context
|
|
43
|
-
* @return {Promise<LH.Artifacts['OptimizedImages']>}
|
|
44
|
-
*/
|
|
45
|
-
getArtifact(context: LH.Gatherer.Context<"DevtoolsLog">): Promise<LH.Artifacts["OptimizedImages"]>;
|
|
46
|
-
}
|
|
47
|
-
import BaseGatherer from '../../base-gatherer.js';
|
|
48
|
-
//# sourceMappingURL=optimized-images.d.ts.map
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2017 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @fileoverview Determines optimized jpeg/webp filesizes for all same-origin and dataURI images
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import log from 'lighthouse-logger';
|
|
12
|
-
|
|
13
|
-
import BaseGatherer from '../../base-gatherer.js';
|
|
14
|
-
import UrlUtils from '../../../lib/url-utils.js';
|
|
15
|
-
import {NetworkRequest} from '../../../lib/network-request.js';
|
|
16
|
-
import {Sentry} from '../../../lib/sentry.js';
|
|
17
|
-
import {NetworkRecords} from '../../../computed/network-records.js';
|
|
18
|
-
import DevtoolsLog from '../devtools-log.js';
|
|
19
|
-
|
|
20
|
-
// Image encoding can be slow and we don't want to spend forever on it.
|
|
21
|
-
// Cap our encoding to 5 seconds, anything after that will be estimated.
|
|
22
|
-
const MAX_TIME_TO_SPEND_ENCODING = 5000;
|
|
23
|
-
// Cap our image file size at 2MB, anything bigger than that will be estimated.
|
|
24
|
-
const MAX_RESOURCE_SIZE_TO_ENCODE = 2000 * 1024;
|
|
25
|
-
|
|
26
|
-
const JPEG_QUALITY = 0.92;
|
|
27
|
-
const WEBP_QUALITY = 0.85;
|
|
28
|
-
|
|
29
|
-
const MINIMUM_IMAGE_SIZE = 4096; // savings of <4 KiB will be ignored in the audit anyway
|
|
30
|
-
|
|
31
|
-
const IMAGE_REGEX = /^image\/((x|ms|x-ms)-)?(png|bmp|jpeg)$/;
|
|
32
|
-
|
|
33
|
-
/** @typedef {{requestId: string, url: string, mimeType: string, resourceSize: number}} SimplifiedNetworkRecord */
|
|
34
|
-
|
|
35
|
-
class OptimizedImages extends BaseGatherer {
|
|
36
|
-
/** @type {LH.Gatherer.GathererMeta<'DevtoolsLog'>} */
|
|
37
|
-
meta = {
|
|
38
|
-
supportedModes: ['timespan', 'navigation'],
|
|
39
|
-
dependencies: {DevtoolsLog: DevtoolsLog.symbol},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
constructor() {
|
|
43
|
-
super();
|
|
44
|
-
this._encodingStartAt = 0;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
49
|
-
* @return {Array<SimplifiedNetworkRecord>}
|
|
50
|
-
*/
|
|
51
|
-
static filterImageRequests(networkRecords) {
|
|
52
|
-
/** @type {Set<string>} */
|
|
53
|
-
const seenUrls = new Set();
|
|
54
|
-
return networkRecords.reduce((prev, record) => {
|
|
55
|
-
// Skip records that we've seen before, never finished, or came from OOPIFs/web workers.
|
|
56
|
-
if (seenUrls.has(record.url) || !record.finished || record.sessionTargetType !== 'page') {
|
|
57
|
-
return prev;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
seenUrls.add(record.url);
|
|
61
|
-
const isOptimizableImage = record.resourceType === NetworkRequest.TYPES.Image &&
|
|
62
|
-
IMAGE_REGEX.test(record.mimeType);
|
|
63
|
-
|
|
64
|
-
const actualResourceSize = NetworkRequest.getResourceSizeOnNetwork(record);
|
|
65
|
-
if (isOptimizableImage && actualResourceSize > MINIMUM_IMAGE_SIZE) {
|
|
66
|
-
prev.push({
|
|
67
|
-
requestId: record.requestId,
|
|
68
|
-
url: record.url,
|
|
69
|
-
mimeType: record.mimeType,
|
|
70
|
-
resourceSize: actualResourceSize,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return prev;
|
|
75
|
-
}, /** @type {Array<SimplifiedNetworkRecord>} */ ([]));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* @param {LH.Gatherer.ProtocolSession} session
|
|
80
|
-
* @param {string} requestId
|
|
81
|
-
* @param {'jpeg'|'webp'} encoding Either webp or jpeg.
|
|
82
|
-
* @return {Promise<LH.Crdp.Audits.GetEncodedResponseResponse>}
|
|
83
|
-
*/
|
|
84
|
-
_getEncodedResponse(session, requestId, encoding) {
|
|
85
|
-
requestId = NetworkRequest.getRequestIdForBackend(requestId);
|
|
86
|
-
|
|
87
|
-
const quality = encoding === 'jpeg' ? JPEG_QUALITY : WEBP_QUALITY;
|
|
88
|
-
const params = {requestId, encoding, quality, sizeOnly: true};
|
|
89
|
-
return session.sendCommand('Audits.getEncodedResponse', params);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* @param {LH.Gatherer.ProtocolSession} session
|
|
94
|
-
* @param {SimplifiedNetworkRecord} networkRecord
|
|
95
|
-
* @return {Promise<{originalSize: number, jpegSize?: number, webpSize?: number}>}
|
|
96
|
-
*/
|
|
97
|
-
async calculateImageStats(session, networkRecord) {
|
|
98
|
-
const originalSize = networkRecord.resourceSize;
|
|
99
|
-
// Once we've hit our execution time limit or when the image is too big, don't try to re-encode it.
|
|
100
|
-
// Images in this execution path will fallback to byte-per-pixel heuristics on the audit side.
|
|
101
|
-
if (Date.now() - this._encodingStartAt > MAX_TIME_TO_SPEND_ENCODING ||
|
|
102
|
-
originalSize > MAX_RESOURCE_SIZE_TO_ENCODE) {
|
|
103
|
-
return {originalSize, jpegSize: undefined, webpSize: undefined};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const jpegData = await this._getEncodedResponse(session, networkRecord.requestId, 'jpeg');
|
|
107
|
-
const webpData = await this._getEncodedResponse(session, networkRecord.requestId, 'webp');
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
originalSize,
|
|
111
|
-
jpegSize: jpegData.encodedSize,
|
|
112
|
-
webpSize: webpData.encodedSize,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* @param {LH.Gatherer.ProtocolSession} session
|
|
118
|
-
* @param {Array<SimplifiedNetworkRecord>} imageRecords
|
|
119
|
-
* @return {Promise<LH.Artifacts['OptimizedImages']>}
|
|
120
|
-
*/
|
|
121
|
-
async computeOptimizedImages(session, imageRecords) {
|
|
122
|
-
this._encodingStartAt = Date.now();
|
|
123
|
-
|
|
124
|
-
/** @type {LH.Artifacts['OptimizedImages']} */
|
|
125
|
-
const results = [];
|
|
126
|
-
|
|
127
|
-
for (const record of imageRecords) {
|
|
128
|
-
try {
|
|
129
|
-
const stats = await this.calculateImageStats(session, record);
|
|
130
|
-
/** @type {LH.Artifacts.OptimizedImage} */
|
|
131
|
-
const image = {failed: false, ...stats, ...record};
|
|
132
|
-
results.push(image);
|
|
133
|
-
} catch (err) {
|
|
134
|
-
log.warn('optimized-images', err.message, record.url);
|
|
135
|
-
|
|
136
|
-
// Track this with Sentry since these errors aren't surfaced anywhere else, but we don't
|
|
137
|
-
// want to tank the entire run due to a single image.
|
|
138
|
-
Sentry.captureException(err, {
|
|
139
|
-
tags: {gatherer: 'OptimizedImages'},
|
|
140
|
-
extra: {imageUrl: UrlUtils.elideDataURI(record.url)},
|
|
141
|
-
level: 'warning',
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
/** @type {LH.Artifacts.OptimizedImageError} */
|
|
145
|
-
const imageError = {failed: true, errMsg: err.message, ...record};
|
|
146
|
-
results.push(imageError);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return results;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* @param {LH.Gatherer.Context<'DevtoolsLog'>} context
|
|
155
|
-
* @return {Promise<LH.Artifacts['OptimizedImages']>}
|
|
156
|
-
*/
|
|
157
|
-
async getArtifact(context) {
|
|
158
|
-
const devtoolsLog = context.dependencies.DevtoolsLog;
|
|
159
|
-
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
|
|
160
|
-
|
|
161
|
-
const imageRecords = OptimizedImages
|
|
162
|
-
.filterImageRequests(networkRecords)
|
|
163
|
-
.sort((a, b) => b.resourceSize - a.resourceSize);
|
|
164
|
-
|
|
165
|
-
return await this.computeOptimizedImages(context.driver.defaultSession, imageRecords);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export default OptimizedImages;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export default ResponseCompression;
|
|
2
|
-
declare class ResponseCompression extends BaseGatherer {
|
|
3
|
-
/**
|
|
4
|
-
* @param {LH.Artifacts.NetworkRequest[]} networkRecords
|
|
5
|
-
* @return {LH.Artifacts['ResponseCompression']}
|
|
6
|
-
*/
|
|
7
|
-
static filterUnoptimizedResponses(networkRecords: LH.Artifacts.NetworkRequest[]): LH.Artifacts["ResponseCompression"];
|
|
8
|
-
/** @type {LH.Gatherer.GathererMeta<'DevtoolsLog'>} */
|
|
9
|
-
meta: LH.Gatherer.GathererMeta<"DevtoolsLog">;
|
|
10
|
-
/**
|
|
11
|
-
* @param {LH.Gatherer.Context} context
|
|
12
|
-
* @param {LH.Artifacts.NetworkRequest[]} networkRecords
|
|
13
|
-
* @return {Promise<LH.Artifacts['ResponseCompression']>}
|
|
14
|
-
*/
|
|
15
|
-
getCompressibleRecords(context: LH.Gatherer.Context, networkRecords: LH.Artifacts.NetworkRequest[]): Promise<LH.Artifacts["ResponseCompression"]>;
|
|
16
|
-
/**
|
|
17
|
-
* @param {LH.Gatherer.Context<'DevtoolsLog'>} context
|
|
18
|
-
* @return {Promise<LH.Artifacts['ResponseCompression']>}
|
|
19
|
-
*/
|
|
20
|
-
getArtifact(context: LH.Gatherer.Context<"DevtoolsLog">): Promise<LH.Artifacts["ResponseCompression"]>;
|
|
21
|
-
}
|
|
22
|
-
import BaseGatherer from '../../base-gatherer.js';
|
|
23
|
-
//# sourceMappingURL=response-compression.d.ts.map
|