Android版Firefoxアドオン開発事始め

本日のテーマ

Android版Firefoxアドオン開発の基礎を学ぶ

ごみた

デスクトップ版Firefox向けアドオン開発暦7年

Mozilla Hackathon 2012

トラックパッドの三本指スワイプ

"Three Finger Swipe" アドオン

アドオン開発実践

開発環境

※パソコンが高スペックならAndroidエミュレータでもいける?

開発作業の流れ①

パソコン

ソース編集
 ▼
インストーラ作成
 ▼
MicroSDカードへコピー

モバイル端末

MicroSDカードを装着
 ▼
file:///mnt/sdcard/ をFirefoxで開く
 ▼
インストール

開発作業の流れ②

パソコン

ソース編集
 ▼
インストーラ作成
 ▼
Dropboxで共有

モバイル端末

Web版DropboxをFirefoxで開く
 ▼
インストール

"View Source" アドオン

"View Source" アドオン

フォルダ構成

install.rdf

<?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:id>viewsource@xuldev.org</em:id>
    <em:type>2</em:type>
    <em:name>View Source</em:name>
    <em:version>0.1</em:version>
    <em:bootstrap>true</em:bootstrap>
    <em:description>Adds 'View Source' menu.</em:description>
    <em:creator>Gomita</em:creator>
    <em:targetApplication>
      <Description>
        <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
        <em:minVersion>14.0</em:minVersion>
        <em:maxVersion>17.0a1</em:maxVersion>
      </Description>
    </em:targetApplication>
  </Description>
</RDF>

bootstrap.js (1/8)

function install(data, reason) {
    // アドオンをインストール時に実行する処理
}

function uninstall(data, reason) {
    // アドオンを削除時に実行する処理
}

function startup(data, reason) {
    // アドオンを起動時(有効化時)に実行する処理
}

function shutdown(data, reason) {
    // アドオンを終了時(無効化時)に実行する処理
}

bootstrap.js (2/8)

とりあえずServices.jsmをインポート

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");

bootstrap.js (3/8)

Firefoxウィンドウの監視①

function startup(data, reason) {
    // アドオン起動時、すでに開いているウィンドウを取得
    var winEnum = Services.wm.getEnumerator("navigator:browser");
    
    while (winEnum.hasMoreElements()) {
        var win = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
        if (win)
            // ウィンドウに対する処理
            loadIntoWindow(win);
    }
    
    // 今後開かれるウィンドウを監視
    Services.wm.addListener(windowListener);
}

bootstrap.js (4/8)

Firefoxウィンドウの監視②

var windowListener = {
    onOpenWindow: function(aWindow) {
        // 新しいウィンドウが開かれたら…
        var win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
                  getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
        
        // UIReadyイベントを監視
        win.addEventListener("UIReady", function() {
            win.removeEventListener("UIReady", arguments.callee, false);
            if (win)
                // UIReadyイベント発生後、ウィンドウに対する処理
                loadIntoWindow(win);
        }, false);
    },
    onCloseWindow: function(aWindow) {},
    onWindowTitleChange: function(aWindow) {},
};

bootstrap.js (5/8)

Firefoxウィンドウの監視を解除

function shutdown(data, reason) {
    // Firefox自体の終了時は何もしなくていい
    if (reason == APP_SHUTDOWN)
        return;
    
    // 今後開かれるウィンドウへの監視を終了
    Services.wm.removeListener(windowListener);
    
    // 現在開かれているウィンドウに対する処理
    var winEnum = Services.wm.getEnumerator("navigator:browser");
    while (winEnum.hasMoreElements()) {
        var win = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
        if (win)
            unloadFromWindow(win);
    }
}

bootstrap.js (6/8)

Firefoxウィンドウに対するUI生成処理

var gMenuId;

function loadIntoWindow(aWindow) {
    // メニュー項目を追加
    gMenuId = aWindow.NativeWindow.menu.add("View Source", null, function() {
        viewSource(aWindow);
    });
}

NativeWindow.menu.add … メニュー項目を追加するAPI

bootstrap.js (7/8)

Firefoxウィンドウに対するUI削除処理

function unloadFromWindow(aWindow) {
    // メニュー項目を削除
    aWindow.NativeWindow.menu.remove(gMenuId);
}

NativeWindow.menu.remove … メニュー項目を削除するAPI

bootstrap.js (8/8)

ソースを表示する処理

function viewSource(aWindow) {
    // 現在のxul:browser要素を取得
    var browser = aWindow.BrowserApp.selectedBrowser;
    
    // 現在のURL
    var url = browser.currentURI.spec;
    
    // view-source:を付加したURLを新しいタブで開く
    aWindow.BrowserApp.addTab("view-source:" + url);
}

BrowserApp.addTab … 新しいタブを開くAPI

インストーラ作成

API解説

ブラウザウィンドウのUI構成

デスクトップ版Firefox

ブラウザウィンドウのUI構成

モバイル版Firefox

<deck id="browsers" />

browser.xul 内のJavaScriptオブジェクト

デスクトップ版Firefox

モバイル版Firefox - デスクトップ版よりも綺麗にモジュール化されている

詳細はbrowser.jsのソースを参照

BrowserApp

アドオンからはタブブラウザ操作用APIとして利用

BrowserApp.tabs      // Tabオブジェクトの配列
BrowserApp.selectedTab   // 現在選択しているTabオブジェクト
BrowserApp.selectedBrowser // 現在選択しているxul:browser要素
BrowserApp.addTab()    // タブを開く
BrowserApp.closeTab()    // タブを閉じる
BrowserApp.selectTab()   // タブを選択する
BrowserApp.deck      // xul:deck要素

など

NativeWindow

Native UIの機能を呼び出すAPI

NativeWindow.menu.add()      // メニュー項目を追加
NativeWindow.contextmenus.add()  // コンテキストメニュー項目を追加
NativeWindow.toast.show()     // トースト型通知を表示
NativeWindow.doorhanger.show()   // ドアハンガー型通知を表示

など

sendMessageToJava()

Javaで実装された機能を呼び出す、より低レベルなAPI
内部的にはnsIAndroidBridge::handleGeckoMessage

sendMessageToJava({
    gecko: { type: "ToggleChrome:Show" }
})
sendMessageToJava({
   gecko: { type: "Tab:Select", tabID: aTab.id }
})

など

ローカライズ(日本語化)

フォルダ構成

chrome.manifest と locale フォルダを追加

chrome.manifest

localeパッケージを追加

locale    viewsource    en-US    locale/en-US/
locale    viewsource    ja    locale/ja/

main.properties

・locale/en-US/main.properties

menu=View Source

・locale/ja/main.properties

menu=ソースを表示

bootstrap.js (1/2)

main.properties のchrome:URL

chrome://viewsource/locale/main.properties

ローカライズされた文字列を取得する関数

var gStringBundle;

function getString(aName) {
    if (!gStringBundle) {
        var uri = "chrome://viewsource/locale/main.properties";
        gStringBundle = Services.strings.createBundle(uri);
    }
    return gStringBundle.GetStringFromName(aName);
}

bootstrap.js (2/2)

function loadIntoWindow(aWindow) {
    gMenuId = aWindow.NativeWindow.menu.add("View Source", null, function() {
        viewSource(aWindow);
    });
}

function loadIntoWindow(aWindow) {
    gMenuId = aWindow.NativeWindow.menu.add(getString("menu"), null, function() {
        viewSource(aWindow);
    });
}

デバッグ

エラーコンソール

chrome://global/content/console.xul

function log(aMsg) {
    Services.console.logStringMessage(aMsg);
}

alertデバッグ

function alert(aMsg) {
    Services.prompt.alert(null, "My Add-on", aMsg);
}

こんなことはできるのか?

ツールバーボタンの追加

ツールバーがNative UIなので不可
将来的に NativeWindow.toolbar.add のようなAPIが追加されるかも?

browser.xul へのオーバーレイ

オーバーレイして独自ツールバーを追加するなど、一応可能
ただでさえ画面が狭いので、現実的でない

XULによるUI構築

chrome.manifestでcontentパッケージを追加して実現可能
aboout:addons やabout:downloads のようにXHTMLによる構築が主流?

ローカルファイルの読み書き

可能
nsILocalFile のインスタンスを作るか、FileUtils.jsm モジュールを利用

再起動が“必要な”アドオン

<em:bootstrap>true</em:bootstrap> を削除すれば可能
XULオーバーレイが実質なくなったので、再起動不要が主流

雑感

ソースコード