後編です。先日のバージョンアップについて書いていきます。

前編 → ニコニコサムネイル更新(前編)

Ver. 1.2.0

Ver. 1.2.0 の更新内容は以下のとおり。
・ Manifest Version 2 に変更。
・ ニコニコ動画:Q のプレイヤーに対応。

更新内容は少ないですが、中身は結構変わっています。
Manifest Version 2
Chrome 拡張機能の Manifest V1 は廃止されたので、 Manifest V2 に移行しました。
Manifest V2 についてのページはこちら → Tutorial: Migrate to Manifest V2
このページを見ながら manifest.json やコードを変更していきます。

新しい manifest.json は以下の様な感じ。
{
  "name": "ニコニコサムネイル",
  "version": "1.2.0",
  "manifest_version": 2,
  "description": "ニコニコ動画内のリンクにマウスオーバーでサムネイルを表示します。",
  "icons": {
    "48": "icon_small.png",
	"128": "icon_large.png"
  },
  "options_page": "options.html",
  "background": {
    "scripts": ["background.js"]
  },
  "content_scripts": [
    {
      "matches": ["http://*.nicovideo.jp/*"],
      "js": ["jquery-1.9.1.min.js", "nico_thumbnail.js"]
    }
  ]
}
"manifest_version" で Manifest Version を指定します。
書かないと Manifest V1 になりますが、 V1ではアップロードできません。

"background_page" プロパティはなくなって "background" プロパティになりました。
"background" の中で "page" または "scripts" を指定できます。
どうせ html には何も書いていなかったので "scripts" で直接 js ファイルを指定しています。

それから、今回 jQuery を導入したので "content_scripts" で jQuery のファイルを追加しています。
jQuery についてはこの記事では詳しく扱いません。

次に Manifest V2 への移行によるコードの変更です。
options.html など拡張機能で使う html ファイルの中に javascript コードを書けなくなりました。
body 要素の onload、input 要素の onclick といったインラインイベントハンドラも同様にNGになり、
script src で読み込んだ js ファイルの中で完結していなければいけません。
参考 → Chrome Extensionのmanifestバージョンを上げる(1から2へ) : ike-daiの日記 
参考ページで jQuery を使っていたので今回 jQuery を導入することにしました。

さて、background.js は以下のようになりました。
if (localStorage.length == 0) {
    localStorage["watch"] = "true";
    localStorage["mylist"] = "true";
    localStorage["community"] = "true";
    localStorage["seiga"] = "true";
    localStorage["removeClick"] = "false";
}

chrome.extension.onConnect.addListener(function (port, name) {
    // メッセージを受け取ったらオプションの情報を返す
    port.onMessage.addListener(function (info, con) {
        con.postMessage({
            watch: getFlag("watch"), mylist: getFlag("mylist"), community: getFlag("community"), seiga: getFlag("seiga"),
            removeClick: getFlag("removeClick")
        });
    });
});

function getFlag(name) {
    return localStorage[name] == "true";
}
onload で関数を呼び出すのではなくなったので、初期化を直に書いています。
また、 chrome.self が chrome.extension に統合されたのでそこも変更。

options.html は onclick などを省いただけなのでソースコードは省略。
options.js はこんな感じ。
$(function () {

    // localstorage のデータを復元。
    $(":checkbox").attr("checked", function () {
        return localStorage[$(this).attr("id")] == "true";
    });

    // localstorage にオプションを保存。
    $("#save").click(function () {
        var elements = $(":checkbox");
        for (i = 0; i < elements.length; i++) {
            localStorage[elements[i].id] = elements[i].checked;
        }

        // 保存完了メッセージを表示。
        $("#status").text("保存しました。");
        setTimeout(function () {
            $("#status").text("");
        }, 1750);
    });

});
jQuery のおかげでかなりシンプルになりました。
保存ボタンの id に "save" と指定して、
コードで $("#save").click とするだけでclick 時のイベントハンドラを定義できます。

ニコニコ動画:Q への対応
Q というか Zero からですが、再生リストなどで javascript によるページ遷移をするようになったので、
移動後の動画説明文ではサムネイル表示用のイベントハンドラが登録されておらず、動作しませんでした。
しかし、これも jQuery の on 関数で解決出来ました。

というわけで、今回の nico_thumbnail.js はこんな感じ。
$(function () {

    // URLの文字列定義
    var iframeStart = '<iframe width="312" height="176" src="';
    var iframeEnd = '" scrolling="no" style="border:solid 1px #CCC;" frameborder="0" />';
    var watchThumbnailUrl = iframeStart + 'http://ext.nicovideo.jp/thumb/';
    var mylistThumbnailUrl = iframeStart + 'http://ext.nicovideo.jp/thumb_';
    var communityThumbnailUrl = iframeStart + 'http://ext.nicovideo.jp/thumb_community/';
    var seigaThumbnailUrl = iframeStart + 'http://ext.seiga.nicovideo.jp/thumb/';

    var thumbnail;
    var option_valid = [];
    var removeClick;
    var isShowed = false;

    var connection = chrome.extension.connect();
    connection.onMessage.addListener(function (info) {
        option_valid["watch"] = info.watch;
        option_valid["mylist"] = info.mylist;
        option_valid["community"] = info.community;
        option_valid["seiga"] = info.seiga;
        removeClick = info.removeClick;
    });
    connection.postMessage();

    // マウスオーバー時サムネイル作成
    $(document).on("mouseover", ".videoDescription a",
        function (e) {
            createThumbnail(e.target, e.pageX + 5, e.pageY + 5);
        }
    );

    // マウスアウト時サムネイル消去(設定による)
    $(document).on("mouseout", ".videoDescription a",
        function (e) {
            if (!removeClick) {
                removeThumbnail();
            }
        }
    );

    // マウスダウン時サムネイル消去(設定による)
    $(this).mousedown(function (e) {
        if (removeClick) {
            removeThumbnail();
        }
    });
    
    // サムネイル消去
    function removeThumbnail() {
        if (isShowed) {
            $(thumbnail).css("display", "none");
            isShowed = false;
        }
    }

    // サムネイルの作成
    function createThumbnail(link, x, y) {

        // リンクの文字列からURLを作成
        var thumbnailUrl = '';
        var id = link.innerText;
        var matchResult;
        if (id == null) return;
        if (option_valid["watch"] && (matchResult = id.match(/([sn]m[0-9]+)/))) {
            thumbnailUrl = watchThumbnailUrl;
            id = matchResult[1];
        }
        else if (option_valid["mylist"] && id.match(/^mylist\/[0-9]+$/)) {
            thumbnailUrl = mylistThumbnailUrl;
        }
        else if (option_valid["watch"] && (matchResult = id.match(/^watch\/([0-9]+)$/))) {
            thumbnailUrl = watchThumbnailUrl;
            id = matchResult[1];
        }
        else if (option_valid["community"] && id.match(/^co[0-9]+$/)) {
            thumbnailUrl = communityThumbnailUrl;
        }
        else if (option_valid["seiga"] && id.match(/^im[0-9]+$/)) {
            thumbnailUrl = seigaThumbnailUrl;
        }
        else {
            return;
        }

        removeThumbnail();

        // サムネイルのスタイル設定
        thumbnail = document.createElement("div");
        $(thumbnail).css("position", "absolute")
                    .css("zIndex", 2147483647)
                    .css("left", x + "px")
                    .css("top", y + "px")
                    .html(thumbnailUrl + id + iframeEnd)
                    .appendTo(document.body);

        isShowed = true;
    }

});
マウスオーバーやマウスアウトの部分の $(document).on が今回のポイントです。
引数に、イベント、セレクター、イベントハンドラを指定することができます。
これによって後から追加された要素にもイベントハンドラが設定されます。
videoDescription は動画説明文の部分のクラスです。

サムネイルの作成、消去にも jQuery の関数を使っています。
LINQ のように繋げて書けるのがいいですね。

マウスクリックでサムネイル消去するのはなんとなくマウスダウンの時のほうがいいかなぁと思って変えています。


まとめ
ここまでで Ver. 1.2.0 の更新は終了です。
ところで、先ほどサムネイル表示のイベントハンドラで videoDescription クラスを指定していますが、
これを指定しちゃうとタグとかで動作しなくなることに後から気づきました……。
なのでそこを修正(a 要素だけを指定)して、現在は Ver. 1.2.1 となっています。

jQuery を初めて使いましたが、とても便利ですね。
最近色んな所で使われているのも分かります。

以前の記事
Ver. 1.0.0 : 拡張機能:ニコニコサムネイル
Ver. 1.1.0 : ニコニコサムネイル更新(前編)