スポンサーリンク

画面上の邪魔なものを自動ブロックする Firefox プラグインの作り方  (XPI アドオンを自作しよう)


Webサイトを閲覧しているとき,

  • 広告が邪魔だ,
  • 画像がけばけばしい,表示したくない

と感じる事がある。


もしページ読み込み時に,「現在表示中のサイトと関係のない要素」をブラウザが自動判別して,非表示にしてくれたら便利だ。



では,そのような Firefox プラグインを作ってみよう。



XMLJavaScript を書くだけですぐにできる。(3分ぐらい)

下記は開発手順。



※なお,本記事の執筆時点ではFirefox 3がターゲットでしたが,最新版のFirefox 7にも対応できるように,記事の内容を改変しました。(2011/11)

ブラウザに持たせたい機能

下のような機能のアドオンを作る。

Webページ読み込み時に,自動的に:

  • 外部ドメインの画像を非表示にする。
  • 外部ドメインのFlashを非表示にする。
  • 外部ドメインのインラインフレーム(iframe要素)を非表示にする。


Webページ閲覧時に:

  • 上記の要素の 表示 / 非表示 を,ショートカットキーで切り替えられる。 
    • すべての画像+Flash+iframeを表示,
    • すべての画像+Flash+iframeを非表示,
    • 外部ドメインの画像+Flash+iframeを非表示, の3機能をキーボード操作で実現する。

これなら,Webサイト閲覧時に広告はほとんどブロックできる。

また,ページ中の画像は全部消してテキストだけを見たい,という時にもキーボードで一発で表示切り替えができる。



注:完成品はこちらからダウンロード・インストールできます。

http://www.name-of-this-site.org/coding/addon/bye-domain.xpi


作り方手順

Firefox プラグインには

  1. XMLによる検索ツールバーのカスタマイズ
  2. XML + JavaScript (XUL形式)によるブラウザ拡張
  3. C++アプリケーションによるブラウザ拡張
  4. Greasemonkeyというプラグインを導入し,その上でユーザスクリプトを走らせる

などの実装方法があるが,ここでは2番目のXUL(ヅール)形式を採用する。
(そうすると Mozilla アプリケーションの仕組みがよくわかるので。)


また,特別なツールは何も使用しない事にする。

Firefox は閉じておくこと。

また,以下で作成するテキストファイルはUTF-8で保存すること。


※ 上記 1 の検索ボックスのカスタマイズの方法は,下記などを参照。

Firefox用 検索プラグインの作り方
http://allabout.co.jp/internet/hpcreate/closeup/CU20060715A/index.htm?FM=rss

(1)開発用のフォルダ作成
   D:\dev\Extensions

というフォルダを作り,ここで Firefox エクステンションの開発を行なう事にする。

(フォルダ名は何でもよい。)



そのフォルダ上に,これから作るアドオンと同じ名前のフォルダを準備する。

これから作るのは「外部ドメインをブロックする」という機能のアドオンだから,名称は適当に "bye-domain" とでもしよう。

   D:\dev\Extensions\bye-domain
(2)開発フォルダを Firefox に認識させる

Firefoxから,前項で作成した bye-domain というフォルダを参照できるようにする。


FF3の場合:

  C:\Program Files\Mozilla Firefox 3\extensions

または,最新版のFirefoxの場合:

  C:\Program Files\Mozilla Firefox\extensions

上記のフォルダを開いて,ここにアドオンのIDと同じ名前のファイルを作る。

アドオンのIDは,「アドオンの名称@作成組織」のような形式になる。


ここでは,私の独自ドメインである name-of-this-site.org を使って

   bye-domain@name-of-this-site.org

という名前のIDにする。


このIDと同じ名前のファイルを作り,中身に下記の文をコピペ。

   D:\dev\Extensions\bye-domain\

※行末の「\」を忘れずに。

※もしVistaやWindows7だと,このフォルダ上にはフォルダしか新規作成できない。また,管理者権限が必要。なので,別フォルダでテキストファイルを作ってから,管理者アカウントでこのフォルダ上にファイルを移動すればよい。


(1)で作成したフォルダへのリダイレクトを宣言し,Firefoxがそのフォルダをプラグイン所在地として認識できるようにしている。

次項から,このリダイレクト先のフォルダ上で具体的なアドオンのソースコードを書いていく。


(3)アドオンの設定ファイル作成

(1)で作った bye-domain というフォルダ上に,2つの設定ファイルを作る。


一つ目は install.rdf という名称のXMLファイル。アドオンの説明が記載される。

下記の内容をコピペする。

<?xml version="1.0"?>

<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
	xmlns:em="http://www.mozilla.org/2004/em-rdf#">
	<Description about="urn:mozilla:install-manifest">
		<em:version>1.0.1</em:version><!-- アドオンのバージョン -->
		<em:id>bye-domain@name-of-this-site.org</em:id><!-- アドオンのid -->
		<em:type>2</em:type>
		<em:iconURL></em:iconURL><!-- アイコンのChromeURL -->
		
		<em:targetApplication>
			<!-- Firefox -->
			<Description>
				<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
				<em:minVersion>3.0.1</em:minVersion><!--対応するFirefoxの最低バージョン-->
				<em:maxVersion>*</em:maxVersion><!--対応するFirefoxの最高バージョン-->
			</Description>
		</em:targetApplication>

		<em:localized>
			<Description>
				<em:locale>en-US</em:locale><!-- 英語環境での情報であることを表す。 -->
				<em:creator>id:language_and_engineering</em:creator><!-- 英語の作者名 -->
				<em:name>bye other domains</em:name><!-- 英語のアドオン名 -->
				<em:description>turns off images from other domains.</em:description><!-- 英語説明 -->
				<em:homepageURL>http://d.hatena.ne.jp/language_and_engineering/</em:homepageURL><!--ホームページ URL -->
			</Description>
		</em:localized>
		<em:localized>
			<Description>
				<em:locale>ja</em:locale><!-- 日本語環境での情報であることを表す。 -->
				<em:creator>id:language_and_engineering</em:creator><!-- 日本語の作者名 -->
				<em:name>他ドメインブロッカー</em:name><!-- 日本語のアドオン名 -->
				<em:description>他ドメインの画像やインラインフレームを非表示にします。</em:description><!-- 日本語説明 -->
				<em:homepageURL>http://d.hatena.ne.jp/language_and_engineering/</em:homepageURL><!--ホームページ URL -->
			</Description>
		</em:localized>
	</Description>
</RDF>

maxVersionを「3.*」とかにすると,Firefoxが自動アップデートされたときにアドオンも無効化されてしまうので注意。

※ここでは creator や homepageURL の項目は私の情報になっている。

※install.rdf の内容は,下記サイトのテンプレートを参考にさせて頂きました。

アドオンの作成:Spketを導入しない場合
http://www.realintegrity.net/~masahal8/addon_lecture/no_spket.html

二つ目は, chrome.manifest という名前のファイル。ここにはMozillaアプリケーションとしての登録情報を記載する。

下記の内容をコピペ。

content bye-domain content/
overlay chrome://browser/content/browser.xul chrome://bye-domain/content/bye-domain.xul

二行目に出てくる "chrome" というのは,MozillaアプリケーションのGUIプログラムの種別を指す。

chrome://browser とは,Mozillaアプリケーション群中のGUIの中でも特にブラウザ,つまりFirefoxのことを指す識別子(URI)だ。



一行目から見ていこう。

一行目では,この chrome.manifest というファイルから見た content/ というパスに,アドオンの実体が存在するという宣言をしている。
(次項でこの content というフォルダを作る。)


二行目ではoverlay(オーバーレイ)という語が出てくるが,これは「機能を追加する」ということ。

FirefoxのGUIプログラム(browser.xul)に,content/bye-domain.xul というパスのアドオンを追加する,という宣言をしている。


(4)アドオンの実体をコーディングする

同じフォルダ上に content というフォルダを作成。

   D:\dev\Extensions\bye-domain\content


ここに,bye-domain.xul という名のファイルを作成する。これがアドオンのソースコードになる。


内容は下記をコピペ。(少し長い)

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>

<overlay id="bye-domain" 
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <!-- 「ツール」メニューに,実行したい関数とショートカットキーを割り当て -->
  <menupopup id="menu_ToolsPopup">
    <menuitem label='hide all images' oncommand='hideImages( true )' key="hideImagesKey" />
    <menuitem label='show all images' oncommand='showImages( true )' key="showImagesKey" />
    <menuitem label='hide external images' oncommand='hideImages( false )' key="hideExtImagesKey" />
  </menupopup>

  <!-- ショートカットキーが呼び出すコマンド -->
  <keyset id="mainKeyset">
    <key id="hideImagesKey" modifiers="control shift" key="N" command="hideImagesCommand" />
    <key id="showImagesKey" modifiers="control shift" key="M" command="showImagesCommand" />
    <key id="hideExtImagesKey" modifiers="control shift" key="O" command="hideExtImagesCommand" />
  </keyset>

  <!-- コマンドが呼び出す関数 -->
  <commandset id="mainCommandSet">
    <command id="hideImagesCommand" oncommand="hideImages( true )"/>
    <command id="showImagesCommand" oncommand="showImages( true )"/>
    <command id="hideExtImagesCommand" oncommand="hideImages( false )"/>
  </commandset>
  

  <!-- JavaScript関数 -->
  <script type="application/x-javascript"><![CDATA[

    /*
    
    このアドオンの機能:
    
    (1) 
      Webページ読み込み時に,
        ・他ドメインの画像
        ・他ドメインのFlash
        ・他ドメインのiframe
      を消去。
      閲覧中のページと関係のない,余計な物を見なくて済みます。
      
    (2) 
      Webページ閲覧中に,(1)の要素の表示切り替えができます。
        ・Ctrl + Shift + M で表示回復。                        [M]ieru
        ・Ctrl + Shift + N でドメインに関わりなく全て非表示。  [N]ot mieru
        ・Ctrl + Shift + O で他ドメインの要素を非表示。        [O]pening state
      使い道としては,
        ・(1)で消去された要素を,Ctrl + Shift + M で画面に回復できます。
        ・(1)で消去しきれなかった要素を,Ctrl + Shift + N で丸ごと消せます。
        ・Ctrl + Shift + O は,(1)と同じ状態に戻ります。
        
    */


    // ページ読み込み時の設定

    var MyExtention = {
      // ブラウザ起動時
      init: function() {
        window.removeEventListener("load", MyExtention.init, false);
        window.addEventListener("DOMContentLoaded", MyExtention.onContentLoad, false);
      },
      // 各ページ読み込み時
      onContentLoad: function() {
        hideImages( false ); // ドメイン制限
      }
    };
    window.addEventListener('load', MyExtention.init, false); 


    // 消去
    function hideImages( isAll ) // isAll:trueならドメイン制限しない
    {
      modifyImages( isAll ? hideInDocument : hideInExtDocument );
    }

    function hideInExtDocument( d )
    {
      // この部分のコードは共通化せず,各操作ごとにカスタマイズできるように
      disp_if_ext( d.getElementsByTagName("img"),    "none");
      disp_if_ext( d.getElementsByTagName("object"), "none");
      disp_if_ext( d.getElementsByTagName("iframe"), "none");
    }

    function hideInDocument( d )
    {
      disp( d.getElementsByTagName("img"),    "none");
      disp( d.getElementsByTagName("object"), "none");
      disp( d.getElementsByTagName("iframe"), "none");
    }

    // 表示
    function showImages( isAll ) // isAll:trueならドメイン制限しない
    {
      modifyImages( isAll ? showInDocument : showInExtDocument );
    }
    
    function showInExtDocument( d ) {
      disp_if_ext( d.getElementsByTagName("img"),    "");
      disp_if_ext( d.getElementsByTagName("object"), "");
      disp_if_ext( d.getElementsByTagName("iframe"), "");
    }

    function showInDocument( d ) {
      disp( d.getElementsByTagName("img"),    "");
      disp( d.getElementsByTagName("object"), "");
      disp( d.getElementsByTagName("iframe"), "");
    }


    // 共通関数


    // func:加えたい操作
    function modifyImages( func )
    {
      // 非フレーム部
      func( window.content.document );
      
      // フレーム有り
      if( window.content.frames.length > 0 )
      {
        for( var i = 0, len = window.content.frames.length; i < len; i ++ )
        {
          func( window.content.frames[i].document );
        }
      }
    }


    // 外部ドメインならエレメント配列の可視性を変化
    function disp_if_ext( elems, prop )
    {
      for (var i = 0, len = elems.length; i < len; i++) {
        if( ! isSameSite( window.content.document.location.href, elems[i].src ) )
        {
          elems[i].style.display = prop;
        }
      }
    }


    // ドメインに関わらずエレメント配列の可視性を変化
    function disp( elems, prop )
    {
      for (var i = 0, len = elems.length; i < len; i++) {
        elems[i].style.display = prop;
      }
    }


    // 2つのURLが同じドメインか
    function isSameSite( loc, src )
    {
      if( ! src.match( /^http/ ) )
      {
        return true;
      }
      else if( getSite( loc ) == getSite( src ) )
      {
        return true;
      }
      else
      {
        return false;
      }
    }


    // URLからサイト固有名を返す。.jpや.netの直前から先を固有名として採用
    function getSite( url )
    {
      var dmn = url.split("/")[2]; // ドメイン

      var dotlevels = dmn.split( "." );
      var end2 = dotlevels[ dotlevels.length - 2 ];
      var end1 = dotlevels[ dotlevels.length - 1 ];
      
      var site = end2
        + "."
        + end1
      ;
      if( end2 == "ne" || end2 == "co" )
      {
        site = dotlevels[ dotlevels.length - 3 ]
          + site
        ;
      }
      return site;
    }


  ]]></script>
</overlay>


このファイルがアドオンの要になる。


まず,menupopup のタグに記入した情報は,Firefoxのメニューバーの「ツール」(=menupopup)でそのまま表示される。

下記画像の赤で囲った部分を参照。



そこで設定したショートカットキーIDに,keysetタグの中でキーセットを割り当てる。


さらに,keysetで指定したコマンドIDに,commandsetタグの中でJavaScript関数を割り当てる。


続く部分で,そのJavaScript関数を記述する。

関数だけでなく,ページ読み込み(onloadイベント)時の操作を記述することもできる。


そして,画像・object要素・iframe要素を非表示に切り替える関数がつらつらと書かれている。



(5)動作確認

これでFirefoxを立ち上げると,アドオンが動作する。

適当なWebページを開いてみると,外部ドメインの要素が一切カットされている。



これは,某ニュースサイトを開いた時に,そのニュースサイトの外部の要素が一切カットされている様子。

画面トップや右柱の画像が何も出てこない。
(Ctrl + Shift + M で表示できる。)


また,ツールメニューを開いて,「hide all images」などのコマンドが実行できる事を確認してみよう。

上記のソースコード中でコメントアウトされているように,

Ctrl + Shift + M で表示回復。                        [M]ieru
Ctrl + Shift + N でドメインに関わりなく全て非表示。  [N]ot mieru
Ctrl + Shift + O で他ドメインの要素を非表示。        [O]pening state

という操作ができる。

このアドオンを使わない場合は,ツール→アドオン から「他ドメインブロッカー」を無効化しておけばよい。


ちなみに,もし上記の手順を踏んでもアドオンが動作しない場合は,次項のようにXPI形式にしてインストールすればちゃんと動作する。


(6)XPI形式にして配布する

前項までの方式だと,ブラウザを再起動すれば,XULファイルの編集結果がすぐにブラウザの動作に反映される。

開発向け,もしくは個人的な用途に適したアドオン設置方法と言えるだろう。


もし自作アドオンを配布して他の人が手軽にインストールできるようにしたい場合,ここまでで作成した内容を xpi ファイルにまとめてアップロードする。

やり方は,

  • install.rdf
  • chrome.manifest
  • contentフォルダ

を zip 圧縮した後で,名称を「bye-domain.xpi」のように変更する。


そしてxpiファイルをFirefoxで開けば,自動的にインストールの確認ダイアログが出る。




なお,XPIからインストールしたアドオンの実体は下記のような場所にある。

C:\Documents and Settings\{ユーザ名}\Application Data\Mozilla\Firefox\Profiles\{ランダムなハッシュ}\extensions\bye-domain@name-of-this-site.org


XPIを更新した場合は,install.rdf中のバージョン情報を書き換えた上で.xpiを再アップロードする。

(ブラウザのキャッシュで古いxpiファイルがインストールされてしまう事もあるので注意。)


なお,XPIファイルのインストールに失敗する場合は,ZIP圧縮時のフォルダ構造がおかしい。

ZIP内に,直下にinstall.rdfが存在すべき。

バージョンの古いFirefox Addonをインストールする : アクネシ
http://akuneshi.exblog.jp/17565036

  • このとき「このアドオンは壊れているため、インストールできませんでした。」となってしまう場合は、自分もハマッタのですが、上位のフォルダごと圧縮してしまうと構成が変わってしまうためエラーになります。 解凍するときに新しいフォルダを作る設定になっていると陥りやすいので注意しましょう。

リンク集

上記のような機能を実現したアドオンは,広告ブロッカーという形で世の中にいくらでも存在する。

例えば下記のようなもの。

サイトから邪魔なものを削除できるアドオン「RIP」
http://www.lifehacker.jp/2008/08/rip.html

しかし,アドオンが多すぎて,欲しい機能を持ったアドオンが探しきれないというのも最近問題になっている。

自分の欲しくない機能まで付加されていて,アドオンばかりでブラウザが重くなってしまうというのもよくあることだ。

  • 自分の欲しい機能をピンポイントで実現できる
  • 内部の仕組みがわかる

というのが,自作の強みだろう。



アドオン自作のための更なる情報は,下記サイトを参照。

Firefoxアドオン(拡張機能)の作り方
http://www.realintegrity.net/~masahal8/addon_lecture/index.html


Mozillaのページ:XULについて
https://developer.mozilla.org/ja/XUL


Firefox拡張機能(extension)の作り方
http://dev.ariel-networks.com/articles/workshop/firefox-extension-development/


Firefoxのキーボードショートカット一覧表
http://support.mozilla.com/ja/kb/Keyboard+shortcuts?style_mode=inproduct&bl=n


[Firefox]アドオン(拡張機能)にショートカットキーを追加する
http://d.hatena.ne.jp/onozaty/20080204/p1

関連する記事:

Firefoxのサイドバーを作ろう (XUL形式のアドオンでbrowser要素を設定する方法) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081203/1228262334


今から3分で,IE 上で .NET のDLLを動かそう (ブラウザ上で C# のコードを動かす方法) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20100705/p1


開いている全タブのURLとタイトルを,列挙して抽出するFirefoxアドオン (XUL形式プラグインのソースコード付) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20121221/p1