uBlock Origin 可以修改 DOM event

Hacker News 上看到的「Just normal web things (heather-buchel.com)」這篇,原文「Just normal web things.」本來在抱怨一些 SPA 年代常遇到的問題,但我注意到的反而是討論裡的 id=37018421 這則,介紹了 uBlock Origin 可以修改特定的 DOM event:

New Reddit also messes with text selection with unnecessary JS crap. Wherever you select some text, they show an "Embed" button that prevents you from dragging the text to a new tab to perform a web search.

Thankfully this behaviour can be blocked with uBlock Origin by adding these rules:

  www.reddit.com##+js(aeld, mousedown, isSelectionOutOfRange)
  www.reddit.com##+js(aeld, mouseup, shouldShowButton)

翻了 uBlock Origin 的 wiki 文件,這應該是 aeld.js / addEventListener-defuser.js 這個功能:

Prevents attaching event listeners.

aeld 可以阻止某些事件掛到 DOM 元素上,這感覺好像可以少寫不少 userscript,單純用 uBlock Origin 設條件就可以做到了?

另外在同一頁上 (Resources Library 這頁) 也有其他有趣的東西,像是拔掉 timeout 設定或是某些 attribute 的內容,另外也可以對 API 傳回來的 JSON 或 XML 內容進行刪除某些欄位...

算是意外注意到,後續希望遇到的時候可以想起來...

瀏覽器裡同一個節點上 JavaScript 的事件觸發順序

瀏覽器裡 JavaScript 的事件觸發順序是先 capture 再 bubble,這個在「Event order」這邊就有一些歷史解釋,IE8 以前只有 capture 模式,到了 IE9+ 才支援,在「Event API: bubbles」這邊也可以看到。

但如果是同一個節點上面的事件觸發順序 (假設同樣是 capture 或是同樣是 bubble),在「Are event handlers in JavaScript called in order?」這邊有些整理資料。

2000 年的「Document Object Model (DOM) Level 2 Events Specification」這邊提到沒有定義順序:

When the event reaches the target, any event listeners registered on the EventTarget are triggered. Although all EventListeners on the EventTarget are guaranteed to be triggered by any event which is received by that EventTarget, no specification is made as to the order in which they will receive the event with regards to the other EventListeners on the EventTarget.

在早期的 draft「Document Object Model (DOM) Level 3 Events Specification」裡面可以看到:

Next, the implementation must determine the current target's candidate event listeners. This must be the list of all event listeners that have been registered on the current target in their order of registration. [HTML5] defines the ordering of listeners registered through event handler attributes.

但在最新的「UI Events」(要注意這還是 draft,在 2016 年更新的) 則是拿掉了這段。

所以在設計架構時,正常還是得保守的假設沒有保證執行順序...

用 GPT-3 解讀程式碼

Hacker News 上看到的方法,Simon Willison 試著把程式碼餵進 GPT-3,然後問 GPT-3 程式碼的意思,看起來答的還不錯:「Using GPT-3 to explain how code works」,對應的討論 (包括 Simon Willison 的回應) 則可以在「Using GPT-3 to explain how code works (simonwillison.net)」這邊看到。

第一個範例裡面可以解讀 regular expression,雖然裡面對 (?xm) 的解讀是錯的,但我會說已經很強了...

第二個範例在解釋 Shadow DOM,看起來也解釋的很不錯...

第三個範例回來原來產生程式碼的例子,拿來生 SQL 指令。

後面的 bonus 題目居然是拿來解釋數學公式,他直接丟 TeX 文字進去要 GPT-3 解釋柯西不等式 (Cauchy–Schwarz inequality)。這樣我想到以前高微作業常常會有一堆證明題,好像可以丟進去要 GPT-3 給證明耶...

在 DOM 操作時的 insertAdjacentElement

看到「Why I'm still using jQuery in 2019」這篇,裡面提到了 jQuery 很多操作上相較於 vanilla javascript 簡單很多,其中一個例子提到了 DOM 操作的 insertAdjacentElement()

el.insertAdjacentElement('afterend', other) undoubtedly works, but $(el).after(other) is actually palatable.

其實我不知道 insertAdjacentElement() 這個功能,我知道的操作都是透過 parentElementfirstChild 在移動位置,然後用 appendChild()insertBefore() 放進去。

跑去 MDN 上查了「Element.insertAdjacentElement() - Web APIs | MDN」後才發現有這個好用的東西:

targetElement.insertAdjacentElement(position, element);

position 的四個變化減少了以前組積木組多了會頭暈的情況。

接下來是研究支援度的問題,才發現可能是因為 Firefox 一直到 48 才支援 (從「Firefox version history」可以看到 48 是 2016 年八月釋出),所以網路上大多數的文章都還是用組積木的方式在介紹 DOM 操作,以避免相容性的問題:

Firefox 48 was released on August 2, 2016 for both desktop and Android.

另外還看到 insert​Adjacent​HTML()insert​Adjacent​Text() 可以用,其中讓我注意到 MDN 上面提到 insert​Adjacent​HTML() 居然是 Firefox 8+?本來以為是 48+ 的誤植,但從 Mozilla 的記錄「Implement insertAdjacentHTML」這邊可以看到,應該是在 Firefox 8 的時候實作的,這樣的話可以當作 insertAdjacentElement() 的替代品 (如果考慮到古早的相容性),只是這邊需要輸入 html string,跟其他操作是是用 element 不太一致...

意外的學到不少歷史故事... @_@

修改 Firefox 的 Pop Up 限制

這是在 Bazqux 使用時遇到的問題,我在 Bazqux 這邊看到想看的文章,會習慣用 c 鍵打開新的 tab,等下再一起看。在 Chrome 上面需要安裝 extension 才能開到背景,在 Firefox 上可以透過修改 about:config 裡的browser.tabs.loadDivertedInBackground,讓他預設開到背景 (雖然變成所有的行為都會到開到背景 tab,但我偏好這樣...)。

但在 Bazqux 用 c 鍵把連結打開到 tab 時,有時發現會跳出遇到開啟上限:

這其實很不方便... 所以故意連打測試發現是 20 個,找了一下資料發現是 dom.popup_maximum 這個值,改成 -1 就好了。這算是某種安全機制吧...

先繼續用看看 :o

Mozilla Developer Network (MDN) 上的 JavaScript 教學

Mozilla Developer Network (MDN) 寫了一篇關於 JavaScript 的介紹文章,算是以現在的角度來教 JavaScript:「A re-introduction to JavaScript (JS tutorial)」。

不是給完全不懂的人入門看的,而是對程式語言有了解的人看的。

文章裡面不單純只是教學,還引用了許多重要的文獻,尤其是 ECMAScript 規格書。有想要考據確認規格書怎麼定義會很方便。

而最後面還提到了 browser 上 DOM 實作時的 memory leak 問題以及解法,這對於現在 single page application 的應用也愈來愈重要了。

GitHub 的 CSS 設計

在「GitHub's CSS」這篇裡面講了很多 GitHub 設計的工具,以及評估的方式。

原文有提到 IE9 以及之前的版本中,單檔有 4095 selector 的限制,這點讓人稍微怔了一下:

The split was added years ago to solve the problem of Internet Explorer’s 4,095 selectors per file limit.

超過的要切割讀入...

另外在文中有提到 2012 年的投影片「GitHub's CSS Performance」吸引我的目光:

針對 diff 頁面大量元素時的 CSS 效能分析,除了各種 browser 裡 index id & class & tag 的方式外,另外還有不少為了加速而直接修改 HTML 的建議 XDDD

在 HTML 內嵌 JSON object 時要注意的事情...

有時候我們會因為效能問題,在 HTML 內嵌入 JSON object,而不是再多一個 HTTP request 取得。

但「嵌入」的行為如果沒有處理好,就產生非常多 XSS attack vector 可以玩。

首先最常犯的錯誤是使用錯誤的 escape function:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "<?= addslashes($str) ?>";
</script>
</body>
</html>

這樣可以用 </script><script>alert(1);// 攻擊 $str。因為 addslashes() 並不會過濾到這個字串,而產生這樣的 HTML:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "</script><script>alert(1);//";
</script>
</body>
</html>

而這個字串會造成 DOM parser 解讀上產生不是我們預期的行為:

可以看到在字串裡面的 </script> 被拆開了。

這是因為瀏覽器會先拆解產生 DOM tree,再把 <script></script> 內的程式碼交給 JavaScript engine 處理。所以在一開始產生 DOM tree 的時候,是看不懂 JavaScript 程式邏輯的...

正確的方法是用 json_encode() 處理,因為 PHPjson_encode() 預設會把 / (slash) 變成 \/ (這是 JSON spec 裡合法的轉換):

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = <?= json_encode($str) ?>;
</script>
</body>
</html>

這會產生出:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "<\/script><script>alert(1);//";
</script>
</body>
</html>

但上面這段 HTML 與 PHP code 仍然有問題,如果 $str<!--<script 時,你會發現 DOM 又爛掉了:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "<!--<script>";
</script>
</body>
</html>

escape.alf.nu 的 Level 15 就是利用這個問題,再加上其他的漏洞而完成 XSS 攻擊。

為了這個問題去 StackOverflow 上問:「Why does <!--<script> cause a DOM tree break on the browser?」,才又發現上面這段 code 並不是合法的 HTML5 (先不管 head & title 的部份,補上後仍然不是合法的 HTML5)。

原因在於 DOM parser 對 <script></script> 的特殊處理:「4.3.1.2 Restrictions for contents of script elements」。(話說這段 ABNF 差點讓我翻桌...)

解法是在 <script></script> 的開頭與結尾加上 HTML 註解:(這剛好是 HTML 4.01 建議的方法)

<!DOCTYPE HTML>
<html>
<body>
<script>
<!--
var a = "<!--<script>";
-->
</script>
</body>
</html>

那段 ABNF 的目的是希望可以盡可能往後找到 --></script> 結尾的地方。

當然你也可以用 json_encode()JSON_HEX_TAG<> 硬轉成 \u003c\u003e 避開這個問題,但這使得呼叫 json_encode() 時要多一個參數 (而非預設參數),用起來比較卡...

這個問題會變得這麼討厭,是因為 DOM parser 與 JavaScript 語法之間有各自的處理方式,然後又有些 pattern 是之前的 spec 遺留下來的包袱 (像是 HTML 4.01 在「18.3.2 Hiding script data from user agents」裡有提到用 <!----> 包裝 <script></script>),變成在設計 HTML5 時都要考慮進去相容...

之前會習慣用 <!--//--> 包裝 <script></script> 倒不是這個原因,而是因為不這樣做的話,jQuery 在 IE 使用 html() 時遇到有 <script></script> 的字串會爛掉,所以後來寫的時候變成習慣了...

反而因為這個習慣而避開了這個問題...

超難搞啊...

Google Chrome 上預防 Clickjacking 的套件...

Gene 寫的「如何在你不知情被自動加入粉絲團的秘技, 以 "粉你的" 作示範」這篇裡面用到的技巧叫做 Clickjacking (點擊劫持)。

Facebook 有給出「What is clickjacking?」的說明,不過相當白話 (而且沒有幫助 Orz)。

技術上的解釋是,其中一種實作是在要點擊的對象上加上一層「透明的」DOM 物件,當 click 時就會點到該物件。目前最常見的是 Facebook 的 Like 按鈕。(i.e. Gene 寫的那篇)

Google Chrome 上可以用「Clickjacking Reveal」這個套件:

This extension tries to warn you if it found clickjacking technique on the page you are viewing.

Tired because of webpage tricks you into clicking social network buttons? This extension will try to detect those hidden bad buys and force them to show themselves.

效果是這樣:(範例出自「胖妞變身大美女 甩肉40斤練出腹肌」這篇)