Sep 04, 2007

他の拡張機能やFirefoxの機能を破壊しないための基本テク

拡張機能勉強会の時に焚き付けられたText Shadowのコード(textshadow.js)を教材にして拡張機能開発のノウハウを解説していくシリーズ。

JavaScriptでは、普通に宣言した変数や関数はグローバルな物になる。

var name = 'hoge';
function getItem(aKey) {
  return array[aKey];
}

だから、Firefoxで最初から定義されてるグローバル変数や関数と同じ名前の変数や関数を定義してしまうと、エラーが起こるし、最悪の場合はFirefoxが動かなくなってしまう。

// ステータスバーだけ表示した
// 新規ウィンドウを開く関数「loadURI」を定義。
function loadURI(aURI) {
  window.open(aURI, 'mytarget', 'status');
}
// でも、これをやってしまうと、事あるごとに
// 新しいウィンドウが開かれるようになってしまう。
// なぜなら、Firefox内で既に「loadURI」という関数が
// 「ページを現在のタブで読み込む関数」として
// 定義されているから。

// 「ブラウザの一覧」のページを新しいウィンドウで開いて、
// そのウィンドウをgBrowserという変数に格納する。
gBrowser = window.open('http://piro.sakura.ne.jp/browsers-list.html');
// でも、これをやってしまうと、Firefoxがまともに
// 動かなくなる。なぜなら、Firefoxのブラウズ領域の
// 要素ノードへの参照としてgBrowserが定義されているから。

これを防ぐ手っ取り早い方法としてお勧めしたいのが、自分の拡張機能で使う変数や関数を、「自分の拡張機能専用のサービスオブジェクト」のプロパティやメソッドとして保持するようにするというやり方だ。

var myService= {
  loadURI : function(aURI) {
    window.open(aURI, 'mytarget', 'status');
  },
  gBrowser : window.open('http://piro.sakura.ne.jp/browsers-list.html')
};

// 呼び出す時は頭に「myService.」が付く以外に変化はない。
myService.loadURI('http://www.google.co.jp/');
myService.gBrowser.location.href = 'http://piro.sakura.ne.jp/browsers-list.html#legacy';
// でも、これによって、Firefoxに元からある
// 関数のloadURIや変数のgBrowserを破壊しなくて
// 済むようになる。

僕自身手抜きしてグローバル変数を使ってしまうこともあるけれども、基本的にグローバル変数やグローバルな名前空間での関数定義は使うべきではない。なぜなら、どんな拡張機能が同じ名前のグローバル変数や関数を定義してくるかなんて予想できないからだ。自分の環境で問題が起こってなくても、他の人の環境で問題が起こってしまっているという時に、調べてみたらその人の使ってた別の拡張機能が同じ名前のグローバル変数を定義していたのが原因だった、ということはザラにある。

だからText Shadowでも、基本的な機能はTextShadowServiceTextShadowBoxServiceという二つのオブジェクトの中にまとめている。あといくつかクラスを定義してるけど、これらのクラス名にもTextShadowUpdateEventListenerのように拡張機能の名前にちなんだprefixを付けて、他の拡張機能で定義される変数や関数と名前がかぶらないようにしている。

ちなみに。ここではオブジェクトリテラルを使ってサービスオブジェクトを簡潔に定義しているけれども、JavaScriptのオブジェクトはクラスを作ってからじゃないと使いたくない!!という人はこう書くかもしれない。

function MyService() {
}
MyService.prototype = {
  loadURI : function(aURI) {
    window.open(aURI, 'mytarget', 'status');
  },
  gBrowser : window.open('http://piro.sakura.ne.jp/browsers-list.html')
};

var myService = new MyService();
myService .loadURI('http://www.google.co.jp/');
myService .gBrowser.location.href = 'http://piro.sakura.ne.jp/browsers-list.html#legacy';

あるいは、クラスをそのままインスタンス化して使うというやり方が好きな人もいるかもしれない。

function MyService() {
  MyService.loadURI = function(aURI) {
    window.open(aURI, 'mytarget', 'status');
  };
  MyService.gBrowser = window.open('http://piro.sakura.ne.jp/browsers-list.html');
} new MyService();

MyService.loadURI('http://www.google.co.jp/');
MyService.gBrowser.location.href = 'http://piro.sakura.ne.jp/browsers-list.html#legacy';

ここでお勧めしてるのは「グローバルな名前空間を汚さないこと」なので、オブジェクトリテラルを使う方法、クラスを定義してインスタンスを生成する方法、クラスをそのままインスタンス化する方法、のいずれを使ってもべつに構わないんだけど、後の2つは「そう書く意味が薄い」と僕は思ってる。

大体、サービスオブジェクトなんてそのウィンドウの中で一つっきりあれば充分だし、クラスにしておいたところで継承だのなんだのを活用できる場面がそうそうあるとは思えない(そういう使い方をするのなら別にいいけど)。というわけで、目的を最短ルートで達成できるやり方として僕は、オブジェクトリテラルを使うようにしているという次第です。

今回のまとめ。グローバル変数やグローバルな名前空間での関数定義は使わない。極力一つのオブジェクトのプロパティやメソッドとしてまとめて、グローバルな名前空間内の汚染を最小限に抑えること。どうしてもグローバルな名前空間に変数や関数を定義したい時は、拡張機能の名前にちなんだprefixを付ける。これ、拡張機能開発で心がけておいて欲しい最も基本的で重要なポイントですね。

エントリを編集します。

wikieditish message: Ready to edit this entry.











拡張機能