Skip to content

fix(core): keep safeJoin side-effect-free for DOM elements (#21353)#21356

Merged
isaacs merged 4 commits into
getsentry:developfrom
archievi:fix/safejoin-element-side-effects-21353
Jun 9, 2026
Merged

fix(core): keep safeJoin side-effect-free for DOM elements (#21353)#21356
isaacs merged 4 commits into
getsentry:developfrom
archievi:fix/safejoin-element-side-effects-21353

Conversation

@archievi

@archievi archievi commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Closes #21353

  • Added a regression test.
  • Code lints (oxlint) and the affected test suite passes (vitest run packages/core/test/lib/utils/string.test.ts → 29/29).
  • Linked the issue.

Summary

In 10.55.0, safeJoin (packages/core/src/utils/string.ts) switched non-primitive serialization from String(value) to stringifyValue(...). For DOM elements, stringifyValue routes through the browser stringifier's htmlTreeAsString, which reads id / className / getAttribute and walks the ancestor chain — so capturing a console breadcrumb now invokes user-defined property getters and traverses the DOM on every console.log(element), even when no event is sent. (<= 10.54.0 was side-effect-free via String(value).)

This short-circuits DOM elements in safeJoin back to a side-effect-free String(value), while keeping stringifyValue for other non-primitive values (functions, plain objects, etc.). htmlTreeAsString is intentionally left intact — it is still used for the click/DOM breadcrumb path that legitimately needs the selector string.

Added a regression test asserting safeJoin does not invoke getters or the stringifier for elements (it fails on the current code and passes with this change).

…#21353)

In 10.55.0 safeJoin switched non-primitive serialization from String() to
stringifyValue(), which for DOM elements routes through the browser stringifier's
htmlTreeAsString and reads id/className/getAttribute, invoking user-defined getters
(and walking the DOM) during console breadcrumb capture. Short-circuit elements via
String() so breadcrumb serialization stays side-effect free. Adds a regression test.
@isaacs isaacs self-requested a review June 8, 2026 18:01

@isaacs isaacs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clearly fixes the regression described, and restores the behavior very surgically without affecting the intended behavior that led to it. Good patch, thanks!

@isaacs isaacs enabled auto-merge (squash) June 8, 2026 18:05
@andreiborza andreiborza requested a review from a team as a code owner June 9, 2026 08:20
@andreiborza andreiborza requested review from logaretm and mydea and removed request for a team June 9, 2026 08:20

@andreiborza andreiborza left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I agree that it restores behavior and avoids invoking user defined getters, I think we can get to a state where it does not invoke getters but supports the nicer tree rendering.

I changed the behavior a bit.

Comment thread packages/browser-utils/src/htmlTreeAsString.ts
@andreiborza andreiborza requested review from chargome and nicohrubec and removed request for logaretm and mydea June 9, 2026 09:08
}

function _safeRead<T>(el: unknown, prop: AccessorKey, arg?: string): T {
return accessors[prop]!.call(el, arg) as T;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: Is this a safe read actually? Should we trycatch these?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, added try/catch with fallback to just using the field in 04f75df


if (typeof Node !== 'undefined') {
// oxlint-disable-next-line typescript-eslint(unbound-method)
accessors.parentNode = Object.getOwnPropertyDescriptor(Node.prototype, 'parentNode')!.get!;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we just defensively put these into trycatch to guard against breaking in case this got stubbed, polyfilled or whatever

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, wrapped over all of them. If one of them fails we just fall back to direct access in 04f75df

@andreiborza andreiborza requested review from Lms24, andreiborza, chargome and logaretm and removed request for nicohrubec June 9, 2026 11:46
@isaacs isaacs merged commit 4b75699 into getsentry:develop Jun 9, 2026
247 checks passed
nicohrubec added a commit that referenced this pull request Jun 9, 2026
This PR adds the external contributor to the CHANGELOG.md file, so that
they are credited for their contribution. See #21356

Co-authored-by: isaacs <9287+isaacs@users.noreply.github.com>
Co-authored-by: Nicolas Hrubec <nicolas.hrubec@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants