uBlock Origin 再び @Firefox

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

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

# それなりに手を入れました @2018-8-15


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 拡張のようなもの。これも基本的に要素を見えなくすることに使う
  • 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 ページのドメインで限定
important 例外指定に勝つブロック
redirect= リクエストを置き換える
inline-script inline script を消しちゃうのかな
badfilter フィルタを無効化する
例外指定ではなくこいつを使った方が良いケースがあるらしい

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

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

Static extended filtering

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

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

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

「ホスト名」は省略することも可能。
「指定」には CSSセレクタが指定できる。

Cosmetic filters
ホスト名 + ## + Cosmetic filters

:style(...) の場合には、ワイルドカードが使用できない。
「Cosmetic filters」には、CSS セレクタProcedural cosmetic filters:style(...) が指定できる(後述)。

HTML filters

「指定」には CSSセレクタが指定できる。

Scriptlet injection
ドメイン + ##script:inject( + scriptlet name + )

「scriptlet name」は、assets/ublock/resources.txt で定義されたスクリプト名を指定する。

script:inject() は、使い方が難しい。
.xpi の HASH が変わっちゃうから、普通の利用者は resources.txt を修正できない。
なので、使えそうなやつを見繕っておく必要がある。

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 じゃないと指定できない要素はあるかな? (後述

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

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

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

:style(...)

非表示にするんじゃなくて、... なスタイルを適用する。
Stylus なんかでもできるやつだけど、uBO でしかできないこともある。

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
      • :style(...)
    • 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 で動的に生成される要素には効かない。

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

script:inject(...) は、スクリプトの断片を大賞のサイトで動かす([http://a-kuma3.hatenablog.com/preview#Static-Filter-%E3%81%AE%E5%AE%9F%E4%BE%8Bscriptinject:title=後述)。
 

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-transparent.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 の script:inject

javascript のコードを注入する。

hentaifr.net,jeu.info,tuxboard.com,xstory-fr.com##script:inject(goyavelab-defuser.js)

goyavelab-defuser.js application/javascript
(function() {
    var noopfn = function() {
        ;
    };
    Object.defineProperty(window, '_$14', {
        get: function() { return noopfn; },
        set: noopfn
    });
})();

window の _$14 プロパティ(Function のインスタンス)を無力化してる。

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

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

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

redirect や script:inject で利用できそうなやつ

redirectscript:inject(...) は、resource.txt にコードを持っているけれど、アドオンのハッシュ値が変わっちゃうから自分で好きなコードを追加する、というわけにいかない。
アドオンに定義されているもので、汎用性があって、他でも使いどころがありそうなやつをピックアップ。

redirect
1x1-transparent.gif とか 透明画像に置き換える
nooptext
noopcss
noopjs
noopframe
noopmp3-0.1s
空っぽ系 : MIME タイプごとにある
noopmp3-0.1s は、無音の MP3
script:inject
antiAdBlock.js
lesechos.fr.js
ideal.es.js
Anti AdBlock 対策
antiAdBlock.js - window.antiAdBlock()
lesechos.fr.js - window.checkAdBlock()
ideal.es.js - window.is_block_adb_enabled
window.name-defuser window.name を空にする
alert-buster.js alert を console.log に置き換える
noeval.js
silent-noeval.js
window.eval を殺す
noeval-if.js 特定の window.eval だけを殺す
{{1}} : コード
setTimeout-logger.js
setInterval-logger.js
addEventListener-logger.js
メソッドの呼び出しをロギング
csp.js meta http-equiv="Content-Security-Policy" を書き換える or 設定する
addEventListener-defuser.js addEventListener をフィルタリングする
パラメータにマッチする Listener の登録をスキップする
{{1}} : イベント
{{2}} : コード
setTimeout-defuser.js setTimeout をフィルタリングする
パラメータにマッチする setTimeout の登録をスキップする
{{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 の実例(script:inject)

script:inject(...) は、使い方のバリエーションが多いので、あらためて実例をいくつか。

addEventListener-defuser.js

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

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

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

setTimeout-defuser.js

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

ville-ideale.fr##script:inject(setTimeout-defuser.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 を使ってますよね、という判定をするみたい。
そういうタイプを無効化するために、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 に合致するページのインラインスクリプトが全て無効になる。

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

My フィルタの実際

自分で追加するときには、フィルタの誤爆対処が一番多くって、次に広告じゃないけど見たくないやつ、そしてちょこざいなガード外し、という感じの頻度かな。

追加でブロック

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

! 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(h3.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= で置き換えられたリクエス

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

2列目
クリックすると、そのページの uBlock ポップアップを表示する。

閉じられたウィンドウやタブのログ。ツールバーの×をクリックすると消える
バックグラウンド通信(behind-the-schene)

Note in the figure above the entry named "Behind the scene": selecting this entry allows to show only behind-the-scene network requests. Behind-the-scene network requests are requests which do not originate from any specific tab, and are denoted by the eye-slash icon in the second column. More about behind-the-scene network requests here.

https://github.com/gorhill/uBlock/wiki/The-logger#page-selector

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

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

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

5列目
対象のタイプ。

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

6列目
基本的に、

  • 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 というのを追加して使ってる。
■豆腐フィルタ
http://tofukko.r.ribbon.to/abp.html
http://tofukko.r.ribbon.to/Adblock_Plus_list.txt
■nocoin-list
https://github.com/hoshsadiq/adblock-nocoin-list

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

日本語系では、もちフィルターシリーズというのも有名らしい。
フィルタのリストを挙げてる人もいるようなので、探してみると良いのかも。

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 の辺りを見直しました。改定前はこちら

# ここまで