打开控制台也删不掉的元素,前端都吓尿了
在一个风和日丽的日子里,突然要运行一段代码,然后顺手打开控制台了。此时,刚好在一个页面。但是,一打开控制台,有一坨东西吸引了我的注意,其实就是那个页面的水印
强迫症引发的好奇心
运行完我的代码了,又切回element板块,想删掉它(谁叫你那么大坨的,被我盯上了)。点一下选中这个div,然后按一下删除
"啪!",应该是我没按下。再“啪!”,啊?div闪了一下?“啪!”,我靠,删不掉!?
那好,我改style。display: none, 安排! 怎么我一输,div又闪了一下,刚刚的修改全没了
此时,怀疑的对象很快就出现了——MutationObserver
MutationObserver: 提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。具体可查看mdn
那么,大概的逻辑就是MutationObserver监听这个水印的变化,如删除、修改attr、新增子节点,然后直接重新渲染一个和原本一模一样的元素出来,实现了“你就算打开控制台也改不了这个节点”的效果

源码中搜索研究
在source板块,找到了页面相关的js文件,搜索MutationObserver,最后发现一个这样的函数:
function observeSelector(e) {
if (e) {
var t = e.cloneNode(!0)
, n = e.parentNode || document.body;
new MutationObserver(function (r) {
e && r.forEach(function (r) {
var o = r.target
, i = Array.prototype.slice.call(r.removedNodes)[0];
if (o === e) {
var a = t.cloneNode(!0);
n.replaceChild(a, e),
e = a
} else
i === e && (e = e.cloneNode(!0),
n.appendChild(e))
})
}
).observe(document.body, {
attributes: !0,
childList: !0,
subtree: !0
})
}
}
改一下,增强可读性:
function observeSelector(element) {
if (element) {
const parentNode = element.parentNode || document.body;
// 为什么这么做?因为这是最原始的节点了
// 如果直接拿element去replace只能拿到具有最新属性的节点
const newClonedNode = element.cloneNode(true);
new MutationObserver(mutations => {
mutations.forEach(mutationRecord => {
const currentTarget = mutationRecord.target;
const removedNode = mutationRecord.removedNodes[0];
// 修改属性的时候,target就是当前元素
if (currentTarget === element) {
const replaceNode = newClonedNode.cloneNode(true);
parentNode.replaceChild(replaceNode, element);
element = replaceNode;
} else {
// 删除元素的时候,removedNodes是一个数组,只删它一个,那第一个就是当前元素
if (removedNode === element) {
element = element.cloneNode(true);
parentNode.appendChild(element);
}
}
});
}).observe(document.body, {
attributes: true,
childList: true,
subtree: true, // 监听后代节点变化
});
}
}
-
修改属性的时候(attributes为true情况下修改节点的属性才能触发这个回调),此时mutations每一个元素mutationRecord下的target就是当前节点。思路就是:改一下就replace回去
-
删除节点的时候,mutationRecord下的removedNodes数组是当前被删掉的所有的节点组成的数组。当然这里我们只删了一个节点,所以就只有它一个节点了。思路就是:删一个就append回去
这个函数可以直接拿来用在“保护元素”上了,给一个element加上MutationObserver,防止其他有技术背景的人打开控制台修改这个元素去做一些其他不可告人的秘密事情(截图造假、越过权限、暴露数据但有水印)
这个函数可以拿出来做保护元素使用,防止一些前端打开控制台修改元素,然后截图。当然,需求中如果需要用的话,需要考虑的事情:及时清除observer、可扩展性,兼容性还行

如何战胜它
魔改样式
改父节点的样式可以解决,但是此页面的水印父节点就是body,改了body,就影响浏览页面了。我们可以换一个角度,给水印的before伪元素加上透明背景样式,让他和水印颜色看起来差不多
// 137是canvas的getimagedata知道的
var str = `.水印div的class::before {
content: '';
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 10000;
background-color: rgba(137, 137, 137, 0.95);
pointer-events: none;
}`;
var style = document.createElement('style');
style.textContent = str;
document.head.appendChild(style);
// 酌情微调一下fliter,如对比度、亮度、饱和度等
document.body.style.filter = 'contrast(6.5)'
但是,这样子会让页面朦朦胧胧铺上一层
挪走主要内容
我们知道,干涉它父节点的样式就可以治理它了,但是我们怕误杀内容。那么,不如我们把内容挪走,再把body隐藏(appendChild具有“吸走”的效果)
// 控制台选中主内容, 即document.querySelector('水印元素选择器')
document.documentElement.appendChild($0)
然后,给body加一句display: none,一个无水印的洁白的页面出现了!
document.body.style.display = 'none';
道高一尺,魔高一丈。其实如果再用MutationObserver监听一下document.documentElement,发现新增了的是水印元素,就把它append回body去,又防住了:
((targetNode) => {
new MutationObserver((mutations) => {
mutations.forEach(({ addedNodes }) => {
addedNodes.forEach(node => {
if (node === targetNode) {
document.body.appendChild(targetNode)
}
})
});
}).observe(document.documentElement, {
childList: true,
});
})(document.querySelector('水印元素选择器'));
em...有没有想过套娃会怎样,观察html下新增目标节点,然后挪到body下;观察body下新增目标节点,然后挪到html下,然后又导致html下新增节点
((targetNode) => {
new MutationObserver((mutations) => {
mutations.forEach(({ addedNodes }) => {
addedNodes.forEach(node => {
if (node === targetNode) {
document.body.appendChild(targetNode)
}
})
});
}).observe(document.documentElement, {
childList: true,
});
})(document.querySelector('水印元素选择器'));
// 新增body的observe
((targetNode) => {
new MutationObserver((mutations) => {
mutations.forEach(({ addedNodes }) => {
addedNodes.forEach(node => {
if (node === targetNode) {
document.documentElement.appendChild(targetNode)
}
})
});
}).observe(document.body, {
childList: true,
});
})(document.querySelector('水印元素选择器'));
别说了,我的电脑热了很多,估计它的健康码已经变红了,需要和我隔离了。死循环的确是会发生的,使用的时候需要注意一下
如果要解决MutationObserver监听document.documentElement阻止挪水印元素,那也还是有办法,documentElement下新增一个div,水印元素挪到div里面即可
既然加了div越过这一步,那防止也可以再加强,MutationObserver来个一刀切,禁止所有的childList、subtree的发生,如果不是水印元素则删除,如果是水印元素则放回body去
都这么绝情,那我就写一个谷歌浏览器插件注入脚本,直接修改全局的MutationObserver看你怎么玩......到此为止吧,事情总是道高一尺魔高一丈,再说就跑到服务端去斗智斗勇了
关注公众号《不一样的前端》,以不一样的视角学习前端,快速成长,一起把玩最新的技术、探索各种黑科技,一起脑洞大开搞事情

转载自:https://juejin.cn/post/6844904167618641927