lighthouse 11.6.0 → 11.7.0
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/test/smokehouse/core-tests.js +2 -2
- package/core/audits/byte-efficiency/byte-efficiency-audit.d.ts +2 -2
- package/core/audits/byte-efficiency/byte-efficiency-audit.js +7 -7
- package/core/audits/byte-efficiency/render-blocking-resources.d.ts +4 -3
- package/core/audits/byte-efficiency/render-blocking-resources.js +6 -6
- package/core/audits/dobetterweb/uses-http2.d.ts +3 -3
- package/core/audits/dobetterweb/uses-http2.js +4 -4
- package/core/audits/installable-manifest.d.ts +2 -0
- package/core/audits/installable-manifest.js +2 -0
- package/core/audits/metrics/interaction-to-next-paint.js +1 -3
- package/core/audits/prioritize-lcp-image.d.ts +1 -1
- package/core/audits/work-during-interaction.js +0 -8
- package/core/computed/document-urls.js +1 -1
- package/core/computed/load-simulator.d.ts +2 -2
- package/core/computed/load-simulator.js +3 -2
- package/core/computed/main-resource.js +1 -1
- package/core/computed/metrics/lantern-first-contentful-paint.d.ts +18 -6
- package/core/computed/metrics/lantern-first-contentful-paint.js +4 -4
- package/core/computed/metrics/lantern-first-meaningful-paint.d.ts +14 -2
- package/core/computed/metrics/lantern-first-meaningful-paint.js +1 -1
- package/core/computed/metrics/lantern-interactive.d.ts +8 -6
- package/core/computed/metrics/lantern-interactive.js +3 -3
- package/core/computed/metrics/lantern-largest-contentful-paint.d.ts +14 -2
- package/core/computed/metrics/lantern-largest-contentful-paint.js +1 -1
- package/core/computed/metrics/lantern-max-potential-fid.d.ts +8 -2
- package/core/computed/metrics/lantern-max-potential-fid.js +3 -3
- package/core/computed/metrics/lantern-metric.d.ts +4 -63
- package/core/computed/metrics/lantern-metric.js +6 -124
- package/core/computed/metrics/lantern-speed-index.d.ts +8 -2
- package/core/computed/metrics/lantern-speed-index.js +3 -3
- package/core/computed/metrics/lantern-total-blocking-time.d.ts +8 -2
- package/core/computed/metrics/lantern-total-blocking-time.js +3 -3
- package/core/computed/metrics/responsiveness.d.ts +5 -13
- package/core/computed/metrics/responsiveness.js +10 -14
- package/core/computed/network-analysis.js +1 -1
- package/core/computed/page-dependency-graph.d.ts +4 -57
- package/core/computed/page-dependency-graph.js +4 -451
- package/core/config/config-helpers.js +1 -1
- package/core/gather/driver/execution-context.d.ts +2 -1
- package/core/gather/driver/execution-context.js +9 -9
- package/core/gather/driver/wait-for-condition.js +4 -2
- package/core/gather/gatherers/accessibility.js +2 -1
- package/core/gather/gatherers/css-usage.d.ts +1 -9
- package/core/gather/gatherers/css-usage.js +23 -25
- package/core/gather/gatherers/devtools-log.d.ts +4 -0
- package/core/gather/gatherers/devtools-log.js +7 -0
- package/core/gather/gatherers/full-page-screenshot.d.ts +4 -0
- package/core/gather/gatherers/full-page-screenshot.js +31 -35
- package/core/gather/gatherers/root-causes.js +1 -3
- package/core/gather/gatherers/trace-elements.js +1 -1
- package/core/gather/gatherers/trace.d.ts +1 -0
- package/core/gather/gatherers/trace.js +4 -0
- package/core/gather/navigation-runner.js +49 -62
- package/core/gather/session.js +5 -0
- package/core/gather/snapshot-runner.js +25 -25
- package/core/gather/timespan-runner.js +24 -25
- package/core/lib/asset-saver.js +1 -1
- package/core/lib/emulation.js +0 -3
- package/core/lib/{dependency-graph → lantern}/base-node.d.ts +12 -8
- package/core/lib/{dependency-graph → lantern}/base-node.js +8 -3
- package/core/lib/{dependency-graph → lantern}/cpu-node.d.ts +5 -1
- package/core/lib/{dependency-graph → lantern}/cpu-node.js +4 -0
- package/core/lib/lantern/lantern.d.ts +8 -0
- package/core/lib/lantern/lantern.js +31 -0
- package/core/lib/lantern/metric.d.ts +71 -0
- package/core/lib/lantern/metric.js +143 -0
- package/core/lib/{dependency-graph → lantern}/network-node.d.ts +17 -10
- package/core/lib/lantern/network-node.js +114 -0
- package/core/lib/lantern/page-dependency-graph.d.ts +58 -0
- package/core/lib/lantern/page-dependency-graph.js +463 -0
- package/core/lib/{dependency-graph → lantern}/simulator/connection-pool.d.ts +14 -14
- package/core/lib/{dependency-graph → lantern}/simulator/connection-pool.js +7 -7
- package/core/lib/{dependency-graph → lantern}/simulator/dns-cache.d.ts +5 -5
- package/core/lib/{dependency-graph → lantern}/simulator/dns-cache.js +3 -3
- package/core/lib/{dependency-graph → lantern}/simulator/network-analyzer.d.ts +36 -32
- package/core/lib/{dependency-graph → lantern}/simulator/network-analyzer.js +21 -17
- package/core/lib/{dependency-graph → lantern}/simulator/simulator.d.ts +18 -15
- package/core/lib/{dependency-graph → lantern}/simulator/simulator.js +18 -15
- package/core/lib/lantern/types/lantern.d.ts +129 -0
- package/core/lib/lantern/types/lantern.js +7 -0
- package/core/lib/lantern-trace-saver.d.ts +4 -4
- package/core/lib/lantern-trace-saver.js +6 -4
- package/core/lib/navigation-error.js +1 -1
- package/core/lib/network-recorder.js +2 -2
- package/core/lib/network-request.d.ts +6 -0
- package/core/lib/network-request.js +12 -0
- package/package.json +4 -5
- 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 +2 -2
- 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 +3 -0
- package/shared/localization/locales/en-XA.json +1 -1
- package/shared/localization/locales/en-XL.json +3 -0
- 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/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/ko.json +1 -1
- package/shared/localization/locales/lt.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/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 +4 -4
- 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/third-party/chromium-synchronization/installability-errors-test.js +1 -1
- package/tsconfig.json +7 -5
- package/types/artifacts.d.ts +1 -1
- package/types/gatherer.d.ts +8 -35
- package/types/lhr/lhr.d.ts +1 -1
- package/core/lib/dependency-graph/network-node.js +0 -99
- /package/core/lib/{dependency-graph → lantern}/simulator/simulator-timing-map.d.ts +0 -0
- /package/core/lib/{dependency-graph → lantern}/simulator/simulator-timing-map.js +0 -0
- /package/core/lib/{dependency-graph → lantern}/simulator/tcp-connection.d.ts +0 -0
- /package/core/lib/{dependency-graph → lantern}/simulator/tcp-connection.js +0 -0
|
@@ -5,463 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {makeComputedArtifact} from './computed-artifact.js';
|
|
8
|
-
import {
|
|
9
|
-
import {CPUNode} from '../lib/dependency-graph/cpu-node.js';
|
|
10
|
-
import {TraceProcessor} from '../lib/tracehouse/trace-processor.js';
|
|
8
|
+
import {PageDependencyGraph as LanternPageDependencyGraph} from '../lib/lantern/page-dependency-graph.js';
|
|
11
9
|
import {NetworkRequest} from '../lib/network-request.js';
|
|
12
10
|
import {ProcessedTrace} from './processed-trace.js';
|
|
13
11
|
import {NetworkRecords} from './network-records.js';
|
|
14
|
-
import {NetworkAnalyzer} from '../lib/dependency-graph/simulator/network-analyzer.js';
|
|
15
12
|
import {DocumentUrls} from './document-urls.js';
|
|
16
13
|
|
|
17
|
-
/** @typedef {import('../lib/
|
|
18
|
-
/** @typedef {Omit<LH.Artifacts['URL'], 'finalDisplayedUrl'>} URLArtifact */
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @typedef {Object} NetworkNodeOutput
|
|
22
|
-
* @property {Array<NetworkNode>} nodes
|
|
23
|
-
* @property {Map<string, NetworkNode>} idToNodeMap
|
|
24
|
-
* @property {Map<string, Array<NetworkNode>>} urlToNodeMap
|
|
25
|
-
* @property {Map<string, NetworkNode|null>} frameIdToNodeMap
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
// Shorter tasks have negligible impact on simulation results.
|
|
29
|
-
const SIGNIFICANT_DUR_THRESHOLD_MS = 10;
|
|
30
|
-
|
|
31
|
-
// TODO: video files tend to be enormous and throw off all graph traversals, move this ignore
|
|
32
|
-
// into estimation logic when we use the dependency graph for other purposes.
|
|
33
|
-
const IGNORED_MIME_TYPES_REGEX = /^video/;
|
|
14
|
+
/** @typedef {import('../lib/lantern/base-node.js').Node<LH.Artifacts.NetworkRequest>} Node */
|
|
34
15
|
|
|
35
16
|
class PageDependencyGraph {
|
|
36
|
-
/**
|
|
37
|
-
* @param {LH.Artifacts.NetworkRequest} record
|
|
38
|
-
* @return {Array<string>}
|
|
39
|
-
*/
|
|
40
|
-
static getNetworkInitiators(record) {
|
|
41
|
-
if (!record.initiator) return [];
|
|
42
|
-
if (record.initiator.url) return [record.initiator.url];
|
|
43
|
-
if (record.initiator.type === 'script') {
|
|
44
|
-
// Script initiators have the stack of callFrames from all functions that led to this request.
|
|
45
|
-
// If async stacks are enabled, then the stack will also have the parent functions that asynchronously
|
|
46
|
-
// led to this request chained in the `parent` property.
|
|
47
|
-
/** @type {Set<string>} */
|
|
48
|
-
const scriptURLs = new Set();
|
|
49
|
-
let stack = record.initiator.stack;
|
|
50
|
-
while (stack) {
|
|
51
|
-
const callFrames = stack.callFrames || [];
|
|
52
|
-
for (const frame of callFrames) {
|
|
53
|
-
if (frame.url) scriptURLs.add(frame.url);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
stack = stack.parent;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return Array.from(scriptURLs);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
67
|
-
* @return {NetworkNodeOutput}
|
|
68
|
-
*/
|
|
69
|
-
static getNetworkNodeOutput(networkRecords) {
|
|
70
|
-
/** @type {Array<NetworkNode>} */
|
|
71
|
-
const nodes = [];
|
|
72
|
-
/** @type {Map<string, NetworkNode>} */
|
|
73
|
-
const idToNodeMap = new Map();
|
|
74
|
-
/** @type {Map<string, Array<NetworkNode>>} */
|
|
75
|
-
const urlToNodeMap = new Map();
|
|
76
|
-
/** @type {Map<string, NetworkNode|null>} */
|
|
77
|
-
const frameIdToNodeMap = new Map();
|
|
78
|
-
|
|
79
|
-
networkRecords.forEach(record => {
|
|
80
|
-
if (IGNORED_MIME_TYPES_REGEX.test(record.mimeType)) return;
|
|
81
|
-
if (record.sessionTargetType === 'worker') return;
|
|
82
|
-
|
|
83
|
-
// Network record requestIds can be duplicated for an unknown reason
|
|
84
|
-
// Suffix all subsequent records with `:duplicate` until it's unique
|
|
85
|
-
// NOTE: This should never happen with modern NetworkRequest library, but old fixtures
|
|
86
|
-
// might still have this issue.
|
|
87
|
-
while (idToNodeMap.has(record.requestId)) {
|
|
88
|
-
record.requestId += ':duplicate';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const node = new NetworkNode(record);
|
|
92
|
-
nodes.push(node);
|
|
93
|
-
|
|
94
|
-
const urlList = urlToNodeMap.get(record.url) || [];
|
|
95
|
-
urlList.push(node);
|
|
96
|
-
|
|
97
|
-
idToNodeMap.set(record.requestId, node);
|
|
98
|
-
urlToNodeMap.set(record.url, urlList);
|
|
99
|
-
|
|
100
|
-
// If the request was for the root document of an iframe, save an entry in our
|
|
101
|
-
// map so we can link up the task `args.data.frame` dependencies later in graph creation.
|
|
102
|
-
if (record.frameId &&
|
|
103
|
-
record.resourceType === NetworkRequest.TYPES.Document &&
|
|
104
|
-
record.documentURL === record.url) {
|
|
105
|
-
// If there's ever any ambiguity, permanently set the value to `false` to avoid loops in the graph.
|
|
106
|
-
const value = frameIdToNodeMap.has(record.frameId) ? null : node;
|
|
107
|
-
frameIdToNodeMap.set(record.frameId, value);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return {nodes, idToNodeMap, urlToNodeMap, frameIdToNodeMap};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
116
|
-
* @return {Array<CPUNode>}
|
|
117
|
-
*/
|
|
118
|
-
static getCPUNodes({mainThreadEvents}) {
|
|
119
|
-
/** @type {Array<CPUNode>} */
|
|
120
|
-
const nodes = [];
|
|
121
|
-
let i = 0;
|
|
122
|
-
|
|
123
|
-
TraceProcessor.assertHasToplevelEvents(mainThreadEvents);
|
|
124
|
-
|
|
125
|
-
while (i < mainThreadEvents.length) {
|
|
126
|
-
const evt = mainThreadEvents[i];
|
|
127
|
-
i++;
|
|
128
|
-
|
|
129
|
-
// Skip all trace events that aren't schedulable tasks with sizable duration
|
|
130
|
-
if (!TraceProcessor.isScheduleableTask(evt) || !evt.dur) {
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Capture all events that occurred within the task
|
|
135
|
-
/** @type {Array<LH.TraceEvent>} */
|
|
136
|
-
const children = [];
|
|
137
|
-
for (
|
|
138
|
-
const endTime = evt.ts + evt.dur;
|
|
139
|
-
i < mainThreadEvents.length && mainThreadEvents[i].ts < endTime;
|
|
140
|
-
i++
|
|
141
|
-
) {
|
|
142
|
-
children.push(mainThreadEvents[i]);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
nodes.push(new CPUNode(evt, children));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return nodes;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* @param {NetworkNode} rootNode
|
|
153
|
-
* @param {NetworkNodeOutput} networkNodeOutput
|
|
154
|
-
*/
|
|
155
|
-
static linkNetworkNodes(rootNode, networkNodeOutput) {
|
|
156
|
-
networkNodeOutput.nodes.forEach(node => {
|
|
157
|
-
const directInitiatorRequest = node.record.initiatorRequest || rootNode.record;
|
|
158
|
-
const directInitiatorNode =
|
|
159
|
-
networkNodeOutput.idToNodeMap.get(directInitiatorRequest.requestId) || rootNode;
|
|
160
|
-
const canDependOnInitiator =
|
|
161
|
-
!directInitiatorNode.isDependentOn(node) &&
|
|
162
|
-
node.canDependOn(directInitiatorNode);
|
|
163
|
-
const initiators = PageDependencyGraph.getNetworkInitiators(node.record);
|
|
164
|
-
if (initiators.length) {
|
|
165
|
-
initiators.forEach(initiator => {
|
|
166
|
-
const parentCandidates = networkNodeOutput.urlToNodeMap.get(initiator) || [];
|
|
167
|
-
// Only add the edge if the parent is unambiguous with valid timing and isn't circular.
|
|
168
|
-
if (parentCandidates.length === 1 &&
|
|
169
|
-
parentCandidates[0].startTime <= node.startTime &&
|
|
170
|
-
!parentCandidates[0].isDependentOn(node)) {
|
|
171
|
-
node.addDependency(parentCandidates[0]);
|
|
172
|
-
} else if (canDependOnInitiator) {
|
|
173
|
-
directInitiatorNode.addDependent(node);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
} else if (canDependOnInitiator) {
|
|
177
|
-
directInitiatorNode.addDependent(node);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Make sure the nodes are attached to the graph if the initiator information was invalid.
|
|
181
|
-
if (node !== rootNode && node.getDependencies().length === 0 && node.canDependOn(rootNode)) {
|
|
182
|
-
node.addDependency(rootNode);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (!node.record.redirects) return;
|
|
186
|
-
|
|
187
|
-
const redirects = [...node.record.redirects, node.record];
|
|
188
|
-
for (let i = 1; i < redirects.length; i++) {
|
|
189
|
-
const redirectNode = networkNodeOutput.idToNodeMap.get(redirects[i - 1].requestId);
|
|
190
|
-
const actualNode = networkNodeOutput.idToNodeMap.get(redirects[i].requestId);
|
|
191
|
-
if (actualNode && redirectNode) {
|
|
192
|
-
actualNode.addDependency(redirectNode);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* @param {Node} rootNode
|
|
200
|
-
* @param {NetworkNodeOutput} networkNodeOutput
|
|
201
|
-
* @param {Array<CPUNode>} cpuNodes
|
|
202
|
-
*/
|
|
203
|
-
static linkCPUNodes(rootNode, networkNodeOutput, cpuNodes) {
|
|
204
|
-
/** @type {Set<LH.Crdp.Network.ResourceType|undefined>} */
|
|
205
|
-
const linkableResourceTypes = new Set([
|
|
206
|
-
NetworkRequest.TYPES.XHR, NetworkRequest.TYPES.Fetch, NetworkRequest.TYPES.Script,
|
|
207
|
-
]);
|
|
208
|
-
|
|
209
|
-
/** @param {CPUNode} cpuNode @param {string} reqId */
|
|
210
|
-
function addDependentNetworkRequest(cpuNode, reqId) {
|
|
211
|
-
const networkNode = networkNodeOutput.idToNodeMap.get(reqId);
|
|
212
|
-
if (!networkNode ||
|
|
213
|
-
// Ignore all network nodes that started before this CPU task started
|
|
214
|
-
// A network request that started earlier could not possibly have been started by this task
|
|
215
|
-
networkNode.startTime <= cpuNode.startTime) return;
|
|
216
|
-
const {record} = networkNode;
|
|
217
|
-
const resourceType = record.resourceType ||
|
|
218
|
-
record.redirectDestination?.resourceType;
|
|
219
|
-
if (!linkableResourceTypes.has(resourceType)) {
|
|
220
|
-
// We only link some resources to CPU nodes because we observe LCP simulation
|
|
221
|
-
// regressions when including images, etc.
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
cpuNode.addDependent(networkNode);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* If the node has an associated frameId, then create a dependency on the root document request
|
|
229
|
-
* for the frame. The task obviously couldn't have started before the frame was even downloaded.
|
|
230
|
-
*
|
|
231
|
-
* @param {CPUNode} cpuNode
|
|
232
|
-
* @param {string|undefined} frameId
|
|
233
|
-
*/
|
|
234
|
-
function addDependencyOnFrame(cpuNode, frameId) {
|
|
235
|
-
if (!frameId) return;
|
|
236
|
-
const networkNode = networkNodeOutput.frameIdToNodeMap.get(frameId);
|
|
237
|
-
if (!networkNode) return;
|
|
238
|
-
// Ignore all network nodes that started after this CPU task started
|
|
239
|
-
// A network request that started after could not possibly be required this task
|
|
240
|
-
if (networkNode.startTime >= cpuNode.startTime) return;
|
|
241
|
-
cpuNode.addDependency(networkNode);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/** @param {CPUNode} cpuNode @param {string} url */
|
|
245
|
-
function addDependencyOnUrl(cpuNode, url) {
|
|
246
|
-
if (!url) return;
|
|
247
|
-
// Allow network requests that end up to 100ms before the task started
|
|
248
|
-
// Some script evaluations can start before the script finishes downloading
|
|
249
|
-
const minimumAllowableTimeSinceNetworkNodeEnd = -100 * 1000;
|
|
250
|
-
const candidates = networkNodeOutput.urlToNodeMap.get(url) || [];
|
|
251
|
-
|
|
252
|
-
let minCandidate = null;
|
|
253
|
-
let minDistance = Infinity;
|
|
254
|
-
// Find the closest request that finished before this CPU task started
|
|
255
|
-
for (const candidate of candidates) {
|
|
256
|
-
// Explicitly ignore all requests that started after this CPU node
|
|
257
|
-
// A network request that started after this task started cannot possibly be a dependency
|
|
258
|
-
if (cpuNode.startTime <= candidate.startTime) return;
|
|
259
|
-
|
|
260
|
-
const distance = cpuNode.startTime - candidate.endTime;
|
|
261
|
-
if (distance >= minimumAllowableTimeSinceNetworkNodeEnd && distance < minDistance) {
|
|
262
|
-
minCandidate = candidate;
|
|
263
|
-
minDistance = distance;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (!minCandidate) return;
|
|
268
|
-
cpuNode.addDependency(minCandidate);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/** @type {Map<string, CPUNode>} */
|
|
272
|
-
const timers = new Map();
|
|
273
|
-
for (const node of cpuNodes) {
|
|
274
|
-
for (const evt of node.childEvents) {
|
|
275
|
-
if (!evt.args.data) continue;
|
|
276
|
-
|
|
277
|
-
const argsUrl = evt.args.data.url;
|
|
278
|
-
const stackTraceUrls = (evt.args.data.stackTrace || []).map(l => l.url).filter(Boolean);
|
|
279
|
-
|
|
280
|
-
switch (evt.name) {
|
|
281
|
-
case 'TimerInstall':
|
|
282
|
-
// @ts-expect-error - 'TimerInstall' event means timerId exists.
|
|
283
|
-
timers.set(evt.args.data.timerId, node);
|
|
284
|
-
stackTraceUrls.forEach(url => addDependencyOnUrl(node, url));
|
|
285
|
-
break;
|
|
286
|
-
case 'TimerFire': {
|
|
287
|
-
// @ts-expect-error - 'TimerFire' event means timerId exists.
|
|
288
|
-
const installer = timers.get(evt.args.data.timerId);
|
|
289
|
-
if (!installer || installer.endTime > node.startTime) break;
|
|
290
|
-
installer.addDependent(node);
|
|
291
|
-
break;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
case 'InvalidateLayout':
|
|
295
|
-
case 'ScheduleStyleRecalculation':
|
|
296
|
-
addDependencyOnFrame(node, evt.args.data.frame);
|
|
297
|
-
stackTraceUrls.forEach(url => addDependencyOnUrl(node, url));
|
|
298
|
-
break;
|
|
299
|
-
|
|
300
|
-
case 'EvaluateScript':
|
|
301
|
-
addDependencyOnFrame(node, evt.args.data.frame);
|
|
302
|
-
// @ts-expect-error - 'EvaluateScript' event means argsUrl is defined.
|
|
303
|
-
addDependencyOnUrl(node, argsUrl);
|
|
304
|
-
stackTraceUrls.forEach(url => addDependencyOnUrl(node, url));
|
|
305
|
-
break;
|
|
306
|
-
|
|
307
|
-
case 'XHRReadyStateChange':
|
|
308
|
-
// Only create the dependency if the request was completed
|
|
309
|
-
// 'XHRReadyStateChange' event means readyState is defined.
|
|
310
|
-
if (evt.args.data.readyState !== 4) break;
|
|
311
|
-
|
|
312
|
-
// @ts-expect-error - 'XHRReadyStateChange' event means argsUrl is defined.
|
|
313
|
-
addDependencyOnUrl(node, argsUrl);
|
|
314
|
-
stackTraceUrls.forEach(url => addDependencyOnUrl(node, url));
|
|
315
|
-
break;
|
|
316
|
-
|
|
317
|
-
case 'FunctionCall':
|
|
318
|
-
case 'v8.compile':
|
|
319
|
-
addDependencyOnFrame(node, evt.args.data.frame);
|
|
320
|
-
// @ts-expect-error - events mean argsUrl is defined.
|
|
321
|
-
addDependencyOnUrl(node, argsUrl);
|
|
322
|
-
break;
|
|
323
|
-
|
|
324
|
-
case 'ParseAuthorStyleSheet':
|
|
325
|
-
addDependencyOnFrame(node, evt.args.data.frame);
|
|
326
|
-
// @ts-expect-error - 'ParseAuthorStyleSheet' event means styleSheetUrl is defined.
|
|
327
|
-
addDependencyOnUrl(node, evt.args.data.styleSheetUrl);
|
|
328
|
-
break;
|
|
329
|
-
|
|
330
|
-
case 'ResourceSendRequest':
|
|
331
|
-
addDependencyOnFrame(node, evt.args.data.frame);
|
|
332
|
-
// @ts-expect-error - 'ResourceSendRequest' event means requestId is defined.
|
|
333
|
-
addDependentNetworkRequest(node, evt.args.data.requestId);
|
|
334
|
-
stackTraceUrls.forEach(url => addDependencyOnUrl(node, url));
|
|
335
|
-
break;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Nodes starting before the root node cannot depend on it.
|
|
340
|
-
if (node.getNumberOfDependencies() === 0 && node.canDependOn(rootNode)) {
|
|
341
|
-
node.addDependency(rootNode);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Second pass to prune the graph of short tasks.
|
|
346
|
-
const minimumEvtDur = SIGNIFICANT_DUR_THRESHOLD_MS * 1000;
|
|
347
|
-
let foundFirstLayout = false;
|
|
348
|
-
let foundFirstPaint = false;
|
|
349
|
-
let foundFirstParse = false;
|
|
350
|
-
|
|
351
|
-
for (const node of cpuNodes) {
|
|
352
|
-
// Don't prune if event is the first ParseHTML/Layout/Paint.
|
|
353
|
-
// See https://github.com/GoogleChrome/lighthouse/issues/9627#issuecomment-526699524 for more.
|
|
354
|
-
let isFirst = false;
|
|
355
|
-
if (!foundFirstLayout && node.childEvents.some(evt => evt.name === 'Layout')) {
|
|
356
|
-
isFirst = foundFirstLayout = true;
|
|
357
|
-
}
|
|
358
|
-
if (!foundFirstPaint && node.childEvents.some(evt => evt.name === 'Paint')) {
|
|
359
|
-
isFirst = foundFirstPaint = true;
|
|
360
|
-
}
|
|
361
|
-
if (!foundFirstParse && node.childEvents.some(evt => evt.name === 'ParseHTML')) {
|
|
362
|
-
isFirst = foundFirstParse = true;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (isFirst || node.event.dur >= minimumEvtDur) {
|
|
366
|
-
// Don't prune this node. The task is long / important so it will impact simulation.
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Prune the node if it isn't highly connected to minimize graph size. Rewiring the graph
|
|
371
|
-
// here replaces O(M + N) edges with (M * N) edges, which is fine if either M or N is at
|
|
372
|
-
// most 1.
|
|
373
|
-
if (node.getNumberOfDependencies() === 1 || node.getNumberOfDependents() <= 1) {
|
|
374
|
-
PageDependencyGraph._pruneNode(node);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Removes the given node from the graph, but retains all paths between its dependencies and
|
|
381
|
-
* dependents.
|
|
382
|
-
* @param {Node} node
|
|
383
|
-
*/
|
|
384
|
-
static _pruneNode(node) {
|
|
385
|
-
const dependencies = node.getDependencies();
|
|
386
|
-
const dependents = node.getDependents();
|
|
387
|
-
for (const dependency of dependencies) {
|
|
388
|
-
node.removeDependency(dependency);
|
|
389
|
-
for (const dependent of dependents) {
|
|
390
|
-
dependency.addDependent(dependent);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
for (const dependent of dependents) {
|
|
394
|
-
node.removeDependent(dependent);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* @param {LH.Artifacts.ProcessedTrace} processedTrace
|
|
400
|
-
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
|
|
401
|
-
* @param {URLArtifact} URL
|
|
402
|
-
* @return {Node}
|
|
403
|
-
*/
|
|
404
|
-
static createGraph(processedTrace, networkRecords, URL) {
|
|
405
|
-
const networkNodeOutput = PageDependencyGraph.getNetworkNodeOutput(networkRecords);
|
|
406
|
-
const cpuNodes = PageDependencyGraph.getCPUNodes(processedTrace);
|
|
407
|
-
const {requestedUrl, mainDocumentUrl} = URL;
|
|
408
|
-
if (!requestedUrl) throw new Error('requestedUrl is required to get the root request');
|
|
409
|
-
if (!mainDocumentUrl) throw new Error('mainDocumentUrl is required to get the main resource');
|
|
410
|
-
|
|
411
|
-
const rootRequest = NetworkAnalyzer.findResourceForUrl(networkRecords, requestedUrl);
|
|
412
|
-
if (!rootRequest) throw new Error('rootRequest not found');
|
|
413
|
-
const rootNode = networkNodeOutput.idToNodeMap.get(rootRequest.requestId);
|
|
414
|
-
if (!rootNode) throw new Error('rootNode not found');
|
|
415
|
-
|
|
416
|
-
const mainDocumentRequest =
|
|
417
|
-
NetworkAnalyzer.findLastDocumentForUrl(networkRecords, mainDocumentUrl);
|
|
418
|
-
if (!mainDocumentRequest) throw new Error('mainDocumentRequest not found');
|
|
419
|
-
const mainDocumentNode = networkNodeOutput.idToNodeMap.get(mainDocumentRequest.requestId);
|
|
420
|
-
if (!mainDocumentNode) throw new Error('mainDocumentNode not found');
|
|
421
|
-
|
|
422
|
-
PageDependencyGraph.linkNetworkNodes(rootNode, networkNodeOutput);
|
|
423
|
-
PageDependencyGraph.linkCPUNodes(rootNode, networkNodeOutput, cpuNodes);
|
|
424
|
-
mainDocumentNode.setIsMainDocument(true);
|
|
425
|
-
|
|
426
|
-
if (NetworkNode.hasCycle(rootNode)) {
|
|
427
|
-
throw new Error('Invalid dependency graph created, cycle detected');
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return rootNode;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
*
|
|
435
|
-
* @param {Node} rootNode
|
|
436
|
-
*/
|
|
437
|
-
static printGraph(rootNode, widthInCharacters = 100) {
|
|
438
|
-
/** @param {string} str @param {number} target */
|
|
439
|
-
function padRight(str, target, padChar = ' ') {
|
|
440
|
-
return str + padChar.repeat(Math.max(target - str.length, 0));
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/** @type {Array<Node>} */
|
|
444
|
-
const nodes = [];
|
|
445
|
-
rootNode.traverse(node => nodes.push(node));
|
|
446
|
-
nodes.sort((a, b) => a.startTime - b.startTime);
|
|
447
|
-
|
|
448
|
-
const min = nodes[0].startTime;
|
|
449
|
-
const max = nodes.reduce((max, node) => Math.max(max, node.endTime), 0);
|
|
450
|
-
|
|
451
|
-
const totalTime = max - min;
|
|
452
|
-
const timePerCharacter = totalTime / widthInCharacters;
|
|
453
|
-
nodes.forEach(node => {
|
|
454
|
-
const offset = Math.round((node.startTime - min) / timePerCharacter);
|
|
455
|
-
const length = Math.ceil((node.endTime - node.startTime) / timePerCharacter);
|
|
456
|
-
const bar = padRight('', offset) + padRight('', length, '=');
|
|
457
|
-
|
|
458
|
-
// @ts-expect-error -- disambiguate displayName from across possible Node types.
|
|
459
|
-
const displayName = node.record ? node.record.url : node.type;
|
|
460
|
-
// eslint-disable-next-line
|
|
461
|
-
console.log(padRight(bar, widthInCharacters), `| ${displayName.slice(0, 30)}`);
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
|
|
465
17
|
/**
|
|
466
18
|
* @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog, URL: LH.Artifacts['URL']}} data
|
|
467
19
|
* @param {LH.Artifacts.ComputedContext} context
|
|
@@ -478,7 +30,8 @@ class PageDependencyGraph {
|
|
|
478
30
|
// Calculates the URL artifact from the processed trace and DT log.
|
|
479
31
|
const URL = data.URL || await DocumentUrls.request(data, context);
|
|
480
32
|
|
|
481
|
-
|
|
33
|
+
const lanternRequests = networkRecords.map(NetworkRequest.asLanternNetworkRequest);
|
|
34
|
+
return LanternPageDependencyGraph.createGraph(processedTrace, lanternRequests, URL);
|
|
482
35
|
}
|
|
483
36
|
}
|
|
484
37
|
|
|
@@ -287,7 +287,7 @@ function requireAudit(auditPath, coreAuditList, configDir) {
|
|
|
287
287
|
let requirePath = `../audits/${auditPath}`;
|
|
288
288
|
if (!coreAudit) {
|
|
289
289
|
if (isBundledEnvironment()) {
|
|
290
|
-
// This is for
|
|
290
|
+
// This is for plugin bundling.
|
|
291
291
|
requirePath = auditPath;
|
|
292
292
|
} else {
|
|
293
293
|
// Otherwise, attempt to find it elsewhere. This throws if not found.
|
|
@@ -53,9 +53,10 @@ export class ExecutionContext {
|
|
|
53
53
|
* page without isolation.
|
|
54
54
|
* @param {string} expression
|
|
55
55
|
* @param {number|undefined} contextId
|
|
56
|
+
* @param {number} timeout
|
|
56
57
|
* @return {Promise<*>}
|
|
57
58
|
*/
|
|
58
|
-
_evaluateInContext(expression: string, contextId: number | undefined): Promise<any>;
|
|
59
|
+
_evaluateInContext(expression: string, contextId: number | undefined, timeout: number): Promise<any>;
|
|
59
60
|
/**
|
|
60
61
|
* Note: Prefer `evaluate` instead.
|
|
61
62
|
* Evaluate an expression in the context of the current page. If useIsolation is true, the expression
|
|
@@ -80,15 +80,10 @@ class ExecutionContext {
|
|
|
80
80
|
* page without isolation.
|
|
81
81
|
* @param {string} expression
|
|
82
82
|
* @param {number|undefined} contextId
|
|
83
|
+
* @param {number} timeout
|
|
83
84
|
* @return {Promise<*>}
|
|
84
85
|
*/
|
|
85
|
-
async _evaluateInContext(expression, contextId) {
|
|
86
|
-
// Use a higher than default timeout if the user hasn't specified a specific timeout.
|
|
87
|
-
// Otherwise, use whatever was requested.
|
|
88
|
-
const timeout = this._session.hasNextProtocolTimeout() ?
|
|
89
|
-
this._session.getNextProtocolTimeout() :
|
|
90
|
-
60000;
|
|
91
|
-
|
|
86
|
+
async _evaluateInContext(expression, contextId, timeout) {
|
|
92
87
|
// `__lighthouseExecutionContextUniqueIdentifier` is only used by the FullPageScreenshot gatherer.
|
|
93
88
|
// See `getNodeDetails` in page-functions.
|
|
94
89
|
const uniqueExecutionContextIdentifier = contextId === undefined ?
|
|
@@ -166,17 +161,22 @@ class ExecutionContext {
|
|
|
166
161
|
* @return {Promise<*>}
|
|
167
162
|
*/
|
|
168
163
|
async evaluateAsync(expression, options = {}) {
|
|
164
|
+
// Use a higher than default timeout if the user hasn't specified a specific timeout.
|
|
165
|
+
// Otherwise, use whatever was requested.
|
|
166
|
+
const timeout = this._session.hasNextProtocolTimeout() ?
|
|
167
|
+
this._session.getNextProtocolTimeout() :
|
|
168
|
+
60000;
|
|
169
169
|
const contextId = options.useIsolation ? await this._getOrCreateIsolatedContextId() : undefined;
|
|
170
170
|
|
|
171
171
|
try {
|
|
172
172
|
// `await` is not redundant here because we want to `catch` the async errors
|
|
173
|
-
return await this._evaluateInContext(expression, contextId);
|
|
173
|
+
return await this._evaluateInContext(expression, contextId, timeout);
|
|
174
174
|
} catch (err) {
|
|
175
175
|
// If we were using isolation and the context disappeared on us, retry one more time.
|
|
176
176
|
if (contextId && err.message.includes('Cannot find context')) {
|
|
177
177
|
this.clearContextId();
|
|
178
178
|
const freshContextId = await this._getOrCreateIsolatedContextId();
|
|
179
|
-
return this._evaluateInContext(expression, freshContextId);
|
|
179
|
+
return this._evaluateInContext(expression, freshContextId, timeout);
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
throw err;
|
|
@@ -497,8 +497,10 @@ async function waitForFullyLoaded(session, networkMonitor, options) {
|
|
|
497
497
|
log.warn('waitFor', 'Timed out waiting for page load. Checking if page is hung...');
|
|
498
498
|
if (await isPageHung(session)) {
|
|
499
499
|
log.warn('waitFor', 'Page appears to be hung, killing JavaScript...');
|
|
500
|
-
await
|
|
501
|
-
|
|
500
|
+
// We don't await these, as we want to exit with PAGE_HUNG
|
|
501
|
+
void session.sendCommand('Emulation.setScriptExecutionDisabled', {value: true})
|
|
502
|
+
.catch(_ => {});
|
|
503
|
+
void session.sendCommand('Runtime.terminateExecution').catch(_ => {});
|
|
502
504
|
throw new LighthouseError(LighthouseError.errors.PAGE_HUNG);
|
|
503
505
|
}
|
|
504
506
|
|
|
@@ -79,7 +79,8 @@ async function runA11yChecks() {
|
|
|
79
79
|
'tabindex': {enabled: true},
|
|
80
80
|
'table-duplicate-name': {enabled: true},
|
|
81
81
|
'table-fake-caption': {enabled: true},
|
|
82
|
-
|
|
82
|
+
// TODO: https://github.com/GoogleChrome/lighthouse/issues/15824
|
|
83
|
+
// 'target-size': {enabled: true},
|
|
83
84
|
'td-has-header': {enabled: true},
|
|
84
85
|
},
|
|
85
86
|
});
|
|
@@ -12,15 +12,7 @@ declare class CSSUsage extends BaseGatherer {
|
|
|
12
12
|
/**
|
|
13
13
|
* @param {LH.Crdp.CSS.StyleSheetAddedEvent} event
|
|
14
14
|
*/
|
|
15
|
-
_onStylesheetAdded(event: LH.Crdp.CSS.StyleSheetAddedEvent):
|
|
16
|
-
/**
|
|
17
|
-
* @param {LH.Gatherer.Context} context
|
|
18
|
-
*/
|
|
19
|
-
startCSSUsageTracking(context: LH.Gatherer.Context): Promise<void>;
|
|
20
|
-
/**
|
|
21
|
-
* @param {LH.Gatherer.Context} context
|
|
22
|
-
*/
|
|
23
|
-
stopCSSUsageTracking(context: LH.Gatherer.Context): Promise<void>;
|
|
15
|
+
_onStylesheetAdded(event: LH.Crdp.CSS.StyleSheetAddedEvent): void;
|
|
24
16
|
/**
|
|
25
17
|
* @param {LH.Gatherer.Context} context
|
|
26
18
|
*/
|
|
@@ -36,7 +36,7 @@ class CSSUsage extends BaseGatherer {
|
|
|
36
36
|
/**
|
|
37
37
|
* @param {LH.Crdp.CSS.StyleSheetAddedEvent} event
|
|
38
38
|
*/
|
|
39
|
-
|
|
39
|
+
_onStylesheetAdded(event) {
|
|
40
40
|
if (!this._session) throw new Error('Session not initialized');
|
|
41
41
|
const styleSheetId = event.header.styleSheetId;
|
|
42
42
|
const sheetPromise = this._session.sendCommand('CSS.getStyleSheetText', {styleSheetId})
|
|
@@ -66,41 +66,43 @@ class CSSUsage extends BaseGatherer {
|
|
|
66
66
|
/**
|
|
67
67
|
* @param {LH.Gatherer.Context} context
|
|
68
68
|
*/
|
|
69
|
-
async
|
|
69
|
+
async startInstrumentation(context) {
|
|
70
70
|
const session = context.driver.defaultSession;
|
|
71
71
|
this._session = session;
|
|
72
|
-
|
|
72
|
+
|
|
73
|
+
// Calling `CSS.enable` will emit events for stylesheets currently on the page.
|
|
74
|
+
// We want to ignore these events in navigation mode because they are not relevant to the
|
|
75
|
+
// navigation that is about to happen. Adding the event listener *after* calling `CSS.enable`
|
|
76
|
+
// ensures that the events for pre-existing stylesheets are ignored.
|
|
77
|
+
const isNavigation = context.gatherMode === 'navigation';
|
|
78
|
+
if (!isNavigation) {
|
|
79
|
+
session.on('CSS.styleSheetAdded', this._onStylesheetAdded);
|
|
80
|
+
}
|
|
73
81
|
|
|
74
82
|
await session.sendCommand('DOM.enable');
|
|
75
83
|
await session.sendCommand('CSS.enable');
|
|
76
84
|
await session.sendCommand('CSS.startRuleUsageTracking');
|
|
85
|
+
|
|
86
|
+
if (isNavigation) {
|
|
87
|
+
session.on('CSS.styleSheetAdded', this._onStylesheetAdded);
|
|
88
|
+
}
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
|
|
80
92
|
/**
|
|
81
93
|
* @param {LH.Gatherer.Context} context
|
|
82
94
|
*/
|
|
83
|
-
async
|
|
95
|
+
async stopInstrumentation(context) {
|
|
84
96
|
const session = context.driver.defaultSession;
|
|
85
97
|
const coverageResponse = await session.sendCommand('CSS.stopRuleUsageTracking');
|
|
86
98
|
this._ruleUsage = coverageResponse.ruleUsage;
|
|
87
99
|
session.off('CSS.styleSheetAdded', this._onStylesheetAdded);
|
|
88
|
-
}
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
*/
|
|
93
|
-
async startInstrumentation(context) {
|
|
94
|
-
if (context.gatherMode !== 'timespan') return;
|
|
95
|
-
await this.startCSSUsageTracking(context);
|
|
96
|
-
}
|
|
101
|
+
// Ensure we finish fetching all stylesheet contents before disabling the CSS domain
|
|
102
|
+
await Promise.all(this._sheetPromises.values());
|
|
97
103
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*/
|
|
101
|
-
async stopInstrumentation(context) {
|
|
102
|
-
if (context.gatherMode !== 'timespan') return;
|
|
103
|
-
await this.stopCSSUsageTracking(context);
|
|
104
|
+
await session.sendCommand('CSS.disable');
|
|
105
|
+
await session.sendCommand('DOM.disable');
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
/**
|
|
@@ -108,17 +110,16 @@ class CSSUsage extends BaseGatherer {
|
|
|
108
110
|
* @return {Promise<LH.Artifacts['CSSUsage']>}
|
|
109
111
|
*/
|
|
110
112
|
async getArtifact(context) {
|
|
111
|
-
const session = context.driver.defaultSession;
|
|
112
113
|
const executionContext = context.driver.executionContext;
|
|
113
114
|
|
|
114
|
-
if (context.gatherMode
|
|
115
|
-
await this.
|
|
115
|
+
if (context.gatherMode === 'snapshot') {
|
|
116
|
+
await this.startInstrumentation(context);
|
|
116
117
|
|
|
117
118
|
// Force style to recompute.
|
|
118
119
|
// Doesn't appear to be necessary in newer versions of Chrome.
|
|
119
120
|
await executionContext.evaluateAsync('getComputedStyle(document.body)');
|
|
120
121
|
|
|
121
|
-
await this.
|
|
122
|
+
await this.stopInstrumentation(context);
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
/** @type {Map<string, LH.Artifacts.CSSStyleSheetInfo>} */
|
|
@@ -140,9 +141,6 @@ class CSSUsage extends BaseGatherer {
|
|
|
140
141
|
dedupedStylesheets.set(sheet.content, sheet);
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
await session.sendCommand('CSS.disable');
|
|
144
|
-
await session.sendCommand('DOM.disable');
|
|
145
|
-
|
|
146
144
|
if (!this._ruleUsage) throw new Error('Issue collecting rule usages');
|
|
147
145
|
|
|
148
146
|
return {
|