From 785c563408381893237a1ac01f48862675ae214c Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 28 Aug 2026 09:30:37 +0200 Subject: [PATCH 1/2] ref(core)!: Remove deprecated `htmlTreeAsString` from core This is exported from browser-utils already. --- packages/core/src/browser-exports.ts | 7 +- packages/core/src/utils/browser.ts | 123 --------------------------- 2 files changed, 1 insertion(+), 129 deletions(-) diff --git a/packages/core/src/browser-exports.ts b/packages/core/src/browser-exports.ts index 24ccbb96c66b..753f65cecbb0 100644 --- a/packages/core/src/browser-exports.ts +++ b/packages/core/src/browser-exports.ts @@ -15,12 +15,7 @@ export { startIdleSpan } from './tracing/idleSpan'; export { spanStreamingIntegration } from './integrations/browserSpanStreaming'; -export { - getComponentName, - getLocationHref, - // eslint-disable-next-line typescript/no-deprecated - htmlTreeAsString, -} from './utils/browser'; +export { getComponentName, getLocationHref } from './utils/browser'; export { supportsDOMError, supportsHistory, supportsNativeFetch, supportsReportingObserver } from './utils/supports'; export type { XhrBreadcrumbData, XhrBreadcrumbHint } from './types/breadcrumb'; export type { diff --git a/packages/core/src/utils/browser.ts b/packages/core/src/utils/browser.ts index d0cbb5a7f6e6..9b3e6ec31780 100644 --- a/packages/core/src/utils/browser.ts +++ b/packages/core/src/utils/browser.ts @@ -1,134 +1,11 @@ -import { isString } from './is'; import { GLOBAL_OBJ } from './worldwide'; const WINDOW = GLOBAL_OBJ as unknown as Window; -const DEFAULT_MAX_STRING_LENGTH = 80; - type SimpleNode = { parentNode: SimpleNode; } | null; -/** - * Given a child DOM element, returns a query-selector statement describing that - * and its ancestors - * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz] - * @returns generated DOM path - * @deprecated This is browser-specific and will be removed from `@sentry/core` in a future major version. - * Import `htmlTreeAsString` from `@sentry/browser-utils` instead. - */ -export function htmlTreeAsString( - elem: unknown, - options: string[] | { keyAttrs?: string[]; maxStringLength?: number } = {}, -): string { - if (!elem) { - return ''; - } - - // try/catch both: - // - accessing event.target (see getsentry/raven-js#838, #768) - // - `htmlTreeAsString` because it's complex, and just accessing the DOM incorrectly - // - can throw an exception in some circumstances. - try { - let currentElem = elem as SimpleNode; - const MAX_TRAVERSE_HEIGHT = 5; - const out = []; - let height = 0; - let len = 0; - const separator = ' > '; - const sepLength = separator.length; - let nextStr; - const keyAttrs = Array.isArray(options) ? options : options.keyAttrs; - const maxStringLength = (!Array.isArray(options) && options.maxStringLength) || DEFAULT_MAX_STRING_LENGTH; - - while (currentElem && height++ < MAX_TRAVERSE_HEIGHT) { - nextStr = _htmlElementAsString(currentElem, keyAttrs); - // bail out if - // - nextStr is the 'html' element - // - the length of the string that would be created exceeds maxStringLength - // (ignore this limit if we are on the first iteration) - if (nextStr === 'html' || (height > 1 && len + out.length * sepLength + nextStr.length >= maxStringLength)) { - break; - } - - out.push(nextStr); - - len += nextStr.length; - currentElem = currentElem.parentNode; - } - - return out.reverse().join(separator); - } catch { - return ''; - } -} - -/** - * Returns a simple, query-selector representation of a DOM element - * e.g. [HTMLElement] => input#foo.btn[name=baz] - * @returns generated DOM path - */ -function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string { - const elem = el as { - tagName?: string; - id?: string; - className?: string; - getAttribute(key: string): string; - }; - - const out = []; - - if (!elem?.tagName) { - return ''; - } - - // @ts-expect-error WINDOW has HTMLElement - if (WINDOW.HTMLElement) { - // If using the component name annotation plugin, this value may be available on the DOM node - if (elem instanceof HTMLElement && elem.dataset) { - if (elem.dataset['sentryComponent']) { - return elem.dataset['sentryComponent']; - } - if (elem.dataset['sentryElement']) { - return elem.dataset['sentryElement']; - } - } - } - - out.push(elem.tagName.toLowerCase()); - - // Pairs of attribute keys defined in `serializeAttribute` and their values on element. - const keyAttrPairs = keyAttrs?.length - ? keyAttrs.filter(keyAttr => elem.getAttribute(keyAttr)).map(keyAttr => [keyAttr, elem.getAttribute(keyAttr)]) - : null; - - if (keyAttrPairs?.length) { - keyAttrPairs.forEach(keyAttrPair => { - out.push(`[${keyAttrPair[0]}="${keyAttrPair[1]}"]`); - }); - } else { - if (elem.id) { - out.push(`#${elem.id}`); - } - - const className = elem.className; - if (className && isString(className)) { - const classes = className.split(/\s+/); - for (const c of classes) { - out.push(`.${c}`); - } - } - } - for (const k of ['aria-label', 'type', 'name', 'title', 'alt']) { - const attr = elem.getAttribute(k); - if (attr) { - out.push(`[${k}="${attr}"]`); - } - } - - return out.join(''); -} - /** * A safe form of location.href */ From 031aa093be622a3fd8a396fd643ed24d2e609b03 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 28 Aug 2026 09:38:44 +0200 Subject: [PATCH 2/2] remove test --- packages/core/test/lib/utils/browser.test.ts | 76 -------------------- 1 file changed, 76 deletions(-) delete mode 100644 packages/core/test/lib/utils/browser.test.ts diff --git a/packages/core/test/lib/utils/browser.test.ts b/packages/core/test/lib/utils/browser.test.ts deleted file mode 100644 index 0bcf71884482..000000000000 --- a/packages/core/test/lib/utils/browser.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { JSDOM } from 'jsdom'; -import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; -import { htmlTreeAsString } from '../../../src/utils/browser'; - -beforeAll(() => { - const dom = new JSDOM(); - global.document = dom.window.document; - global.HTMLElement = new JSDOM().window.HTMLElement; -}); - -describe('htmlTreeAsString', () => { - beforeEach(() => { - document.body.innerHTML = ''; - }); - - it('generates html tree for a simple element', () => { - const el = document.createElement('ul'); - el.innerHTML = `
  • -
  • `; - document.body.appendChild(el); - - expect(htmlTreeAsString(document.getElementById('err-btn'))).toBe( - 'body > ul > li.container > button#err-btn.button', - ); - }); - - it('generates unknown for an empty element', () => { - expect(htmlTreeAsString(undefined)).toBe(''); - }); - - it('inserts pre-defined attribute values by default', () => { - const el = document.createElement('ul'); - el.innerHTML = `
  • - kitten -
  • `; - document.body.appendChild(el); - - expect(htmlTreeAsString(document.getElementById('cat'))).toBe( - 'body > ul > li.container[title="container-title"] > img#cat[alt="kitten"]', - ); - }); - - it('insert key attribute instead of class names or ids when serializeAttribute is defined and the element has it', () => { - const el = document.createElement('ul'); - el.innerHTML = `
  • - -
  • `; - document.body.appendChild(el); - - // Two formats for specifying keyAttrs - expect(htmlTreeAsString(document.getElementById('cat-2'), ['test-id'])).toBe( - 'body > ul > li.li-class[title="li-title"] > img[test-id="cat-2-test-id"]', - ); - expect(htmlTreeAsString(document.getElementById('cat-2'), { keyAttrs: ['test-id'] })).toBe( - 'body > ul > li.li-class[title="li-title"] > img[test-id="cat-2-test-id"]', - ); - }); - - it('caps string output according to provided maxStringLength', () => { - const el = document.createElement('div'); - el.innerHTML = `
    -
    -
    -
    `; - document.body.appendChild(el); - - expect(htmlTreeAsString(document.querySelector('button'))).toBe( - 'button.bg-blue-500.hover:bg-blue-700.text-white.hover:text-blue-100', - ); - expect(htmlTreeAsString(document.querySelector('button'), { maxStringLength: 100 })).toBe( - 'div#main-cta > div.container > button.bg-blue-500.hover:bg-blue-700.text-white.hover:text-blue-100', - ); - }); -});