メモ

調べたり思いついたりしたことをメモします

Firefox OS に独自のWebAPI(DeviceAPI)を追加する方法

Geckoには予め多くのWebAPIが実装されていますが、独自のAPIを追加したいと思ったら改造するしかありません。 改造は案外簡単にできるようになっていますので HelloWorld を出力するサンプルを作ってみました。

Firefox OS と書きましたが普通のFirefoxブラウザでもまったく同様です。

事前準備

mozilla-central から hg pull 等して環境一式を持ってきてビルドできる状態にします。

※ここでは2014.06.21のナイトリーを使っており、Geckoは33.0a1です。

WebIDLの作成と登録

ここから本題です。

webidl

WebIDLはインタフェース定義です。JavaScriptから呼ぶインタフェースを定義する記述言語で、W3C等で規定されるWebAPIはこの書式が使われます。

WebIDLを入れる場所は mozilla-central/dom/webidl/ です。 この場所に HelloWorld.webidl というファイルを新規作成します。 中身は簡単な例としてこんな内容にします。ここではPrintメソッドを呼んだら文字列を返すインタフェースにしています。

[Constructor]
interface HelloWorld
{
    [Throws]
    DOMString Print();
};

moz.build

次に同じディレクトリ上にある moz.build 内の WEBIDL_FILES にファイル名を追加します。追加する場所はアルファベット順にソートして入れる必要がありますので場所に気をつけましょう。 これを書いておかないと先ほどのwebidlファイルがビルドに含まれてくれません。

'GetUserMediaRequest.webidl',
'HelloWorld.webidl',           ←※コレを追加
'History.webidl',

バインディングの設定

Bindings.conf

webidlと、ネイティブ層実装をつなぐためのバインディングを設定します。 ファイルは mozilla-central/dom/bindings/Bindings.conf です。 ファイル内のDOMInterfacesにHelloWolrdのバインディングを追加しましょう。

'HelloWorld': {
    'nativeType': 'mozilla::dom::HelloWorld',
}

本体の実装

バインディングから実際に呼び出されるネイティブコードを作成します。 専用のディレクトリを用意してその中に実装を置くようにすると置き場所がすっきりして気持ちいいです。 ここでは mozilla-central/dom/mywebapi を掘ってその中に作ることにします。

$ cd mozilla-central/dom
$ mkdir mywebapi

mywebapi というディレクトリを掘ったら mozilla-central/dom/moz.build を編集します。 PARALLEL_DIRS に mywebapi を追加してやればディレクトリを認識してくれてビルドが通るようになります。

'xbl',
'xslt',
'mywebapi',         ←※コレを追加
]

mywebapiに移動して HelloWorld.h、HelloWorld.cpp、moz.build の3ファイルを作ります。中身はこれです。

HelloWorld.h の中身

#include "mozilla/ErrorResult.h"
#include "nsWrapperCache.h"
#include "jsapi.h"
#include "nsIDocument.h"
#include "mozilla/dom/TypedArray.h"
#include "nsString.h"

namespace mozilla {
namespace dom {

class HelloWorld  : public nsISupports,
                    public nsWrapperCache
{
public:
    NS_DECL_CYCLE_COLLECTING_ISUPPORTS
    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(HelloWorld)

    HelloWorld( nsPIDOMWindow* aWindow);
    ~HelloWorld();

    nsIDOMWindow* GetParentObject() const
    {
        return mWindow;
    }

    virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;

    static already_AddRefed<HelloWorld>
    Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);

    void Print(nsString& ret,ErrorResult& aRv)
    {
        nsString* tmp = new nsString((nsString::char_type*)L"Hello World!",24);
        ret = *tmp;
    }

private:
    nsRefPtr<nsPIDOMWindow> mWindow;

};

} // namespace dom
} // namespace mozilla

HelloWorld.cpp の中身

#include "HelloWorld.h"
#include "mozilla/dom/HelloWorldBinding.h"
#include "mozilla/HoldDropJSObjects.h"
#include "nsContentUtils.h"

namespace mozilla {
namespace dom {

NS_IMPL_CYCLE_COLLECTION_CLASS(HelloWorld)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HelloWorld)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HelloWorld)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(HelloWorld)
  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(HelloWorld)
NS_IMPL_CYCLE_COLLECTING_RELEASE(HelloWorld)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HelloWorld)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

already_AddRefed<HelloWorld>
HelloWorld::Constructor(const GlobalObject& aGlobal,
                        ErrorResult& aRv)
{
    nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aGlobal.GetAsSupports());
    if (!win) {
        aRv.Throw(NS_ERROR_FAILURE);
        return nullptr;
    }

    nsRefPtr<HelloWorld> hello = new HelloWorld(win);
    if (!hello) {
        aRv.Throw(NS_ERROR_FAILURE);
        return nullptr;
    }
    return hello.forget();
}

HelloWorld::HelloWorld( nsPIDOMWindow* aWindow)
{
    mWindow = aWindow; // For GetParentObject()
    SetIsDOMBinding();
}

HelloWorld::~HelloWorld()
{
}

JSObject*
HelloWorld::WrapObject(JSContext* aCx)
{
    return HelloWorldBinding::Wrap(aCx, this);
}


} // namespace dom
} // namespace mozilla

moz.build の中身

EXPORTS.mozilla.dom += [
    'HelloWorld.h',
]
SOURCES += [
    'HelloWorld.cpp',
]

FAIL_ON_WARNINGS = True

FINAL_LIBRARY = 'gklayout'

ビルド

あとは普通にビルドすれば出来上がります。

$ cd mozilla-central
$ make -f client.mk

テストコード

HelloWorldの Print(); を呼び出すサンプルコンテンツです。出来上がったb2gやfirefoxでこのコンテンツを表示させてみれば先ほど作成した Print() が文字列を返してくれることを確認できます。

<!DOCUTYPE html>
<html>
<head>
    <meta charset=UTF-8>
    <script>
        function Hello() {
            var helloWorld = new HelloWorld(window);
            var moji = helloWorld.Print();
            document.getElementById("str").textContent=moji;
        };

    </script>
</head>
<body onload="Hello()">
    <p id="str">
    </p>
</body>
</html>