打开控制台也删不掉的元素,前端都吓尿了

在一个风和日丽的日子里,突然要运行一段代码,然后顺手打开控制台了。此时,刚好在一个页面。但是,一打开控制台,有一坨东西吸引了我的注意, 其实就是那个页面的水印

强迫症引发的好奇心

运行完我的代码了,又切回element板块,想删掉它(谁叫你那么大坨的,被我盯上了)。 点一下选中这个div,然后按一下删除

"啪!",应该是我没按下。再“啪!”,啊?div闪了一下?“啪!”,我靠,删不掉!?

那好,我改style。 display: none , 安排! 怎么我一输,div又闪了一下,刚刚的修改全没了

此时,怀疑的对象很快就出现了—— MutationObserver

MutationObserver: 提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。具体可查看 mdn: https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver

那么,大概的逻辑就是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去

都这么绝情, 那我就 写一个谷歌浏览器插件注入脚本,直接修改全局的 Mutation Observer看你怎么玩 . .. ...到此为止吧,事情总是道高一尺魔高一丈,再说就跑到服务端去斗智斗勇了

  • MutationObserver能监听dom的各种变化,可以实现一些更强大的功能,毕竟它监听的范围更广

  • 需要注意判断条件,防止死循环出现

  • 不需要的时候,需要使用disconnect来取消observe

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章