Repository navigation
fix(browser): Skip CLS on browsers without Array.prototype.at - #26347
Merged
Merged
Conversation
web-vitals' CLS code calls Array.prototype.at, which Chromium only ships from 92. Chromium 80-91 is in our supported range and emits layout shifts, so every layout shift threw an uncaught TypeError. Co-Authored-By: Francesco Gringl-Novy <2411343+mydea@users.noreply.github.com>
Contributor
size-limit report 📦Compared: 42eb99d (PR test merge using develop at cb9e84c) Against: 90c9bf0 (develop) · base run #65962
|
Contributor
Optional E2E testsSome optional checks were not successful. They do not block the merge. 4 not successful, 56 successful. See the run.
Successful checks
|
Loads the tracing CDN bundles in Chromium 80 over the DevTools protocol and checks that FCP, FP, TTFB and LCP are sent, CLS and INP are not, and nothing throws or is reported as an error. Co-Authored-By: Francesco Gringl-Novy <2411343+mydea@users.noreply.github.com>
Pins the snapshot's SHA-256 and writes the zip next to the extracted build instead of into the shared temp dir.
logaretm
approved these changes
Nov 13, 2026
mydea
reviewed
Nov 13, 2026
msonnb
approved these changes
Nov 13, 2026
mydea
reviewed
Nov 13, 2026
Drives Chromium with CDPClient and receives envelopes with createBasicSentryServer instead of hand-rolled versions. CDPClient now clears its request timeouts so finished requests don't keep the process alive, and createBasicSentryServer allows cross-origin requests so browsers can read its responses. Co-Authored-By: Francesco Gringl-Novy <2411343+mydea@users.noreply.github.com>
nx ran it as a unit test target on every Node unit test job, which run on arm runners without the x64 Chromium build. CI runs it in its own job.
sentry-junior Bot
added a commit
that referenced
this pull request
Nov 13, 2026
web-vitals 6 is inlined untranspiled. Its Array.prototype.at use is guarded since #26347, and its remaining ES2022 syntax (public instance class fields) runs on all supported browsers. Co-Authored-By: Francesco Gringl-Novy <2411343+mydea@users.noreply.github.com>
mydea
added a commit
that referenced
this pull request
Nov 13, 2026
**Why:** The way we use `es-check` (no `--checkFeatures`, no
browserslist), all it does is run `acorn.parse(code, { ecmaVersion })`
on files from a glob. `scripts/es-check.mjs` does that in ~40 lines with
`acorn` and `fs.globSync`, and it drops one package (`acorn`,
`browserslist` and `fast-glob` are all still used elsewhere).
The bigger fix is coverage. The old globs, like `./build/esm/*.js`,
weren't recursive. They checked about 560 of the roughly 3,100 built
files (core: 58/436, node: 8/158, nextjs: 4/224). The script now checks
every `.js/.mjs/.cjs` file under `build/` (skipping `node_modules`).
Every file is parsed as a module, so we no longer need `--module` or
separate cjs/esm calls. Hashbangs are allowed for `aws-serverless`'s
lambda extension.
**Remix exceptions:** `build/esm/v3/client-bundle.js` (the `./v3/client`
browser entry) inlines `web-vitals@6` untranspiled, so it is checked
against es2022 instead. Its `Array.prototype.at` use is guarded since
#26347, and its only ES2022 syntax is public instance class fields
(Chrome 72 / Firefox 69 / Safari 14). `build/v3-node.mjs` is Node-only
and uses top-level await, so it's also checked against es2022. To
exclude the bundle from the es2020 pass, the script now accepts `!`
globs.
**Why not `--checkFeatures`:** I ran it over all of the current build
output. It matches on method names, so it gets both kinds of result
wrong. Every OTel `context.with()` is reported as
`Array.prototype.with`. Meanwhile `arr.at(-1)` isn't reported at all (I
reproduced that with a two-line file).
<!-- junior-request-attribution:start -->
via @mydea.
<!-- junior-request-attribution:end -->
<!-- junior-session-footer:start -->
<!-- junior-conversation-id:slack%3AD0CA8FXFXAS%3A1794484218.498979 -->
--
[View
Session](https://junior-prod.sentry.dev/conversations/slack%3AD0CA8FXFXAS%3A1794484218.498979)
[[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AD0CA8FXFXAS%3A1794484218.498979/?project=4510944073809921)
<!-- junior-session-footer:end -->
---------
Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: Francesco Gringl-Novy <2411343+mydea@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why:
web-vitals6 callsArray.prototype.at(-1)in its CLS code (LayoutShiftManager). Chromium only has.at()from 92, but we support Chrome 80+ / Edge 80+ / Opera 67+ / Samsung Internet 13+, and Chromium has emittedlayout-shiftentries since 77. So on Chromium 80–91, every layout shift throws an uncaughtTypeError: this.o.at is not a functionfrom web-vitals' observer callback, and the SDK reports it as an error event to the customer's project. CLS doesn't get recorded either.This PR skips the CLS observer when
.at()is missing, instead of polyfillingArray.prototypein customer apps. Those browsers just report no CLS, the same as Firefox and Safari already do. The other.at()call (INP'sInteractionManager) can't be reached there, becauseonINPbails unlessPerformanceEventTiminghasinteractionId(Chrome 96+).es-check es2020doesn't catch any of this:web-vitalsis external in our npm build, and es-check's feature check doesn't flag.at()calls in the CDN bundles either.It also adds a CI job,
Minimum Chromium Version Tests, that runs real web vitals in Chromium 80, our minimum. Playwright only drives the Chromium it ships with, sodev-packages/browser-min-version-tests/run.mjsdownloads the Chromium 80 snapshot (SHA-256 pinned) and drives it over the DevTools protocol withCDPClient, whilecreateBasicSentryServerreceives the envelopes. Both come from@sentry-internal/test-utils. For the tracing CDN bundle and the full debug bundle, it:console.erroror browser error logs, and no error events sent to the DSNEnvelopes go cross-origin to a local DSN, like they do to sentry.io. The test takes ~2.5 s, or ~6 s including the download. Against
develop's bundles it fails with the.atTypeError, an error event, and a CLS span. On this branch it passes.Two small
test-utilschanges made this possible.CDPClientnow clears its request timeouts, so finished requests no longer keep the process alive for 30 s.createBasicSentryServersendsAccess-Control-Allow-Origin: *, so browsers can read its responses.How to review: The fix is the guard in
instrumentCls(). The Playwright test inweb-vitals-cls-without-array-atdeletesArray.prototype.atto cover the npm/ESM builds too.run.mjsis the Chromium 80 harness.Unsure about:
changed_browser_integrationis true, same as the Playwright jobs.tunnel, the SDK sendsContent-Type: application/x-sentry-envelopewithkeepalive: true. Chromium < 81 rejects that with "Preflight request for request with keepalive specified is currently not supported", so every envelope fails. That's why the harness uses a cross-origin DSN instead of a tunnel.via @mydea.
--
View Session [Sentry]