From 74819490049a975272031ba074ced48ed31a7042 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 12 Jul 2023 17:17:20 -0400 Subject: [PATCH] feat: Allow for masking of attributes via `maskAttributesFn` This currently adds a new API, `maskAttributesFn: (key: string, value: string, element: HTMLElement) => string`, that is used as a callback in `transformAttribute`. I prefer this API as it gives more flexibility for users (though it may need to pass the el node for most flexibility), but it is inconsistent with `maskTextFn` and `maskInputFn`. other options: * Rename this to something else (open to ideas) * Change this to pass value, and dom element (similar to MaskInputFn) to customize masking instead of decision maker of when to mask and introduce a simpler declarative API for what attributes to mask * ??? --- .changeset/twenty-tables-call.md | 6 + guide.md | 1 + packages/rrweb-snapshot/src/snapshot.ts | 24 + packages/rrweb-snapshot/src/types.ts | 5 + .../__snapshots__/integration.test.ts.snap | 6 + packages/rrweb-snapshot/test/snapshot.test.ts | 47 ++ packages/rrweb/src/record/index.ts | 4 + packages/rrweb/src/record/mutation.ts | 5 + packages/rrweb/src/types.ts | 4 + .../__snapshots__/integration.test.ts.snap | 780 +++++++++++++++++- packages/rrweb/test/html/form.html | 2 +- packages/rrweb/test/integration.test.ts | 77 +- .../cross-origin-iframes.test.ts.snap | 8 +- packages/rrweb/test/utils.ts | 1 + 14 files changed, 920 insertions(+), 50 deletions(-) create mode 100644 .changeset/twenty-tables-call.md diff --git a/.changeset/twenty-tables-call.md b/.changeset/twenty-tables-call.md new file mode 100644 index 0000000000..add796cc59 --- /dev/null +++ b/.changeset/twenty-tables-call.md @@ -0,0 +1,6 @@ +--- +'rrweb-snapshot': patch +'rrweb': patch +--- + +Add `maskAttributesFn` to be called when transforming an attribute. This is typically used to determine if an attribute should be masked or not. diff --git a/guide.md b/guide.md index e2dbf0d23f..8307cb756d 100644 --- a/guide.md +++ b/guide.md @@ -148,6 +148,7 @@ The parameter of `rrweb.record` accepts the following options. | maskTextSelector | null | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter | | maskAllInputs | false | mask all input content as \* | | maskInputOptions | { password: true } | mask some kinds of input \*
refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95) | +| maskAttributeFn | - | callback before transforming attribute. can be used to mask specific attributes | | maskInputFn | - | customize mask input content recording logic | | maskTextFn | - | customize mask text content recording logic | | slimDOMOptions | {} | remove unnecessary parts of the DOM
refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108) | diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts index bb18542d26..326be8ce1f 100644 --- a/packages/rrweb-snapshot/src/snapshot.ts +++ b/packages/rrweb-snapshot/src/snapshot.ts @@ -11,6 +11,7 @@ import { KeepIframeSrcFn, ICanvas, serializedElementNodeWithId, + MaskAttributeFn, } from './types'; import { Mirror, @@ -229,6 +230,8 @@ export function transformAttribute( tagName: Lowercase, name: Lowercase, value: string | null, + element: HTMLElement, + maskAttributeFn: MaskAttributeFn | undefined, ): string | null { if (!value) { return value; @@ -257,6 +260,11 @@ export function transformAttribute( return absoluteToDoc(doc, value); } + // Custom attribute masking + if (typeof maskAttributeFn === 'function') { + return maskAttributeFn(name, value, element); + } + return value; } @@ -437,6 +445,7 @@ function serializeNode( mirror: Mirror; blockClass: string | RegExp; blockSelector: string | null; + maskAttributeFn: MaskAttributeFn | undefined; maskTextClass: string | RegExp; maskTextSelector: string | null; inlineStylesheet: boolean; @@ -458,6 +467,7 @@ function serializeNode( mirror, blockClass, blockSelector, + maskAttributeFn, maskTextClass, maskTextSelector, inlineStylesheet, @@ -500,6 +510,7 @@ function serializeNode( blockClass, blockSelector, inlineStylesheet, + maskAttributeFn, maskInputOptions, maskInputFn, dataURLOptions, @@ -605,6 +616,7 @@ function serializeElementNode( blockClass: string | RegExp; blockSelector: string | null; inlineStylesheet: boolean; + maskAttributeFn: MaskAttributeFn | undefined; maskInputOptions: MaskInputOptions; maskInputFn: MaskInputFn | undefined; dataURLOptions?: DataURLOptions; @@ -624,6 +636,7 @@ function serializeElementNode( blockSelector, inlineStylesheet, maskInputOptions = {}, + maskAttributeFn, maskInputFn, dataURLOptions = {}, inlineImages, @@ -644,6 +657,8 @@ function serializeElementNode( tagName, toLowerCase(attr.name), attr.value, + n, + maskAttributeFn, ); } } @@ -938,6 +953,7 @@ export function serializeNodeWithId( inlineStylesheet: boolean; newlyAddedElement?: boolean; maskInputOptions?: MaskInputOptions; + maskAttributeFn: MaskAttributeFn | undefined; maskTextFn: MaskTextFn | undefined; maskInputFn: MaskInputFn | undefined; slimDOMOptions: SlimDOMOptions; @@ -969,6 +985,7 @@ export function serializeNodeWithId( skipChild = false, inlineStylesheet = true, maskInputOptions = {}, + maskAttributeFn, maskTextFn, maskInputFn, slimDOMOptions, @@ -993,6 +1010,7 @@ export function serializeNodeWithId( maskTextSelector, inlineStylesheet, maskInputOptions, + maskAttributeFn, maskTextFn, maskInputFn, dataURLOptions, @@ -1066,6 +1084,7 @@ export function serializeNodeWithId( skipChild, inlineStylesheet, maskInputOptions, + maskAttributeFn, maskTextFn, maskInputFn, slimDOMOptions, @@ -1126,6 +1145,7 @@ export function serializeNodeWithId( skipChild: false, inlineStylesheet, maskInputOptions, + maskAttributeFn, maskTextFn, maskInputFn, slimDOMOptions, @@ -1173,6 +1193,7 @@ export function serializeNodeWithId( skipChild: false, inlineStylesheet, maskInputOptions, + maskAttributeFn, maskTextFn, maskInputFn, slimDOMOptions, @@ -1213,6 +1234,7 @@ function snapshot( maskTextSelector?: string | null; inlineStylesheet?: boolean; maskAllInputs?: boolean | MaskInputOptions; + maskAttributeFn?: MaskAttributeFn; maskTextFn?: MaskTextFn; maskInputFn?: MaskTextFn; slimDOM?: 'all' | boolean | SlimDOMOptions; @@ -1244,6 +1266,7 @@ function snapshot( inlineImages = false, recordCanvas = false, maskAllInputs = false, + maskAttributeFn, maskTextFn, maskInputFn, slimDOM = false, @@ -1309,6 +1332,7 @@ function snapshot( skipChild: false, inlineStylesheet, maskInputOptions, + maskAttributeFn, maskTextFn, maskInputFn, slimDOMOptions, diff --git a/packages/rrweb-snapshot/src/types.ts b/packages/rrweb-snapshot/src/types.ts index 9edb4dd6d4..4afa81ee0a 100644 --- a/packages/rrweb-snapshot/src/types.ts +++ b/packages/rrweb-snapshot/src/types.ts @@ -155,6 +155,11 @@ export type DataURLOptions = Partial<{ export type MaskTextFn = (text: string) => string; export type MaskInputFn = (text: string, element: HTMLElement) => string; +export type MaskAttributeFn = ( + attributeName: string, + attributeValue: string, + element: HTMLElement, +) => string; export type KeepIframeSrcFn = (src: string) => boolean; diff --git a/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap b/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap index 529a51eeff..d6d424d92e 100644 --- a/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap +++ b/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap @@ -363,6 +363,12 @@ exports[`integration tests [html file]: picture-in-frame.html 1`] = ` " `; +exports[`integration tests [html file]: picture-with-inline-onload.html 1`] = ` +" + \\"This + " +`; + exports[`integration tests [html file]: preload.html 1`] = ` " diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts index 75d635e0c0..fa01a4d9a8 100644 --- a/packages/rrweb-snapshot/test/snapshot.test.ts +++ b/packages/rrweb-snapshot/test/snapshot.test.ts @@ -5,6 +5,7 @@ import { JSDOM } from 'jsdom'; import { absoluteToStylesheet, serializeNodeWithId, + transformAttribute, _isBlockedElement, } from '../src/snapshot'; import { serializedNodeWithId } from '../src/types'; @@ -110,6 +111,50 @@ describe('absolute url to stylesheet', () => { }); }); +describe('transformAttribute()', () => { + it('handles empty attribute value', () => { + expect( + transformAttribute( + document, + 'a', + 'data-loading', + null, + document.createElement('span'), + undefined, + ), + ).toBe(null); + expect( + transformAttribute( + document, + 'a', + 'data-loading', + '', + document.createElement('span'), + undefined, + ), + ).toBe(''); + }); + + it('handles custom masking function', () => { + const maskAttributeFn = jest + .fn() + .mockImplementation((_key, value): string => { + return value.split('').reverse().join(''); + }) as any; + expect( + transformAttribute( + document, + 'a', + 'data-loading', + 'foo', + document.createElement('span'), + maskAttributeFn, + ), + ).toBe('oof'); + expect(maskAttributeFn).toHaveBeenCalledTimes(1); + }); +}); + describe('isBlockedElement()', () => { const subject = (html: string, opt: any = {}) => _isBlockedElement(render(html), 'rr-block', opt.blockSelector); @@ -147,6 +192,7 @@ describe('style elements', () => { maskTextSelector: null, skipChild: false, inlineStylesheet: true, + maskAttributeFn: undefined, maskTextFn: undefined, maskInputFn: undefined, slimDOMOptions: {}, @@ -192,6 +238,7 @@ describe('scrollTop/scrollLeft', () => { maskTextSelector: null, skipChild: false, inlineStylesheet: true, + maskAttributeFn: undefined, maskTextFn: undefined, maskInputFn: undefined, slimDOMOptions: {}, diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 563942312f..fb6bde26c3 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -70,6 +70,7 @@ function record( maskAllInputs, maskInputOptions: _maskInputOptions, slimDOMOptions: _slimDOMOptions, + maskAttributeFn, maskInputFn, maskTextFn, hooks, @@ -328,6 +329,7 @@ function record( inlineStylesheet, maskInputOptions, dataURLOptions, + maskAttributeFn, maskTextFn, maskInputFn, recordCanvas, @@ -370,6 +372,7 @@ function record( maskTextSelector, inlineStylesheet, maskAllInputs: maskInputOptions, + maskAttributeFn, maskTextFn, slimDOM: slimDOMOptions, dataURLOptions, @@ -532,6 +535,7 @@ function record( userTriggeredOnInput, collectFonts, doc, + maskAttributeFn, maskInputFn, maskTextFn, keepIframeSrcFn, diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index 39a6107635..18be827e55 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -175,6 +175,7 @@ export default class MutationBuffer { private maskTextSelector: observerParam['maskTextSelector']; private inlineStylesheet: observerParam['inlineStylesheet']; private maskInputOptions: observerParam['maskInputOptions']; + private maskAttributeFn: observerParam['maskAttributeFn']; private maskTextFn: observerParam['maskTextFn']; private maskInputFn: observerParam['maskInputFn']; private keepIframeSrcFn: observerParam['keepIframeSrcFn']; @@ -200,6 +201,7 @@ export default class MutationBuffer { 'maskTextSelector', 'inlineStylesheet', 'maskInputOptions', + 'maskAttributeFn', 'maskTextFn', 'maskInputFn', 'keepIframeSrcFn', @@ -303,6 +305,7 @@ export default class MutationBuffer { newlyAddedElement: true, inlineStylesheet: this.inlineStylesheet, maskInputOptions: this.maskInputOptions, + maskAttributeFn: this.maskAttributeFn, maskTextFn: this.maskTextFn, maskInputFn: this.maskInputFn, slimDOMOptions: this.slimDOMOptions, @@ -601,6 +604,8 @@ export default class MutationBuffer { toLowerCase(target.tagName), toLowerCase(attributeName), value, + target, + this.maskAttributeFn, ); } break; diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts index dd9a516709..e73ef50618 100644 --- a/packages/rrweb/src/types.ts +++ b/packages/rrweb/src/types.ts @@ -5,6 +5,7 @@ import type { MaskInputFn, MaskTextFn, DataURLOptions, + MaskAttributeFn, } from 'rrweb-snapshot'; import type { PackFn, UnpackFn } from './packer/base'; import type { IframeManager } from './record/iframe-manager'; @@ -50,6 +51,7 @@ export type recordOptions = { maskTextSelector?: string; maskAllInputs?: boolean; maskInputOptions?: MaskInputOptions; + maskAttributeFn?: MaskAttributeFn; maskInputFn?: MaskInputFn; maskTextFn?: MaskTextFn; slimDOMOptions?: SlimDOMOptions | 'all' | true; @@ -87,6 +89,7 @@ export type observerParam = { maskTextClass: maskTextClass; maskTextSelector: string | null; maskInputOptions: MaskInputOptions; + maskAttributeFn?: MaskAttributeFn; maskInputFn?: MaskInputFn; maskTextFn?: MaskTextFn; keepIframeSrcFn: KeepIframeSrcFn; @@ -130,6 +133,7 @@ export type MutationBufferParam = Pick< | 'maskTextSelector' | 'inlineStylesheet' | 'maskInputOptions' + | 'maskAttributeFn' | 'maskTextFn' | 'maskInputFn' | 'keepIframeSrcFn' diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap index f244948b67..abb7e71378 100644 --- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap @@ -600,6 +600,170 @@ exports[`record integration tests can freeze mutations 1`] = ` ]" `; +exports[`record integration tests can mask attribute on mutation 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 4 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 6 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"p\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"mutation observer\\", + \\"id\\": 8 + } + ], + \\"id\\": 7 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"ul\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"li\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 + } + ], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"canvas\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 15 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 16 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 18 + } + ], + \\"id\\": 17 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\", + \\"id\\": 19 + } + ], + \\"id\\": 5 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [ + { + \\"parentId\\": 5, + \\"id\\": 10 + } + ], + \\"adds\\": [] + } + } +]" +`; + exports[`record integration tests can mask character data mutations 1`] = ` "[ { @@ -1764,7 +1928,9 @@ exports[`record integration tests can record form interactions 1`] = ` \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { - \\"type\\": \\"text\\" + \\"type\\": \\"text\\", + \\"title\\": \\"title text\\", + \\"placeholder\\": \\"placeholder text\\" }, \\"childNodes\\": [], \\"id\\": 22 @@ -3545,7 +3711,9 @@ exports[`record integration tests can use maskInputOptions to configure which ty \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { - \\"type\\": \\"text\\" + \\"type\\": \\"text\\", + \\"title\\": \\"title text\\", + \\"placeholder\\": \\"placeholder text\\" }, \\"childNodes\\": [], \\"id\\": 22 @@ -5311,34 +5479,526 @@ exports[`record integration tests should handle recursive console messages 1`] = \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"Log record\\", - \\"id\\": 13 + \\"textContent\\": \\"Log record\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 19 + } + ], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 20 + } + ], + \\"id\\": 16 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 6, + \\"data\\": { + \\"plugin\\": \\"rrweb/console@1\\", + \\"payload\\": { + \\"level\\": \\"log\\", + \\"trace\\": [ + \\"__puppeteer_evaluation_script__:20:21\\" + ], + \\"payload\\": [ + \\"\\\\\\"Proxied object:\\\\\\"\\", + \\"\\\\\\"[object Object]\\\\\\"\\" + ] + } + } + } +]" +`; + +exports[`record integration tests should mask attribute via function call 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"http-equiv\\": \\"X-UA-Compatible\\", + \\"content\\": \\"ie=edge\\" + }, + \\"childNodes\\": [], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 11 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"form fields\\", + \\"id\\": 13 + } + ], + \\"id\\": 12 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 14 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n\\\\n \\", + \\"id\\": 15 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 17 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"form\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"text\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 21 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"text\\", + \\"title\\": \\"title text\\", + \\"placeholder\\": \\"*********** ****\\" + }, + \\"childNodes\\": [], + \\"id\\": 22 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 23 + } + ], + \\"id\\": 20 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 24 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 26 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"radio\\", + \\"name\\": \\"toggle\\", + \\"value\\": \\"on\\" + }, + \\"childNodes\\": [], + \\"id\\": 27 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 28 + } + ], + \\"id\\": 25 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 29 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 31 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"radio\\", + \\"name\\": \\"toggle\\", + \\"value\\": \\"off\\", + \\"checked\\": true + }, + \\"childNodes\\": [], + \\"id\\": 32 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 33 + } + ], + \\"id\\": 30 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 34 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"checkbox\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 36 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"checkbox\\" + }, + \\"childNodes\\": [], + \\"id\\": 37 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 38 + } + ], + \\"id\\": 35 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 39 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"textarea\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 41 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"textarea\\", + \\"attributes\\": { + \\"name\\": \\"\\", + \\"id\\": \\"\\", + \\"cols\\": \\"30\\", + \\"rows\\": \\"10\\", + \\"data-unmask-example\\": \\"true\\" + }, + \\"childNodes\\": [], + \\"id\\": 42 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 43 + } + ], + \\"id\\": 40 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 44 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"select\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 46 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"select\\", + \\"attributes\\": { + \\"name\\": \\"\\", + \\"id\\": \\"\\", + \\"value\\": \\"1\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 48 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"option\\", + \\"attributes\\": { + \\"value\\": \\"1\\", + \\"selected\\": true + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"1\\", + \\"id\\": 50 + } + ], + \\"id\\": 49 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 51 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"option\\", + \\"attributes\\": { + \\"value\\": \\"2\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"2\\", + \\"id\\": 53 + } + ], + \\"id\\": 52 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 54 + } + ], + \\"id\\": 47 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 55 + } + ], + \\"id\\": 45 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 56 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"password\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 58 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"input\\", + \\"attributes\\": { + \\"type\\": \\"password\\" + }, + \\"childNodes\\": [], + \\"id\\": 59 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 60 + } + ], + \\"id\\": 57 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 61 } ], - \\"id\\": 12 + \\"id\\": 18 }, { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 14 - } - ], - \\"id\\": 4 - }, - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 15 - }, - { - \\"type\\": 2, - \\"tagName\\": \\"body\\", - \\"attributes\\": {}, - \\"childNodes\\": [ - { - \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 17 + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 62 }, { \\"type\\": 2, @@ -5348,15 +6008,15 @@ exports[`record integration tests should handle recursive console messages 1`] = { \\"type\\": 3, \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", - \\"id\\": 19 + \\"id\\": 64 } ], - \\"id\\": 18 + \\"id\\": 63 }, { \\"type\\": 3, \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", - \\"id\\": 20 + \\"id\\": 65 } ], \\"id\\": 16 @@ -5374,19 +6034,47 @@ exports[`record integration tests should handle recursive console messages 1`] = } }, { - \\"type\\": 6, + \\"type\\": 3, \\"data\\": { - \\"plugin\\": \\"rrweb/console@1\\", - \\"payload\\": { - \\"level\\": \\"log\\", - \\"trace\\": [ - \\"__puppeteer_evaluation_script__:20:21\\" - ], - \\"payload\\": [ - \\"\\\\\\"Proxied object:\\\\\\"\\", - \\"\\\\\\"[object Object]\\\\\\"\\" - ] - } + \\"source\\": 2, + \\"type\\": 5, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"t\\", + \\"isChecked\\": false, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"te\\", + \\"isChecked\\": false, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"tes\\", + \\"isChecked\\": false, + \\"id\\": 22 + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 5, + \\"text\\": \\"test\\", + \\"isChecked\\": false, + \\"id\\": 22 } } ]" @@ -5546,7 +6234,9 @@ exports[`record integration tests should mask inputs via function call 1`] = ` \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { - \\"type\\": \\"text\\" + \\"type\\": \\"text\\", + \\"title\\": \\"title text\\", + \\"placeholder\\": \\"placeholder text\\" }, \\"childNodes\\": [], \\"id\\": 22 @@ -9007,7 +9697,9 @@ exports[`record integration tests should not record input values if maskAllInput \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { - \\"type\\": \\"text\\" + \\"type\\": \\"text\\", + \\"title\\": \\"title text\\", + \\"placeholder\\": \\"placeholder text\\" }, \\"childNodes\\": [], \\"id\\": 22 @@ -12715,7 +13407,9 @@ exports[`record integration tests should record input userTriggered values if us \\"type\\": 2, \\"tagName\\": \\"input\\", \\"attributes\\": { - \\"type\\": \\"text\\" + \\"type\\": \\"text\\", + \\"title\\": \\"title text\\", + \\"placeholder\\": \\"placeholder text\\" }, \\"childNodes\\": [], \\"id\\": 22 diff --git a/packages/rrweb/test/html/form.html b/packages/rrweb/test/html/form.html index 9125e983fa..db1c127320 100644 --- a/packages/rrweb/test/html/form.html +++ b/packages/rrweb/test/html/form.html @@ -10,7 +10,7 @@