在一个风和日丽的日子里,忽然要运行一段代码,而后顺手打开控制台了。此时,恰好在一个页面。可是,一打开控制台,有一坨东西吸引了个人注意,其实就是那个页面的水印html
运行完个人代码了,又切回element板块,想删掉它(谁叫你那么大坨的,被我盯上了)。点一下选中这个div,而后按一下删除前端
"啪!",应该是我没按下。再“啪!”,啊?div闪了一下?“啪!”,我靠,删不掉!?node
那好,我改style。display: none, 安排! 怎么我一输,div又闪了一下,刚刚的修改全没了canvas
此时,怀疑的对象很快就出现了——MutationObserver数组
MutationObserver: 提供了监视对DOM树所作更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。具体可查看mdn浏览器
那么,大概的逻辑就是MutationObserver监听这个水印的变化,如删除、修改attr、新增子节点,而后直接从新渲染一个和本来如出一辙的元素出来,实现了“你就算打开控制台也改不了这个节点”的效果markdown
在source板块,找到了页面相关的js文件,搜索MutationObserver,最后发现一个这样的函数:app
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回去oop
删除节点的时候,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看你怎么玩......到此为止吧,事情老是道高一尺魔高一丈,再说就跑到服务端去斗智斗勇了
关注公众号《不同的前端》,以不同的视角学习前端,快速成长,一块儿把玩最新的技术、探索各类黑科技,一块儿脑洞大开搞事情