Home > Latest topics

Latest topics > Minefield 4.0b2preではどうも同期的な処理がことごとく失敗するようになっている気がする

宣伝1。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

宣伝2。Firefox Hacks Rebooted発売中。本書の1/3を使って、再起動不要なアドオンの作り方のテクニックや非同期処理の効率のいい書き方などを解説しています。既刊のFirefox 3 Hacks拡張機能開発チュートリアルと併せてどうぞ。

Firefox Hacks Rebooted ―Mozillaテクノロジ徹底活用テクニック
浅井 智也 池田 譲治 小山田 昌史 五味渕 大賀 下田 洋志 寺田 真 松澤 太郎
オライリージャパン

Minefield 4.0b2preではどうも同期的な処理がことごとく失敗するようになっている気がする - Jul 06, 2010

XUL/MigemoのMinefield 4.0b2pre対応のために色々検証していて、1つとても困った問題にぶち当たった。rangefindを使ってWebページ中の要素を装飾する時に、前から後ろに向かって処理を行うと検索が止まってしまう。

Components.utils.import('resource://gre/modules/debug.js'); 
const Cc = Components.classes;
const Ci = Components.interfaces;

function decorate() {
  var span = d.createElement('span');
  span.setAttribute('style','font-size:150%;');
  foundRange.surroundContents(span);
}

/* コンテンツ領域にテスト用の内容をロードする */
var d = Cc['@mozilla.org/appshell/window-mediator;1']
          .getService(Ci.nsIWindowMediator)
          .getMostRecentWindow('navigator:browser')
          .content.document;
d.documentElement.innerHTML = 'Firefox, Firefox, Firefox.';
d.documentElement.clientTop; /* ←伏線 */

/* rangefindを初期化する */
var find = Cc['@mozilla.org/embedcomp/rangefind;1'].createInstance(Ci.nsIFind);
find.findBackwards = false; /* 前から後ろに向かって検索 */
find.caseSensitive = false;

var findRange= d.createRange(); /* 検索する範囲 */
findRange.selectNodeContents(d.documentElement);
var startPoint = findRange.cloneRange(); /* 検索の始点 */
startPoint.collapse(true);
var endPoint = findRange.cloneRange(); /* 検索の終点 */
endPoint.collapse(false);

/* 検索を実行 */
var term = 'Firefox';
var foundRange = find.Find(term, findRange, startPoint, endPoint);
NS_ASSERT(foundRange !== null, '1回目で失敗');

decorate(); /* DOMツリーを編集して装飾する */

/* 検索の範囲を変える(編集した箇所より後を検索の範囲にする)*/
findRange.setStart(foundRange.endContainer, foundRange.endOffset);
startPoint.setEnd(foundRange.endContainer, foundRange.endOffset);
startPoint.collapse(false);

/* もう一度検索を実行 */
foundRange = find.Find(term, findRange, startPoint, endPoint);
NS_ASSERT(foundRange !== null, '2回目で失敗');

decorate(); /* DOMツリーを編集して装飾する */

エラーコンソールにこれをコピペして実行してみると、2回目の方で必ず失敗してしまう事が分かる。本当は「Firefox」という文字列が2箇所装飾されて欲しいのに、最初の1箇所だけで処理が止まってしまう。

これ、上のサンプルの中で伏線と書いている部分が鍵なんだけど、どうもこういうことらしい。

  • DOMツリーを編集すると、編集した箇所から先の範囲が「不確定」な状態になる。
  • 「不確定」な範囲に対しては、rangefindは一切の検索を行えない。
  • DOM要素のプロパティにアクセスするなどしてレイアウト情報を参照すると、状態が強制的に「確定」される。
  • または、setTimeout()等で少し遅らせて処理を行えば、その時には状態が「確定」されている。
  • 状態が「確定」されると、その範囲をまた検索できるようになる(最初の方にある d.documentElement.clientTop は、実はそのための物)。

1回目の検索結果のRangeの箇所でDOMツリーを切った貼ったしているので、その箇所より後の部分はどう頑張ってもそのままの流れでは検索できないようになってしまっている、ということのようだ。

なので、

  • 2回目以降の検索を実行する前に span.clientTop あたりにアクセスしてやれば(これ以外にもclientLeftでもoffsetWidthでもレイアウト系のプロパティなら何でもいいっぽい)、whileforのループを回し続ける事ができる。
  • 毎回setTimeout()で状態の「確定」を待ってやるというやり方でもよい。

という風な回避策があると言える。でも、後者は言わずもがな、前者も毎回レイアウト情報を参照するからクソ重くなりそうで、できればどっちの方法もとりたくない所だ。

将来的にどうなるのかは知らんけど、とりあえず今のところは、後方検索(Rangeの後ろの方から前の方に向かって検索する)ならこの問題に引っかからずに済むみたい。編集した箇所から先の部分が「不確定」になっても、後方検索だと「編集した箇所から先=もう検索が終わった範囲」なので。(→と思ってたけどやっぱり動作が怪しいので安全めな方に倒すということで毎回clientTopにアクセスする方法を使う事にした。なんか負けた気分。)

この「rangefindでループを回してDOMツリーを切った貼ったして装飾する」というやり方はFirefox 2以前のページ内検索における「すべて強調表示」の実装方法だったんだけど、今のFirefoxではDOMツリーはいじらずに強調箇所を選択範囲として処理するようになってて、この問題は問題にならないようだ。今でもFirefox本体でこういうことをやってるところがあれば「これってregressionなんじゃないの」とbugzillaに報告できるところだと思うんだけど……

分類:Mozilla > XUL, , , , , 時刻:20:41 | Comments/Trackbacks (0) | Edit

Comments/Trackbacks

TrackBack ping me at


の末尾に2014年1月19日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2010-07-06_dynamic.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。

Post a comment

writeback message: Ready to post a comment.

2014年1月19日時点の日本の首相のファミリーネーム(ひらがなで回答)

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき

オススメ

Mozilla Firefox ブラウザ無料ダウンロード