uBlock Origin 再び @Firefox

uBlock Origin(以降 uBO)を入れてから一年ちょい
快適に使っているけれど、副作用というか誤爆も まあまあ ある。
気が付いたら その都度、Myフィルターにルールを追加して対応するのだけれど、たまにしかいじらないから文法とかぜんぜん頭に入ってないし、参考URL とかリンクを置いとくだけじゃダメだ。

というわけで、きちんと公式のページとか読んだ。
以下、未来の自分に向けてのメモ書き。



と、記事を書いてから随分と経ちます。
ちょいちょい機能変更があるので、ときどき記事を見直してはいますが、正確なところは公式を見てください。
この記事を書いた 2017年のときと比べると、公式のドキュメントも充実してきたことですし。


TL;DR

まずは、ブロックの種類から

https://github.com/gorhill/uBlock/wiki/Overview-of-uBlock's-network-filtering-engine
https://github.com/gorhill/uBlock/wiki/Overview-of-uBlock's-network-filtering-engine:-details

(1) whitelist ポップアップの青い電源ボタンで OFF にすると追加される
(2) url filtering rule ログの 4列目をつついて表示されるところで指定するやつ
(3) local dynamic filtering rule ポップアップの advanced user で表示される 2列目で指定するやつ(My ルールの一時的なルール)
(4) global dynamic filtering rule My ルールで、一時的なルールをコミットして、恒久的なルールにしたやつ
(5) static filtering rule 外部フィルター、My フィルター

ざくっとした指定しかできないけど、その分 軽いから、先に判定されるようになってるのがホワイトリストとかダイナミックルールで、このサイトは一括で OK とか NG みたいなのは、こちらが推奨。
コードをいじるような使い方だったら、Static Filter だけ覚えておけば良い、という感じか。

Static Filter の文法

https://github.com/gorhill/uBlock/wiki/Static-filter-syntax

Static network filtering と Static extended filtering の二種類。
Adblock Plus(以降 ABP)では、Filter と Element hiding と呼んでいたやつ。

Static network filtering は、指定されたリクエスト(通信)をブロックする。
スクリプトやフレームなどの通信がキャンセルされるので、広告を表示するための処理がスキップされる。
なので、通信量の削減(スマホだと顕著)や動作が軽くなるというおまけがつく。

Static extended filtering は、リクエストをブロックする以外の機能。大きく分けて五つある。

  • Entity ── CSSセレクタで指定した要素を見えなくする
  • Cosmetic filters ── CSS セレクタの uBO 拡張のようなもの。これも基本的に要素を見えなくすることに使う
  • Acrion operators ── CSS セレクタを指定して、要素を操作する
  • HTML filters ── CSSセレクタを指定して要素を見えなくするのだけれど、ブラウザのレンダリング前に処理される
  • Scriptlet injection ── スクリプトを注入する

テキスト埋め込みの広告のような「リクエストを出さない広告を消す」とか、anti ad-blocker を無効化したりすることができる。

基本は ABP の文法。
https://adblockplus.org/en/filter-cheatsheet
https://adblockplus.org/en/filters

Static network filtering

  • ブロック
    対象のURL + $ + オプション
  • 例外(ブロックの指定を打ち消す)
    @@ + 対象のURL + $ + オプション

#灰色は省略可

対象のURL

ワイルドキャラクタなんかが使える URL のパターン、もしくは、正規表現

special character
* wildcard character
| start or end anchor
|| domain name anchor
^ separator

セパレータは、正規表現の単語境界だと思ってると はまる。
_ - . %は、境界として扱われない。
ドメインに含まれるピリオドが、実際に踏んだ地雷だったり。

start anchor や domain name anchor は、検索パラメータ中にドメインっぽい表現がある場合なんかを除外できる。

オプション

ブロックの適用を種類やドメインで限定したり、動作を決めたりできる。
ABP のチートシートに書いてないオプションもあるし、uBO で拡張されたやつもある。
覚えておけば良いのは、このくらいか。

script, image, object, xmlhttprequest, popup リクエストの種類で限定
domain=, third-party, 1p, 3p ページのドメインで限定
important 例外指定よりも強いブロック
redirect= リクエストを置き換える
inline-script inline script をばさっと消しちゃう
csp meta http-equiv="Content-Security-Policy" を書き換える
badfilter フィルタを無効化する
例外指定ではなくこいつを使った方が良いケースがある

domain= に指定するドメインは、

  • | で区切って複数指定できる
  • ~ を頭につけて、「それ以外」を表現できる

Static extended filtering

  • 適用
    ドメイン名 or ホスト名 + ## + 指定
  • 例外(適用を打ち消す)
    ドメイン名 or ホスト名 + #@# + 指定

「ホスト名」は、URL ではなくホスト名。特定のページだけを対象にすることはできない。
カンマで区切って複数を指定できる。
一部の機能を除いて、一般的な suffix を *ワイルドカード)でマッチできる。
また、一部の機能では「ドメイン名」での指定ができて、サブドメインを省略することができる。
ドメインの話は、公式ドキュメントでは触れられていないので、そのうち変わるかも

Entity
ホスト名 + ## + CSS セレクタ

CSS セレクタで指定した要素を見えなくする。
「ホスト名」は省略すると、全てのドメインが対象となる。

「ホスト名」に * を指定すると、"Specific-generic" と呼ばれる指定になる。
ホスト名を省略したときとの違いは、以下の通り。

  • 省略:CSS セレクタに該当する要素が見つかった場合のみ、見えなくするスタイルが適用される
  • "*":CSS セレクタに該当する要素が見つからなくても、見えなくするスタイルが挿入される

何かのアクションで埋め込まれる要素や、Shadow DOM に対する効果が違うっぽい。

Cosmetic filters
ホスト名 + ## + Filters

「Filters」には、Procedural cosmetic filters が指定できる(後述)。

:style(...) は、以前は Procedural cosmetic filters に分類されていたが、uBO 1.28.0 くらいから "Action operators" に分類されるようになった。

Action operators
ドメイン + ##^ + Action operator

要素を見えなくするのではなく、要素に対しての操作を指定する。
ドメイン名にワイルドカードが使用できない。

HTML filters
ドメイン + ##^ + CSS セレクタ / Filters

要素を見えなくするのではなく、DOM を parse する前に response data から文字列として削除してしまう。
特に効果を発揮するのが、インラインの script タグ。

Scriptlet injection
ドメイン + ##+js( + scriptlet + )

「scriptlet」には、処理の名前(ショートカットもある)と、ものによってはパラメータがいくつか指定できる。
公式の解説 がある(uBO 1.24.0 くらいにできたらしい)。

以前は、assets/ublock/resources.txt で定義されていたのだけれど、それは "Legacy RAW Version" と言われていて、汎用的なものは .js として実装されている。

自由にコードを書けるわけではない(ということもない)ので、使えそうなやつを見繕っておく必要がある。

例外指定の場合、scriptlet の指定をしない ...#@#+js() という書き方だと、複数の指定をまるっと例外指定できる。

古いバージョンでは、script:inject(...) という書き方をだった。
uBO 1.15.12 で、+js(...) 書き方が実装されているけれど、公式のフィルターの定義でも script:inject(...) というコードは残ってる。

Procedural cosmetic filters

https://github.com/gorhill/uBlock/wiki/Procedural-cosmetic-filters

  • :has(...)
    ... な子要素を持つ親とか祖先を指定。
  • :has-text(...)
    ... なテキストを含む子要素を持つ親とか祖先を指定。正規表現も指定できる。 広告な要素は目印がついてない可能性は大きいので、:has() よりも強力か?
  • :if(...)
    :if-not(...)
    :has(...) には CSSセレクタしか指定できないけれど、一方、:if(...) の方は :has(...):xpath(...) のような uBO 拡張も書くことができる(ドキュメント)。
  • :matches-css(...)
    :matches-css-before(...)
    :matches-css-after(...)
    style で要素を限定。
    -before と -after は、:before と :after 疑似要素に対応
  • :xpath(...)
    XPath じゃないと指定できない要素はあるかな? (後述
  • :upward(N)
    指定された要素の先祖を対象にする。基本的にチェインとして使う。

ABP のセレクタ拡張は、uBO に対応するものがあるので、使う必要がないはず。

  • :-abp-properties():matches-css()
  • :-abp-has():has()

ABP のセレクタ拡張を使うときには、## の代わりに #?# を使うみたい(覚えなくて良い)。

AdBlock Plus との違い

ABP にあって uBO にない

uBO では、例外ルールの document オプションが効かない。
Whitelist を使ってくれ、ということだと思う。

ABP にはなくて uBO にある

Cosmetic Filter には、結構 追加されている。
Network Filter の HOSTS files って何だ?
domain anchor や、wildcard を書かなくても、ホスト名っぽい表記だったら通すよ、ってことかなあ……

ABP と決別したわけだから、ABP のページを参照しなくても済むように、GitHub の方に書いといて欲しい、という気はする。

Static Filter について整理

大きく分けて、Static network filtering と Static extended filtering の二種類。
Static extended filtering は、更に五つに分類される

  • Static network filtering
  • Static extended filtering
    • Entity
    • Cosmetic filters
      • Procedural cosmetic filters
    • Action operators
    • HTML filters
    • Scriptlet injection


Static network filtering は、指定されたリクエストをブロックする。
ページの URL が引っかかれば警告のページが表示されて、進むかどうかを聞いてくる。
ページ中の記述にある URL が引っかかれば、音なしで遮断。
「設定」の「ブロックされた要素の設置場所全てを非表示にする」にチェックを入れていると、大きさを持っている img や iframe なんかは display: none; が指定されて、要素が存在してないことになる。

Entity、Procedual cosmetic filters、HTML filters は、指定された要素を見えなくする。
テキスト埋め込みの広告など、リクエストを出さない広告をブロックする。

Entity と Procedual cosmetic filters は、CSS で見えなくしている。DOM のパースは行われるので、重いページや回線が細いと一瞬見えたりする。
一方、HTML filters は、DOM のパースが行われる前に、ページのレスポンスから文字列として取り除いてしまう。なので、javascript で動的に生成される要素には効かない。

Action operators は、要素に対しての操作を指定する。
uBO 1.28.4 までで、指定できるのは :remove():style(...) のふたつ。

:style(...) は、Entity や Procedual cosmetic filters で特定された要素に新たにスタイルを追加できる。
Stylus とかでもできるやつだけど、Procedual cosmetic filters があるので、CSSセレクタでは素直に指定できない要素に対してもスタイルを定義できる。

:remove() は、要素を削除する。
通常の要素に対しては、HTML filters と機能が被る。
DOM API で探すか、文字列処理で探すかの違い。

  • 削除されるタイミング
  • 実行時の負荷(:remove() の方が軽い?)
  • HTML filters には使えない Procedural cosmetic filters があるかも

という辺りの差異がある。

Scriptlet injection は、スクリプトの断片を対象のサイトで動かす(後述)。
 

My フィルタを書くにあたって

Static Filter の実例

uBlock filters から。

ドメインを指定して全てブロック
||mac-system-alert.com^
広告を見せない
youtube.com##.ad-div
外部フィルタの例外指定を打ち消す
# `important` must be used as there is one EasyList exception preventing # complete block. ||username1.link^$important
Static network filtering の redirect
||adswithsalt.com/*/ad-loading.pic$image,redirect=3x2.png

画像を消しちゃうんじゃなくて、置き換える。
レイアウトを保ちたいときとかに使うのかなあ。

*/fuckadblock-$script,redirect=fuckadblock.js-3.2.0

# fuckadblock defuser
fuckadblock.js-3.2.0 application/javascript
(function() {
    var noopfn = function() {
        ;
    };
    //
    var Fab = function() {};
    Fab.prototype.check = noopfn;
    Fab.prototype.clearEvent = noopfn;
    Fab.prototype.emitEvent = noopfn;
    Fab.prototype.on = function(a, b) {
        if ( !a ) { b(); }
        return this;
    };
    Fab.prototype.onDetected = function() {
        return this;
    };
    Fab.prototype.onNotDetected = function(a) {
        a();
        return this;
    };
    Fab.prototype.setOption = noopfn;
    var fab = new Fab(),
        getSetFab = {
            get: function() { return Fab; },
            set: function() {}
        },
        getsetfab = {
            get: function() { return fab; },
            set: function() {}
        };
    if ( window.hasOwnProperty('FuckAdBlock') ) { window.FuckAdBlock = Fab; }
    else { Object.defineProperty(window, 'FuckAdBlock', getSetFab); }
    if ( window.hasOwnProperty('BlockAdBlock') ) { window.BlockAdBlock = Fab; }
    else { Object.defineProperty(window, 'BlockAdBlock', getSetFab); }
    if ( window.hasOwnProperty('SniffAdBlock') ) { window.SniffAdBlock = Fab; }
    else { Object.defineProperty(window, 'SniffAdBlock', getSetFab); }
    if ( window.hasOwnProperty('fuckAdBlock') ) { window.fuckAdBlock = fab; }
    else { Object.defineProperty(window, 'fuckAdBlock', getsetfab); }
    if ( window.hasOwnProperty('blockAdBlock') ) { window.blockAdBlock = fab; }
    else { Object.defineProperty(window, 'blockAdBlock', getsetfab); }
    if ( window.hasOwnProperty('sniffAdBlock') ) { window.sniffAdBlock = fab; }
    else { Object.defineProperty(window, 'sniffAdBlock', getsetfab); }
})();

この redirect は、処理を置き換えちゃう。
Anti AdBlocker のクラス、または window のプロパティを置き換えることで、それ以降の処理がエラーにならない。

Static extended filtering の Scriptlet injection

javascript のコードを注入する。

kisscartoon.*##script:inject(noeval.js)

noeval.js は、こんな感じのコード。

(function() {
	window.eval = function(s) {
		console.log('Document tried to eval... \n' + s);
	}.bind(window);
})();

window の eval プロパティ(Function のインスタンス)を、ログを残すだけの関数に置き換えている。

注入するスクリプトをユーザが自由に追加できるわけではないので、使いまわしが効くやつを知っておく必要がある。

HTML filter によるインラインスクリプトの禁止
msn.com##^script:has-text(/i10C/i)

:has-text(...) には、正規表現も指定できて、i と m のフラグが利用できる。

redirect や +js() で利用できそうなやつ

redirect+js() は、自分で好きなコードを追加する、というわけにいかない(というわけでもないけど)。
使い廻しが効きそうなやつをいくつかピックアップ。
公式の解説ができた(uBO 1.24.0 くらい)ので、そちらをみるといいk

redirect
1x1.gif とか 透明画像に置き換える
noop.txt
noop.js
noop.html
noop-0.1s.mp3
noop-1s.mp4
空っぽ系 : MIME タイプごとにある
noop-0.1s.mp3 は、無音の MP3 で、noop-1s.mp4 は空の動画
+js / script:inject

scriptlet の .js は省略できる。また、ものによっては短い名前の alias がある。

adfly-defuser.js Anti AdBlock 対策
window.name-defuser window.name を空にする
alert-buster.js alert を console.log に置き換える
noeval.js
silent-noeval.js
window.eval を殺す
noeval-if.js 特定の window.eval だけを殺す
{{1}} : コード
addEventListener-logger.js メソッドの呼び出しをロギング
addEventListener-defuser.js addEventListener をフィルタリングする
パラメータにマッチする Listener の登録をスキップする
{{1}} : イベント
{{2}} : コード
no-setTimeout-if.js setTimeout をフィルタリングする
パラメータにマッチする setTimeout の登録をスキップする。パラメータが無いと実行をすっ飛ばして console にロギング。
{{1}} : コード
{{2}} : delay
abort-on-property-write.js
abort-on-property-read.js
プロパティがインラインスクリプトで使われたときにエラーにする(それ以降が処理されない)。
{{1}} : プロパティ
abort-current-inline-script.js プロパティがインラインスクリプトで使われたときにエラーにする(それ以降が処理されない)。
{{1}} : プロパティ
{{2}} : コード

abort-current-inline-script.js は、メソッドもガードできる(メソッドの実体を取得するために getter が使われるから)。
エラーにしたときの window.onerror も、自分の分だけ握りつぶしているという芸の細かさ :-)
##script,redirect=noopjs との違いは、普通は目印がない script タグで、特定のものだけ向こうにするとか、途中までは実行させられることとか。

Static Filter の実例(+js / script:inject)

+js(...) は、使い方のバリエーションが多いので、公式のフィルタから実例をいくつか。

addEventListener-defuser.js

イベントとコードの一部を指定して、特定の addEventListener() を拒否する。

1337x.*##script:inject(addEventListener-defuser.js, /^(click|mousedown|mousemove|touchstart|touchend|touchmove)/, system.popunder)

スラッシュでくくると正規表現が使える。
第2引数も、スラッシュでくくると正規表現が使える。

no-setTimeout-if.js

コードの一部と時間指定で、特定の setTimeout() を拒否する。

ville-ideale.fr##script:inject(no-setTimeout-if.js, contrformpub, 10000)

第2引数は必要なんだろうか、という気がしなくもない。

sport365.live##script:inject(setTimeout-defuser.js, /location\.replace|setRefreshAdFloat|setRotateAdSlice/)

スラッシュでくくると正規表現が使える。

abort-on-property-write.js, abort-on-property-read.js

あるプロパティがインラインスクリプトで使われたときに例外を送出する(それ以降が処理されない)。

dpstream.net##script:inject(abort-on-property-write.js, Fingerprint2)

グローバルな関数は、window のプロパティを定義するので、関数の登録自体をなかったことにできる。

aranzulla.it##script:inject(abort-on-property-read.js, navigator.userAgent)

第1引数のプロパティは、ピリオドで区切って深いところまで指定ができる。

abort-current-inline-script.js

特定のコードが含まれるインラインスクリプトで、あるプロパティが使われたときに例外を送出する(それ以降が処理されない)。

hackintosh.zone##script:inject(abort-current-inline-script.js, document.readyState, /(?:\\x[0-9a-f]{2}){20}/)

第2引数で、該当のコードを含むインラインスクリプトだけを指定して殺せる。
スラッシュでくくると正規表現が使える。
ひとつの inject で、getter / setter の両方とも殺せるのは、abort-on-property-read.js と同じ。

sc2casts.com##script:inject(abort-current-inline-script.js, setTimeout, ins.adsbygoogle)

特定の setTimeout を殺す、setTimeout-defuser.js ではない別の書き方。

その他

redirect=noopjs の意義
foo.bar$script foo.bar$script,redirect=noopjs

どちらも外部スクリプトのリクエストはブロックされるわけだし、中身を何もしない関数に置き換えている意義が分からなかった。

ふたつ書き方の大きな違いは、script タグの onload イベントが発火されるかどうか。

Anti AdBlock 系の処理では、script の onload をチェックしている奴がいるらしく、src 属性で指定したスクリプトの URL をブロックすると onload が発火されず、AdBlock を使ってますよね、という判定をするみたい。
そういうタイプの Anti AdBlocker なんかを無効化するために、redirect=noopjs が利用されているみたいだ。

Cosmetic Filter の例外指定の仕様

郵便局の郵便番号検索の結果ページがこんな感じになっている(deteil は、ぼくの typo ではない)。

<div class="adArea deteil">
    ...
</div>

こいつが、EasyList の以下の定義に引っかかる。

##.adArea

郵便局だから広告エリアはないだろうけれど、例外の指定は範囲を狭めておきたい。
というわけで、こういうふうにした。

www.post.japanpost.jp#@#div.adArea.deteil

これが、例外の指定が効いてない。

セレクタにクラスを複数指定するのがダメなのかと思ったけれど、そうではなく、例外の指定は、対象のブロックをする定義と同じセレクタを指定しないと打ち消さない、ということみたい。

www.post.japanpost.jp#@#div.adArea

ちなみに、これを追記している途中(2017-11-16)に、.adArea が .bnrArea に変更された。
まさか、AdBlock を考慮したわけでもないだろうけれど。

インラインスクリプト

リクエストを出さず、もともとブラウザに見えてないインラインスクリプトへの対応は、三種類。

Static network filters に inline-script オプションが指定された場合は、対象の URL に合致するページのインラインスクリプトが全て無効になる。
今どきの javascript 満載のページの作りだと、出番はあまりなさそう。

特定のインラインスクリプトだけを無効にするなら、HTML filter を使う。
script タグに id や class の目印がついていることは稀だけど、:has-text(...) を使うことで、特定のインラインスクリプトを狙い撃ちにできる。

特定のインラインスクリプトを狙い撃ちにするもうひとつの方法は、Scriptlet injection 。
ある所までは実行させたい、というときにはこちらを使う。

badfilter

公式のまんまだけど、読んでて「ん?」ってなったので書いておく。

Static network filters の badfilter オプションは、他のブロックするフィルタの打ち消しに使う。
フィルタの打ち消しは @@ があるけれど、これを使っちゃうとフィルタの定義によってはガバガバな許可になっちゃう(本来、ブロックしておきたい定義も打ち消しちゃうかもしれない)ので、「許可する」んじゃなくて「元々のフィルタの定義を無かったことにする」のが badfilter オプション(だと思う)。


My フィルタの実際

自分で追加するのは、こんなのが多い。

  • フィルタの誤爆対処
  • 広告じゃないけど見たくないやつを消す
  • コピペガード外し
  • Anti Adblocker 対策
  • スマホチェック外し

追加でブロック

あまり追加してないけど、こんな感じ。

! Internet Archive の寄付を募るやつ
web.archive.org###donate-banner

! 「Google をホームページに」のポップアップ
www.google.com,www.google.co.jp##div[role="dialog"]:has(a[title="No thanks"])

「Edge お勧め」とか「GDPR のせいで確認してます」系もうるさいなあ、という気もしてる。

誤爆の対処

以下のような手順で対処をする。

  1. 動的に作ってるようなページできちんと動いてない、もしくは、見えてるはずのものが見えてないことに気が付く
  2. uBO のリクエストログから、
    • 動いてない → ピンクになってる行で、5列目が script か xhr を探す
    • 見えない → 黄色になっている行で 5列目が dom か image を探す
  3. 3列目をクリックして、ブロックしているフィルタとルールを確認
  4. それを打ち消すルールを My フィルタに書く

これを書いている時点では、大体こんな感じになってる(そのまんまじゃない)。

! adlib さんのアイコン   http://q.hatena.ne.jp/1468372807#a1257828
@@||www.*hatena.*^*^adlib^

! http://candycrush.wikia.com の画像が見えなくなっちゃう
@@||slot1.images.wikia.nocookie.net^*_ads_*$script
! こっちは必要なかった(というか、入れるとビデオ広告がうるさい)
!@@||slot1.images.wikia.nocookie.net^*_analytics_*$script

! localhost のコンテンツは、ブロックする必要ない
@@*$domain=localhost

! "amazon lambda" で検索すると、「ツール」が効かなくなってる (´・ω・`)
! ABP Japanese Filter
! amazon が地雷を踏む
! @@*$domain=www.google.com
@@www.google.com/*^adinfo^$script,first-party

! 2ch で Bookmarklet が動かない(豆腐フィルタ)
! |http://$third-party,script,domain=2ch.net
@@http://localhost$script,domain=2ch.net

! National Geographic for Web の動画
@@||uliza.jp^$domain=natgeo.nikkeibp.co.jp

! 人力検索の ASIN 記法の画像が無いと、やっぱり寂しい
@@amazon.com/images$domain=q.hatena.ne.jp

! 人力検索の「関連する商品」
! JPN: ABP Japanese filters (日本用フィルタ) の &affid= に引っかかる
! パラメータは、空なのに :-/
@@||q.hatena.ne.jp/api/products?keyword=$domain=q.hatena.ne.jp

! Google Search Console が動かない
@@||www.google.com/webmasters/tools^*-analytics-

! Mozilla WebExtension Example には /popup/ に配置されたソースが結構ある
@@||github.com/*^popup^$popup,first-party

コピペガード外し

某質問で、検索して見つけたこのページ
内容はきちんとしているっぽいのだけれど、右クリックメニューに始まり、範囲選択の禁止などのコピーガードが癪に障る。果ては、ソース表示やインスペクタのキーボードショートカットまで殺してある。

まずは、イベントハンドラの確認。
ご丁寧に ctrl + shift + i や、ALT でメニューバーを出す操作も殺されてる。
でも、アドレスバーにフォーカスが当たっている状態であれば、コンテンツのイベントハンドラは無効化できる。

インスペクタで、html のイベントを確認。
keydowncontextmenudragstart が無効化されるイベントハンドラが登録されている。
document.bodyselectstart も無効化されている。

My フィルタに以下の設定を追加して、再表示する。

www.kosodatedou.com##script:inject(addEventListener-defuser.js, /^(keydown|contextmenu|dragstart|selectstart)/)

addEventListener-defuser.js は、addEventListener を Hook して、イベントハンドラの登録を阻止する。
キーは効くようになったけど、右クリックメニューや範囲選択ができない。

ctrl + u で、ソースを確認。

<script type="text/javascript">
  document.oncontextmenu = function(e) {
    var t = e || window.event;
    var n = t.target || t.srcElement;
    if (n.nodeName != "A")
      return false
  };
  document.ondragstart = function() {
    return false
  };
</script>

こんな感じで、addEventListener メソッドを使わずに oncontextmenu プロパティに、直接 ハンドラを代入しているので、addEventListener の Hook が効かない。

My フィルタに以下の設定を追加して、再表示する。

www.kosodatedou.com##script:inject(abort-current-inline-script.js, document.oncontextmenu)
www.kosodatedou.com##script:inject(abort-current-inline-script.js, document.ondragstart)
www.kosodatedou.com##script:inject(abort-current-inline-script.js, document.body.onselectstart)

abort-current-inline-script.js は、特定のプロパティの setter、getter を Hook して、その設定や参照を阻止する。
右クリックメニューは出るようになったけど、ドラッグや ctrl-a での範囲選択ができない。
document.body.onselectstart のイベントハンドラを無効化できていない様子。

onselectstart を阻害しているコードは、こんなの。

function disableSelection(e) {
  if (typeof e.onselectstart != "undefined")
    e.onselectstart = function() {
      return false
    };
  else if (typeof e.style.MozUserSelect != "undefined")
    e.style.MozUserSelect = "none";
  else
    e.onmousedown = function() {
      return false
    };
  e.style.cursor = "default"
}
window.onload = function() {
  disableSelection(document.body)
}

document.body.onselectstart を置き換えるコードが onload で走っているので、Cosmetic Filter の script:inject が動くときには、未だ document.body.onselectstart は null のままなんだ。
script:inject は、レンダリング時に実行されて、実行された後には消される
abort-current-inline-script.js は、実行されたときに存在しているプロパティの getter / setter を書き換えているから、素直に使うと空振りしちゃう。

ちょっと悩んで、追加したのは この My フィルタ。

www.kosodatedou.com##script:inject(abort-current-inline-script.js, disableSelection)

global なスコープの関数は window のプロパティで、呼び出すときには、その getter が呼ばれる。
なので、その関数名の getter を無効にすることで、その呼び出しを無効化できる。

最終的に追加した My フィルタは、こんな感じになってる。

www.kosodatedou.com##script:inject(addEventListener-defuser.js, /^keydown/)
www.kosodatedou.com##script:inject(abort-current-inline-script.js, document.oncontextmenu)
www.kosodatedou.com##script:inject(abort-current-inline-script.js, document.ondragstart)
www.kosodatedou.com##script:inject(abort-current-inline-script.js, disableSelection)

アンチ AdBlocker 対策

Image Raiderという画像検索サイト。マッシュアップ(他人の褌)のくせに AdBlocker 検出をして「AD-BLOCKER 外してね」メッセージとともに YouTube の動画を流す。

うざったいのは、こんなコード。

jQuery(document).ready(function(){
    if (jQuery('#myGContainer').height() == 0) {
        document.getElementById('myGContainer').innerHTML="<h1><strong>Ad-blocker Detected!</strong> ...";
        usingAdBlock = true;
        ga('send', 'event', 'Adblock', 'Active');
    }
});

こいつも onload 的なタイミングで処理を仕込んでくるし、長めのインラインスクリプトの一部なので、微妙に手強い。

検索してみたら、こんなページがありました。

imageraider.com ・ Issue #2819 ・ AdguardTeam/AdguardFilters
Added to English filter

imageraider.com#@#.adsbygoogle

なるほど。敢えて、Cosmetic Filter の例外を追加する、と。
広告を表示するためのスクリプトはブロックされているので、真っ白なスペースとして表示される。
あとは邪魔なスペースを小さくするだけ。

imageraider.com###myGContainer:style(height: 1px !important; overflow: hidden;)

Google の検索結果から、特定サイトを排除

よく「ノイズレスサーチ」と言われるアレ。
例えば、クックパッドを検索結果から消しちゃうには、こんな感じ。

www.google.com, www.google.co.jp##div.g:has(div.r > a[href*="cookpad.com"])

こんなこともできちゃう :-)

:xpath() の使いどころ

人力検索で、特定のユーザの回答だけを消したい。
DOM の構造はこんな感じ。

div#read_answer_list
  div.answer.clearfix
    div.f-left
    h3.answer-title     # ★これは残したい
      span.user-name
        a               # ユーザ名
    div.answer-body     # ★これを消したい
      ...               # 回答内容

最初に試したのは、こんな感じのフィルタ。

q.hatena.ne.jp##h3.answer-title:has(a[href="/★★★/"]) + div.answer-body

:has() は、uBO が処理しているので、後続する隣接セレクタが効かないんだな。
試行錯誤してたどり着いたのが、:xpath()

q.hatena.ne.jp##div.answer-body:if(:xpath(../h3//a[@href="/★★★/"]))

CSSセレクタではできない「前に戻る」が XPATH だと実現できる。

リクエストログの画面の見方

https://github.com/gorhill/uBlock/wiki/The-logger
本家の方がちょっと良くなってて、ほぼそのままなんですけれど。

無色 通ったリクエス
ルールによってブロックされたリクエス
ブロックするルールがあるけど、例外のルールで通されたリクエス
黄色 Static extended filtering で非表示にされた
redirect= でリクエストが置き換えられた
+js で Scriptlet が注入された

Static extended filtering の例外指定は、ログに残らないっぽい。

2列目
適用されたルール。
クリックすると、どのフィルターに含まれているかが分かる。

3列目
処理結果。
クリックすると、そのリクエストをルールに追加する画面になる

  白:許可されたリクエス
黄:Static extended filtering の対象
ーー ブロックされたリダイレクト
++ 例外設定で許可されたリクエス
<< リダイレクト

4列目
ページのドメイン

5列目
ページとリクエストの関係。

1 ページと同じドメイン
3 サードパーティ
0 バックグラウンド通信

6列目
リクエストの種類。

doc 表示ページへのリクエス
inline-script inline-script
script script のリクエス
css css
image image
xhr XHR or fetch
dom Static extended filtering
HTML filter や script:inject も dom

7列目
基本的に、

  • Network Filter では、ブロックされた URL
  • Cosmetic Filter の場合には、そのページの URL

その他

多分、(自分にとっては)忘れても良いことだけど、一応 残しておく。

Dynamic Filter

https://github.com/gorhill/uBlock/wiki/Dynamic-filtering:-quick-guide
https://github.com/gorhill/uBlock/wiki/Dynamic-filtering:-rule-syntax
https://github.com/gorhill/uBlock/wiki/Dynamic-URL-filtering

Dynamic の命名は、ルールをテキストで書くんじゃなくて、画面からポチポチやって設定するのを「動的(dynamic)」と言っているような気がする。

Dynamic Filter も、Dynamic URL Filter も定義は同じところに書かれる。
source のサイトを表示したときに、destination へのリクエストを、block / allow / noop するだけじゃないかな。
画面からの入力に制約があるので、そのうちのいくつかのパターンに名前を付けているという。

アイコンをクリックして出てくるポップアップで、上に寄せられているホスト名じゃない部分で、緑 / 灰 / 赤 が "Type based rule" 。
下のホスト名のところが、"Hostname based rule" 。

リクエストログの 4列目をクリックして出てくるポップアップで、ダイナミック... のタブの方で指定するのが Dynamic URL Filter 。

デフォルトの Whitelist

about-scheme
behind-the-scene
chrome-extension-scheme
chrome-scheme
moz-extension-scheme
opera-scheme
vivaldi-scheme

デフォルトの、この七つ。
多分、消しちゃいけないやつ。
Firefox の WebExtension だと、about-scheme とか関係ないような気がしなくもないけれど。

外部フィルタ

もちフィルタと、nocoin-list というのを追加して使ってる。
■もちフィルタ
https://eeii0a5l.github.io/mochifilter_homepage/mochi.html
https://raw.githubusercontent.com/eEIi0A5L/adblock_filter/master/mochi_filter.txt
■nocoin-list
https://github.com/hoshsadiq/adblock-nocoin-list

デフォルトで入っているものでは、"EasyList" と "JPN: ABP Japanese filters (日本用フィルタ)" の誤爆がときどき気になる。
しばらく様子を見ていたけど、"JPN: ABP Japanese filters (日本用フィルタ)" は使うのを止めた。

日本語系の追加フィルタでは、豆腐フィルタをしばらく使っていたのだけれど、定義が肥大する一方なので、もちフィルタに切り替えた。
■豆腐フィルタ
http://tofukko.r.ribbon.to/abp.html
http://tofukko.r.ribbon.to/Adblock_Plus_list.txt

もちフィルタには、他のフィルタもある。

フィルタのリストを挙げてる人もいるようなので、探してみると良いのかも。
例えば、 https://github.com/k2jp/abp-japanese-filters/wiki/OtherJapaneseFilters


script:inject は、どこに注入されるのか

DOM Inspector では、どこに注入されているか分からない。
js/contentscript.js を見ると、head の末尾に追加されるようだけれども。

            if ( cfeDetails.scripts ) {
                // Have the injected script tag remove itself when execution completes:
                // to keep DOM as clean as possible.
                text = cfeDetails.scripts +
                    "\n" +
                    "(function() {\n" +
                    "    var c = document.currentScript,\n" +
                    "        p = c && c.parentNode;\n" +
                    "    if ( p ) {\n" +
                    "        p.removeChild(c);\n" +
                    "    }\n" +
                    "})();";

ああ、消されるのか。
実行中の inline script が、どの script タグだ、って欲しくなるときがたまにあるけど、currentScript なんてプロパティがあるんだ。

フィルタに 8文字以上

http://cojocco.blog113.fc2.com/blog-entry-26.html
ここに書いてある「フィルタに8文字以上」は、どこソースだろう...
まあ、ABP の話だし、

  • すべてのフィルタは、内部で正規表現に変換されます。
  • 正規表現のフィルタはできるだけ少なくする。

は、矛盾した内容だから、話半分以下かな。
2010年4月の記載だし、気にしなくて良いだろう。

https://adblockplus.org/forum/viewtopic.php?t=17056
2013年8月のやりとり

https://github.com/chrisaljoudi/uBlock/issues/161#issuecomment-58547364
2014年10月
これかな。

デフォルトで取り込む対象の外部フィルタとかもそのままでも、uBO の導入前に比べて確実に速くなってるんだから、ルールの記載とかは それほど気にしなくても良いような気がする(それほど広告の展開が遅い)。
遅いなあと感じたら、誤爆の多そうな外部フィルタを外していけば良いという感じかな。

script:inject には、パラメータを渡すことができる

resources.txt 中の、{{1}}{{2}} というようなのがパラメータの指定。

setTimeout-defuser.js

mio.to##script:inject(setTimeout-defuser.js, e=d(), 1000) geektime.co.il##script:inject(setTimeout-defuser.js, adObjects)
setTimeout-defuser.js application/javascript
(function() {
    var z = window.setTimeout,
        needle = '{{1}}',
        delay = parseInt('{{2}}', 10);
    if ( needle === '' || needle === '{{1}}' ) {
        needle = '.?';
    } else if ( needle.slice(0,1) === '/' && needle.slice(-1) === '/' ) {
        needle = needle.slice(1,-1);
    ...

最初の {{1}} しか置き換えないのかなあ。
じゃなければ、最初の if が不思議すぎる。

js/cosmetic-filtering.js

FilterContainer.prototype._fillupUserScript = function(content, args) {
    var i = 1,
        pos, arg;
    while ( args !== '' ) {
        pos = args.indexOf(',');
        if ( pos === -1 ) { pos = args.length; }
        arg = args.slice(0, pos).trim().replace(this._reEscapeScriptArg, '\\$&');
        content = content.replace('{{' + i + '}}', arg);
        args = args.slice(pos + 1).trim();
        i++;
    }
    return content;
};

置き換えが一回だけなのは、そういう風に作ってるから良いとして、needle === '{{1}}' という判定を入れなければならない理由が謎。
不思議な if は、こんなのを想定しているんだ。

foo.bar##script:inject(setTimeout-defuser.js) foo.bar##script:inject(setTimeout-defuser.js, , 1000)

パラメータの置き換えは、フィルターに指定された引数のループ。
引数自体を省略すると、'{{1}}' は、置換されずに そのまま残る。
カンマを続けて書いて、空の引数にすると、'{{1}}' が空文字列で置換される。

アドオンのソースの参照、古いね (´・ω・`)

記事を見直してて、ソースの実装位置が変わってることに気がついた。
uBO 1.15.10 で気がついたのだけれど、script:inject したコードを外したり、引数展開しているところは、js/contentscript.js や js/cosmetic-filtering.js じゃなくて、js/scriptlet-filtering.js になってる。

script:contains(...)

以前あった script:contains(...) は、Static filter の説明からは削除されて、inline script tag filteringのページに追いやられている。
下位互換が取られていて、内部的に HTML filterに置き換えられている。

example.com##^script:has-text(...)

ログにも HTML filter で残されるんだけど、実際にフィルターに書かれているのは script:contains(...) だから、クリックしても「見つかりません」って言われちゃう (´・ω・`)

足跡

@2018-8-15

久しぶりに公式ドキュメントを読んでたら、公式の Static Filter で、Cosmetic Filter の扱いが変わってて、他にも、

  • Cosmetic Filter の hostname の扱いに誤解があった
  • script:contais(...) がなくなったり
  • HTML Filters なるものが増えてたり

ということがあったので、旧Cosmetic Filter の辺りを見直しました。改定前はこちら

@2020-3-16

  • logger の画面がちょっと変わってた(目玉の列がなくなってる)
  • 細かいところ
  • 公式ドキュメントのリンクを最後に

改定前はこちら

@2020-3-31

@2020-8-3

  • Specific-generic
  • Action operators
  • Scriptlet がかなり見直されていたので、deprecated じゃないほうでサンプルを書き直し

改定前はこちら

Scriptlet は、本家の情報が充実してきたので、別に書き起こしても良いかなという気はする。

# ここまで