簡單粗暴,偵測網頁元素的建立

說到偵測網頁元素的建立,我想有在 follow web platform 的人們應該會立刻想到 MutationObserver ;不過這邊既然說了是 簡單粗暴 ,表示並不是要用 MutationObserver 來達成這個目標。

其實這招是一個行之有年的方法,不過礙於我本人最近才知道(長知識)所以才在這邊分享。

原理

利用 CSS selector 來對指定的元素套上一個無意義(不會影響原本元素顯示)的 CSS animation,於是在該元素建立的時候將會觸發 animationstart 事件,而我們就可以利用此事件找到觸發的來源而取得新建立的元素。

實作

原理簡單粗暴,實作也是簡單粗暴。

CSS

套用 CSS animation 的關鍵在於確實讓各主要瀏覽器都能夠確實的觸發 animationstart 事件(或它的 vender specific 版本),經過測試之後,以下的 CSS animation 可以確實觸發各主要瀏覽器(Chrome, Firefox, IE)上的事件。

@-webkit-keyframes pymaster-detect-dom-insertion { from {opacity: 0.99;} to {opacity: 1;} }
@keyframes         pymaster-detect-dom-insertion { from {opacity: 0.99;} to {opacity: 1;} }
{selector} {
  -webkit-animation-delay: 0s !important;
  -webkit-animation-name: pymaster-detect-dom-insertion !important;
  -webkit-animation-duration: 1ms !important;
  -webkit-animation-play-state: running !important;
  animation-delay: 0s !important;
  animation-name: pymaster-detect-dom-insertion !important;
  animation-duration: 1ms !important;
  animation-play-state: running !important;
}

{selector} 的部分可以自行代換成任何想要偵測的 selector。

另外,像 Chrome 這種可怕的瀏覽器在某些嚴苛條件下也還是可以觸發 animationstart 事件,於是套用的 CSS animation 可以顯得更無關緊要:

/* 切記,在你的目標只有 Chrome 時才可以這樣用 */

@-webkit-keyframes pymaster-detect-dom-insertion { from {} }
@keyframes         pymaster-detect-dom-insertion { from {} }
{selector} {
  -webkit-animation-delay: 0s !important;
  -webkit-animation-name: pymaster-detect-dom-insertion !important;
  -webkit-animation-play-state: running !important;
  animation-delay: 0s !important;
  animation-name: pymaster-detect-dom-insertion !important;
  animation-play-state: running !important;
}

JavaScript

要取得事件的方法也很單純,直接在 document 上 listen 就好啦! 至於要不要 use capture 就看自己的使用情況。

document.addEventListener('animationstart', onAnimationStart, true);
document.addEventListener('webkitAnimationStart', onAnimationStart, true);

function onAnimationStart(evt) {
	// Created element
  console.log(evt.target);
}

Demo

把上面講的東西組合起來就是一個小 demo 啦!

相關參考

Detect DOM Node Insertions with JavaScript and CSS Animations http://davidwalsh.name/detect-node-insertion

後記

看完之後你可能會想說,平常到底是怎麼樣會用到這樣的功能?

其實我是日前在翻 PDF.js chrome extension 的 source 的時候看到一段很有趣的東西,翻出當時的 PR 來看:

pdfjs_embed_pr.png

竟然可以在 Chrome / Opera 裡面攔截 <object><embed> 元素!?

原始 PR 的 commit 內容在這兒 https://github.com/Rob—W/pdf.js/commit/3fdd334a7321ba2020701492590b669b4a91d8a7

後來就在裡面發現了使用 CSS animation 來做偵測的寫法,它的流程大致上是:

  1. 利用 Chrome extension 的 content script 功能插入到每個網頁 (with document_start)
  2. 設定好偵測 <object><embed> 建立的 CSS, JavaScript
  3. 一旦發現目標是 application/pdf 就用 Shadow Dom 取代原本的元素
  4. Shadow Dom 裡面塞的就是 PDF.js viewer 的 iframe

真的是太酷了…