@paulirish/trace_engine 0.0.10 → 0.0.11
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/analyze-trace.mjs +1 -1
- package/core/platform/DevToolsPath.d.ts +4 -13
- package/core/platform/DevToolsPath.js +7 -4
- package/core/platform/DevToolsPath.js.map +1 -7
- package/core/platform/MimeType.d.ts +27 -0
- package/core/platform/MimeType.js +119 -86
- package/core/platform/MimeType.js.map +1 -7
- package/core/platform/Timing.d.ts +7 -0
- package/core/platform/Timing.js +7 -4
- package/core/platform/Timing.js.map +1 -7
- package/core/platform/UIString.d.ts +2 -5
- package/core/platform/UIString.js +5 -2
- package/core/platform/UIString.js.map +1 -7
- package/core/platform/UserVisibleError.js +19 -10
- package/core/platform/UserVisibleError.js.map +1 -7
- package/core/platform/array-utilities.d.ts +48 -10
- package/core/platform/array-utilities.js +160 -124
- package/core/platform/array-utilities.js.map +1 -7
- package/core/platform/brand.d.ts +14 -0
- package/core/platform/brand.js +5 -1
- package/core/platform/brand.js.map +1 -7
- package/core/platform/date-utilities.js +10 -6
- package/core/platform/date-utilities.js.map +1 -7
- package/core/platform/dom-utilities.d.ts +3 -1
- package/core/platform/dom-utilities.js +94 -83
- package/core/platform/dom-utilities.js.map +1 -7
- package/core/platform/keyboard-utilities.d.ts +2 -0
- package/core/platform/keyboard-utilities.js +15 -24
- package/core/platform/keyboard-utilities.js.map +1 -7
- package/core/platform/map-utilities.d.ts +4 -0
- package/core/platform/map-utilities.js +66 -60
- package/core/platform/map-utilities.js.map +1 -7
- package/core/platform/number-utilities.js +66 -55
- package/core/platform/number-utilities.js.map +1 -7
- package/core/platform/platform.d.ts +5 -1
- package/core/platform/platform.js +54 -37
- package/core/platform/platform.js.map +1 -7
- package/core/platform/promise-utilities.d.ts +10 -0
- package/core/platform/promise-utilities.js +16 -8
- package/core/platform/promise-utilities.js.map +1 -7
- package/core/platform/set-utilities.js +20 -17
- package/core/platform/set-utilities.js.map +1 -7
- package/core/platform/string-utilities.d.ts +32 -1
- package/core/platform/string-utilities.js +453 -379
- package/core/platform/string-utilities.js.map +1 -7
- package/core/platform/typescript-utilities.d.ts +5 -5
- package/core/platform/typescript-utilities.js +19 -7
- package/core/platform/typescript-utilities.js.map +1 -7
- package/generated/protocol.d.ts +2081 -347
- package/generated/protocol.js +5 -2230
- package/models/cpu_profile/CPUProfileDataModel.d.ts +77 -0
- package/models/cpu_profile/CPUProfileDataModel.js +492 -359
- package/models/cpu_profile/CPUProfileDataModel.js.map +1 -7
- package/models/cpu_profile/ProfileTreeModel.d.ts +29 -0
- package/models/cpu_profile/ProfileTreeModel.js +87 -82
- package/models/cpu_profile/ProfileTreeModel.js.map +1 -7
- package/models/cpu_profile/cpu_profile.d.ts +3 -0
- package/models/cpu_profile/cpu_profile.js +7 -7
- package/models/cpu_profile/cpu_profile.js.map +1 -7
- package/models/trace/EntriesFilter.d.ts +55 -0
- package/models/trace/EntriesFilter.js +227 -166
- package/models/trace/EntriesFilter.js.map +1 -7
- package/models/trace/LegacyTracingModel.js.map +1 -7
- package/models/trace/ModelImpl.d.ts +110 -0
- package/models/trace/ModelImpl.js +161 -102
- package/models/trace/ModelImpl.js.map +1 -7
- package/models/trace/Processor.d.ts +36 -0
- package/models/trace/Processor.js +197 -163
- package/models/trace/Processor.js.map +1 -7
- package/models/trace/TracingManager.js.map +1 -7
- package/models/trace/extras/FetchNodes.d.ts +46 -0
- package/models/trace/extras/FetchNodes.js +132 -91
- package/models/trace/extras/FetchNodes.js.map +1 -7
- package/models/trace/extras/FilmStrip.d.ts +19 -0
- package/models/trace/extras/FilmStrip.js +38 -31
- package/models/trace/extras/FilmStrip.js.map +1 -7
- package/models/trace/extras/MainThreadActivity.d.ts +2 -0
- package/models/trace/extras/MainThreadActivity.js +72 -56
- package/models/trace/extras/MainThreadActivity.js.map +1 -7
- package/models/trace/extras/Metadata.d.ts +2 -0
- package/models/trace/extras/Metadata.js +42 -26
- package/models/trace/extras/Metadata.js.map +1 -7
- package/models/trace/extras/extras.js.map +1 -7
- package/models/trace/handlers/AnimationHandler.d.ts +8 -0
- package/models/trace/handlers/AnimationHandler.js +22 -20
- package/models/trace/handlers/AnimationHandler.js.map +1 -7
- package/models/trace/handlers/AuctionWorkletsHandler.d.ts +8 -0
- package/models/trace/handlers/AuctionWorkletsHandler.js +143 -89
- package/models/trace/handlers/AuctionWorkletsHandler.js.map +1 -7
- package/models/trace/handlers/FramesHandler.d.ts +76 -0
- package/models/trace/handlers/FramesHandler.js +424 -355
- package/models/trace/handlers/FramesHandler.js.map +1 -7
- package/models/trace/handlers/GPUHandler.d.ts +11 -0
- package/models/trace/handlers/GPUHandler.js +41 -37
- package/models/trace/handlers/GPUHandler.js.map +1 -7
- package/models/trace/handlers/InitiatorsHandler.d.ts +10 -0
- package/models/trace/handlers/InitiatorsHandler.js +164 -113
- package/models/trace/handlers/InitiatorsHandler.js.map +1 -7
- package/models/trace/handlers/InvalidationsHandler.d.ts +10 -0
- package/models/trace/handlers/InvalidationsHandler.js +101 -79
- package/models/trace/handlers/InvalidationsHandler.js.map +1 -7
- package/models/trace/handlers/LargestImagePaintHandler.d.ts +5 -0
- package/models/trace/handlers/LargestImagePaintHandler.js +32 -12
- package/models/trace/handlers/LargestImagePaintHandler.js.map +1 -7
- package/models/trace/handlers/LargestTextPaintHandler.d.ts +5 -0
- package/models/trace/handlers/LargestTextPaintHandler.js +20 -12
- package/models/trace/handlers/LargestTextPaintHandler.js.map +1 -7
- package/models/trace/handlers/LayerTreeHandler.d.ts +13 -0
- package/models/trace/handlers/LayerTreeHandler.js +96 -70
- package/models/trace/handlers/LayerTreeHandler.js.map +1 -7
- package/models/trace/handlers/LayoutShiftsHandler.d.ts +44 -0
- package/models/trace/handlers/LayoutShiftsHandler.js +304 -227
- package/models/trace/handlers/LayoutShiftsHandler.js.map +1 -7
- package/models/trace/handlers/MemoryHandler.d.ts +7 -0
- package/models/trace/handlers/MemoryHandler.js +14 -11
- package/models/trace/handlers/MemoryHandler.js.map +1 -7
- package/models/trace/handlers/MetaHandler.d.ts +37 -0
- package/models/trace/handlers/MetaHandler.js +314 -226
- package/models/trace/handlers/MetaHandler.js.map +1 -7
- package/models/trace/handlers/ModelHandlers.d.ts +21 -0
- package/models/trace/handlers/ModelHandlers.js +25 -22
- package/models/trace/handlers/ModelHandlers.js.map +1 -7
- package/models/trace/handlers/NetworkRequestsHandler.d.ts +17 -0
- package/models/trace/handlers/NetworkRequestsHandler.js +342 -218
- package/models/trace/handlers/NetworkRequestsHandler.js.map +1 -7
- package/models/trace/handlers/PageLoadMetricsHandler.d.ts +67 -0
- package/models/trace/handlers/PageLoadMetricsHandler.js +357 -284
- package/models/trace/handlers/PageLoadMetricsHandler.js.map +1 -7
- package/models/trace/handlers/RendererHandler.d.ts +101 -0
- package/models/trace/handlers/RendererHandler.js +295 -191
- package/models/trace/handlers/RendererHandler.js.map +1 -7
- package/models/trace/handlers/SamplesHandler.d.ts +46 -0
- package/models/trace/handlers/SamplesHandler.js +195 -158
- package/models/trace/handlers/SamplesHandler.js.map +1 -7
- package/models/trace/handlers/ScreenshotsHandler.d.ts +7 -0
- package/models/trace/handlers/ScreenshotsHandler.js +63 -41
- package/models/trace/handlers/ScreenshotsHandler.js.map +1 -7
- package/models/trace/handlers/Threads.d.ts +33 -0
- package/models/trace/handlers/Threads.js +85 -67
- package/models/trace/handlers/Threads.js.map +1 -7
- package/models/trace/handlers/UserInteractionsHandler.d.ts +57 -0
- package/models/trace/handlers/UserInteractionsHandler.js +240 -141
- package/models/trace/handlers/UserInteractionsHandler.js.map +1 -7
- package/models/trace/handlers/UserTimingsHandler.d.ts +28 -0
- package/models/trace/handlers/UserTimingsHandler.js +91 -80
- package/models/trace/handlers/UserTimingsHandler.js.map +1 -7
- package/models/trace/handlers/WarningsHandler.d.ts +14 -0
- package/models/trace/handlers/WarningsHandler.js +100 -62
- package/models/trace/handlers/WarningsHandler.js.map +1 -7
- package/models/trace/handlers/WorkersHandler.d.ts +11 -0
- package/models/trace/handlers/WorkersHandler.js +40 -38
- package/models/trace/handlers/WorkersHandler.js.map +1 -7
- package/models/trace/handlers/handlers.d.ts +3 -0
- package/models/trace/handlers/handlers.js +7 -4
- package/models/trace/handlers/handlers.js.map +1 -7
- package/models/trace/handlers/types.d.ts +45 -0
- package/models/trace/handlers/types.js +15 -15
- package/models/trace/handlers/types.js.map +1 -7
- package/models/trace/helpers/SamplesIntegrator.d.ts +49 -0
- package/models/trace/helpers/SamplesIntegrator.js +381 -204
- package/models/trace/helpers/SamplesIntegrator.js.map +1 -7
- package/models/trace/helpers/Timing.d.ts +26 -0
- package/models/trace/helpers/Timing.js +131 -110
- package/models/trace/helpers/Timing.js.map +1 -7
- package/models/trace/helpers/Trace.d.ts +37 -0
- package/models/trace/helpers/Trace.js +200 -166
- package/models/trace/helpers/Trace.js.map +1 -7
- package/models/trace/helpers/TreeHelpers.d.ts +90 -0
- package/models/trace/helpers/TreeHelpers.js +203 -100
- package/models/trace/helpers/TreeHelpers.js.map +1 -7
- package/models/trace/helpers/helpers.d.ts +4 -0
- package/models/trace/helpers/helpers.js +8 -5
- package/models/trace/helpers/helpers.js.map +1 -7
- package/models/trace/root-causes/LayoutShift.d.ts +119 -0
- package/models/trace/root-causes/LayoutShift.js +470 -323
- package/models/trace/root-causes/LayoutShift.js.map +1 -7
- package/models/trace/root-causes/RootCauses.d.ts +14 -0
- package/models/trace/root-causes/RootCauses.js +9 -6
- package/models/trace/root-causes/RootCauses.js.map +1 -7
- package/models/trace/root-causes/root-causes.d.ts +1 -0
- package/models/trace/root-causes/root-causes.js +5 -2
- package/models/trace/root-causes/root-causes.js.map +1 -7
- package/models/trace/trace.d.ts +11 -0
- package/models/trace/trace.js +17 -23
- package/models/trace/trace.js.map +1 -7
- package/models/trace/types/Configuration.d.ts +33 -0
- package/models/trace/types/Configuration.js +25 -14
- package/models/trace/types/Configuration.js.map +1 -7
- package/models/trace/types/File.d.ts +23 -0
- package/models/trace/types/File.js +5 -6
- package/models/trace/types/File.js.map +1 -7
- package/models/trace/types/Timing.d.ts +25 -0
- package/models/trace/types/Timing.js +10 -11
- package/models/trace/types/Timing.js.map +1 -7
- package/models/trace/types/TraceEvents.d.ts +1571 -0
- package/models/trace/types/TraceEvents.js +174 -381
- package/models/trace/types/TraceEvents.js.map +1 -7
- package/models/trace/types/types.d.ts +4 -0
- package/models/trace/types/types.js +8 -5
- package/models/trace/types/types.js.map +1 -7
- package/package.json +1 -1
- package/TracingManager.js +0 -0
- package/extras/extras.js +0 -0
- package/trace.mjs +0 -6980
- package/trace.mjs.map +0 -8
|
@@ -1,370 +1,517 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// Copyright 2023 The Chromium Authors. All rights reserved.
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
import * as Platform from '../../../core/platform/platform.js';
|
|
5
|
+
import * as Helpers from '../helpers/helpers.js';
|
|
6
|
+
import * as Types from '../types/types.js';
|
|
7
|
+
const fontRequestsByPrePaint = new Map();
|
|
8
|
+
const renderBlocksByPrePaint = new Map();
|
|
6
9
|
function setDefaultValue(map, shift) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
Platform.MapUtilities.getWithDefault(map, shift, () => {
|
|
11
|
+
return {
|
|
12
|
+
unsizedMedia: [],
|
|
13
|
+
iframes: [],
|
|
14
|
+
fontChanges: [],
|
|
15
|
+
renderBlockingRequests: [],
|
|
16
|
+
scriptStackTrace: [],
|
|
17
|
+
};
|
|
18
|
+
});
|
|
16
19
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
// Important: we purposefully treat `potentially_blocking` as
|
|
21
|
+
// non-render-blocking here because:
|
|
22
|
+
// 1. An async script can run on the main thread at any point, including before
|
|
23
|
+
// the page is loaded
|
|
24
|
+
// 2. An async script will never block the parsing and rendering process of the
|
|
25
|
+
// browser.
|
|
26
|
+
// 3. Therefore, from a developer's point of view, there is nothing more they
|
|
27
|
+
// can do if they've put `async` on, and within the context of Insights, we
|
|
28
|
+
// shouldn't report an async script as render blocking.
|
|
29
|
+
// In the future we may want to consider suggesting the use of `defer` over
|
|
30
|
+
// `async`, as it doesn't have this concern, but for now we'll allow `async`
|
|
31
|
+
// and not report it as an issue.
|
|
32
|
+
const NON_RENDER_BLOCKING_VALUES = new Set([
|
|
33
|
+
'non_blocking',
|
|
34
|
+
'potentially_blocking',
|
|
20
35
|
]);
|
|
21
36
|
function networkRequestIsRenderBlockingInFrame(event, frameId) {
|
|
22
|
-
|
|
23
|
-
|
|
37
|
+
const isRenderBlocking = !NON_RENDER_BLOCKING_VALUES.has(event.args.data.renderBlocking);
|
|
38
|
+
return isRenderBlocking && event.args.data.frame === frameId;
|
|
24
39
|
}
|
|
25
40
|
export class LayoutShiftRootCauses {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
async rootCausesForEvent(modelData, event) {
|
|
32
|
-
const cachedResult = this.#rootCauseCacheMap.get(event);
|
|
33
|
-
if (cachedResult) {
|
|
34
|
-
return cachedResult;
|
|
35
|
-
}
|
|
36
|
-
const allLayoutShifts = modelData.LayoutShifts.clusters.flatMap((cluster) => cluster.events);
|
|
37
|
-
allLayoutShifts.forEach((shift) => setDefaultValue(this.#rootCauseCacheMap, shift));
|
|
38
|
-
await this.blameShifts(allLayoutShifts, modelData);
|
|
39
|
-
const resultForEvent = this.#rootCauseCacheMap.get(event);
|
|
40
|
-
if (!resultForEvent) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
return resultForEvent;
|
|
44
|
-
}
|
|
45
|
-
async blameShifts(layoutShifts, modelData) {
|
|
46
|
-
await this.linkShiftsToLayoutInvalidations(layoutShifts, modelData);
|
|
47
|
-
this.linkShiftsToLayoutEvents(layoutShifts, modelData);
|
|
48
|
-
}
|
|
49
|
-
async linkShiftsToLayoutInvalidations(layoutShifts, modelData) {
|
|
50
|
-
const { prePaintEvents, layoutInvalidationEvents, scheduleStyleInvalidationEvents, backendNodeIds } = modelData.LayoutShifts;
|
|
51
|
-
const eventsForLayoutInvalidation = [...layoutInvalidationEvents, ...scheduleStyleInvalidationEvents];
|
|
52
|
-
const nodes = await this.#protocolInterface.pushNodesByBackendIdsToFrontend(backendNodeIds);
|
|
53
|
-
const nodeIdsByBackendIdMap = /* @__PURE__ */ new Map();
|
|
54
|
-
for (let i = 0; i < backendNodeIds.length; i++) {
|
|
55
|
-
nodeIdsByBackendIdMap.set(backendNodeIds[i], nodes[i]);
|
|
41
|
+
#protocolInterface;
|
|
42
|
+
#rootCauseCacheMap = new Map();
|
|
43
|
+
constructor(protocolInterface) {
|
|
44
|
+
this.#protocolInterface = protocolInterface;
|
|
56
45
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const renderBlockRootCause = this.getRenderBlockRootCause(layoutInvalidation, nextPrePaint, modelData);
|
|
70
|
-
const layoutInvalidationNodeId = nodeIdsByBackendIdMap.get(layoutInvalidation.args.data.nodeId);
|
|
71
|
-
const layoutInvalidationNode = layoutInvalidationNodeId !== void 0 ? await this.#protocolInterface.getNode(layoutInvalidationNodeId) : null;
|
|
72
|
-
let unsizedMediaRootCause = null;
|
|
73
|
-
let iframeRootCause = null;
|
|
74
|
-
if (layoutInvalidationNode && layoutInvalidation.args.data.reason) {
|
|
75
|
-
unsizedMediaRootCause = await this.getUnsizedMediaRootCause(layoutInvalidation.args.data.reason, layoutInvalidationNode);
|
|
76
|
-
iframeRootCause = this.getIframeRootCause(layoutInvalidation.args.data.reason, layoutInvalidationNode);
|
|
77
|
-
}
|
|
78
|
-
if (!unsizedMediaRootCause && !iframeRootCause && !fontChangeRootCause && !renderBlockRootCause) {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
for (const shift of subsequentShifts) {
|
|
82
|
-
const rootCausesForShift = Platform.MapUtilities.getWithDefault(this.#rootCauseCacheMap, shift, () => {
|
|
83
|
-
return {
|
|
84
|
-
unsizedMedia: [],
|
|
85
|
-
iframes: [],
|
|
86
|
-
fontChanges: [],
|
|
87
|
-
renderBlockingRequests: [],
|
|
88
|
-
scriptStackTrace: []
|
|
89
|
-
};
|
|
90
|
-
});
|
|
91
|
-
if (unsizedMediaRootCause && !rootCausesForShift.unsizedMedia.some((media) => media.node.nodeId === unsizedMediaRootCause?.node.nodeId) && shift.args.frame === layoutInvalidation.args.data.frame) {
|
|
92
|
-
rootCausesForShift.unsizedMedia.push(unsizedMediaRootCause);
|
|
46
|
+
/**
|
|
47
|
+
* Calculates the potential root causes for a given layout shift event. Once
|
|
48
|
+
* calculated, this data is cached.
|
|
49
|
+
* Note: because you need all layout shift data at once to calculate these
|
|
50
|
+
* correctly, this function will parse the root causes for _all_ layout shift
|
|
51
|
+
* events the first time that it's called. That then populates the cache for
|
|
52
|
+
* each shift, so any subsequent calls are just a constant lookup.
|
|
53
|
+
*/
|
|
54
|
+
async rootCausesForEvent(modelData, event) {
|
|
55
|
+
const cachedResult = this.#rootCauseCacheMap.get(event);
|
|
56
|
+
if (cachedResult) {
|
|
57
|
+
return cachedResult;
|
|
93
58
|
}
|
|
94
|
-
|
|
95
|
-
|
|
59
|
+
const allLayoutShifts = modelData.LayoutShifts.clusters.flatMap(cluster => cluster.events);
|
|
60
|
+
// Make sure a value in the cache is set even for shifts that don't have a root cause,
|
|
61
|
+
// so that we don't have to recompute when no root causes are found. In case a cause
|
|
62
|
+
// for a shift is found, the default value is replaced.
|
|
63
|
+
allLayoutShifts.forEach(shift => setDefaultValue(this.#rootCauseCacheMap, shift));
|
|
64
|
+
// Populate the cache
|
|
65
|
+
await this.blameShifts(allLayoutShifts, modelData);
|
|
66
|
+
const resultForEvent = this.#rootCauseCacheMap.get(event);
|
|
67
|
+
if (!resultForEvent) {
|
|
68
|
+
// No root causes available for this layout shift.
|
|
69
|
+
return null;
|
|
96
70
|
}
|
|
97
|
-
|
|
98
|
-
|
|
71
|
+
return resultForEvent;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Determines potential root causes for shifts
|
|
75
|
+
*/
|
|
76
|
+
async blameShifts(layoutShifts, modelData) {
|
|
77
|
+
await this.linkShiftsToLayoutInvalidations(layoutShifts, modelData);
|
|
78
|
+
this.linkShiftsToLayoutEvents(layoutShifts, modelData);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* "LayoutInvalidations" are a set of trace events dispatched in Blink under the name
|
|
82
|
+
* "layoutInvalidationTracking", which track invalidations on the "Layout"stage of the
|
|
83
|
+
* rendering pipeline. This function utilizes this event to flag potential root causes
|
|
84
|
+
* to layout shifts.
|
|
85
|
+
*/
|
|
86
|
+
async linkShiftsToLayoutInvalidations(layoutShifts, modelData) {
|
|
87
|
+
const { prePaintEvents, layoutInvalidationEvents, scheduleStyleInvalidationEvents, backendNodeIds } = modelData.LayoutShifts;
|
|
88
|
+
// For the purposes of determining root causes of layout shifts, we
|
|
89
|
+
// consider scheduleStyleInvalidationTracking and
|
|
90
|
+
// LayoutInvalidationTracking events as events that could have been the
|
|
91
|
+
// cause of the layout shift.
|
|
92
|
+
const eventsForLayoutInvalidation = [...layoutInvalidationEvents, ...scheduleStyleInvalidationEvents];
|
|
93
|
+
const nodes = await this.#protocolInterface.pushNodesByBackendIdsToFrontend(backendNodeIds);
|
|
94
|
+
const nodeIdsByBackendIdMap = new Map();
|
|
95
|
+
for (let i = 0; i < backendNodeIds.length; i++) {
|
|
96
|
+
nodeIdsByBackendIdMap.set(backendNodeIds[i], nodes[i]);
|
|
99
97
|
}
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
// Maps from PrePaint events to LayoutShifts that occured in each one.
|
|
99
|
+
const shiftsByPrePaint = getShiftsByPrePaintEvents(layoutShifts, prePaintEvents);
|
|
100
|
+
for (const layoutInvalidation of eventsForLayoutInvalidation) {
|
|
101
|
+
// Get the first PrePaint event that happened after the current LayoutInvalidation event.
|
|
102
|
+
const nextPrePaintIndex = Platform.ArrayUtilities.nearestIndexFromBeginning(prePaintEvents, prePaint => prePaint.ts > layoutInvalidation.ts);
|
|
103
|
+
if (nextPrePaintIndex === null) {
|
|
104
|
+
// No PrePaint event registered after this LayoutInvalidation. Continue.
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const nextPrePaint = prePaintEvents[nextPrePaintIndex];
|
|
108
|
+
const subsequentShifts = shiftsByPrePaint.get(nextPrePaint);
|
|
109
|
+
if (!subsequentShifts) {
|
|
110
|
+
// The PrePaint after the current LayoutInvalidation doesn't contain shifts.
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const fontChangeRootCause = this.getFontChangeRootCause(layoutInvalidation, nextPrePaint, modelData);
|
|
114
|
+
const renderBlockRootCause = this.getRenderBlockRootCause(layoutInvalidation, nextPrePaint, modelData);
|
|
115
|
+
const layoutInvalidationNodeId = nodeIdsByBackendIdMap.get(layoutInvalidation.args.data.nodeId);
|
|
116
|
+
const layoutInvalidationNode = layoutInvalidationNodeId !== undefined ?
|
|
117
|
+
await this.#protocolInterface.getNode(layoutInvalidationNodeId) :
|
|
118
|
+
null;
|
|
119
|
+
let unsizedMediaRootCause = null;
|
|
120
|
+
let iframeRootCause = null;
|
|
121
|
+
if (layoutInvalidationNode && layoutInvalidation.args.data.reason) {
|
|
122
|
+
unsizedMediaRootCause =
|
|
123
|
+
await this.getUnsizedMediaRootCause(layoutInvalidation.args.data.reason, layoutInvalidationNode);
|
|
124
|
+
iframeRootCause = this.getIframeRootCause(layoutInvalidation.args.data.reason, layoutInvalidationNode);
|
|
125
|
+
}
|
|
126
|
+
if (!unsizedMediaRootCause && !iframeRootCause && !fontChangeRootCause && !renderBlockRootCause) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Add found potential root causes to all the shifts in this PrePaint and populate the cache.
|
|
130
|
+
for (const shift of subsequentShifts) {
|
|
131
|
+
const rootCausesForShift = Platform.MapUtilities.getWithDefault(this.#rootCauseCacheMap, shift, () => {
|
|
132
|
+
return {
|
|
133
|
+
unsizedMedia: [],
|
|
134
|
+
iframes: [],
|
|
135
|
+
fontChanges: [],
|
|
136
|
+
renderBlockingRequests: [],
|
|
137
|
+
scriptStackTrace: [],
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
if (unsizedMediaRootCause &&
|
|
141
|
+
!rootCausesForShift.unsizedMedia.some(media => media.node.nodeId === unsizedMediaRootCause?.node.nodeId) &&
|
|
142
|
+
shift.args.frame === layoutInvalidation.args.data.frame) {
|
|
143
|
+
rootCausesForShift.unsizedMedia.push(unsizedMediaRootCause);
|
|
144
|
+
}
|
|
145
|
+
if (iframeRootCause &&
|
|
146
|
+
!rootCausesForShift.iframes.some(injectedIframe => injectedIframe.iframe.nodeId === iframeRootCause?.iframe.nodeId)) {
|
|
147
|
+
rootCausesForShift.iframes.push(iframeRootCause);
|
|
148
|
+
}
|
|
149
|
+
if (fontChangeRootCause) {
|
|
150
|
+
// Unlike other root causes, we calculate fonts causing a shift only once,
|
|
151
|
+
// which means we assign the built array instead of appending new objects
|
|
152
|
+
// to it.
|
|
153
|
+
rootCausesForShift.fontChanges = fontChangeRootCause;
|
|
154
|
+
}
|
|
155
|
+
if (renderBlockRootCause) {
|
|
156
|
+
rootCausesForShift.renderBlockingRequests = renderBlockRootCause;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
102
159
|
}
|
|
103
|
-
}
|
|
104
160
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
161
|
+
/**
|
|
162
|
+
* For every shift looks up the initiator of its corresponding Layout event. This initiator
|
|
163
|
+
* is assigned by the RendererHandler and contains the stack trace of the point in a script
|
|
164
|
+
* that caused a style recalculation or a relayout. This stack trace is added to the shift's
|
|
165
|
+
* potential root causes.
|
|
166
|
+
* Note that a Layout cannot always be linked to a script, in that case, we cannot add a
|
|
167
|
+
* "script causing reflow" as a potential root cause to the corresponding shift.
|
|
168
|
+
*/
|
|
169
|
+
linkShiftsToLayoutEvents(layoutShifts, modelData) {
|
|
170
|
+
const { prePaintEvents } = modelData.LayoutShifts;
|
|
171
|
+
// Maps from PrePaint events to LayoutShifts that occured in each one.
|
|
172
|
+
const shiftsByPrePaint = getShiftsByPrePaintEvents(layoutShifts, prePaintEvents);
|
|
173
|
+
const eventTriggersLayout = ({ name }) => {
|
|
174
|
+
const knownName = name;
|
|
175
|
+
return knownName === "Layout" /* Types.TraceEvents.KnownEventName.Layout */;
|
|
176
|
+
};
|
|
177
|
+
const layoutEvents = modelData.Renderer.allTraceEntries.filter(eventTriggersLayout);
|
|
178
|
+
for (const layout of layoutEvents) {
|
|
179
|
+
// Get the first PrePaint event that happened after the current layout event.
|
|
180
|
+
const nextPrePaintIndex = Platform.ArrayUtilities.nearestIndexFromBeginning(prePaintEvents, prePaint => prePaint.ts > layout.ts + (layout.dur || 0));
|
|
181
|
+
if (nextPrePaintIndex === null) {
|
|
182
|
+
// No PrePaint event registered after this LayoutInvalidation. Continue.
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const nextPrePaint = prePaintEvents[nextPrePaintIndex];
|
|
186
|
+
const subsequentShifts = shiftsByPrePaint.get(nextPrePaint);
|
|
187
|
+
if (!subsequentShifts) {
|
|
188
|
+
// The PrePaint after the current LayoutInvalidation doesn't contain shifts.
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const layoutNode = modelData.Renderer.entryToNode.get(layout);
|
|
192
|
+
const initiator = layoutNode ? modelData.Initiators.eventToInitiator.get(layoutNode.entry) : null;
|
|
193
|
+
const stackTrace = initiator?.args?.data?.stackTrace;
|
|
194
|
+
if (!stackTrace) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
// Add found potential root causes to all the shifts in this PrePaint and populate the cache.
|
|
198
|
+
for (const shift of subsequentShifts) {
|
|
199
|
+
const rootCausesForShift = Platform.MapUtilities.getWithDefault(this.#rootCauseCacheMap, shift, () => {
|
|
200
|
+
return {
|
|
201
|
+
unsizedMedia: [],
|
|
202
|
+
iframes: [],
|
|
203
|
+
fontChanges: [],
|
|
204
|
+
renderBlockingRequests: [],
|
|
205
|
+
scriptStackTrace: [],
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
if (rootCausesForShift.scriptStackTrace.length === 0) {
|
|
209
|
+
rootCausesForShift.scriptStackTrace = stackTrace;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
142
212
|
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
async getUnsizedMediaRootCause(reason, layoutInvalidationNode) {
|
|
147
|
-
if (reason !== Types.TraceEvents.LayoutInvalidationReason.SIZE_CHANGED) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
const computedStylesList = await this.#protocolInterface.getComputedStyleForNode(layoutInvalidationNode.nodeId);
|
|
151
|
-
const computedStyles = new Map(computedStylesList.map((item) => [item.name, item.value]));
|
|
152
|
-
if (computedStyles && !await nodeIsUnfixedMedia(layoutInvalidationNode, computedStyles)) {
|
|
153
|
-
return null;
|
|
154
213
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const req2EndTime = req2.ts + req2.dur;
|
|
176
|
-
return req1EndTime - req2EndTime;
|
|
177
|
-
});
|
|
178
|
-
const lastRequestIndex = Platform.ArrayUtilities.nearestIndexFromEnd(requestsSortedByEndTime, (request) => request.ts + request.dur < layoutInvalidation.ts);
|
|
179
|
-
if (lastRequestIndex === null) {
|
|
180
|
-
return [];
|
|
181
|
-
}
|
|
182
|
-
const MAX_DELTA_FOR_FONT_REQUEST = Helpers.Timing.secondsToMicroseconds(Types.Timing.Seconds(0.5));
|
|
183
|
-
const requestsInInvalidationWindow = [];
|
|
184
|
-
for (let i = lastRequestIndex; i > -1; i--) {
|
|
185
|
-
const previousRequest = requestsSortedByEndTime[i];
|
|
186
|
-
const previousRequestEndTime = previousRequest.ts + previousRequest.dur;
|
|
187
|
-
if (layoutInvalidation.ts - previousRequestEndTime < MAX_DELTA_FOR_FONT_REQUEST) {
|
|
188
|
-
const requestInInvalidationWindow = { request: previousRequest };
|
|
189
|
-
const initiator = this.#protocolInterface.getInitiatorForRequest(previousRequest.args.data.url);
|
|
190
|
-
requestInInvalidationWindow.initiator = initiator || void 0;
|
|
191
|
-
requestsInInvalidationWindow.push(requestInInvalidationWindow);
|
|
192
|
-
} else {
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return requestsInInvalidationWindow;
|
|
197
|
-
}
|
|
198
|
-
getFontChangeRootCause(layoutInvalidation, nextPrePaint, modelData) {
|
|
199
|
-
if (layoutInvalidation.args.data.reason !== Types.TraceEvents.LayoutInvalidationReason.FONTS_CHANGED) {
|
|
200
|
-
return null;
|
|
214
|
+
/**
|
|
215
|
+
* Given a LayoutInvalidation trace event, determines if it was dispatched
|
|
216
|
+
* because a media element without dimensions was resized.
|
|
217
|
+
*/
|
|
218
|
+
async getUnsizedMediaRootCause(reason, layoutInvalidationNode) {
|
|
219
|
+
// Filter events to resizes only.
|
|
220
|
+
if (reason !== "Size changed" /* Types.TraceEvents.LayoutInvalidationReason.SIZE_CHANGED */) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const computedStylesList = await this.#protocolInterface.getComputedStyleForNode(layoutInvalidationNode.nodeId);
|
|
224
|
+
const computedStyles = new Map(computedStylesList.map(item => [item.name, item.value]));
|
|
225
|
+
if (computedStyles && !(await nodeIsUnfixedMedia(layoutInvalidationNode, computedStyles))) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const authoredDimensions = await this.getNodeAuthoredDimensions(layoutInvalidationNode);
|
|
229
|
+
if (dimensionsAreExplicit(authoredDimensions)) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
const computedDimensions = computedStyles ? getNodeComputedDimensions(computedStyles) : {};
|
|
233
|
+
return { node: layoutInvalidationNode, authoredDimensions, computedDimensions };
|
|
201
234
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Given a LayoutInvalidation trace event, determines if it was dispatched
|
|
237
|
+
* because a node, which is an ancestor to an iframe, was injected.
|
|
238
|
+
*/
|
|
239
|
+
getIframeRootCause(reason, layoutInvalidationDOMNode) {
|
|
240
|
+
if (layoutInvalidationDOMNode.nodeName !== 'IFRAME' &&
|
|
241
|
+
reason !== "Style changed" /* Types.TraceEvents.LayoutInvalidationReason.STYLE_CHANGED */ &&
|
|
242
|
+
reason !== "Added to layout" /* Types.TraceEvents.LayoutInvalidationReason.ADDED_TO_LAYOUT */) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const iframe = firstIframeInDOMTree(layoutInvalidationDOMNode);
|
|
246
|
+
if (!iframe) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
return { iframe };
|
|
205
250
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Given a layout invalidation event and a sorted array, returns the subset of requests that arrived within a
|
|
253
|
+
* 500ms window before the layout invalidation.
|
|
254
|
+
*/
|
|
255
|
+
requestsInInvalidationWindow(layoutInvalidation, modelData) {
|
|
256
|
+
const requestsSortedByEndTime = modelData.NetworkRequests.byTime.sort((req1, req2) => {
|
|
257
|
+
const req1EndTime = req1.ts + req1.dur;
|
|
258
|
+
const req2EndTime = req2.ts + req2.dur;
|
|
259
|
+
return req1EndTime - req2EndTime;
|
|
260
|
+
});
|
|
261
|
+
const lastRequestIndex = Platform.ArrayUtilities.nearestIndexFromEnd(requestsSortedByEndTime, request => request.ts + request.dur < layoutInvalidation.ts);
|
|
262
|
+
if (lastRequestIndex === null) {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
const MAX_DELTA_FOR_FONT_REQUEST = Helpers.Timing.secondsToMicroseconds(Types.Timing.Seconds(0.5));
|
|
266
|
+
const requestsInInvalidationWindow = [];
|
|
267
|
+
// Get all requests finished within the valid window.
|
|
268
|
+
for (let i = lastRequestIndex; i > -1; i--) {
|
|
269
|
+
const previousRequest = requestsSortedByEndTime[i];
|
|
270
|
+
const previousRequestEndTime = previousRequest.ts + previousRequest.dur;
|
|
271
|
+
if (layoutInvalidation.ts - previousRequestEndTime < MAX_DELTA_FOR_FONT_REQUEST) {
|
|
272
|
+
const requestInInvalidationWindow = { request: previousRequest };
|
|
273
|
+
const initiator = this.#protocolInterface.getInitiatorForRequest(previousRequest.args.data.url);
|
|
274
|
+
requestInInvalidationWindow.initiator = initiator || undefined;
|
|
275
|
+
requestsInInvalidationWindow.push(requestInInvalidationWindow);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
// No more requests fit in the time window.
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return requestsInInvalidationWindow;
|
|
223
283
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Given a LayoutInvalidation trace event, determines if it was dispatched
|
|
286
|
+
* because fonts were changed and if so returns the information of all network
|
|
287
|
+
* request with which the fonts were possibly fetched, if any. The computed
|
|
288
|
+
* network requests are cached for the corresponding prepaint event, meaning
|
|
289
|
+
* that other LayoutInvalidation events that correspond to the same prepaint
|
|
290
|
+
* are not processed and the cached network requests for the prepaint is
|
|
291
|
+
* returned instead.
|
|
292
|
+
*/
|
|
293
|
+
getFontChangeRootCause(layoutInvalidation, nextPrePaint, modelData) {
|
|
294
|
+
if (layoutInvalidation.args.data.reason !== "Fonts changed" /* Types.TraceEvents.LayoutInvalidationReason.FONTS_CHANGED */) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
// Prevent computing the result of this function multiple times per PrePaint event.
|
|
298
|
+
const fontRequestsForPrepaint = fontRequestsByPrePaint.get(nextPrePaint);
|
|
299
|
+
if (fontRequestsForPrepaint !== undefined) {
|
|
300
|
+
return fontRequestsForPrepaint;
|
|
301
|
+
}
|
|
302
|
+
const fontRequestsInThisPrepaint = this.getFontRequestsInInvalidationWindow(this.requestsInInvalidationWindow(layoutInvalidation, modelData));
|
|
303
|
+
fontRequestsByPrePaint.set(nextPrePaint, fontRequestsInThisPrepaint);
|
|
304
|
+
return fontRequestsInThisPrepaint;
|
|
230
305
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Given the requests that arrived within a 500ms window before the layout invalidation, returns the font
|
|
308
|
+
* requests of them.
|
|
309
|
+
*/
|
|
310
|
+
getFontRequestsInInvalidationWindow(requestsInInvalidationWindow) {
|
|
311
|
+
const fontRequests = [];
|
|
312
|
+
// Get all requests finished within the valid window.
|
|
313
|
+
for (let i = 0; i < requestsInInvalidationWindow.length; i++) {
|
|
314
|
+
const fontRequest = requestsInInvalidationWindow[i];
|
|
315
|
+
if (!fontRequest.request.args.data.mimeType.startsWith('font')) {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const fontFace = this.#protocolInterface.fontFaceForSource(fontRequest.request.args.data.url);
|
|
319
|
+
if (!fontFace || fontFace.fontDisplay === 'optional') {
|
|
320
|
+
// Setting font-display to optional is part of what the developer
|
|
321
|
+
// can do to avoid layout shifts due to FOIT/FOUT, as such we cannot
|
|
322
|
+
// suggest any actionable insight here.
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
fontRequest.fontFace = fontFace;
|
|
326
|
+
fontRequests.push(fontRequest);
|
|
246
327
|
}
|
|
247
|
-
|
|
248
|
-
prop = response.attributesStyle?.cssProperties.find((prop2) => prop2.name === cssProperty);
|
|
249
|
-
if (prop) {
|
|
250
|
-
return prop.value;
|
|
251
|
-
}
|
|
252
|
-
return null;
|
|
328
|
+
return fontRequests;
|
|
253
329
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
330
|
+
/**
|
|
331
|
+
* Given a LayoutInvalidation trace event, determines if it arrived within a 500ms window before the layout
|
|
332
|
+
* invalidation and if so returns the information of all network request, if any. The computed network
|
|
333
|
+
* requests are cached for the corresponding prepaint event, meaning that other LayoutInvalidation events
|
|
334
|
+
* that correspond to the same prepaint are not processed and the cached network requests for the prepaint is
|
|
335
|
+
* returned instead.
|
|
336
|
+
*/
|
|
337
|
+
getRenderBlockRootCause(layoutInvalidation, nextPrePaint, modelData) {
|
|
338
|
+
// Prevent computing the result of this function multiple times per PrePaint event.
|
|
339
|
+
const renderBlocksInPrepaint = renderBlocksByPrePaint.get(nextPrePaint);
|
|
340
|
+
if (renderBlocksInPrepaint !== undefined) {
|
|
341
|
+
return renderBlocksInPrepaint;
|
|
342
|
+
}
|
|
343
|
+
const renderBlocksInThisPrepaint = getRenderBlockRequestsInInvalidationWindow(this.requestsInInvalidationWindow(layoutInvalidation, modelData));
|
|
344
|
+
renderBlocksByPrePaint.set(nextPrePaint, renderBlocksInThisPrepaint);
|
|
345
|
+
return renderBlocksInThisPrepaint;
|
|
261
346
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Returns a function that retrieves the active value of a given
|
|
349
|
+
* CSS property within the matched styles of the param node.
|
|
350
|
+
* The first occurence within the matched styles is returned and the
|
|
351
|
+
* value is looked up in the following order, which follows CSS
|
|
352
|
+
* specificity:
|
|
353
|
+
* 1. Inline styles.
|
|
354
|
+
* 2. CSS rules matching this node, from all applicable stylesheets.
|
|
355
|
+
* 3. Attribute defined styles.
|
|
356
|
+
*/
|
|
357
|
+
async nodeMatchedStylesPropertyGetter(node) {
|
|
358
|
+
const response = await this.#protocolInterface.getMatchedStylesForNode(node.nodeId);
|
|
359
|
+
function cssPropertyValueGetter(cssProperty) {
|
|
360
|
+
let prop = response.inlineStyle?.cssProperties.find(prop => prop.name === cssProperty);
|
|
361
|
+
if (prop) {
|
|
362
|
+
return prop.value;
|
|
363
|
+
}
|
|
364
|
+
for (const { rule } of response.matchedCSSRules || []) {
|
|
365
|
+
const prop = rule.style.cssProperties.find(prop => prop.name === cssProperty);
|
|
366
|
+
if (prop) {
|
|
367
|
+
return prop.value;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
prop = response.attributesStyle?.cssProperties.find(prop => prop.name === cssProperty);
|
|
371
|
+
if (prop) {
|
|
372
|
+
return prop.value;
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
return cssPropertyValueGetter;
|
|
266
377
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
378
|
+
/**
|
|
379
|
+
* Returns the CSS dimensions set to the node from its matched styles.
|
|
380
|
+
*/
|
|
381
|
+
async getNodeAuthoredDimensions(node) {
|
|
382
|
+
const authoredDimensions = {};
|
|
383
|
+
const cssMatchedRulesGetter = await this.nodeMatchedStylesPropertyGetter(node);
|
|
384
|
+
if (!cssMatchedRulesGetter) {
|
|
385
|
+
return authoredDimensions;
|
|
386
|
+
}
|
|
387
|
+
const attributesFlat = node.attributes || [];
|
|
388
|
+
const attributes = [];
|
|
389
|
+
for (let i = 0; i < attributesFlat.length; i += 2) {
|
|
390
|
+
attributes.push({ name: attributesFlat[i], value: attributesFlat[i + 1] });
|
|
391
|
+
}
|
|
392
|
+
const htmlHeight = attributes.find(attr => attr.name === 'height' && htmlAttributeIsExplicit(attr));
|
|
393
|
+
const htmlWidth = attributes.find(attr => attr.name === 'width' && htmlAttributeIsExplicit(attr));
|
|
394
|
+
const cssExplicitAspectRatio = cssMatchedRulesGetter('aspect-ratio') || undefined;
|
|
395
|
+
if (htmlHeight && htmlWidth && cssExplicitAspectRatio) {
|
|
396
|
+
return { height: htmlHeight.value, width: htmlWidth.value, aspectRatio: cssExplicitAspectRatio };
|
|
397
|
+
}
|
|
398
|
+
const cssHeight = cssMatchedRulesGetter('height') || undefined;
|
|
399
|
+
const cssWidth = cssMatchedRulesGetter('width') || undefined;
|
|
400
|
+
return { height: cssHeight, width: cssWidth, aspectRatio: cssExplicitAspectRatio };
|
|
272
401
|
}
|
|
273
|
-
const cssHeight = cssMatchedRulesGetter("height") || void 0;
|
|
274
|
-
const cssWidth = cssMatchedRulesGetter("width") || void 0;
|
|
275
|
-
return { height: cssHeight, width: cssWidth, aspectRatio: cssExplicitAspectRatio };
|
|
276
|
-
}
|
|
277
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Given the requests that arrived within a 500ms window before the layout invalidation, returns the render
|
|
405
|
+
* block requests of them.
|
|
406
|
+
*/
|
|
278
407
|
function getRenderBlockRequestsInInvalidationWindow(requestsInInvalidationWindow) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
408
|
+
const renderBlockingRequests = [];
|
|
409
|
+
// Get all requests finished within the valid window.
|
|
410
|
+
for (let i = 0; i < requestsInInvalidationWindow.length; i++) {
|
|
411
|
+
const mainFrameId = requestsInInvalidationWindow[i].request.args.data.frame;
|
|
412
|
+
if (!networkRequestIsRenderBlockingInFrame(requestsInInvalidationWindow[i].request, mainFrameId)) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
renderBlockingRequests.push(requestsInInvalidationWindow[i]);
|
|
284
416
|
}
|
|
285
|
-
renderBlockingRequests
|
|
286
|
-
}
|
|
287
|
-
return renderBlockingRequests;
|
|
417
|
+
return renderBlockingRequests;
|
|
288
418
|
}
|
|
289
419
|
function firstIframeInDOMTree(root) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
420
|
+
if (root.nodeName === 'IFRAME') {
|
|
421
|
+
return root;
|
|
422
|
+
}
|
|
423
|
+
const children = root.children;
|
|
424
|
+
if (!children) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
for (const child of children) {
|
|
428
|
+
const iFrameInChild = firstIframeInDOMTree(child);
|
|
429
|
+
if (iFrameInChild) {
|
|
430
|
+
return iFrameInChild;
|
|
431
|
+
}
|
|
301
432
|
}
|
|
302
|
-
|
|
303
|
-
return null;
|
|
433
|
+
return null;
|
|
304
434
|
}
|
|
305
435
|
function cssPropertyIsExplicitlySet(propertyValue) {
|
|
306
|
-
|
|
436
|
+
return !['auto', 'initial', 'unset', 'inherit'].includes(propertyValue);
|
|
307
437
|
}
|
|
308
438
|
function htmlAttributeIsExplicit(attr) {
|
|
309
|
-
|
|
439
|
+
return parseInt(attr.value, 10) >= 0;
|
|
310
440
|
}
|
|
311
441
|
function computedStyleHasBackroundImage(computedStyle) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
442
|
+
const CSS_URL_REGEX = /^url\("([^"]+)"\)$/;
|
|
443
|
+
const backgroundImage = computedStyle.get('background-image');
|
|
444
|
+
if (!backgroundImage) {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
return CSS_URL_REGEX.test(backgroundImage);
|
|
318
448
|
}
|
|
319
449
|
function computedStyleHasFixedPosition(computedStyle) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
450
|
+
const position = computedStyle.get('position');
|
|
451
|
+
if (!position) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
return position === 'fixed' || position === 'absolute';
|
|
325
455
|
}
|
|
326
456
|
function getNodeComputedDimensions(computedStyle) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
457
|
+
const computedDimensions = {};
|
|
458
|
+
computedDimensions.height = computedStyle.get('height');
|
|
459
|
+
computedDimensions.width = computedStyle.get('width');
|
|
460
|
+
computedDimensions.aspectRatio = computedStyle.get('aspect-ratio');
|
|
461
|
+
return computedDimensions;
|
|
332
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* Determines if a node is a media element and is not fixed positioned
|
|
465
|
+
* (i.e. "position: fixed;" or "position: absolute;")
|
|
466
|
+
*/
|
|
333
467
|
async function nodeIsUnfixedMedia(node, computedStyle) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
468
|
+
const localName = node.localName;
|
|
469
|
+
const isBackgroundImage = computedStyleHasBackroundImage(computedStyle);
|
|
470
|
+
if (localName !== 'img' && localName !== 'video' && !isBackgroundImage) {
|
|
471
|
+
// Not a media element.
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
const isFixed = computedStyleHasFixedPosition(computedStyle);
|
|
475
|
+
return !isFixed;
|
|
341
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Determines if a CSS dimensions object explicitly defines both width and height
|
|
479
|
+
* (i.e. not set to auto, inherit, etc.)
|
|
480
|
+
*/
|
|
342
481
|
function dimensionsAreExplicit(dimensions) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
482
|
+
const { height, width, aspectRatio } = dimensions;
|
|
483
|
+
const explicitHeight = Boolean(height && cssPropertyIsExplicitlySet(height));
|
|
484
|
+
const explicitWidth = Boolean(width && cssPropertyIsExplicitlySet(width));
|
|
485
|
+
const explicitAspectRatio = Boolean(aspectRatio && cssPropertyIsExplicitlySet(aspectRatio));
|
|
486
|
+
const explicitWithAR = (explicitHeight || explicitWidth) && explicitAspectRatio;
|
|
487
|
+
return (explicitHeight && explicitWidth) || explicitWithAR;
|
|
349
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Given an array of layout shift and PrePaint events, returns a mapping from
|
|
491
|
+
* PrePaint events to layout shifts dispatched within it.
|
|
492
|
+
*/
|
|
350
493
|
function getShiftsByPrePaintEvents(layoutShifts, prePaintEvents) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
494
|
+
// Maps from PrePaint events to LayoutShifts that occured in each one.
|
|
495
|
+
const shiftsByPrePaint = new Map();
|
|
496
|
+
// Associate all shifts to their corresponding PrePaint.
|
|
497
|
+
for (const prePaintEvent of prePaintEvents) {
|
|
498
|
+
const firstShiftIndex = Platform.ArrayUtilities.nearestIndexFromBeginning(layoutShifts, shift => shift.ts >= prePaintEvent.ts);
|
|
499
|
+
if (firstShiftIndex === null) {
|
|
500
|
+
// No layout shifts registered after this PrePaint start. Continue.
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
for (let i = firstShiftIndex; i < layoutShifts.length; i++) {
|
|
504
|
+
const shift = layoutShifts[i];
|
|
505
|
+
if (shift.ts >= prePaintEvent.ts && shift.ts <= prePaintEvent.ts + prePaintEvent.dur) {
|
|
506
|
+
const shiftsInPrePaint = Platform.MapUtilities.getWithDefault(shiftsByPrePaint, prePaintEvent, () => []);
|
|
507
|
+
shiftsInPrePaint.push(shift);
|
|
508
|
+
}
|
|
509
|
+
if (shift.ts > prePaintEvent.ts + prePaintEvent.dur) {
|
|
510
|
+
// Reached the end of this PrePaint. Continue to the next one.
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
366
514
|
}
|
|
367
|
-
|
|
368
|
-
return shiftsByPrePaint;
|
|
515
|
+
return shiftsByPrePaint;
|
|
369
516
|
}
|
|
370
|
-
//# sourceMappingURL=LayoutShift.js.map
|
|
517
|
+
//# sourceMappingURL=LayoutShift.js.map
|