はてなブックマーク Firefox 拡張
実装の舞台裏
nanto_vi, 2009-11-08
自己紹介
概略
- コーディング規約
- スクリプトのモジュール化
- データベースと O/R マッパ
- サイドバーとカスタムツリービュー
- ブックマーク追加ダイアログと XBL
- FUEL のメモリリーク
- おすすめタグと本文抽出ライブラリ
コーディング規約
- 変数宣言には
let
を使う
- メソッドとなる関数式には名前を付ける
- インデントはスペース 4 つ (はてなの慣習)
function FooBar() { ... }
extend(FooBar.prototype, {
method: function FB_method() {
let x = ...
...
},
get property FB_get_property() { ... },
set property FB_set_property(value) { ... },
});
スクリプトのモジュール化
モジュール化の必要性
- × 1 ファイルにすべてのスクリプトを書く
- × グローバル変数、グローバル関数を大量に使う
スクリプトを読み込む手段
mozIJSSubScriptLoader
- コンテキストを指定することで、変数のスコープをファイル単位に制限可能
- 読み込むたびに評価される
- JavaScript モジュール
- コンテキストはモジュールファイルごとに独立
- 評価は一度のみ
- Firefox 3 以降
- JavaScript XPCOM コンポーネント
- コンテキストはコンポーネントファイルごとに独立
- 評価は一度のみ
- インターフェースを定義すれば C++ からも呼び出し可能
ローダの作成
- 複数の読み込み手段を使い分け
- シングルトンオブジェクトが必要なら JavaScript モジュール
- 特定のウィンドウに適用するなら
mozIJSSubScriptLoader
- 外部に公開するのは
hBookmark
のみ
- 特定ディレクトリ下の JavaScript ファイルをすべて読み込み
nsIExtensionManager
から拡張のインストールディレクトリを取得
- JavaScript モジュールでは
__LOCATION__
から nsILocalFile
を取得
- ファイル名で読み込み順を指定
bookmark-xul/
chrome/
content/
autoloader.js
common/
02-utils.js
20-database.js
...
sidebar.xul
sidebar/
10-TagTreeView.js
10-BookmarkTreeView.js
...
resource/
modules/
00-utils.jsm
10-event.jsm
...
Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm");
loadPrecedingModules();
const EXPORTED_SYMBOLS = ['MyModule'];
var MyModule = { ... };
const EXPORT = ['hello', 'world'];
function hello() { ... }
var world = ...;
function mysterious() { ... }
<?xml version="1.0" encoding="utf-8"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="autoloader.js"/>
<script type="application/javascript">
hBookmark.MyModule;
hBookmark.hello();
</script>
...
</window>
データベースと O/R マッパ
- Tombloo の
database.js
を使用
Entity
関数で、テーブルに対応する Model
を作成
30-Models.js
の Model
とは別物
- アドバイスを使って拡張
- Tombloo の
addBefore
、addAround
関数
let Bookmark = Model.Entity({
name : 'bookmarks',
fields : {
id : 'INTEGER PRIMARY KEY',
url : 'TEXT UNIQUE NOT NULL',
title : 'TEXT',
comment : 'TEXT',
...
}
});
extend(Bookmark, {
findByTags: function(tags, limit, ascending, offset) { ... },
search: function(str, limit, ascending, offset) { ... },
searchByTitle: function(str, limit, ascending, offset) { ... },
searchByUrl: function(str, limit, ascending, offset) { ... },
searchByComment: function(str, limit, ascending, offset) { ... },
...
});
addAround(Bookmark, 'searchBy*', function(proceed, args, target) {
target.db.setPragma('case_sensitive_like', 0);
try {
return proceed(args);
} finally {
target.db.setPragma('case_sensitive_like', 1);
}
});
addAround(Bookmark.prototype, 'save', function(proceed, args, target) {
target.search = [target.title, target.comment, target.url].join("\n").toLowerCase();
proceed(args);
target.updateTags();
Prefs.bookmark.set('everBookmarked', true);
});
Model.Bookmark = Bookmark;
Model.MODELS.push("Bookmark");
サイドバーとカスタムツリービュー
- タグ一覧、ブックマーク一覧にカスタムツリービューを使用
- タグ一覧の 1 行ごとに、関連するタグの有無を確認
- 当初は 1 行ごとに SQL クエリを複数回発行
- 同階層の行の情報を 1 つの SQL クエリで取得、結果のキャッシュにより高速化
ブックマーク追加ダイアログと XBL
XML Bindng Language
- XML 要素のバインディング (雛形) を定義
- 表示内容、コンストラクタ、メソッド、プロパティ、etc.
- あるバインディングからの継承も可能
- CSS を使って要素とバインディングとを結びつける
- XUL ウィジェット (ボタン、メニュー、テキストボックス、etc.) の動作は XBL で定義されている
<?xml version="1.0" encoding="utf-8"?>
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="hello">
<content>
<xul:label value="Hello, XBL world!"/>
</content>
<implementation>
<method name="sayHello">
<parameter name="target"/>
<body>
alert('Hello, ' + target + ' world!');
</body>
</method>
</implementation>
</binding>
</bindings>
.hello {
-moz-binding: url("hello.xml#hello");
}
ブックマークの追加
- 当初はページ下部にパネルとして表示
- 1 タブにつき 1 パネル (1 ウィンドウに複数のパネルのインスタンス)
- レイアウト上の都合からポップアップウィンドウに
- XBL を使う必要性はなくなるが、実際はそのまま流用
FUEL のメモリリーク
- 当初、ブラウザタブや設定 (prefs) の取得に FUEL を使用
- Firefox のメモリ使用量がどんどん増大
- FUEL は一度作成したオブジェクトをアプリケーション終了時まで解放しない
おすすめタグと本文抽出ライブラリ
- はてなブックマーク側におすすめタグの情報がないときに使用
- tarao 氏の extract-content-javascript
- 元は中谷氏の extractcontent.rb
- Perl (奥氏) → Ruby (中谷氏) → Perl (tarao 氏) → JavaScript (tarao 氏)
- 高速化
- 当初、450 KB の文書に対して約 10000 ms
- たどるノード数を制限: 600 ms
- 不可視の要素の検出 (CSS 使用値の取得) を簡略化: 200 ms
google_ad_section_start
を XPath で探索: 数 ms
var start = d.evaluate(
'descendant::comment()[contains(., "google_ad_section_start") and not(contains(., "ignore"))]',
d.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
).singleNodeValue;