2010-02-23

あるchromeのextensionを作りたいが、効率のよい実装方法が分からない

dilbert.comというサイトがある。これは、Scott Adamsの手による、非常に有名なマンガである。一日一作、欠かさず更新される。

私は、このマンガが好きであるが、今まで、まともに読んではいなかった。理由は、読むのが面倒だからだ。

面倒というのはどういうことか。それは、このマンガの画像が、縮小版で提供されているのである。それゆえ、実に読みにくい。原寸大で表示するためには、公式サイトから、ルーペのアイコンをクリックしなければならない。原寸大画像のURLが、新しいタブで開かれる

このサイトは、もちろん、フィードを提供している。しかも、ちゃんとimg要素で画像が埋め込まれているフィードである。しかし、そのURLは、画像の縮小版である。

縮小版で、読めないということはない。しかし、非常に劣化していて、読みにくい。そこで、真にマンガを楽しもうと思ったならば、フィードから公式サイトに飛んで、さらに原寸大画像のURLを開かなければならない。こんな短い画像ひとつを閲覧するために、みっつもタブを開かなければならないのは、苦痛である。私は、原寸大の画像を、一覧表示して、だらだらと読みたいのだ。ひとつの画像のために、タブを三つも開くのは、時間の無駄である。

dilbert.comのフィードは、かなり前にGoogle Readerに放り込んでいたが、上記の理由で、まともに読んではいなかった。さて、手間を軽減するために、手間ひまかけるのが、プログラマのすることである。幸い、今日は朝早く目覚めて、たまたま面白いDilbertのマンガを読んだので、手間をかけようという気分になった。

実は、dilbertのマンガの画像は、単純なURL直リンでアクセスできるし、公式サイトは、べつにURLを隠そうともしていない。原寸大表示をすれば、その画像のURLを、新しいタブで表示するのである。URLは、以下の通りである。

原寸大URL:http://dilbert.com/dyn/str_strip/000000000/00000000/0000000/000000/80000/3000/000/83078/83078.strip.zoom.gif (1000×311)
公式サイトの縮小URL:http://dilbert.com/dyn/str_strip/000000000/00000000/0000000/000000/80000/3000/000/83078/83078.strip.gif (640×199)
フィードのURL:http://dilbert.com/dyn/str_strip/000000000/00000000/0000000/000000/80000/3000/000/83078/83078.strip.print.gif (560×174)

見ての通り、規則性のある、非常に単純なURLである。単に、URLの最後を、strip.zoom.gifに置き換えればよいのだ。だらだらと流し読みするには、Google Readerが最もふさわしい。ならば、Google ReaderのDOMを書き換えてやればいいのではないか。そのようなextensionは、Chromeならば、簡単につくれる。

単純にURLを置換するコードは、5分とかからずに書ける。

(function()
{
        // query for all img element in the Google Reader's items.
        var list = document.querySelectorAll("div.item-body img") ;
        
        for ( var i = 0 ; i != list.length ; ++i )
        {
                var node = list.item( i ) ;
                var src = node.getAttribute("src") ;

                // if img element is for dilbert cartoon URL.
                if ( src.match(/http:\/\/dilbert\.com/) )
                {// replace it to bigger image URL.
                        node.setAttribute("src", src.replace(/strip\..*\.gif/, "strip.zoom.gif") ) ;
                }
        }
})();

これは動く。しかし問題は、Google Readerは、非常に動的なサイトだということだ。DOMが、ユーザーの操作によって、後から後から、際限なく追加される。Google Readerの読み込み時に、一度置換すればいいというわけではない。

かといって、タイマーを使うのも、無粋である。img要素が挿入された時だけ呼び出されるEventがあればいいのだが。

DOMNodeInsertedを使ってみたが、どうも上手くいかない。これは、説明にあるように、childだけで、孫には働かないのだろうか。DOMSubtreeModifiedや、DOMNodeInsertedIntoDocumentも、どうも上手くいかない。なぜだろう。このようなハンドラを書いたのだが。

var listener = {
handleEvent : function( event )
{
        var target = event.target ;

        if (    target == target.ELEMENT_NODE
                && target.nodeName.toLowerCase() == "img"
            )
        {
                var src = target.getAttribute("src") ;
                console.log(src) ;

                /* if img element is for dilbert cartoon URL. */
                if ( src.match(/http:\/\/dilbert\.com/) )
                {/* replace it to bigger image URL. */
                        target.setAttribute("src", src.replace(/strip\..*\.gif/, "strip.zoom.gif") ) ;
                }
        }
}
} ;

Google Readerのスクリプトをいじるしかないのだろうか。しかし、それは、Google Readerの実装が変わると、動かなくなる恐れがある。

追記:target.nodeType == target.ELEMENT_NODEとすべきところを、target == target.ELEMENT_NODEとしてしまっていた。道理で動かないわけだ。

6 comments:

os0x said...

target == target.ELEMENT_NODEは、
target.nodeType == target.ELEMENT_NODE
のミスでしょうか。
あとは大体問題ないと思いますが、DOMNodeInsertedはGoogle ReaderのようなDOMを頻繁に書き換えるサイトのパフォーマンスを大きく影響するので、loadイベントをキャプチャする方法がお薦めです。
複数回処理しないようにする、大きい画像がみつからなかった場合の処理も入れてこんな感じでしょうか。
document.addEventListener('load',function(event){
var target = event.target;
if ( target.nodeType == target.ELEMENT_NODE &&
target.nodeName.toLowerCase() == "img") {
var src = target.getAttribute("src") ;
console.log(src) ;
/* if img element is for dilbert cartoon URL. */
if ( src.match(/^http:\/\/dilbert\.com/) && !/zoom\.gif$/.test(src)){
target.onerror = function(){
target.setAttribute("src", src);
};
/* replace it to bigger image URL. */
target.setAttribute("src", src.replace(/strip\.(\w+\.)?gif$/, "strip.zoom.gif") ) ;
}
}
},true);

Anonymous said...

本題とは関係ありませんが、handleEventは関数がfirst class objectではない言語のためのもので、ECMAScript bindingでは関数オブジェクトを直接listenerとして登録するのではありませんでしたっけ。
http://www.w3.org/TR/DOM-Level-2-Events/ecma-script-binding.html
> Object EventListener
> This is an ECMAScript function reference. This method has no return value. > The parameter is a Event object.

江添亮 said...

うお・・・申し訳ないorz
動かないのも道理というわけだ。

そうなんです。DOMNodeInsertedのようなイベントで実装するのは、どうも好きになれなかったので
そうか。documentに対するload Eventでも良かったのか。

でも、DOMNodeInsertedIntoDocumentの方が、縮小版の画像を読み込む前に、img要素が追加された段階で動けるので、
一瞬、縮小版の画像が表示されてしまうということがないんですよね。

江添亮 said...

あ、Javascriptだと、規格的にも、関数になるのか。
直さないと。

Anonymous said...

Yahoo Pipes (http://pipes.yahoo.com/pipes/) で、簡単にRSSの中身を変更することができますよ。
今回のようなURLの一部置換ならすぐじゃないでしょうか。

江添亮 said...

へぇ、Yahoo Pipes話に聞いていましたが、そんな事もできるのですか。
面白そうではありますね。