From cd7a51334ae4776a506b2747046ad1381366260d Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 20 Apr 2023 16:08:08 +0200 Subject: [PATCH] fix(utils): Normalize HTML elements as string (#7916) Currently, we do not special-case html elements in normalization. This means they are still normalized as objects, leading to potentially deeply nested stuff, and to problems with e.g. replay. --- .../non_serializable_context/test.ts | 2 +- packages/utils/src/normalize.ts | 11 +++++++- packages/utils/test/normalize.test.ts | 26 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/browser-integration-tests/suites/public-api/setContext/non_serializable_context/test.ts b/packages/browser-integration-tests/suites/public-api/setContext/non_serializable_context/test.ts index 3c6d17dbdb03..9b270205f109 100644 --- a/packages/browser-integration-tests/suites/public-api/setContext/non_serializable_context/test.ts +++ b/packages/browser-integration-tests/suites/public-api/setContext/non_serializable_context/test.ts @@ -9,6 +9,6 @@ sentryTest('should normalize non-serializable context', async ({ getLocalTestPat const eventData = await getFirstSentryEnvelopeRequest(page, url); - expect(eventData.contexts?.non_serializable).toMatchObject({}); + expect(eventData.contexts?.non_serializable).toEqual('[HTMLElement: HTMLBodyElement]'); expect(eventData.message).toBe('non_serializable'); }); diff --git a/packages/utils/src/normalize.ts b/packages/utils/src/normalize.ts index 508442c2d14e..4b2dd611f8e2 100644 --- a/packages/utils/src/normalize.ts +++ b/packages/utils/src/normalize.ts @@ -170,6 +170,7 @@ function visit( // TODO remove this in v7 (this means the method will no longer be exported, under any name) export { visit as walk }; +/* eslint-disable complexity */ /** * Stringify the given value. Handles various known special values and types. * @@ -242,11 +243,19 @@ function stringifyValue( // them to strings means that instances of classes which haven't defined their `toStringTag` will just come out as // `"[object Object]"`. If we instead look at the constructor's name (which is the same as the name of the class), // we can make sure that only plain objects come out that way. - return `[object ${getConstructorName(value)}]`; + const objName = getConstructorName(value); + + // Handle HTML Elements + if (/^HTML(\w*)Element$/.test(objName)) { + return `[HTMLElement: ${objName}]`; + } + + return `[object ${objName}]`; } catch (err) { return `**non-serializable** (${err})`; } } +/* eslint-enable complexity */ function getConstructorName(value: unknown): string { const prototype: Prototype | null = Object.getPrototypeOf(value); diff --git a/packages/utils/test/normalize.test.ts b/packages/utils/test/normalize.test.ts index 94676c1449da..008bde5dfebe 100644 --- a/packages/utils/test/normalize.test.ts +++ b/packages/utils/test/normalize.test.ts @@ -263,6 +263,32 @@ describe('normalize()', () => { }); }); + describe('handles HTML elements', () => { + test('HTMLDivElement', () => { + expect( + normalize({ + div: document.createElement('div'), + div2: document.createElement('div'), + }), + ).toEqual({ + div: '[HTMLElement: HTMLDivElement]', + div2: '[HTMLElement: HTMLDivElement]', + }); + }); + + test('input elements', () => { + expect( + normalize({ + input: document.createElement('input'), + select: document.createElement('select'), + }), + ).toEqual({ + input: '[HTMLElement: HTMLInputElement]', + select: '[HTMLElement: HTMLSelectElement]', + }); + }); + }); + describe('calls toJSON if implemented', () => { test('primitive values', () => { const a = new Number(1) as any;