Home > Latest topics

Latest topics > Split Browser開発のよもやま話(7):状態の保存と復元

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

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

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

Split Browser開発のよもやま話(7):状態の保存と復元 - Jan 31, 2007

作り込みのフェーズの続きですよ。だいぶ間が空いてしまったけど。

ブラウザの分割の状態を保存する、と一口に言っても、保存するべき内容は2つのレイヤに分けることができる。

  1. それぞれの分割されたブラウザの履歴の状態
  2. どの方向にどれだけ分割されているか、という状態

1の情報の保存と復元の処理は、nsISHistoryの状態の保存と復元が鍵になる。これはFirefox 2のセッション保存機能を実現しているnsSessionStore.jsの中身を見れば理解できるだろう。要約すると、処理の流れは以下のようになる。

  1. browser要素のsessionHistoryプロパティ(nsISHistoryのインスタンス)のgetEntryAtIndex()メソッドで、個々のヒストリエントリ(「戻る」「進む」で辿れる個々の履歴)を取得する。
  2. 個々のエントリのオブジェクト(nsISHEntry/nsIHistoryEntryのインスタンス)の、URIやタイトル、キャッシュのキーなどの情報を取得する。
  3. 取得した情報を何らかの形で保存する。
  4. Firefoxを再起動する。
  5. 保存されていた情報を読み込む。
  6. 保存されていた情報から、同じ情報を持ったnsISHEntryのインスタンスを生成する。
  7. browser要素のsessionHistoryプロパティにnsISHistoryInternalインターフェースでアクセスして、addEntry()メソッドでヒストリエントリとして追加する。

tabbrowser要素に対しては、これをタブの数だけ繰り返せばいい。

Split Browserでは、nsSessionStore.jsではなく、タブブラウザ拡張の同等の機能の方のコードをコピペして使った。nsSessionStore.jsのコードに比べて、このコードにはフォームの入力内容を保存・復元する処理が含まれていないんだけど、個人情報が云々というツッコミを受けるのも嫌だし、まあ、要望があれば追い追い……ってことで。

3と5のステップをいかにして実現するかは後述するとして、先にもう一つの要素の方に話を移す。つまり、ブラウザの分割そのものの状態――どの方向にどれだけ分割されているのか、という情報の収集についてだ。

入れ子の構造であるという点自体は、特に問題は無い。厄介なのは、親ノードとなるブラウザに対して上下左右4方向、どの方向に分割されたブラウザであるかという点。タブの状態の保存なんかではコンテナ要素から見たときの子ノードを頭から順番に見ていってそれぞれの状態を保存してるんだけど、Split Browserの実装方法では、素直にそうできないという問題がある。

例えばこんな状態になっていたとして……

<vbox>
  <subbrowser-container/> (1)
  <subbrowser-container/> (2)
  <hbox>
    <subbrowse-container/> (3)
    <tabbrowser/> (4:最初からある)
    <subbrowser-container>
      <vbox>
        <subbrowse-container/> (5)
        <hbox>
          <subbrowse/> (6)
        </hbox>
      </vbox>
      <subbrowse-container/> (7)
    </subbrowser-container>
    <subbrowser-container/> (8)
  </hbox>
  <subbrowser-container/> (9)
</vbox>

この状態でトップレベルのコンテナから内容を順に走査して(1、2、3……)情報を収集しても、それを「再現」するのは大変だ。最初からあるtabbrowser要素は、この場所を動かせないし後から挿入することもできないから、これのためにいくつも例外処理を書かないといけなくなる。tabbrowser要素より後だったら挿入位置のインデックスを1増やす、みたいな感じで。これは、真面目に考えるのがアホらしくなるくらいやる気がしない作業だ。

そもそも、要素の位置を指定して「n番目の子として追加」みたいなことをする機能自体が無くて、「特定のsubbrowser要素をキーとして指定し、そのsubbrowser要素から見て上下左右いずれかの方向にコンテナを追加する(ブラウザを分割する)」、という機能しか今の所は無いのでそこから作らないといけないことになる。そんな面倒な事、とてもとてもやってられません……

そこでSplit Browserでは、今あるものを最大限活用する、考えられる手法の中で一番泥臭いやりかたを採用することにした。コンテナの中を頭からスキャンするのではなく、コンテナの中に最初からあるtabbrowser要素、つまりFirefoxの本来のページ表示領域を中心にしてそこから右方向、左方向、上方向、下方向に走査していくというやり方だ。これは具体的に先の例で言えば「4、3、8、7、6、5、1、2、9」という順番なんだけど、これってよくよく考えてみれば、ユーザがFirefoxを起動してから行った操作手順ほとんどそのままである。だからその手順をそのまま記憶しておいて、次回起動時には「その手順どおりに処理した結果を得よう。……というのが、このやり方の基本的な考え方だ。

この場合のメリットはやはり、簡単な再帰だけで処理を書ける事だろう。指定したtabbrowserまたはsubbrowser要素を出発点として走査していき、さらに分割されたブラウザがあれば、またそこを新たな出発点として走査していく。これによって、コンテナ同士の入れ子関係に基づいたごくごくシンプルなツリー構造のデータを得ることができる。うん、再帰ってやっぱ重要だわ……

ちなみに、ブラウザの分割方向についてはPOSITION_LEFT、POSITION_RIGHT、POSITION_TOP、POSITION_BOTTOMという4つのフラグで管理してる。フラグで管理することによってまた別のメリットが生まれてるんだけど、それは今回の話からはズレるので、割愛する。

さて。分割の状態とブラウザの履歴、両方の情報をうまく収集して、さらにその状態を復元するところまでの、見通しはついた。残っているのは、先ほどスルーした部分。すなわち、「前述の方法で取得した情報を、どのようにしてどんな形式で保存するか」という部分だ。

セッションヒストリは、階層構造を持ったデータになっている。フレームを使用したページにおいては、個々のヒストリエントリはさらに「子供」のエントリを持っていて、全体としては入れ子の構造を取っている。こういうデータは単純なカンマ区切りやタブ区切りの形式で保存するのにはあんまり向いていない。

こういう複雑な情報を保存する方法として一番手っ取り早いのは、JSONまたはそれによく似た形式でデータを文字列化することだ。

JSONはAjaxが流行りだしてからよく使われるようになった(?)データ形式で、簡単に言えば、JavaScriptのオブジェクトリテラルとして評価可能な文字列によってデータを表現する形式だ。複雑な情報をただの文字列に一旦変換すれば、通信などで扱いやすくなる。また、受け取った側でJavaScriptのeval()で文字列を評価すれば、そのまま階層構造なども含めて元の情報を完全に復元できる。

JavaScriptではオブジェクトのtoSource()メソッドを使えばオブジェクトリテラルとして評価可能な文字列を取得できる。これは厳密なJSON形式と比べると若干ルーズなものなんだけれども、どうせ書き出すのも読み込むのもSplit Browser内部での話なので、そこはこだわらないでおく。ともかく、これをFirefox標準の設定システムで文字列値として保存して、次回起動時に復元すれば、OKというわけだ。

参考に、以下に実際に保存されているデータの例を示そう。

user_pref("splitbrowser.state", "({children:[{children:[{children:[], content:{histories:[{entries:[{id:3, uri:\"http://ja.www.mozilla.com/ja/firefox/central/\", title:\"Firefox \\u3092\\u4F7F\\u3063\\u3066\\u307F\\u3088\\u3046\", isSubFrame:false, x:0, y:0, children:[], cacheKey:0}, {id:4, uri:\"http://ja.www.mozilla.com/ja/firefox/help/\", title:\"\\u30D8\\u30EB\\u30D7\\u3068\\u30C1\\u30E5\\u30FC\\u30C8\\u30EA\\u30A2\\u30EB\", isSubFrame:false, x:0, y:0, children:[], cacheKey:0}], index:1}], selectedTab:0, uri:\"http://ja.www.mozilla.com/ja/firefox/help/\", width:245, height:233, type:\"subbrowser\"}, position:4, height:233}], content:{histories:[{entries:[{id:1, uri:\"http://ja.www.mozilla.com/ja/firefox/central/\", title:\"Firefox \\u3092\\u4F7F\\u3063\\u3066\\u307F\\u3088\\u3046\", isSubFrame:false, x:0, y:0, children:[], cacheKey:0}, {id:2, uri:\"http://www.mozilla-japan.org/addons/firefox/\", title:\"Mozilla Japan - Firefox \\u7528\\u30A2\\u30C9\\u30AA\\u30F3\", isSubFrame:false, x:0, y:0, children:[], cacheKey:0}], index:1}], selectedTab:0, uri:\"http://www.mozilla-japan.org/addons/firefox/\", width:245, height:356, type:\"subbrowser\"}, position:2, width:245}], content:{type:\"root\", width:627, height:596}})");

これじゃ読みにくいので、適当に改行などを入れて整形してみたのが以下のもの。

({
  content:{
    type:"root",
    width:627,
    height:596
  },
  children:[
    {
      content:{
        selectedTab:0,
        uri:"http://www.mozilla-japan.org/addons/firefox/",
        width:245,
        height:356,
        type:"subbrowser",
        histories:[
          {
            entries:[
              {
                id:1,
                uri:"http://ja.www.mozilla.com/ja/firefox/central/",
                title:"Firefox \u3092\u4F7F\u3063\u3066\u307F\u3088\u3046",
                isSubFrame:false,
                x:0,
                y:0,
                children:[],
                cacheKey:0
              },
              {
                id:2,
                uri:"http://www.mozilla-japan.org/addons/firefox/",
                title:"Mozilla Japan - Firefox \u7528\u30A2\u30C9\u30AA\u30F3",
                isSubFrame:false,
                x:0,
                y:0,
                children:[],
                cacheKey:0
              }
            ],
            index:1
          }
        ]
      },
      position:2,
      width:245,
      children:[
        {
          content:{
            selectedTab:0,
            uri:"http://ja.www.mozilla.com/ja/firefox/help/",
            width:245,
            height:233,
            type:"subbrowser",
            histories:[
              {
                entries:[
                  {
                    id:3,
                    uri:"http://ja.www.mozilla.com/ja/firefox/central/",
                    title:"Firefox \u3092\u4F7F\u3063\u3066\u307F\u3088\u3046",
                    isSubFrame:false,
                    x:0,
                    y:0,
                    children:[],
                    cacheKey:0
                  },
                  {
                    id:4,
                    uri:"http://ja.www.mozilla.com/ja/firefox/help/",
                    title:"\u30D8\u30EB\u30D7\u3068\u30C1\u30E5\u30FC\u30C8\u30EA\u30A2\u30EB",
                    isSubFrame:false,
                    x:0,
                    y:0,
                    children:[],
                    cacheKey:0
                  }
                ],
                index:1
              }
            ]
          },
          position:4,
          height:233,
          children:[]
        }
      ]
    }
  ]
})

これを見れば、階層構造を持ったデータが、オブジェクトリテラルや配列リテラルの組み合わせによって、きちんとただの文字列に変換されて保存されているのが分かると思う。

セッションヒストリの保存と復元のコードのコピペ元であるタブブラウザ拡張では、JSON風形式ではなくRDFを使って無駄にややこしいことをしていた。このせいで色々トラブルが起こっていたので、次バージョンではこれもJSON風の形式に統一したいところだなあ。

とにかくまあこんな感じで、ブラウザの分割の状態は完璧に保存・復元できるようになったわけですよ。そしてこの話題はまだしつこく続く。次は、ブラウズ領域の上下左右でポップアップ表示されるボタンの実装についての話

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

Comments/Trackbacks

TrackBack ping me at


の末尾に2014年1月19日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2007-01-31_splitbrowser-subbrowser.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 ブラウザ無料ダウンロード