fix block strategy

If an element was blocked, its child nodes should also be blocked.
The interactions and mutations on the element and its child nodes
also need to be blocked.
This commit is contained in:
Yanzhen Yu
2018-12-30 21:46:23 +08:00
parent e563dc1638
commit 8f24cb78b3
5 changed files with 256 additions and 6 deletions

View File

@@ -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<Node>();
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<UIEvent>(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;
}

View File

@@ -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);
}

View File

@@ -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`] = `
"[
{

12
test/html/block.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Block record</title>
</head>
<body>
<div class="rr-block"><input type="text" /> <span id="text"></span></div>
</body>
</html>

View File

@@ -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());
});
});