Skip to content

Commit 6fc18fe

Browse files
authored
feat(browser): Update web-vitals to 5.0.2 (#16492)
Bump vendored `web-vitals` library. Important changes from the original library: - For now until the next SDK major, we'll keep reporting FID. `web-vitals` removed the already deprecated APIs for it in v5 but we simply keep them from v4 - `web-vitals` further removed compatibility for older iOS Safari versions. Unfortunately, [we still support Safari 14.4 ](https://docs.sentry.io/platforms/javascript/troubleshooting/supported-browsers/) which is the last version that doesn't yet fully support the `visibilitychange` event. This requires us to keep the `onHidden` helper around which also listens to `pagehide` events that this Safari version supports. I adjusted our integration tests to keep one around that fails if we remove this special handling in a future upgrade (also added some context for future us). I will follow up with at least one more PR to do some more refactorings but I decided to keep them minimal in this PR to get better diffs for reviewing: - rename the `get*` files to `on*`, since this is how they're named now in the official library closes #16310
1 parent 65310d5 commit 6fc18fe

File tree

32 files changed

+563
-180
lines changed

32 files changed

+563
-180
lines changed

.size-limit.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ module.exports = [
120120
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
121121
ignore: ['react/jsx-runtime'],
122122
gzip: true,
123-
limit: '40.5 KB',
123+
limit: '41 KB',
124124
},
125125
// Vue SDK (ESM)
126126
{
@@ -215,7 +215,7 @@ module.exports = [
215215
import: createImport('init'),
216216
ignore: ['$app/stores'],
217217
gzip: true,
218-
limit: '39 KB',
218+
limit: '40 KB',
219219
},
220220
// Node SDK (ESM)
221221
{

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { sentryTest } from '../../../../utils/fixtures';
44
import {
55
getFirstSentryEnvelopeRequest,
66
getMultipleSentryEnvelopeRequests,
7+
hidePage,
78
properFullEnvelopeRequestParser,
89
shouldSkipTracingTest,
910
} from '../../../../utils/helpers';
@@ -33,9 +34,7 @@ sentryTest('should capture an INP click event span after pageload', async ({ bro
3334
await page.waitForTimeout(500);
3435

3536
// Page hide to trigger INP
36-
await page.evaluate(() => {
37-
window.dispatchEvent(new Event('pagehide'));
38-
});
37+
await hidePage(page);
3938

4039
// Get the INP span envelope
4140
const spanEnvelope = (await spanEnvelopePromise)[0];

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { sentryTest } from '../../../../utils/fixtures';
44
import {
55
getFirstSentryEnvelopeRequest,
66
getMultipleSentryEnvelopeRequests,
7+
hidePage,
78
properFullEnvelopeRequestParser,
89
shouldSkipTracingTest,
910
} from '../../../../utils/helpers';
@@ -35,9 +36,7 @@ sentryTest(
3536
await page.waitForTimeout(500);
3637

3738
// Page hide to trigger INP
38-
await page.evaluate(() => {
39-
window.dispatchEvent(new Event('pagehide'));
40-
});
39+
await hidePage(page);
4140

4241
// Get the INP span envelope
4342
const spanEnvelope = (await spanEnvelopePromise)[0];

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { SpanEnvelope } from '@sentry/core';
33
import { sentryTest } from '../../../../utils/fixtures';
44
import {
55
getMultipleSentryEnvelopeRequests,
6+
hidePage,
67
properFullEnvelopeRequestParser,
78
shouldSkipTracingTest,
89
} from '../../../../utils/helpers';
@@ -33,9 +34,7 @@ sentryTest(
3334
await page.waitForTimeout(500);
3435

3536
// Page hide to trigger INP
36-
await page.evaluate(() => {
37-
window.dispatchEvent(new Event('pagehide'));
38-
});
37+
await hidePage(page);
3938

4039
// Get the INP span envelope
4140
const spanEnvelope = (await spanEnvelopePromise)[0];

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/init.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Sentry.init({
1414
}),
1515
],
1616
tracesSampleRate: 1,
17+
debug: true,
1718
});
1819

1920
const client = Sentry.getClient();

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { sentryTest } from '../../../../utils/fixtures';
44
import {
55
getFirstSentryEnvelopeRequest,
66
getMultipleSentryEnvelopeRequests,
7+
hidePage,
78
properFullEnvelopeRequestParser,
89
shouldSkipTracingTest,
910
} from '../../../../utils/helpers';
@@ -32,9 +33,7 @@ sentryTest('should capture an INP click event span during pageload', async ({ br
3233
await page.waitForTimeout(500);
3334

3435
// Page hide to trigger INP
35-
await page.evaluate(() => {
36-
window.dispatchEvent(new Event('pagehide'));
37-
});
36+
await hidePage(page);
3837

3938
// Get the INP span envelope
4039
const spanEnvelope = (await spanEnvelopePromise)[0];
@@ -118,6 +117,14 @@ sentryTest(
118117
});
119118

120119
// Page hide to trigger INP
120+
121+
// Important: Purposefully not using hidePage() here to test the hidden state
122+
// via the `pagehide` event. This is necessary because iOS Safari 14.4
123+
// still doesn't fully emit the `visibilitychange` events but it's the lower
124+
// bound for Safari on iOS that we support.
125+
// If this test times out or fails, it's likely because we tried updating
126+
// the web-vitals library which officially already dropped support for
127+
// this iOS version
121128
await page.evaluate(() => {
122129
window.dispatchEvent(new Event('pagehide'));
123130
});

packages/browser-utils/src/metrics/cls.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export function trackClsAsStandaloneSpan(): void {
5858
standaloneClsEntry = entry;
5959
}, true);
6060

61+
// TODO: Figure out if we can switch to using whenIdleOrHidden instead of onHidden
6162
// use pagehide event from web-vitals
6263
onHidden(() => {
6364
_collectClsOnce();

packages/browser-utils/src/metrics/web-vitals/README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
> A modular library for measuring the [Web Vitals](https://web.dev/vitals/) metrics on real users.
44
5-
This was vendored from: https://github.com/GoogleChrome/web-vitals: v3.5.2
5+
This was vendored from: https://github.com/GoogleChrome/web-vitals: v5.0.2
66

77
The commit SHA used is:
8-
[3d2b3dc8576cc003618952fa39902fab764a53e2](https://github.com/GoogleChrome/web-vitals/tree/3d2b3dc8576cc003618952fa39902fab764a53e2)
8+
[463abbd425cda01ed65e0b5d18be9f559fe446cb](https://github.com/GoogleChrome/web-vitals/tree/463abbd425cda01ed65e0b5d18be9f559fe446cb)
99

1010
Current vendored web vitals are:
1111

@@ -27,6 +27,12 @@ web-vitals only report once per pageload.
2727

2828
## CHANGELOG
2929

30+
https://github.com/getsentry/sentry-javascript/pull/16492
31+
32+
- Bumped from Web Vitals 4.2.5 to 5.0.2
33+
- Mainly fixes some INP, LCP and FCP edge cases
34+
- Original library removed FID; we still keep it around for now
35+
3036
https://github.com/getsentry/sentry-javascript/pull/14439
3137

3238
- Bumped from Web Vitals v3.5.2 to v4.2.4

packages/browser-utils/src/metrics/web-vitals/getCLS.ts

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { WINDOW } from '../../types';
1718
import { bindReporter } from './lib/bindReporter';
1819
import { initMetric } from './lib/initMetric';
20+
import { initUnique } from './lib/initUnique';
21+
import { LayoutShiftManager } from './lib/LayoutShiftManager';
1922
import { observe } from './lib/observe';
20-
import { onHidden } from './lib/onHidden';
2123
import { runOnce } from './lib/runOnce';
2224
import { onFCP } from './onFCP';
2325
import type { CLSMetric, MetricRatingThresholds, ReportOpts } from './types';
@@ -54,58 +56,37 @@ export const onCLS = (onReport: (metric: CLSMetric) => void, opts: ReportOpts =
5456
const metric = initMetric('CLS', 0);
5557
let report: ReturnType<typeof bindReporter>;
5658

57-
let sessionValue = 0;
58-
let sessionEntries: LayoutShift[] = [];
59+
const layoutShiftManager = initUnique(opts, LayoutShiftManager);
5960

6061
const handleEntries = (entries: LayoutShift[]) => {
61-
entries.forEach(entry => {
62-
// Only count layout shifts without recent user input.
63-
if (!entry.hadRecentInput) {
64-
const firstSessionEntry = sessionEntries[0];
65-
const lastSessionEntry = sessionEntries[sessionEntries.length - 1];
66-
67-
// If the entry occurred less than 1 second after the previous entry
68-
// and less than 5 seconds after the first entry in the session,
69-
// include the entry in the current session. Otherwise, start a new
70-
// session.
71-
if (
72-
sessionValue &&
73-
firstSessionEntry &&
74-
lastSessionEntry &&
75-
entry.startTime - lastSessionEntry.startTime < 1000 &&
76-
entry.startTime - firstSessionEntry.startTime < 5000
77-
) {
78-
sessionValue += entry.value;
79-
sessionEntries.push(entry);
80-
} else {
81-
sessionValue = entry.value;
82-
sessionEntries = [entry];
83-
}
84-
}
85-
});
62+
for (const entry of entries) {
63+
layoutShiftManager._processEntry(entry);
64+
}
8665

8766
// If the current session value is larger than the current CLS value,
8867
// update CLS and the entries contributing to it.
89-
if (sessionValue > metric.value) {
90-
metric.value = sessionValue;
91-
metric.entries = sessionEntries;
68+
if (layoutShiftManager._sessionValue > metric.value) {
69+
metric.value = layoutShiftManager._sessionValue;
70+
metric.entries = layoutShiftManager._sessionEntries;
9271
report();
9372
}
9473
};
9574

9675
const po = observe('layout-shift', handleEntries);
9776
if (po) {
98-
report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges);
77+
report = bindReporter(onReport, metric, CLSThresholds, opts!.reportAllChanges);
9978

100-
onHidden(() => {
101-
handleEntries(po.takeRecords() as CLSMetric['entries']);
102-
report(true);
79+
WINDOW.document?.addEventListener('visibilitychange', () => {
80+
if (WINDOW.document?.visibilityState === 'hidden') {
81+
handleEntries(po.takeRecords() as CLSMetric['entries']);
82+
report(true);
83+
}
10384
});
10485

10586
// Queue a task to report (if nothing else triggers a report first).
10687
// This allows CLS to be reported as soon as FCP fires when
10788
// `reportAllChanges` is true.
108-
setTimeout(report, 0);
89+
WINDOW?.setTimeout?.(report);
10990
}
11091
}),
11192
);

packages/browser-utils/src/metrics/web-vitals/getFID.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
15+
*
16+
* // Sentry: web-vitals removed FID reporting from v5. We're keeping it around
17+
* for the time being.
18+
* // TODO(v10): Remove FID reporting!
1519
*/
1620

1721
import { bindReporter } from './lib/bindReporter';
@@ -60,6 +64,7 @@ export const onFID = (onReport: (metric: FIDMetric) => void, opts: ReportOpts =
6064
report = bindReporter(onReport, metric, FIDThresholds, opts.reportAllChanges);
6165

6266
if (po) {
67+
// sentry: TODO: Figure out if we can use new whinIdleOrHidden insteard of onHidden
6368
onHidden(
6469
runOnce(() => {
6570
handleEntries(po.takeRecords() as FIDMetric['entries']);

0 commit comments

Comments
 (0)