Skip to content

Commit c820ae1

Browse files
paulirishDevtools-frontend LUCI CQ
authored andcommitted
RPP: Track invalidations by frame ID.
And rename UpdateLayoutTree to RecalcStyle for usability. This shouldn't have user-facing changes; it lays groundwork for logic fixes. Bug: 444481354 Change-Id: I2fcecaa318585f09d7bc0f4ef600dbd84295090d Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6917594 Commit-Queue: Connor Clark <cjamcl@chromium.org> Reviewed-by: Connor Clark <cjamcl@chromium.org> Auto-Submit: Paul Irish <paulirish@chromium.org>
1 parent 70c9c11 commit c820ae1

23 files changed

+197
-173
lines changed

front_end/models/trace/extras/StackTraceForEvent.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ describeWithEnvironment('StackTraceForTraceEvent', function() {
268268
profileCall1.callFrame.lineNumber = 0;
269269
profileCall2.callFrame.columnNumber = 0;
270270
profileCall2.callFrame.lineNumber = 0;
271-
const traceEvent = makeCompleteEvent(Trace.Types.Events.Name.UPDATE_LAYOUT_TREE, 100, 10, '', pid, tid) as
272-
Trace.Types.Events.UpdateLayoutTree;
271+
const traceEvent = makeCompleteEvent(Trace.Types.Events.Name.RECALC_STYLE, 100, 10, '', pid, tid) as
272+
Trace.Types.Events.RecalcStyle;
273273
const payloadCallStack = [
274274
{columnNumber: payloadColumnNumber, functionName: 'bar', lineNumber: payloadLineNumber, scriptId: '115', url: ''},
275275
{columnNumber: payloadColumnNumber, functionName: 'foo', lineNumber: payloadLineNumber, scriptId: '115', url: ''},

front_end/models/trace/handlers/InitiatorsHandler.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,27 @@ describeWithEnvironment('InitiatorsHandler', () => {
1212
Trace.Handlers.ModelHandlers.Initiators.reset();
1313
});
1414

15-
it('for an UpdateLayoutTree event it sets the initiator to the previous ScheduledStyleRecalculation event',
15+
it('for an RecalcStyle event it sets the initiator to the previous ScheduledStyleRecalculation event',
1616
async function() {
1717
const traceEvents = await TraceLoader.rawEvents(this, 'web-dev-with-commit.json.gz');
1818
for (const event of traceEvents) {
1919
Trace.Handlers.ModelHandlers.Initiators.handleEvent(event);
2020
}
2121
await Trace.Handlers.ModelHandlers.Initiators.finalize();
2222
const {eventToInitiator} = Trace.Handlers.ModelHandlers.Initiators.data();
23-
const updateLayoutTreeEvent = traceEvents.find(event => {
24-
return Trace.Types.Events.isUpdateLayoutTree(event) && event.ts === 122411039965;
23+
const recalcStyleEvent = traceEvents.find(event => {
24+
return Trace.Types.Events.isRecalcStyle(event) && event.ts === 122411039965;
2525
});
26-
if (!updateLayoutTreeEvent || !Trace.Types.Events.isUpdateLayoutTree(updateLayoutTreeEvent)) {
26+
if (!recalcStyleEvent || !Trace.Types.Events.isRecalcStyle(recalcStyleEvent)) {
2727
throw new Error('Could not find layout tree event.');
2828
}
29-
const initiator = eventToInitiator.get(updateLayoutTreeEvent);
29+
const initiator = eventToInitiator.get(recalcStyleEvent);
3030
if (!initiator) {
31-
throw new Error('Did not find expected initiator for updateLayoutTreeEvent');
31+
throw new Error('Did not find expected initiator for recalcStyleEvent');
3232
}
3333
assert.isTrue(Trace.Types.Events.isScheduleStyleRecalculation(initiator));
34-
assert.strictEqual(updateLayoutTreeEvent.args.beginData?.frame, '25D2F12F1818C70B5BD4325CC9ACD8FF');
35-
assert.strictEqual(updateLayoutTreeEvent.args.beginData?.frame, initiator.args?.data?.frame);
34+
assert.strictEqual(recalcStyleEvent.args.beginData?.frame, '25D2F12F1818C70B5BD4325CC9ACD8FF');
35+
assert.strictEqual(recalcStyleEvent.args.beginData?.frame, initiator.args?.data?.frame);
3636
});
3737

3838
it('for a Layout event it sets the initiator to the last InvalidateLayout event on that frame', async function() {

front_end/models/trace/handlers/InitiatorsHandler.ts

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@ let lastScheduleStyleRecalcByFrame = new Map<string, Types.Events.ScheduleStyleR
1717
// invalidated.
1818
let lastInvalidationEventForFrame = new Map<string, Types.Events.Event>();
1919

20-
// Important: although the event is called UpdateLayoutTree, in the UI we
21-
// present these to the user as "Recalculate Style". So don't get confused!
22-
// These are the same - just UpdateLayoutTree is what the event from Chromium
23-
// is called.
24-
let lastUpdateLayoutTreeByFrame = new Map<string, Types.Events.UpdateLayoutTree>();
20+
let lastRecalcByFrame = new Map<string, Types.Events.RecalcStyle>();
2521

2622
// These two maps store the same data but in different directions.
2723
// For a given event, tell me what its initiator was. An event can only have one initiator.
@@ -39,7 +35,7 @@ let schedulePostTaskCallbackEventsById = new Map<number, Types.Events.SchedulePo
3935
export function reset(): void {
4036
lastScheduleStyleRecalcByFrame = new Map();
4137
lastInvalidationEventForFrame = new Map();
42-
lastUpdateLayoutTreeByFrame = new Map();
38+
lastRecalcByFrame = new Map();
4339
timerInstallEventsById = new Map();
4440
eventToInitiatorMap = new Map();
4541
initiatorToEventsMap = new Map();
@@ -68,14 +64,11 @@ function storeInitiator(data: {initiator: Types.Events.Event, event: Types.Event
6864
export function handleEvent(event: Types.Events.Event): void {
6965
if (Types.Events.isScheduleStyleRecalculation(event)) {
7066
lastScheduleStyleRecalcByFrame.set(event.args.data.frame, event);
71-
} else if (Types.Events.isUpdateLayoutTree(event)) {
72-
// IMPORTANT: although the trace event is called UpdateLayoutTree, this
73-
// represents a Styles Recalculation. This event in the timeline is shown to
74-
// the user as "Recalculate Styles."
67+
} else if (Types.Events.isRecalcStyle(event)) {
7568
if (event.args.beginData) {
76-
// Store the last UpdateLayout event: we use this when we see an
69+
// Store the last RecalcStyle event: we use this when we see an
7770
// InvalidateLayout and try to figure out its initiator.
78-
lastUpdateLayoutTreeByFrame.set(event.args.beginData.frame, event);
71+
lastRecalcByFrame.set(event.args.beginData.frame, event);
7972

8073
// If this frame has seen a ScheduleStyleRecalc event, then that event is
8174
// considered to be the initiator of this StylesRecalc.
@@ -96,17 +89,17 @@ export function handleEvent(event: Types.Events.Event): void {
9689
// cause of this layout invalidation.
9790
if (!lastInvalidationEventForFrame.has(event.args.data.frame)) {
9891
// 1. If we have not had an invalidation event for this frame
99-
// 2. AND we have had an UpdateLayoutTree for this frame
100-
// 3. AND the UpdateLayoutTree event ended AFTER the InvalidateLayout startTime
101-
// 4. AND we have an initiator for the UpdateLayoutTree event
102-
// 5. Then we set the last invalidation event for this frame to be the UpdateLayoutTree's initiator.
103-
const lastUpdateLayoutTreeForFrame = lastUpdateLayoutTreeByFrame.get(event.args.data.frame);
104-
if (lastUpdateLayoutTreeForFrame) {
105-
const {endTime} = Helpers.Timing.eventTimingsMicroSeconds(lastUpdateLayoutTreeForFrame);
106-
const initiatorOfUpdateLayout = eventToInitiatorMap.get(lastUpdateLayoutTreeForFrame);
107-
108-
if (initiatorOfUpdateLayout && endTime && endTime > event.ts) {
109-
invalidationInitiator = initiatorOfUpdateLayout;
92+
// 2. AND we have had an RecalcStyle for this frame
93+
// 3. AND the RecalcStyle event ended AFTER the InvalidateLayout startTime
94+
// 4. AND we have an initiator for the RecalcStyle event
95+
// 5. Then we set the last invalidation event for this frame to be the RecalcStyle's initiator.
96+
const lastRecalcStyleForFrame = lastRecalcByFrame.get(event.args.data.frame);
97+
if (lastRecalcStyleForFrame) {
98+
const {endTime} = Helpers.Timing.eventTimingsMicroSeconds(lastRecalcStyleForFrame);
99+
const initiatorOfRecalcStyle = eventToInitiatorMap.get(lastRecalcStyleForFrame);
100+
101+
if (initiatorOfRecalcStyle && endTime && endTime > event.ts) {
102+
invalidationInitiator = initiatorOfRecalcStyle;
110103
}
111104
}
112105
}

front_end/models/trace/handlers/InvalidationsHandler.test.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,17 @@ describe('InvalidationsHandler', () => {
3333
}
3434
await Trace.Handlers.ModelHandlers.Invalidations.finalize();
3535
const data = Trace.Handlers.ModelHandlers.Invalidations.data();
36-
// Find the Layout event that we want to test - we are testing
37-
// the layout that happens after button click that happened in
38-
// the trace.
39-
const updateLayoutTreeEvent = events.find(event => {
40-
return Trace.Types.Events.isUpdateLayoutTree(event) &&
36+
// Find the recalc event that we want to test - we are testing the recalc that happens after button click that
37+
// happened in the trace.
38+
const recalcStyleEvent = events.find(event => {
39+
return Trace.Types.Events.isRecalcStyle(event) &&
4140
event.args.beginData?.stackTrace?.[0].functionName === 'testFuncs.changeAttributeAndDisplay';
4241
});
43-
if (!updateLayoutTreeEvent) {
44-
throw new Error('Could not find UpdateLayoutTree event.');
42+
if (!recalcStyleEvent) {
43+
throw new Error('Could not find RecalcStyle event.');
4544
}
4645

47-
const invalidations =
48-
data.invalidationsForEvent.get(updateLayoutTreeEvent)?.map(invalidationDataForTestAssertion) ?? [];
46+
const invalidations = data.invalidationsForEvent.get(recalcStyleEvent)?.map(invalidationDataForTestAssertion) ?? [];
4947

5048
assert.deepEqual(invalidations, [
5149
{
@@ -168,7 +166,7 @@ describe('InvalidationsHandler', () => {
168166
await Trace.Handlers.ModelHandlers.Invalidations.finalize();
169167
const data = Trace.Handlers.ModelHandlers.Invalidations.data();
170168

171-
// Find the UpdateLayoutEvent that had 26 invalidations
169+
// Find the RecalcStyleEvent that had 26 invalidations
172170
const layoutEvent = Array.from(data.invalidationCountForEvent.entries())
173171
.filter(entry => {
174172
const [, count] = entry;

front_end/models/trace/handlers/InvalidationsHandler.ts

Lines changed: 81 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,68 @@
44

55
import * as Types from '../types/types.js';
66

7-
let invalidationsForEvent = new Map<Types.Events.Event, Types.Events.InvalidationTrackingEvent[]>();
8-
let invalidationCountForEvent = new Map<Types.Events.Event, number>();
9-
10-
let lastRecalcStyleEvent: Types.Events.UpdateLayoutTree|null = null;
11-
12-
// Used to track paints so we track invalidations correctly per paint.
13-
let hasPainted = false;
7+
/**
8+
* @file Associates invalidation to recalc/layout events; mostly used in "invalidation tracking" experiment.
9+
* "Invalidations" == "mutations" == "damage".
10+
* A DOM change that means we need to recompute style or layout is an invalidation that's tracked here.
11+
* If the experiment `timeline-invalidation-tracking` is enabled, the `disabledByDefault('devtools.timeline.invalidationTracking')` trace category is enabled, which contains most of these events.
12+
*/
13+
14+
interface InvalidationsStatePerFrame {
15+
invalidationsForEvent: Map<Types.Events.Event, Types.Events.InvalidationTrackingEvent[]>;
16+
invalidationCountForEvent: Map<Types.Events.Event, number>;
17+
lastRecalcStyleEvent: Types.Events.RecalcStyle|null;
18+
hasPainted: boolean;
19+
pendingInvalidations: Types.Events.InvalidationTrackingEvent[];
20+
}
1421

15-
let allInvalidationTrackingEvents: Types.Events.InvalidationTrackingEvent[] = [];
22+
const frameStateByFrame = new Map<string, InvalidationsStatePerFrame>();
23+
let maxInvalidationsPerEvent: number|null = null;
1624

1725
export function reset(): void {
18-
invalidationsForEvent = new Map();
19-
invalidationCountForEvent = new Map();
20-
lastRecalcStyleEvent = null;
21-
allInvalidationTrackingEvents = [];
22-
hasPainted = false;
26+
frameStateByFrame.clear();
2327
maxInvalidationsPerEvent = null;
2428
}
2529

26-
let maxInvalidationsPerEvent: number|null = null;
2730
export function handleUserConfig(userConfig: Types.Configuration.Configuration): void {
2831
maxInvalidationsPerEvent = userConfig.maxInvalidationEventsPerEvent;
2932
}
3033

31-
function addInvalidationToEvent(event: Types.Events.Event, invalidation: Types.Events.InvalidationTrackingEvent): void {
32-
const existingInvalidations = invalidationsForEvent.get(event) || [];
34+
function getState(frameId: string): InvalidationsStatePerFrame {
35+
let frameState = frameStateByFrame.get(frameId);
36+
if (!frameState) {
37+
frameState = {
38+
invalidationsForEvent: new Map(),
39+
invalidationCountForEvent: new Map(),
40+
lastRecalcStyleEvent: null,
41+
pendingInvalidations: [],
42+
hasPainted: false,
43+
};
44+
frameStateByFrame.set(frameId, frameState);
45+
}
46+
return frameState;
47+
}
48+
49+
function getFrameId(event: Types.Events.Event): string|null {
50+
if (Types.Events.isRecalcStyle(event) || Types.Events.isLayout(event)) {
51+
return event.args.beginData?.frame ?? null;
52+
}
53+
return event.args?.data?.frame ?? null;
54+
}
55+
56+
function addInvalidationToEvent(
57+
frameState: InvalidationsStatePerFrame, event: Types.Events.Event,
58+
invalidation: Types.Events.InvalidationTrackingEvent): void {
59+
const existingInvalidations = frameState.invalidationsForEvent.get(event) || [];
3360
existingInvalidations.push(invalidation);
3461

3562
if (maxInvalidationsPerEvent !== null && existingInvalidations.length > maxInvalidationsPerEvent) {
3663
existingInvalidations.shift();
3764
}
38-
invalidationsForEvent.set(event, existingInvalidations);
65+
frameState.invalidationsForEvent.set(event, existingInvalidations);
3966

40-
const count = invalidationCountForEvent.get(event) ?? 0;
41-
invalidationCountForEvent.set(event, count + 1);
67+
const count = frameState.invalidationCountForEvent.get(event) ?? 0;
68+
frameState.invalidationCountForEvent.set(event, count + 1);
4269
}
4370

4471
export function handleEvent(event: Types.Events.Event): void {
@@ -49,72 +76,68 @@ export function handleEvent(event: Types.Events.Event): void {
4976
return;
5077
}
5178

52-
if (Types.Events.isUpdateLayoutTree(event)) {
53-
lastRecalcStyleEvent = event;
79+
const frameId = getFrameId(event);
80+
if (!frameId) {
81+
return;
82+
}
83+
const thisFrame = getState(frameId);
84+
85+
if (Types.Events.isRecalcStyle(event)) {
86+
thisFrame.lastRecalcStyleEvent = event;
5487

5588
// Associate any prior invalidations with this recalc event.
56-
for (const invalidation of allInvalidationTrackingEvents) {
89+
for (const invalidation of thisFrame.pendingInvalidations) {
5790
if (Types.Events.isLayoutInvalidationTracking(invalidation)) {
5891
// LayoutInvalidation events cannot be associated with a LayoutTree
5992
// event.
6093
continue;
6194
}
62-
63-
const recalcFrameId = lastRecalcStyleEvent.args.beginData?.frame;
64-
65-
if (recalcFrameId && invalidation.args.data.frame === recalcFrameId) {
66-
addInvalidationToEvent(event, invalidation);
67-
}
95+
addInvalidationToEvent(thisFrame, event, invalidation);
6896
}
6997
return;
7098
}
7199

72100
if (Types.Events.isInvalidationTracking(event)) {
73-
if (hasPainted) {
101+
if (thisFrame.hasPainted) {
74102
// If we have painted, then we can clear out the list of all existing
75103
// invalidations, as we cannot associate them across frames.
76-
allInvalidationTrackingEvents.length = 0;
77-
lastRecalcStyleEvent = null;
78-
hasPainted = false;
104+
thisFrame.pendingInvalidations.length = 0;
105+
thisFrame.lastRecalcStyleEvent = null;
106+
thisFrame.hasPainted = false;
79107
}
80108

81-
// Style invalidation events can occur before and during recalc styles. When we get a recalc style event (aka UpdateLayoutTree), we check and associate any prior invalidations with it.
82-
// But any invalidations that occur during a UpdateLayoutTree
109+
// Style invalidation events can occur before and during recalc styles. When we get a recalc style event, we check and associate any prior invalidations with it.
110+
// But any invalidations that occur during a RecalcStyle
83111
// event would be reported in trace events after. So each time we get an
84112
// invalidation that might be due to a style recalc, we check if the
85113
// timings overlap and if so associate them.
86-
if (lastRecalcStyleEvent &&
114+
if (thisFrame.lastRecalcStyleEvent &&
87115
(Types.Events.isScheduleStyleInvalidationTracking(event) ||
88116
Types.Events.isStyleRecalcInvalidationTracking(event) ||
89117
Types.Events.isStyleInvalidatorInvalidationTracking(event))) {
90-
const recalcEndTime = lastRecalcStyleEvent.ts + (lastRecalcStyleEvent.dur || 0);
91-
if (event.ts >= lastRecalcStyleEvent.ts && event.ts <= recalcEndTime &&
92-
lastRecalcStyleEvent.args.beginData?.frame === event.args.data.frame) {
93-
addInvalidationToEvent(lastRecalcStyleEvent, event);
118+
const recalcLastRecalc = thisFrame.lastRecalcStyleEvent;
119+
const recalcEndTime = recalcLastRecalc.ts + (recalcLastRecalc.dur || 0);
120+
if (event.ts >= recalcLastRecalc.ts && event.ts <= recalcEndTime) {
121+
addInvalidationToEvent(thisFrame, recalcLastRecalc, event);
94122
}
95123
}
96124

97-
allInvalidationTrackingEvents.push(event);
125+
thisFrame.pendingInvalidations.push(event);
98126
return;
99127
}
100128

101129
if (Types.Events.isPaint(event)) {
102-
// Used to ensure that we do not create relationships across frames.
103-
hasPainted = true;
130+
thisFrame.hasPainted = true;
104131
return;
105132
}
106133

107134
if (Types.Events.isLayout(event)) {
108-
const layoutFrame = event.args.beginData.frame;
109-
for (const invalidation of allInvalidationTrackingEvents) {
135+
for (const invalidation of thisFrame.pendingInvalidations) {
110136
// The only invalidations that cause a Layout are LayoutInvalidations :)
111137
if (!Types.Events.isLayoutInvalidationTracking(invalidation)) {
112138
continue;
113139
}
114-
115-
if (invalidation.args.data.frame === layoutFrame) {
116-
addInvalidationToEvent(event, invalidation);
117-
}
140+
addInvalidationToEvent(thisFrame, event, invalidation);
118141
}
119142
}
120143
}
@@ -128,6 +151,16 @@ interface InvalidationsData {
128151
}
129152

130153
export function data(): InvalidationsData {
154+
const invalidationsForEvent = new Map<Types.Events.Event, Types.Events.InvalidationTrackingEvent[]>();
155+
const invalidationCountForEvent = new Map<Types.Events.Event, number>();
156+
for (const frame of frameStateByFrame.values()) {
157+
for (const [event, invalidations] of frame.invalidationsForEvent.entries()) {
158+
invalidationsForEvent.set(event, invalidations);
159+
}
160+
for (const [event, count] of frame.invalidationCountForEvent.entries()) {
161+
invalidationCountForEvent.set(event, count);
162+
}
163+
}
131164
return {
132165
invalidationsForEvent,
133166
invalidationCountForEvent,

front_end/models/trace/handlers/RendererHandler.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const makeRendererThread = (): RendererThread => ({
5858
entries: [],
5959
profileCalls: [],
6060
layoutEvents: [],
61-
updateLayoutTreeEvents: [],
61+
recalcStyleEvents: [],
6262
});
6363

6464
const getOrCreateRendererProcess =
@@ -118,10 +118,10 @@ export function handleEvent(event: Types.Events.Event): void {
118118
thread.layoutEvents.push(event);
119119
}
120120

121-
if (Types.Events.isUpdateLayoutTree(event)) {
121+
if (Types.Events.isRecalcStyle(event)) {
122122
const process = getOrCreateRendererProcess(processes, event.pid);
123123
const thread = getOrCreateRendererThread(process, event.tid);
124-
thread.updateLayoutTreeEvents.push(event);
124+
thread.recalcStyleEvents.push(event);
125125
}
126126
}
127127

@@ -423,6 +423,6 @@ export interface RendererThread {
423423
entries: Types.Events.Event[];
424424
profileCalls: Types.Events.SyntheticProfileCall[];
425425
layoutEvents: Types.Events.Layout[];
426-
updateLayoutTreeEvents: Types.Events.UpdateLayoutTree[];
426+
recalcStyleEvents: Types.Events.RecalcStyle[];
427427
tree?: Helpers.TreeHelpers.TraceEntryTree;
428428
}

0 commit comments

Comments
 (0)