lighthouse 9.5.0-dev.20221128 → 9.5.0-dev.20221129
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/lighthouse-runners/devtools.js +1 -1
- package/core/audits/byte-efficiency/offscreen-images.js +1 -1
- package/core/audits/byte-efficiency/render-blocking-resources.js +1 -1
- package/core/audits/critical-request-chains.js +6 -6
- package/core/audits/font-display.js +1 -1
- package/core/audits/network-requests.js +5 -5
- package/core/audits/redirects.js +2 -2
- package/core/audits/uses-rel-preconnect.js +5 -5
- package/core/config/config-helpers.js +12 -3
- package/core/config/default-config.js +1 -0
- package/core/gather/base-gatherer.js +0 -1
- package/core/gather/driver/target-manager.js +12 -5
- package/core/gather/driver.js +1 -1
- package/core/gather/gatherers/bf-cache-failures.js +158 -0
- package/core/gather/gatherers/seo/font-size.js +9 -52
- package/core/legacy/config/legacy-default-config.js +1 -0
- package/core/lib/asset-saver.js +1 -1
- package/core/lib/dependency-graph/base-node.js +2 -0
- package/core/lib/dependency-graph/network-node.js +2 -2
- package/core/lib/dependency-graph/simulator/network-analyzer.js +3 -3
- package/core/lib/dependency-graph/simulator/simulator.js +4 -1
- package/core/lib/network-request.js +16 -11
- package/core/runner.js +0 -1
- package/dist/report/bundle.esm.js +19 -1
- package/dist/report/flow.js +1 -1
- package/dist/report/standalone.js +5 -5
- package/package.json +3 -2
- package/report/renderer/i18n.js +19 -1
- package/types/artifacts.d.ts +12 -0
|
@@ -47,7 +47,7 @@ async function setup() {
|
|
|
47
47
|
*/
|
|
48
48
|
async function runLighthouse(url, configJson, testRunnerOptions = {}) {
|
|
49
49
|
const chromeFlags = [
|
|
50
|
-
`--custom-devtools-frontend=file://${devtoolsDir}/out/
|
|
50
|
+
`--custom-devtools-frontend=file://${devtoolsDir}/out/LighthouseIntegration/gen/front_end`,
|
|
51
51
|
];
|
|
52
52
|
const {lhr, artifacts, logs} = await testUrlFromDevtools(url, {
|
|
53
53
|
config: configJson,
|
|
@@ -151,7 +151,7 @@ class OffscreenImages extends ByteEfficiencyAudit {
|
|
|
151
151
|
return images.filter(image => {
|
|
152
152
|
if (image.wastedBytes < IGNORE_THRESHOLD_IN_BYTES) return false;
|
|
153
153
|
if (image.wastedPercent < IGNORE_THRESHOLD_IN_PERCENT) return false;
|
|
154
|
-
return image.requestStartTime < interactiveTimestamp /
|
|
154
|
+
return image.requestStartTime < interactiveTimestamp / 1000 - IGNORE_THRESHOLD_IN_MS;
|
|
155
155
|
});
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -158,7 +158,7 @@ class RenderBlockingResources extends Audit {
|
|
|
158
158
|
const deferredNodeIds = new Set();
|
|
159
159
|
for (const resource of artifacts.TagsBlockingFirstPaint) {
|
|
160
160
|
// Ignore any resources that finished after observed FCP (they're clearly not render-blocking)
|
|
161
|
-
if (resource.endTime
|
|
161
|
+
if (resource.endTime > fcpTsInMs) continue;
|
|
162
162
|
// TODO: beacon to Sentry, https://github.com/GoogleChrome/lighthouse/issues/7041
|
|
163
163
|
if (!nodesByUrl[resource.tag.url]) continue;
|
|
164
164
|
|
|
@@ -70,8 +70,8 @@ class CriticalRequestChains extends Audit {
|
|
|
70
70
|
depth,
|
|
71
71
|
id,
|
|
72
72
|
node: child,
|
|
73
|
-
chainDuration:
|
|
74
|
-
chainTransferSize:
|
|
73
|
+
chainDuration: child.request.endTime - startTime,
|
|
74
|
+
chainTransferSize: transferSize + child.request.transferSize,
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
// Carry on walking.
|
|
@@ -96,7 +96,7 @@ class CriticalRequestChains extends Audit {
|
|
|
96
96
|
transferSize: 0,
|
|
97
97
|
};
|
|
98
98
|
CriticalRequestChains._traverse(tree, opts => {
|
|
99
|
-
const duration = opts.chainDuration;
|
|
99
|
+
const duration = opts.chainDuration * 1000;
|
|
100
100
|
if (duration > longest.duration) {
|
|
101
101
|
longest.duration = duration;
|
|
102
102
|
longest.transferSize = opts.chainTransferSize;
|
|
@@ -123,9 +123,9 @@ class CriticalRequestChains extends Audit {
|
|
|
123
123
|
const request = opts.node.request;
|
|
124
124
|
const simpleRequest = {
|
|
125
125
|
url: request.url,
|
|
126
|
-
startTime: request.startTime,
|
|
127
|
-
endTime: request.endTime,
|
|
128
|
-
responseReceivedTime: request.responseReceivedTime,
|
|
126
|
+
startTime: request.startTime / 1000,
|
|
127
|
+
endTime: request.endTime / 1000,
|
|
128
|
+
responseReceivedTime: request.responseReceivedTime / 1000,
|
|
129
129
|
transferSize: request.transferSize,
|
|
130
130
|
};
|
|
131
131
|
|
|
@@ -165,7 +165,7 @@ class FontDisplay extends Audit {
|
|
|
165
165
|
.map(record => {
|
|
166
166
|
// In reality the end time should be calculated with paint time included
|
|
167
167
|
// all browsers wait 3000ms to block text so we make sure 3000 is our max wasted time
|
|
168
|
-
const wastedMs = Math.min(
|
|
168
|
+
const wastedMs = Math.min(record.endTime - record.startTime, 3000);
|
|
169
169
|
|
|
170
170
|
return {
|
|
171
171
|
url: record.url,
|
|
@@ -47,8 +47,8 @@ class NetworkRequests extends Audit {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/** @param {number} time */
|
|
50
|
-
const
|
|
51
|
-
undefined : (time - earliestStartTime)
|
|
50
|
+
const normalizeTime = time => time < earliestStartTime || !Number.isFinite(time) ?
|
|
51
|
+
undefined : (time - earliestStartTime);
|
|
52
52
|
|
|
53
53
|
const results = records.map(record => {
|
|
54
54
|
const endTimeDeltaMs = record.lrStatistics?.endTimeDeltaMs;
|
|
@@ -64,8 +64,8 @@ class NetworkRequests extends Audit {
|
|
|
64
64
|
return {
|
|
65
65
|
url: UrlUtils.elideDataURI(record.url),
|
|
66
66
|
protocol: record.protocol,
|
|
67
|
-
startTime:
|
|
68
|
-
endTime:
|
|
67
|
+
startTime: normalizeTime(record.startTime),
|
|
68
|
+
endTime: normalizeTime(record.endTime),
|
|
69
69
|
finished: record.finished,
|
|
70
70
|
transferSize: record.transferSize,
|
|
71
71
|
resourceSize: record.resourceSize,
|
|
@@ -112,7 +112,7 @@ class NetworkRequests extends Audit {
|
|
|
112
112
|
|
|
113
113
|
// Include starting timestamp to allow syncing requests with navStart/metric timestamps.
|
|
114
114
|
const networkStartTimeTs = Number.isFinite(earliestStartTime) ?
|
|
115
|
-
earliestStartTime *
|
|
115
|
+
earliestStartTime * 1000 : undefined;
|
|
116
116
|
tableDetails.debugData = {
|
|
117
117
|
type: 'debugdata',
|
|
118
118
|
networkStartTimeTs,
|
package/core/audits/redirects.js
CHANGED
|
@@ -125,9 +125,9 @@ class Redirects extends Audit {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const lanternTimingDeltaMs = redirectedTiming.startTime - initialTiming.startTime;
|
|
128
|
-
const
|
|
128
|
+
const observedTimingDeltaMs = redirectedRequest.startTime - initialRequest.startTime;
|
|
129
129
|
const wastedMs = settings.throttlingMethod === 'simulate' ?
|
|
130
|
-
lanternTimingDeltaMs :
|
|
130
|
+
lanternTimingDeltaMs : observedTimingDeltaMs;
|
|
131
131
|
totalWastedMs += wastedMs;
|
|
132
132
|
|
|
133
133
|
tableRows.push({
|
|
@@ -20,7 +20,7 @@ import {LanternLargestContentfulPaint} from '../computed/metrics/lantern-largest
|
|
|
20
20
|
// around for 10s. Meaning, the time delta between processing preconnect a request should be <10s,
|
|
21
21
|
// otherwise it's wasted. We add a 5s margin so we are sure to capture all key requests.
|
|
22
22
|
// @see https://github.com/GoogleChrome/lighthouse/issues/3106#issuecomment-333653747
|
|
23
|
-
const
|
|
23
|
+
const PRECONNECT_SOCKET_MAX_IDLE_IN_MS = 15_000;
|
|
24
24
|
|
|
25
25
|
const IGNORE_THRESHOLD_IN_MS = 50;
|
|
26
26
|
|
|
@@ -96,7 +96,7 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
96
96
|
* @return {boolean}
|
|
97
97
|
*/
|
|
98
98
|
static socketStartTimeIsBelowThreshold(record, mainResource) {
|
|
99
|
-
return Math.max(0, record.startTime - mainResource.endTime) <
|
|
99
|
+
return Math.max(0, record.startTime - mainResource.endTime) < PRECONNECT_SOCKET_MAX_IDLE_IN_MS;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/**
|
|
@@ -150,7 +150,7 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
150
150
|
!lcpGraphURLs.has(record.url) ||
|
|
151
151
|
// Filter out all resources where origins are already resolved.
|
|
152
152
|
UsesRelPreconnectAudit.hasAlreadyConnectedToOrigin(record) ||
|
|
153
|
-
// Make sure the requests are below the
|
|
153
|
+
// Make sure the requests are below the PRECONNECT_SOCKET_MAX_IDLE_IN_MS (15s) mark.
|
|
154
154
|
!UsesRelPreconnectAudit.socketStartTimeIsBelowThreshold(record, mainResource)
|
|
155
155
|
) {
|
|
156
156
|
return;
|
|
@@ -189,8 +189,8 @@ class UsesRelPreconnectAudit extends Audit {
|
|
|
189
189
|
if (firstRecordOfOrigin.parsedURL.scheme === 'https') connectionTime = connectionTime * 2;
|
|
190
190
|
|
|
191
191
|
const timeBetweenMainResourceAndDnsStart =
|
|
192
|
-
firstRecordOfOrigin.startTime
|
|
193
|
-
mainResource.endTime
|
|
192
|
+
firstRecordOfOrigin.startTime -
|
|
193
|
+
mainResource.endTime +
|
|
194
194
|
firstRecordOfOrigin.timing.dnsStart;
|
|
195
195
|
|
|
196
196
|
const wastedMs = Math.min(connectionTime, timeBetweenMainResourceAndDnsStart);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import {createRequire} from 'module';
|
|
9
|
+
import url from 'url';
|
|
9
10
|
|
|
10
11
|
import isDeepEqual from 'lodash/isEqual.js';
|
|
11
12
|
|
|
@@ -217,6 +218,11 @@ const bundledModules = new Map(/* BUILD_REPLACE_BUNDLED_MODULES */);
|
|
|
217
218
|
* @param {string} requirePath
|
|
218
219
|
*/
|
|
219
220
|
async function requireWrapper(requirePath) {
|
|
221
|
+
// For windows.
|
|
222
|
+
if (path.isAbsolute(requirePath)) {
|
|
223
|
+
requirePath = url.pathToFileURL(requirePath).href;
|
|
224
|
+
}
|
|
225
|
+
|
|
220
226
|
/** @type {any} */
|
|
221
227
|
let module;
|
|
222
228
|
if (bundledModules.has(requirePath)) {
|
|
@@ -231,7 +237,6 @@ async function requireWrapper(requirePath) {
|
|
|
231
237
|
if (module.default) return module.default;
|
|
232
238
|
|
|
233
239
|
// Find a valid named export.
|
|
234
|
-
// TODO(esmodules): actually make all the audits/gatherers use named exports
|
|
235
240
|
const methods = new Set(['meta']);
|
|
236
241
|
const possibleNamedExports = Object.keys(module).filter(key => {
|
|
237
242
|
if (!(module[key] && module[key] instanceof Object)) return false;
|
|
@@ -287,8 +292,12 @@ function requireAudit(auditPath, coreAuditList, configDir) {
|
|
|
287
292
|
} else {
|
|
288
293
|
// Otherwise, attempt to find it elsewhere. This throws if not found.
|
|
289
294
|
const absolutePath = resolveModulePath(auditPath, configDir, 'audit');
|
|
290
|
-
|
|
291
|
-
|
|
295
|
+
if (isBundledEnvironment()) {
|
|
296
|
+
// Use a relative path so bundler can easily expose it.
|
|
297
|
+
requirePath = path.relative(getModuleDirectory(import.meta), absolutePath);
|
|
298
|
+
} else {
|
|
299
|
+
requirePath = absolutePath;
|
|
300
|
+
}
|
|
292
301
|
}
|
|
293
302
|
}
|
|
294
303
|
|
|
@@ -58,15 +58,22 @@ class TargetManager extends ProtocolEventEmitter {
|
|
|
58
58
|
async _onFrameNavigated(frameNavigatedEvent) {
|
|
59
59
|
// Child frames are handled in `_onSessionAttached`.
|
|
60
60
|
if (frameNavigatedEvent.frame.parentId) return;
|
|
61
|
+
if (!this._enabled) return;
|
|
61
62
|
|
|
62
63
|
// It's not entirely clear when this is necessary, but when the page switches processes on
|
|
63
64
|
// navigating from about:blank to the `requestedUrl`, resetting `setAutoAttach` has been
|
|
64
65
|
// necessary in the past.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
try {
|
|
67
|
+
await this._rootCdpSession.send('Target.setAutoAttach', {
|
|
68
|
+
autoAttach: true,
|
|
69
|
+
flatten: true,
|
|
70
|
+
waitForDebuggerOnStart: true,
|
|
71
|
+
});
|
|
72
|
+
} catch (err) {
|
|
73
|
+
// The page can be closed at the end of the run before this CDP function returns.
|
|
74
|
+
// In these cases, just ignore the error since we won't need the page anyway.
|
|
75
|
+
if (this._enabled) throw err;
|
|
76
|
+
}
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
/**
|
package/core/gather/driver.js
CHANGED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright 2022 The Lighthouse Authors. All Rights Reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import FRGatherer from '../base-gatherer.js';
|
|
8
|
+
import {waitForFrameNavigated, waitForLoadEvent} from '../driver/wait-for-condition.js';
|
|
9
|
+
import DevtoolsLog from './devtools-log.js';
|
|
10
|
+
|
|
11
|
+
class BFCacheFailures extends FRGatherer {
|
|
12
|
+
/** @type {LH.Gatherer.GathererMeta<'DevtoolsLog'>} */
|
|
13
|
+
meta = {
|
|
14
|
+
supportedModes: ['navigation', 'timespan'],
|
|
15
|
+
dependencies: {DevtoolsLog: DevtoolsLog.symbol},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {LH.Crdp.Page.BackForwardCacheNotRestoredExplanation[]} errorList
|
|
20
|
+
* @return {LH.Artifacts.BFCacheFailure}
|
|
21
|
+
*/
|
|
22
|
+
static processBFCacheEventList(errorList) {
|
|
23
|
+
/** @type {LH.Artifacts.BFCacheNotRestoredReasonsTree} */
|
|
24
|
+
const notRestoredReasonsTree = {
|
|
25
|
+
Circumstantial: {},
|
|
26
|
+
PageSupportNeeded: {},
|
|
27
|
+
SupportPending: {},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (const err of errorList) {
|
|
31
|
+
const bfCacheErrorsMap = notRestoredReasonsTree[err.type];
|
|
32
|
+
bfCacheErrorsMap[err.reason] = [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {notRestoredReasonsTree};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {LH.Crdp.Page.BackForwardCacheNotRestoredExplanationTree} errorTree
|
|
40
|
+
* @return {LH.Artifacts.BFCacheFailure}
|
|
41
|
+
*/
|
|
42
|
+
static processBFCacheEventTree(errorTree) {
|
|
43
|
+
/** @type {LH.Artifacts.BFCacheNotRestoredReasonsTree} */
|
|
44
|
+
const notRestoredReasonsTree = {
|
|
45
|
+
Circumstantial: {},
|
|
46
|
+
PageSupportNeeded: {},
|
|
47
|
+
SupportPending: {},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {LH.Crdp.Page.BackForwardCacheNotRestoredExplanationTree} node
|
|
52
|
+
*/
|
|
53
|
+
function traverse(node) {
|
|
54
|
+
for (const error of node.explanations) {
|
|
55
|
+
const bfCacheErrorsMap = notRestoredReasonsTree[error.type];
|
|
56
|
+
const frameUrls = bfCacheErrorsMap[error.reason] || [];
|
|
57
|
+
frameUrls.push(node.url);
|
|
58
|
+
bfCacheErrorsMap[error.reason] = frameUrls;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const child of node.children) {
|
|
62
|
+
traverse(child);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
traverse(errorTree);
|
|
67
|
+
|
|
68
|
+
return {notRestoredReasonsTree};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {LH.Crdp.Page.BackForwardCacheNotUsedEvent|undefined} event
|
|
73
|
+
* @return {LH.Artifacts.BFCacheFailure}
|
|
74
|
+
*/
|
|
75
|
+
static processBFCacheEvent(event) {
|
|
76
|
+
if (event?.notRestoredExplanationsTree) {
|
|
77
|
+
return BFCacheFailures.processBFCacheEventTree(event.notRestoredExplanationsTree);
|
|
78
|
+
}
|
|
79
|
+
return BFCacheFailures.processBFCacheEventList(event?.notRestoredExplanations || []);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {LH.Gatherer.FRTransitionalContext} context
|
|
84
|
+
* @return {Promise<LH.Crdp.Page.BackForwardCacheNotUsedEvent|undefined>}
|
|
85
|
+
*/
|
|
86
|
+
async activelyCollectBFCacheEvent(context) {
|
|
87
|
+
const session = context.driver.defaultSession;
|
|
88
|
+
|
|
89
|
+
/** @type {LH.Crdp.Page.BackForwardCacheNotUsedEvent|undefined} */
|
|
90
|
+
let bfCacheEvent = undefined;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {LH.Crdp.Page.BackForwardCacheNotUsedEvent} event
|
|
94
|
+
*/
|
|
95
|
+
function onBfCacheNotUsed(event) {
|
|
96
|
+
bfCacheEvent = event;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
session.on('Page.backForwardCacheNotUsed', onBfCacheNotUsed);
|
|
100
|
+
|
|
101
|
+
const history = await session.sendCommand('Page.getNavigationHistory');
|
|
102
|
+
const entry = history.entries[history.currentIndex];
|
|
103
|
+
|
|
104
|
+
await Promise.all([
|
|
105
|
+
session.sendCommand('Page.navigate', {url: 'about:blank'}),
|
|
106
|
+
waitForLoadEvent(session, 0).promise,
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
await Promise.all([
|
|
110
|
+
session.sendCommand('Page.navigateToHistoryEntry', {entryId: entry.id}),
|
|
111
|
+
waitForFrameNavigated(session).promise,
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
session.off('Page.backForwardCacheNotUsed', onBfCacheNotUsed);
|
|
115
|
+
|
|
116
|
+
return bfCacheEvent;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {LH.Gatherer.FRTransitionalContext<'DevtoolsLog'>} context
|
|
121
|
+
* @return {LH.Crdp.Page.BackForwardCacheNotUsedEvent[]}
|
|
122
|
+
*/
|
|
123
|
+
passivelyCollectBFCacheEvents(context) {
|
|
124
|
+
const events = [];
|
|
125
|
+
for (const event of context.dependencies.DevtoolsLog) {
|
|
126
|
+
if (event.method === 'Page.backForwardCacheNotUsed') {
|
|
127
|
+
events.push(event.params);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return events;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {LH.Gatherer.FRTransitionalContext<'DevtoolsLog'>} context
|
|
135
|
+
* @return {Promise<LH.Artifacts['BFCacheFailures']>}
|
|
136
|
+
*/
|
|
137
|
+
async getArtifact(context) {
|
|
138
|
+
const events = this.passivelyCollectBFCacheEvents(context);
|
|
139
|
+
if (context.gatherMode === 'navigation') {
|
|
140
|
+
const activelyCollectedEvent = await this.activelyCollectBFCacheEvent(context);
|
|
141
|
+
if (activelyCollectedEvent) events.push(activelyCollectedEvent);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return events.map(BFCacheFailures.processBFCacheEvent);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param {LH.Gatherer.PassContext} passContext
|
|
149
|
+
* @param {LH.Gatherer.LoadData} loadData
|
|
150
|
+
* @return {Promise<LH.Artifacts['BFCacheFailures']>}
|
|
151
|
+
*/
|
|
152
|
+
async afterPass(passContext, loadData) {
|
|
153
|
+
return this.getArtifact({...passContext, dependencies: {DevtoolsLog: loadData.devtoolsLog}});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export default BFCacheFailures;
|
|
158
|
+
|
|
@@ -34,38 +34,6 @@ function hasFontSizeDeclaration(style) {
|
|
|
34
34
|
return !!style && !!style.cssProperties.find(({name}) => name === FONT_SIZE_PROPERTY_NAME);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* Computes the CSS specificity of a given selector, i.e. #id > .class > div
|
|
39
|
-
* TODO: Handle pseudo selectors (:not(), :where, :nth-child) and attribute selectors
|
|
40
|
-
* LIMITATION: !important is not respected
|
|
41
|
-
*
|
|
42
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
|
|
43
|
-
* @see https://www.smashingmagazine.com/2010/04/css-specificity-and-inheritance/
|
|
44
|
-
* @see https://drafts.csswg.org/selectors-4/#specificity-rules
|
|
45
|
-
*
|
|
46
|
-
* @param {string} selector
|
|
47
|
-
* @return {number}
|
|
48
|
-
*/
|
|
49
|
-
function computeSelectorSpecificity(selector) {
|
|
50
|
-
// Remove universal selector and separator characters, then split.
|
|
51
|
-
const tokens = selector.replace(/[*\s+>~]/g, ' ').split(' ');
|
|
52
|
-
|
|
53
|
-
let numIDs = 0;
|
|
54
|
-
let numClasses = 0;
|
|
55
|
-
let numTypes = 0;
|
|
56
|
-
|
|
57
|
-
for (const token of tokens) {
|
|
58
|
-
const ids = token.match(/(\b|^)#[a-z0-9_-]+/gi) || [];
|
|
59
|
-
const classes = token.match(/(\b|^)\.[a-z0-9_-]+/gi) || [];
|
|
60
|
-
const types = token.match(/^[a-z]+/i) ? [1] : [];
|
|
61
|
-
numIDs += ids.length;
|
|
62
|
-
numClasses += classes.length;
|
|
63
|
-
numTypes += types.length;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return Math.min(9, numIDs) * 100 + Math.min(9, numClasses) * 10 + Math.min(9, numTypes);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
37
|
/**
|
|
70
38
|
* Finds the most specific directly matched CSS font-size rule from the list.
|
|
71
39
|
*
|
|
@@ -74,31 +42,21 @@ function computeSelectorSpecificity(selector) {
|
|
|
74
42
|
* @return {NodeFontData['cssRule']|undefined}
|
|
75
43
|
*/
|
|
76
44
|
function findMostSpecificMatchedCSSRule(matchedCSSRules = [], isDeclarationOfInterest) {
|
|
77
|
-
let
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (isDeclarationOfInterest(rule.style)) {
|
|
83
|
-
const specificities = matchingSelectors.map(idx =>
|
|
84
|
-
computeSelectorSpecificity(rule.selectorList.selectors[idx].text)
|
|
85
|
-
);
|
|
86
|
-
const specificity = Math.max(...specificities);
|
|
87
|
-
// Use greater OR EQUAL so that the last rule wins in the event of a tie
|
|
88
|
-
if (specificity >= maxSpecificity) {
|
|
89
|
-
maxSpecificity = specificity;
|
|
90
|
-
maxSpecificityRule = rule;
|
|
91
|
-
}
|
|
45
|
+
let mostSpecificRule;
|
|
46
|
+
for (let i = matchedCSSRules.length - 1; i >= 0; i--) {
|
|
47
|
+
if (isDeclarationOfInterest(matchedCSSRules[i].rule.style)) {
|
|
48
|
+
mostSpecificRule = matchedCSSRules[i].rule;
|
|
49
|
+
break;
|
|
92
50
|
}
|
|
93
51
|
}
|
|
94
52
|
|
|
95
|
-
if (
|
|
53
|
+
if (mostSpecificRule) {
|
|
96
54
|
return {
|
|
97
55
|
type: 'Regular',
|
|
98
|
-
...
|
|
56
|
+
...mostSpecificRule.style,
|
|
99
57
|
parentRule: {
|
|
100
|
-
origin:
|
|
101
|
-
selectors:
|
|
58
|
+
origin: mostSpecificRule.origin,
|
|
59
|
+
selectors: mostSpecificRule.selectorList.selectors,
|
|
102
60
|
},
|
|
103
61
|
};
|
|
104
62
|
}
|
|
@@ -376,7 +334,6 @@ class FontSize extends FRGatherer {
|
|
|
376
334
|
|
|
377
335
|
export default FontSize;
|
|
378
336
|
export {
|
|
379
|
-
computeSelectorSpecificity,
|
|
380
337
|
getEffectiveFontRule,
|
|
381
338
|
findMostSpecificMatchedCSSRule,
|
|
382
339
|
};
|
package/core/lib/asset-saver.js
CHANGED
|
@@ -20,7 +20,7 @@ import {LighthouseError} from '../lib/lh-error.js';
|
|
|
20
20
|
|
|
21
21
|
const {promisify} = util;
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Rollup does not support `promisfy` or `stream.pipeline`. Bundled files
|
|
24
24
|
// don't need anything in this file except for `stringifyReplacer`, so a check for
|
|
25
25
|
// truthiness before using is enough.
|
|
26
26
|
// TODO: Can remove promisify(pipeline) in Node 15.
|
|
@@ -25,14 +25,14 @@ class NetworkNode extends BaseNode {
|
|
|
25
25
|
* @return {number}
|
|
26
26
|
*/
|
|
27
27
|
get startTime() {
|
|
28
|
-
return this._record.startTime * 1000
|
|
28
|
+
return this._record.startTime * 1000;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* @return {number}
|
|
33
33
|
*/
|
|
34
34
|
get endTime() {
|
|
35
|
-
return this._record.endTime * 1000
|
|
35
|
+
return this._record.endTime * 1000;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -150,7 +150,7 @@ class NetworkAnalyzer {
|
|
|
150
150
|
if (!Number.isFinite(timing.receiveHeadersEnd) || timing.receiveHeadersEnd < 0) return;
|
|
151
151
|
|
|
152
152
|
// Compute the amount of time downloading everything after the first congestion window took
|
|
153
|
-
const totalTime =
|
|
153
|
+
const totalTime = record.endTime - record.startTime;
|
|
154
154
|
const downloadTimeAfterFirstByte = totalTime - timing.receiveHeadersEnd;
|
|
155
155
|
const numberOfRoundTrips = Math.log2(record.transferSize / INITIAL_CWD);
|
|
156
156
|
|
|
@@ -399,8 +399,8 @@ class NetworkAnalyzer {
|
|
|
399
399
|
|
|
400
400
|
// If we've made it this far, all the times we need should be valid (i.e. not undefined/-1).
|
|
401
401
|
totalBytes += record.transferSize;
|
|
402
|
-
boundaries.push({time: record.responseReceivedTime, isStart: true});
|
|
403
|
-
boundaries.push({time: record.endTime, isStart: false});
|
|
402
|
+
boundaries.push({time: record.responseReceivedTime / 1000, isStart: true});
|
|
403
|
+
boundaries.push({time: record.endTime / 1000, isStart: false});
|
|
404
404
|
return boundaries;
|
|
405
405
|
}, /** @type {Array<{time: number, isStart: boolean}>} */([])).sort((a, b) => a.time - b.time);
|
|
406
406
|
|
|
@@ -236,8 +236,11 @@ class Simulator {
|
|
|
236
236
|
* currently in flight.
|
|
237
237
|
*/
|
|
238
238
|
_updateNetworkCapacity() {
|
|
239
|
+
const inFlight = this._numberInProgress(BaseNode.TYPES.NETWORK);
|
|
240
|
+
if (inFlight === 0) return;
|
|
241
|
+
|
|
239
242
|
for (const connection of this._connectionPool.connectionsInUse()) {
|
|
240
|
-
connection.setThroughput(this._throughput /
|
|
243
|
+
connection.setThroughput(this._throughput / inFlight);
|
|
241
244
|
}
|
|
242
245
|
}
|
|
243
246
|
|