diff --git a/src/record/observer.ts b/src/record/observer.ts index 53622267..c3112532 100644 --- a/src/record/observer.ts +++ b/src/record/observer.ts @@ -6,6 +6,7 @@ import { hookSetter, getWindowHeight, getWindowWidth, + isBlocked, } from '../utils'; import { mutationCallBack, @@ -53,6 +54,9 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver { const addsSet = new Set(); const genAdds = (n: Node) => { + if (isBlocked(n)) { + return; + } addsSet.add(n); n.childNodes.forEach(childN => genAdds(childN)); }; @@ -68,7 +72,7 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver { switch (type) { case 'characterData': { const value = target.textContent; - if (value !== oldValue) { + if (!isBlocked(target) && value !== oldValue) { texts.push({ value, node: target, @@ -78,7 +82,7 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver { } case 'attributes': { const value = (target as HTMLElement).getAttribute(attributeName!); - if (value === oldValue) { + if (isBlocked(target) || value === oldValue) { return; } let item: attributeCursor | undefined = attributes.find( @@ -93,10 +97,14 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver { } // overwrite attribute if the mutations was triggered in same time item.attributes[attributeName!] = value; + break; } case 'childList': { addedNodes.forEach(n => genAdds(n)); removedNodes.forEach(n => { + if (isBlocked(n)) { + return; + } // removed node has not been serialized yet, just remove it from the Set if (addsSet.has(n)) { addsSet.delete(n); @@ -171,7 +179,7 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver { } }); - cb({ + const payload = { texts: texts .map(text => ({ id: mirror.getId(text.node as INode), @@ -188,7 +196,17 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver { .filter(attribute => mirror.has(attribute.id)), removes, adds, - }); + }; + // payload may be empty if the mutations happened in some blocked elements + if ( + !payload.texts.length && + !payload.attributes.length && + !payload.removes.length && + !payload.adds.length + ) { + return; + } + cb(payload); }); observer.observe(document, { attributes: true, @@ -243,6 +261,9 @@ function initMouseInteractionObserver( const handlers: listenerHandler[] = []; const getHandler = (eventKey: keyof typeof MouseInteractions) => { return (event: MouseEvent) => { + if (isBlocked(event.target as Node)) { + return; + } const id = mirror.getId(event.target as INode); const { clientX, clientY } = event; cb({ @@ -267,7 +288,7 @@ function initMouseInteractionObserver( function initScrollObserver(cb: scrollCallback): listenerHandler { const updatePosition = throttle(evt => { - if (!evt.target) { + if (!evt.target || isBlocked(evt.target as Node)) { return; } const id = mirror.getId(evt.target as INode); @@ -318,7 +339,8 @@ function initInputObserver(cb: inputCallback): listenerHandler { if ( !target || !(target as Element).tagName || - INPUT_TAGS.indexOf((target as Element).tagName) < 0 + INPUT_TAGS.indexOf((target as Element).tagName) < 0 || + isBlocked(target as Node) ) { return; } diff --git a/src/utils.ts b/src/utils.ts index 4d588cfd..b770259e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -112,3 +112,17 @@ export function getWindowWidth(): number { (document.body && document.body.clientWidth) ); } + +const BLOCK_CLASS = 'rr-block'; +export function isBlocked(node: Node | null): boolean { + if (!node) { + return false; + } + if (node.nodeType === node.ELEMENT_NODE) { + return ( + (node as HTMLElement).classList.contains(BLOCK_CLASS) || + isBlocked(node.parentNode) + ); + } + return isBlocked(node.parentNode); +} diff --git a/test/__snapshots__/integration.test.ts.snap b/test/__snapshots__/integration.test.ts.snap index 7d9ebb6e..4d42c7d6 100644 --- a/test/__snapshots__/integration.test.ts.snap +++ b/test/__snapshots__/integration.test.ts.snap @@ -157,6 +157,190 @@ exports[`attributes 1`] = ` ]" `; +exports[`block 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {}, + \\"timestamp\\": 1542268800000 + }, + { + \\"type\\": 1, + \\"data\\": {}, + \\"timestamp\\": 1542268800000 + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + }, + \\"timestamp\\": 1542268800000 + }, + { + \\"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\\": \\"Block 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\\": \\"div\\", + \\"attributes\\": { + \\"class\\": \\"rr-block\\", + \\"rr_width\\": \\"1904px\\", + \\"rr_height\\": \\"21px\\" + }, + \\"childNodes\\": [], + \\"id\\": 18 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 19 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 21 + } + ], + \\"id\\": 20 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 22 + } + ], + \\"id\\": 16 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + }, + \\"timestamp\\": 1542268800000 + } +]" +`; + exports[`character-data 1`] = ` "[ { diff --git a/test/html/block.html b/test/html/block.html new file mode 100644 index 00000000..84088997 --- /dev/null +++ b/test/html/block.html @@ -0,0 +1,12 @@ + + + + + + + Block record + + +
+ + diff --git a/test/integration.test.ts b/test/integration.test.ts index 46458461..8f062946 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -252,4 +252,22 @@ describe('record integration tests', () => { ); assert(result.pass, result.pass ? '' : result.report()); }); + + it('should not record blocked elements and its child nodes', async () => { + const page: puppeteer.Page = await this.browser.newPage(); + await page.goto('about:blank'); + await page.setContent(getHtml.call(this, 'block.html')); + + await page.type('input', 'should not be record'); + await page.evaluate(`document.getElementById('text').innerText = '1'`); + await page.click('#text'); + + const snapshots = await page.evaluate('window.snapshots'); + const result = matchSnapshot( + stringifySnapshots(snapshots), + __filename, + 'block', + ); + assert(result.pass, result.pass ? '' : result.report()); + }); });