記事一覧はこちら

GoogleChromeで完璧なAdblockが来る日も近いであろう

GoogleChromeは拡張から通信を操作出来ないので、FireFoxの様な広告を読み込まない拡張を作るのは不可能でした。現時点でのGoogleChromeAdblockはロード後非表示にする事しか出来ません。ですが、GoogleChromeの新しい拡張機能で任意の通信を切断する事が出来るかも知れません。実際に任意の通信を切断する拡張機能を付せて紹介します。
執筆時点でのブラウザはGoogleChrome v13。chrome://flags/で試験運用版の拡張機能 APIを有効にしています。

今回使うAPIはWebRequest APIです。このAPIはまだ正式リリース前なので、上記の方法で試験運用版の拡張機能を使えるようにしておいて下さい。また、このAPIを使った拡張はGoogleの公式拡張ギャラリーにアップロード出来ません。

WebRequest API - Google Chrome Extensions - Google Code

このapiは、接続前や通信前、受信後のイベント時にヘッダーを取得したりする事が出来るAPIです。現時点ではヘッダーの取得がメインで、書き換えや通信の切断が可能なイベントは限られていますが、幸いAdblockの作成は可能でした。正式リリース時には全てのイベント時にレスポンスボディ含む全ての通信内容の書き換えが可能になっている事を望みます。

chrome.experimental.webRequest.onBeforeRequestイベントはhttpやhttps以外にもchrome-extensionやblob:blobinternalと言った見たことの無いプロトコルがありますので、ばっさり切り捨てました。

当然ですが、Youtubeのフラッシュプラグインから取得している動画URLは取得出来ませんでした。HTML5版のプレイヤーなら取得出来るかも知れません。

今回作成した拡張では、オプションページとバックグラウンドページで同時にchrome.experimental.webRequest.onBeforeRequestイベントを取得しましたが、特に問題は有りませんでした。



拡張のファイルとコードです。リストボックスのワイルドカードは未実装です。フィルタはhttp://とhttps://のプロトコルを区別しません。ですのでhttp://やhttps://のプロトコルは省略して表示しています。

使い方ですが、インストール後chrome://extensions/にアクセスし、オプションページを開きます。そしてリクエスト一覧の下の表示するのチェックボックスをonにし他のタブで適当なサイトを読み込むと、表示するのチェックボックスの下のテキストエリアにURLが表示されると思います。表示するのチェックボックスをonにしていると、他の拡張機能含む全ての機能、タブの通信がテキストエリアに表示されます。

次に下の処理対象のチェックボックスを有効にし、リストボックスから処理方法を選びます。処理対象のテキストボックスは1行ずつマッチ判定を行い、マッチした瞬間に通信を切断します。例えば図のようにwww.youtube.com/get_video_infoを入力すると動画の様なYoutubeの動画広告が非表示になると思います。(広告は常に表示されるとは限らないので、掲載期間が終了しているかも知れません)。手っ取り早く動作確認をしたい時は、リストから部分一致を選択しテキストボックスに.と一文字だけ入力するとほぼ大半の通信が切断されます。

{
  "name": "HTTPリクエストをブロックする拡張",
  "version": "0.1",
  "description": "HTTPリクエストをブロックする拡張",
  "permissions": [ "experimental" ],
  "options_page":"option.html",
  "background_page":"background.html"
}
<html lang="ja">
<meta charset="UTF-8" />
<script>
saveDatas=[];
saveDatas.push({"id":"tempMode","key":"selectedIndex"});
saveDatas.push({"id":"tempList","key":"value"});
saveDatas.push({"id":"traceON","key":"checked"});
saveDatas.push({"id":"tempOn","key":"checked"});
window.addEventListener("load",function(event){init()});
chrome.experimental.webRequest.onBeforeRequest.addListener(function(details){
    var ret,url;
    if(!details.url.match(/^https?://(.+)/)){return;}
    url=RegExp.$1;
    if($("traceON").checked){
        $("requestList").innerHTML+=url+"n";
    }
},{},["blocking"]);
function init(){
    $=function(e){return document.getElementById(e);}
    $("traceLogClear").addEventListener("click",function(e){$("requestList").innerHTML="";});
    var d=JSON.parse(localStorage.getItem("save"));
    for(var i=0;i<saveDatas.length;i++){
        $(saveDatas[i]["id"])[saveDatas[i]["key"]]=d[saveDatas[i]["id"]];
    }
    window.setInterval(saveSetting,1000);
}
function saveSetting(){
    var d={};
    for(var i=0;i<saveDatas.length;i++){
        d[saveDatas[i]["id"]]=$(saveDatas[i]["id"])[saveDatas[i]["key"]];
    }
    localStorage.setItem("save",JSON.stringify(d));
    chrome.extension.sendRequest({
            action:"init",
            args  :[]
        },function(response){}
    );
}
</script>
<style>
textarea#requestList,textarea#tempList{
    width:100%;
    height:300px;
}
</style>
<div id="analyze">
<h1>解析フォーム</h1>
<h2>リクエスト一覧</h2>
<label><input type="checkbox" id="traceON" />表示する</label><button id="traceLogClear">クリア</button><br>
<textarea id="requestList" wrap="off"></textarea>
<h2>処理対象</h2>
<label><input type="checkbox" id="tempOn" />有効</label> <select id="tempMode"><option value="1">完全一致</option><option value="2">先頭一致</option><option value="3">部分一致</option><option value="4">ワイルドカード</option><option value="5">正規表現</option></select><br>
<textarea id="tempList" wrap="off"></textarea>
</div>

<!-- <select name="tempMode"><option value="1">完全一致</option><option value="2">先頭一致</option><option value="3">部分一致</option><option value="4">ワイルドカード</option><option value="5">正規表現</option></select> -->

<html lang="ja">
<meta charset="UTF-8" />
<script>
saveDatas=[];
saveDatas.push({"id":"tempMode","key":"selectedIndex"});
saveDatas.push({"id":"tempList","key":"value"});
saveDatas.push({"id":"traceON","key":"checked"});
saveDatas.push({"id":"tempOn","key":"checked"});
urlPats=[];
urlMode=-1;
ready=false;
chrome.extension.onRequest.addListener(function(message,sender,sendResponse){
    sendResponse(eval(message.action).apply(sender,message.args));
});

chrome.experimental.webRequest.onBeforeRequest.addListener(function(details){ var ret; if(!ready){return;} if(!details.url.match(/^https?://(.+)/)){return;} ret=checkURL(RegExp.$1); if(ret===true){ return {"cancel":true}; } },{},["blocking"]); function init(){ var d={},vls,i; ready=false;urlPats=[]; d=JSON.parse(localStorage.getItem("save")); vls=d["tempList"].split("n"); for(i=0;i<vls.length;i++){ if(vls[i].match(/(.+)/)){urlPats.push(RegExp.$1);} } urlMode=d["tempMode"]; if(d["tempOn"]){ ready=true; }else{ ready=false; } } function checkURL(url){ var i; for(i=0;i<urlPats.length;i++){ if(urlMatch(url,urlPats[i])===true){ return true; } } } function urlMatch(url,match){ if(urlMode==0){//完全一致 if(url===match){return true;} }else if(urlMode==1){//先頭一致 if(url.indexOf(match,0)==0){return true;} }else if(urlMode==2){//部分一致 if(url.indexOf(match,0)>=0){return true;} }else if(urlMode==3){//ワイルドカード }else if(urlMode==4){//正規表現 var p= new RegExp(match,"i"); if(url.match(p)){return true;} } return false; } </script>

HTTPリクエストを監視する拡張

シンプルなコードにしようと思ったのですが、予想外にゴチャゴチャしてしまいました。最初は直接フィルタを埋め込んでやればいいやと思っていましたが、どのURLをフィルタリングすればいいのかを調査するのも一苦労だと気付いたので、調査と登録を同時に出来る本拡張を作成しました。

ここまで書いておいてなんですが、実はWebRequest APIに一番期待しているのは広告ブロックではありません。WebRequest APIで一番作りたいのが、動画/画像ダウンローダー。レスポンスヘッダーを書き換える事が出来ればattachment; filenameの追加も可能になると期待しています。後はFileAPIでフォルダ分類も出来れば言う事無しだけど、それは流石にセキュリティの面で難しそうだ。