Skip to content

Commit 3c522b4

Browse files
szuendDevtools-frontend LUCI CQ
authored andcommitted
Revert "[RPP Observations] Add phases for each interaction"
This reverts commit a51d9f9. Reason for revert: e2e test on mac consistently fails since this CL landed. First failure: https://ci.chromium.org/ui/p/devtools-frontend/builders/ci/Stand-alone%20Mac-arm64/4134/overview. Original change's description: > [RPP Observations] Add phases for each interaction > > - Interaction list items are expandable > - Creates separate interaction if events happen in different frames > - Interactions are restored from the buffer > - `eventNames` property added to each interaction for future use > > Bug: 369097528, 365160880, 371052022 > Change-Id: Iab39cb300143aad3a6f2cc5ef5ecab79d7fd879e > Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5933344 > Reviewed-by: Paul Irish <[email protected]> > Commit-Queue: Adam Raine <[email protected]> Bug: 369097528, 365160880, 371052022 Change-Id: I7cd208c5e2620c682f9babaec573df4c0880cfb6 No-Presubmit: true No-Tree-Checks: true No-Try: true Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5938363 Auto-Submit: Simon Zünd <[email protected]> Commit-Queue: Rubber Stamper <[email protected]> Bot-Commit: Rubber Stamper <[email protected]>
1 parent 746e82a commit 3c522b4

File tree

13 files changed

+144
-397
lines changed

13 files changed

+144
-397
lines changed

front_end/models/live-metrics/LiveMetrics.ts

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import * as Common from '../../core/common/common.js';
66
import * as Host from '../../core/host/host.js';
7-
import * as Platform from '../../core/platform/platform.js';
87
import * as Root from '../../core/root/root.js';
98
import * as SDK from '../../core/sdk/sdk.js';
109
import type * as Protocol from '../../generated/protocol.js';
@@ -27,8 +26,6 @@ class InjectedScript {
2726
}
2827
}
2928

30-
export type InteractionMap = Map<Spec.UniqueInteractionId, Interaction>;
31-
3229
export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes> implements SDK.TargetManager.Observer {
3330
#enabled = false;
3431
#target?: SDK.Target.Target;
@@ -37,7 +34,7 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
3734
#lcpValue?: LCPValue;
3835
#clsValue?: CLSValue;
3936
#inpValue?: INPValue;
40-
#interactions: InteractionMap = new Map();
37+
#interactions: Interaction[] = [];
4138
#layoutShifts: LayoutShift[] = [];
4239
#mutex = new Common.Mutex.Mutex();
4340

@@ -67,7 +64,7 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
6764
return this.#inpValue;
6865
}
6966

70-
get interactions(): InteractionMap {
67+
get interactions(): Interaction[] {
7168
return this.#interactions;
7269
}
7370

@@ -143,7 +140,7 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
143140

144141
const allLayoutAffectedNodes = this.#layoutShifts.flatMap(shift => shift.affectedNodes);
145142
const toRefresh: Array<{node?: SDK.DOMModel.DOMNode}> =
146-
[this.#lcpValue || {}, ...this.#interactions.values(), ...allLayoutAffectedNodes];
143+
[this.#lcpValue || {}, ...this.#interactions, ...allLayoutAffectedNodes];
147144

148145
const allPromises = toRefresh.map(item => {
149146
const node = item.node;
@@ -201,30 +198,20 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
201198
this.#inpValue = inpEvent;
202199
break;
203200
}
204-
case 'InteractionEntry': {
205-
const interaction: Interaction = Platform.MapUtilities.getWithDefault(
206-
this.#interactions, webVitalsEvent.uniqueInteractionId,
207-
() => ({
208-
interactionType: webVitalsEvent.interactionType,
209-
duration: webVitalsEvent.duration,
210-
eventNames: [],
211-
phases: webVitalsEvent.phases,
212-
uniqueInteractionId: webVitalsEvent.uniqueInteractionId,
213-
}));
214-
215-
// We can get multiple instances of the first input interaction since web-vitals.js installs
216-
// an extra listener for events of type `first-input`. This is a simple way to de-dupe those
217-
// events without adding complexity to the injected code.
218-
if (!interaction.eventNames.includes(webVitalsEvent.eventName)) {
219-
interaction.eventNames.push(webVitalsEvent.eventName);
220-
}
221-
201+
case 'Interaction': {
202+
const interaction: Interaction = {
203+
duration: webVitalsEvent.duration,
204+
interactionType: webVitalsEvent.interactionType,
205+
uniqueInteractionId: webVitalsEvent.uniqueInteractionId,
206+
};
222207
if (webVitalsEvent.nodeIndex !== undefined) {
223208
const node = await this.#resolveDomNode(webVitalsEvent.nodeIndex, executionContextId);
224209
if (node) {
225210
interaction.node = node;
226211
}
227212
}
213+
214+
this.#interactions.push(interaction);
228215
break;
229216
}
230217
case 'LayoutShift': {
@@ -248,7 +235,7 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
248235
this.#lcpValue = undefined;
249236
this.#clsValue = undefined;
250237
this.#inpValue = undefined;
251-
this.#interactions.clear();
238+
this.#interactions = [];
252239
this.#layoutShifts = [];
253240
break;
254241
}
@@ -335,7 +322,7 @@ export class LiveMetrics extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
335322
}
336323

337324
clearInteractions(): void {
338-
this.#interactions.clear();
325+
this.#interactions = [];
339326
this.#sendStatusUpdate();
340327
}
341328

@@ -480,10 +467,8 @@ export interface LayoutShift {
480467
}
481468

482469
export interface Interaction {
483-
interactionType: Spec.InteractionEntryEvent['interactionType'];
484-
eventNames: string[];
485-
duration: Spec.InteractionEntryEvent['duration'];
486-
phases: Spec.INPPhases;
470+
interactionType: Spec.InteractionEvent['interactionType'];
471+
duration: Spec.InteractionEvent['duration'];
487472
uniqueInteractionId: Spec.UniqueInteractionId;
488473
node?: SDK.DOMModel.DOMNode;
489474
}
@@ -492,7 +477,7 @@ export interface StatusEvent {
492477
lcp?: LCPValue;
493478
cls?: CLSValue;
494479
inp?: INPValue;
495-
interactions: InteractionMap;
480+
interactions: Interaction[];
496481
layoutShifts: LayoutShift[];
497482
}
498483

front_end/models/live-metrics/web-vitals-injected/OnEachInteraction.ts

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,68 @@
44

55
/**
66
* @fileoverview web-vitals.js doesn't provide a log of all interactions.
7-
* This solution is hacky but it was recommended by web-vitals devs:
8-
* b/371052022
7+
* This was largely copied from the web vitals extension:
8+
* https://github.com/GoogleChrome/web-vitals-extension/blob/main/src/browser_action/on-each-interaction.js
99
*/
1010

11-
import * as WebVitals from '../../../third_party/web-vitals/web-vitals.js';
12-
13-
export function onEachInteraction(onReport: (metric: WebVitals.INPMetricWithAttribution) => void): void {
14-
WebVitals.entryPreProcessingCallbacks.push((entry: PerformanceEventTiming) => {
15-
// Wait a microtask so this "pre" processing callback actually
16-
// becomes a "post" processing callback.
17-
void Promise.resolve().then(() => {
18-
if (entry.interactionId) {
19-
const interaction = WebVitals.attributeINP({
20-
entries: [entry],
21-
// The only value we really need for `attributeINP` is `entries`
22-
// Everything else is included to fill out the type.
23-
name: 'INP',
24-
rating: 'good',
25-
value: entry.duration,
26-
delta: entry.duration,
27-
navigationType: 'navigate',
28-
id: 'N/A',
29-
});
30-
onReport(interaction);
31-
}
32-
});
11+
import type * as WebVitals from '../../../third_party/web-vitals/web-vitals.js';
12+
13+
export interface InteractionWithAttribution {
14+
attribution: {
15+
interactionTargetElement: Node|null,
16+
interactionType: WebVitals.INPAttribution['interactionType'],
17+
};
18+
entries: PerformanceEventTiming[];
19+
value: number;
20+
}
21+
22+
export function onEachInteraction(callback: (interaction: InteractionWithAttribution) => void): void {
23+
const eventObserver = new PerformanceObserver(list => {
24+
const entries = list.getEntries();
25+
const interactions = new Map<number, PerformanceEventTiming[]>();
26+
27+
const performanceEventTimings = entries.filter((entry): entry is PerformanceEventTiming => 'interactionId' in entry)
28+
.filter(entry => entry.interactionId);
29+
30+
for (const entry of performanceEventTimings) {
31+
const interaction = interactions.get(entry.interactionId) || [];
32+
interaction.push(entry);
33+
interactions.set(entry.interactionId, interaction);
34+
}
35+
36+
// Will report as a single interaction even if parts are in separate frames.
37+
// Consider splitting by animation frame.
38+
for (const interaction of interactions.values()) {
39+
const longestEntry = interaction.reduce((prev, curr) => prev.duration >= curr.duration ? prev : curr);
40+
const value = longestEntry.duration;
41+
42+
const firstEntryWithTarget = interaction.find(entry => entry.target);
43+
44+
callback({
45+
attribution: {
46+
interactionTargetElement: firstEntryWithTarget?.target ?? null,
47+
interactionType: longestEntry.name.startsWith('key') ? 'keyboard' : 'pointer',
48+
},
49+
entries: interaction,
50+
value,
51+
});
52+
}
53+
});
54+
55+
eventObserver.observe({
56+
type: 'first-input',
57+
buffered: false,
58+
});
59+
60+
eventObserver.observe({
61+
type: 'event',
62+
durationThreshold: 0,
63+
// Interaction events can only be stored to the buffer if their duration is >=104ms.
64+
// https://www.w3.org/TR/event-timing/#sec-events-exposed
65+
//
66+
// This means we can only collect a subset of interactions that happen before this observer is started.
67+
// To avoid confusion, we only collect interactions that after this observer has started.
68+
// Note: This DOES NOT affect the collection for the INP metric and so INP will still be restored from the buffer.
69+
buffered: false,
3370
});
3471
}

front_end/models/live-metrics/web-vitals-injected/spec/spec.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,11 @@ export interface INPChangeEvent extends MetricChangeEvent {
6262
uniqueInteractionId: UniqueInteractionId;
6363
}
6464

65-
/**
66-
* This event is not 1:1 with the interactions that the user sees in the interactions log.
67-
* It is 1:1 with a `PerformanceEventTiming` entry that will be combined by `uniqueInteractionId`
68-
* in the DevTools client.
69-
*/
70-
export interface InteractionEntryEvent {
71-
name: 'InteractionEntry';
65+
export interface InteractionEvent {
66+
name: 'Interaction';
7267
interactionType: INPAttribution['interactionType'];
73-
eventName: string;
7468
uniqueInteractionId: UniqueInteractionId;
7569
duration: number;
76-
phases: INPPhases;
7770
nodeIndex?: number;
7871
}
7972

@@ -88,5 +81,4 @@ export interface ResetEvent {
8881
name: 'reset';
8982
}
9083

91-
export type WebVitalsEvent =
92-
LCPChangeEvent|CLSChangeEvent|INPChangeEvent|InteractionEntryEvent|LayoutShiftEvent|ResetEvent;
84+
export type WebVitalsEvent = LCPChangeEvent|CLSChangeEvent|INPChangeEvent|InteractionEvent|LayoutShiftEvent|ResetEvent;

front_end/models/live-metrics/web-vitals-injected/web-vitals-injected.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -152,23 +152,14 @@ function initialize(): void {
152152
interactionType: metric.attribution.interactionType,
153153
};
154154
sendEventToDevTools(event);
155-
}, {reportAllChanges: true, durationThreshold: 0});
155+
}, {reportAllChanges: true});
156156

157157
onEachInteraction(interaction => {
158-
// Multiple `InteractionEntry` events can be emitted for the same `uniqueInteractionId`
159-
// However, it is easier to combine these entries in the DevTools client rather than in
160-
// this injected code.
161-
const event: Spec.InteractionEntryEvent = {
162-
name: 'InteractionEntry',
158+
const event: Spec.InteractionEvent = {
159+
name: 'Interaction',
163160
duration: interaction.value,
164-
phases: {
165-
inputDelay: interaction.attribution.inputDelay,
166-
processingDuration: interaction.attribution.processingDuration,
167-
presentationDelay: interaction.attribution.presentationDelay,
168-
},
169161
uniqueInteractionId: Spec.getUniqueInteractionId(interaction.entries),
170162
interactionType: interaction.attribution.interactionType,
171-
eventName: interaction.entries[0].name,
172163
};
173164
const node = interaction.attribution.interactionTargetElement;
174165
if (node) {

0 commit comments

Comments
 (0)