日向夏特殊応援部隊

俺様向けメモ

Inside Ex DOM Storage

Ex DOM Storage の中の実装ですが、結構苦労したので折角だから解説しちゃうぞ的なエントリです。

なので興味のある人以外にはだいぶニッチですw

Ex DOM Storage の技術的概観

簡潔に書いてしまうと以下の二点につきます。

  • データの格納先は userData behavior
  • Storage オブジェクトの実態は script 要素に適用した element behavior

userData behavior とは?

userData Behavior とは、IE が標準で用意しているクライアントサイドストレージです。

使い方は非常に簡単で、

element.addBehavior("#default#userData");
element.load("myStorage");
element.setAttribute("myData", "blah blah");
element.save("myStorage");

のようにすると、element の myData と言う属性に対してデータを永続的に保存出来ます。

userData behavior もドメイン単位での保存なので、保存先はこれを使っています。

Storage オブジェクトのプロパティに対する代入と onstorage イベント

プロパティへの代入の検出

そもそも Storage オブジェクトには getItem(key), setItem(key, newValue) と言うメソッドがあり、こちらを律儀に叩いてくれるなら問題は無かったのですが、

sessionStorage.hoge = "fuga";

と言うような、ただの代入に対しても onstorage イベントが fire するのですが、素の JS ではこれを実現する手だてが無いです。

Object#watch() がかなり近いですが、IE の JScript には無いし、watch はそもそも指定したプロパティへの代入を監視なので、任意のプロパティに対してはどうにもなりません。

そこで思いついたのが onpropertychange イベント。つまり Storage オブジェクトの実態を HTMLElement にしてしまえば補足出来るよねと。

具体的には、

element.attachEvent("onpropertychange", function(evt) {
  alert("key: " + evt.key);
  alert("newValue: " + element[key]);
});

のように取得出来る感じです。

Behavior で作ったカスタムイベントの制約

onstorage イベントですが、当初は Behavior でカスタムイベントを使ってました。つまり、htcで

<public:event id="storegeEvent" name="onstorage" />

のようにしておいて、

var evt = document.createEventObject();
// evt のプロパティとか設定
storageEvent.fire(evt);

な感じでやってたのですが、カスタムイベントってイベントバブリングしないし、この要素のonstorage属性に直接ハンドラを定義しないと捕捉出来ないと言う制約があります。*1

さらに追い打ちをかける話で、document.createEventObject() で作ったイベントの type プロパティの値が IE にとって未知の値 (例えば storage と言う値) だとやはり attachEvent 出来ないと言う罠。

これは悩んだ末に attachEvent を hack すると言う、多少 evil な手法を取ってます。この割り切りは互換性重視のためです。

HTC にもレンダリングモードがある

document.compatMode の値ですね。でこれが互換モードの場合だと onpropertychange イベントを扱えないので、htc の冒頭に DOCTYPE 宣言が付いてます。これ、地味だけど凄い重要。

sessionStorage の実装

ブラウザを閉じると消える と言う挙動を実現する為に、cookie を使ってます。これは userData ではどうにも出来ない事が判明したので苦肉の策です。

length, remainingSpace プロパティの実装

これは HTML Component の getter 設定を使ってます。つまり、htc で

<public:property name="length" get="getLength" />
<public:property name="remainingSpace" get="getRemainingSpace" />

と設定してあるように、プロパティの取得の時に指定したメソッドが走るようになってます。実態は length だったら、

function getLength() {
  var length = 0;
  for (var p in storage)
    length++;
  return length;
}

みたいになってます。

まとめ

ここまで読んでくれたあなたは、だいぶニッチです。

*1:つまりattachEventが使えない