Skip to content

Commit

Permalink
INP breakdown and LoAF integration with web-vitals v4 (#177)
Browse files Browse the repository at this point in the history
* Update web-vitals-extension to work with web vitals v4

* Add back interaction type

* Simplify first-start

* More simplier

* Tidy up

* TTFB redirect breakdown

* Update interaction logging to be similar to v4 INP

* Script order, invoker, and latest web-vitals v4

* With LCP fixes

* Fix marks

* Handle when no metric has target

* Import thresholds from web-vitals.js

* Standardise units

* Fix TTFB sub part name

* Latest v4 web-vitals

* Review feedback

* Move LoAF observer

* Remove interaction breakdowns to simplify code

* Better filtering of sub parts

* Improve formatting and stop hardcoding thresholds

* Further clean ups

* More clean up

* More cleanup

* Interaction threshold

* Default green status for waiting

* Merge issue

* Update to latest v4 changes

* Review feedback

* cleanup

* Fix merge issues

* clickable script sources

* formatting

* Better target

* Latest build

* Latest web-vitals build

* Add element

* t commit -a latest web-vitals v4

* Upgrade web-vitals

* Minor version bump
  • Loading branch information
tunetheweb committed May 13, 2024
1 parent be694b7 commit e371300
Show file tree
Hide file tree
Showing 6 changed files with 1,143 additions and 1,052 deletions.
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web-vitals-extension",
"version": "1.4.2",
"version": "1.5.0",
"description": "Instant Web Vitals metrics",
"main": "src/browser_action/vitals.js",
"repository": "https://github.com/googlechrome/web-vitals-extension",
Expand All @@ -21,6 +21,6 @@
"eslint-config-google": "^0.14.0"
},
"dependencies": {
"web-vitals": "^3.5.2"
"web-vitals": "^4.0.0"
}
}
83 changes: 76 additions & 7 deletions src/browser_action/on-each-interaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,44 @@

import {INPThresholds} from './web-vitals.js';


/**
* @param {Function} callback
*/
export function onEachInteraction(callback) {
const valueToRating = (score) => score <= INPThresholds[0] ? 'good' : score <= INPThresholds[1] ? 'needs-improvement' : 'poor';

const observer = new PerformanceObserver((list) => {
const eventObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const interactions = {};

for (const entry of list.getEntries().filter((entry) => entry.interactionId)) {
const getSelector = (node, maxLen) => {
let sel = '';

try {
while (node && node.nodeType !== 9) {
const el = node;
const part = el.id
? '#' + el.id
: getName(el) +
(el.classList &&
el.classList.value &&
el.classList.value.trim() &&
el.classList.value.trim().length
? '.' + el.classList.value.trim().replace(/\s+/g, '.')
: '');
if (sel.length + part.length > (maxLen || 100) - 1) return sel || part;
sel = sel ? part + '>' + sel : part;
if (el.id) break;
node = el.parentNode;
}
} catch (err) {
// Do nothing...
}
return sel;
};

// Filter all events to those with interactionids
for (const entry of entries.filter((entry) => entry.interactionId)) {
interactions[entry.interactionId] = interactions[entry.interactionId] || [];
interactions[entry.interactionId].push(entry);
}
Expand All @@ -33,12 +60,24 @@ export function onEachInteraction(callback) {
for (const interaction of Object.values(interactions)) {
const entry = interaction.reduce((prev, curr) => prev.duration >= curr.duration ? prev : curr);
const value = entry.duration;
const interactionId = entry.interactionId;

// Filter down LoAFs to ones that intersected any event startTime and any processingEnd
const longAnimationFrameEntries = getIntersectingLoAFs(entry.startTime, entry.startTime + entry.value)

let firstEntryWithTarget = interaction[0].target;
firstEntryWithTarget = null;
if (!firstEntryWithTarget) {
firstEntryWithTarget = Object.values(interactions).flat().find(entry => entry.interactionId === interactionId && entry.target)?.target;
}

callback({
attribution: {
eventEntry: entry,
eventTime: entry.startTime,
eventType: entry.name,
interactionTarget: getSelector(firstEntryWithTarget),
interactionTargetElement: firstEntryWithTarget,
interactionTime: entry.startTime,
interactionType: entry.name.startsWith('key') ? 'keyboard' : 'pointer',
longAnimationFrameEntries: longAnimationFrameEntries
},
entries: interaction,
name: 'Interaction',
Expand All @@ -48,9 +87,39 @@ export function onEachInteraction(callback) {
}
});

observer.observe({
eventObserver.observe({
type: 'event',
durationThreshold: 0,
buffered: true,
});

let recentLoAFs = [];

const getIntersectingLoAFs = (start, end) => {
const intersectingLoAFs = [];

for (let i = 0, loaf; (loaf = recentLoAFs[i]); i++) {
// If the LoAF ends before the given start time, ignore it.
if (loaf.startTime + loaf.duration < start) continue;

// If the LoAF starts after the given end time, ignore it and all
// subsequent pending LoAFs (because they're in time order).
if (loaf.startTime > end) break;

// Still here? If so this LoAF intersects with the interaction.
intersectingLoAFs.push(loaf);
}
return intersectingLoAFs;
};

const loafObserver = new PerformanceObserver((list) => {
// We report interactions immediately, so don't need to keep many LoAFs around.
// Let's keep the last 5.
recentLoAFs = recentLoAFs.concat(list.getEntries()).slice(-5);

});
loafObserver.observe({
type: 'long-animation-frame',
buffered: true,
});
}

0 comments on commit e371300

Please sign in to comment.