Mitsugu Oyama のソフトウェア倉庫
 > コラム

2017.01.28
WebExtensions 版 Firefox アドオンを書いてみた

Firefox のアドオンで WebExtensions を使ったもの以外のアドオンが全廃されることになったのは周知のとおりだ。なので WebExtensions を使ったアドオンを書いてみた。

どのようなアドオンかというとトーチ Web に掲載されているマンガが開いている時に、コンテキスト・メニューからダウンローダを選択すると、開いているマンガをダウンロードするという、ちょびっとだけ手間を軽減するアドオンだ。アドオン名は安易だが tochi とした。トーチ Web によると正しくは to-ti らしいが個人使用用のアドオンなのでそのままでいいことにした。

WebExtensions 版アドオンにはまず最初に manifest.json が必要になるので、以下のように書いた。

{
  "manifest_version": 2,
  "name": "tochi",
  "version": "1.0.0",
  "description": "Download Tochi's manga",
  "icons": {
    "16": "icons/16.png",
    "48": "icons/48.png"
  },
  "background": {
    "scripts": ["background.js"]
  },
  "content_scripts": [
    {
      "matches": ["http://www.to-ti.in/product/*"],
      "js": ["page.js"],
      "run_at":"document_end"
    }
  ],
  "permissions": [
    "tabs",
    "contextMenus",
    "downloads"
  ]
}

ざっくり見ていくと、バックグラウンドスクリプトの利用を宣言しているが、これは downloads API がバックグラウンドスクリプト上でないと動かないからだ。

次にコンテンツスクリプトの利用も宣言されているが、これは表示中のページから DOM を使って IMG 要素の SRC 属性の値を取得するのだけれど、そのためにはコンテンツスクリプト上から DOM アクセスする必要があるからだ。

このようなことから、当然バックグラウンドスクリプトとコンテンツスクリプトはメッセージングにより強調して動作しないといけない。なのでバックグラウンドスクリプトには以下のようにメッセージングの処理が入っている。

(function(){
  function onError(error) {
    console.error(`Error: ${error}`);
  }

  function logTabs(tabs){
    console.log("tabId : "+tabs[0].id);
    browser.tabs.sendMessage(
      tabs[0].id,
      {request:"send urls"},
      function(response){
        for(var i=0,cnt=response.length;i<cnt;i++){
          browser.downloads.download({
            "url":response[i],
            "conflictAction":"uniquify"
          });
        }
      }
    );
  }

  function createContextMenu(){
    chrome.contextMenus.create({
      "title":"save tochi",
      "type":"normal",
      "contexts":["all"],
      "documentUrlPatterns":["http://www.to-ti.in/product/*"],
      "onclick":function(){
        var getting=browser.tabs.query({"active":true});
        getting.then(logTabs,onError);
      }
    });
  };
  createContextMenu();
})();

コンテンツスクリプトからレスポンスとして URL のリストを配列で受け取り、それを downloads.download に渡してダウンロードを行うようになっている。

それと注目して欲しいのは 29 行目だ。browser.tabs.query で現在アクティブになっている tab の情報を取得している。これがメッセージの送り先のコンテンツスクリプトを指定するのに必要になる。

注目すべきは browser.tabs.query の戻り地が Promise になっているということだ。WebExtensions は Google Chrome API と互換性があるとのことだが、Google Chrome API では普通に引数にコールバック関数を指定するようになっている。互換と言っても完全ではないので注意が必要だ。

またメッセージはブロードキャストっぽく送られるので、自分は 10 行目のようにレシーブしたい側で自分へのメッセージを特定できる情報を付加している。

コンテンツスクリプトを以下に示すが、特記すべきことはない。メッセージを受け取ったら DOM を使ってドキュメントから IMG 要素の SRC 属性の内容を取得し、配列に push して、その配列をメッセージの送信元に返しているだけだ。

(function(){
  chrome.runtime.onMessage.addListener(
    function(request,sender,sendResponse){
      var urls=new Array();
      var elmImages;
      if(request.request=="send urls"){
        elmImages=document.getElementsByClassName("images")[0]
            .getElementsByTagName("img");
        for(var i=0,cnt=elmImages.length;i<cnt;i++){
          urls.push(elmImages[i].getAttribute("src"));
        }
      }
      sendResponse(urls);
      return true;
    }
  );
})();

これら 100 行にも満たないコードで、このアドオンは構成されている。なおこのアドオンは Firefox Developer Edition でないと動かない ( 本校執筆時点で Firefox Developer Edition のバージョンは 52.0a2 )。リリース版の Firefox ( 本稿執筆時点で Firefox のバージョンは 51.0.1 ) では動かない。WebExtensions の実装、はたして Firefox 53.0 までに間に合うのか?