打開控制檯也刪不掉的元素,前端都嚇尿了

在一個風和日麗的日子裏,忽然要運行一段代碼,而後順手打開控制檯了。此時,恰好在一個頁面。可是,一打開控制檯,有一坨東西吸引了個人注意,其實就是那個頁面的水印html

強迫症引起的好奇心

運行完個人代碼了,又切回element板塊,想刪掉它(誰叫你那麼大坨的,被我盯上了)。點一下選中這個div,而後按一下刪除前端

"啪!",應該是我沒按下。再「啪!」,啊?div閃了一下?「啪!」,我靠,刪不掉!?node

那好,我改style。display: none, 安排! 怎麼我一輸,div又閃了一下,剛剛的修改全沒了canvas

此時,懷疑的對象很快就出現了——MutationObserver數組

MutationObserver: 提供了監視對DOM樹所作更改的能力。它被設計爲舊的Mutation Events功能的替代品,該功能是DOM3 Events規範的一部分。具體可查看mdn瀏覽器

那麼,大概的邏輯就是MutationObserver監聽這個水印的變化,如刪除、修改attr、新增子節點,而後直接從新渲染一個和本來如出一轍的元素出來,實現了「你就算打開控制檯也改不了這個節點」的效果app

源碼中搜索研究

在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回去ui

  • 刪除節點的時候,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看你怎麼玩......到此爲止吧,事情老是道高一尺魔高一丈,再說就跑到服務端去鬥智鬥勇了

關注公衆號《不同的前端》,以不同的視角學習前端,快速成長,一塊兒把玩最新的技術、探索各類黑科技,一塊兒腦洞大開搞事情