IE6で behavior を使わずに DOMNodeInserted をエミュレートしてみる

* { behavior: expression(...) }

とすると、DOMツリーへのノードの追加を検出できるんだけど、behavior はよく使われるので、他のライブラリとバッティングする可能性が高い。
そこで、behavior を使わずに DOMNodeInserted 相当の機能を実装できないか考えてみた。

<html><head><title>emurate DOMNodeInserted event for IE6</title><style>
body * { text-underline-position: expression(onDOMNodeInserted(this)); }
.box { border: 1px solid red }
</style></head><body><script>
function onDOMNodeInserted(elm) {
  setTimeout(function() {
    done(elm);
  }, 0);
}
function done(elm) {
  if (!elm.style.getExpression("textUnderlinePosition")) { return; }

  alert(["tagName=", elm.tagName,
         "expr=", elm.style.getExpression("textUnderlinePosition")].join(" "));
  elm.style.removeExpression("textUnderlinePosition");
  /*
    新しいノードを検出したときの処理
   */
}
function addNode() {
  var node = document.createElement("div");
  node.className = "box";
  document.body.appendChild(node);
}
</script>
<input type="button" value="add node" onclick="addNode()" />
</div></body></html>

説明

  • IE独自のCSSプロパティ(text-underline-position) に expressionを設定してある。body要素以下にノードが新しく追加されると、onDOMNodeInserted(this) を呼び出す。this は 追加されたノード を示している。
body * { text-underline-position: expression(onDOMNodeInserted(this)); }
  • setTimeoutを挟み、一人時間差でdoneを呼び出す。expression から呼ばれた関数(コンテキスト/スレッド)上で removeExpression() を実行すると、例外「予期しないメソッドの呼び出し、またはプロパティアクセスです」 が発生するのを回避している。
function onDOMNodeInserted(elm) { // elm = 追加されたノード
  setTimeout(function() {
    done(elm);
  }, 0);
}

以下のように削除(removeExpression)だけを setTimeout で別スレッドにくくりだす方法もあると思う。

function onDOMNodeInserted(elm) { // elm = 追加されたノード
  if (!elm.style.getExpression("textUnderlinePosition")) { return; }
  done(elm);
  setTimeout(function() {
    elm.style.removeExpression("textUnderlinePosition");
  }, 0);
}
  • removeExpression("textUnderlinePosition") でexpressionをつぶしたはずなのに、呼び出されることがあるため(一人時間差やってるからかも)、!getExpression("textUnderlinePosition") でガードしている(removeExpression後は、getExpressionがundefeindを返す)。getExpression や removeExpression に指定するCSSプロパティ名は キャメルケースなので、"textUnderlinePosition" となる。
function done(elm) { // elm = 追加されたノード
  if (!elm.style.getExpression("textUnderlinePosition")) { return; } // ガード

  alert(["tagName=", elm.tagName,
         "expr=", elm.style.getExpression("textUnderlinePosition")].join(" "));
  elm.style.removeExpression("textUnderlinePosition"); // expression 削除
  /*
    新しいノードを検出したときの処理
   */
}

実行するとこうなる

ページ読み込み直後に以下のダイアログが順番に表示される。ページ読み込み直後は静的なノードがDOMツリーの順番で検出される。

  1. [tagName= SCRIPT expr= onDOMNodeInserted(this)]
  2. [tagName= INPUT expr= onDOMNodeInserted(this)]
  3. [tagName= DIV expr= onDOMNodeInserted(this)]

add node ボタンをクリックしdivノードを追加すると、動的に追加されたノードを検知しダイアログが表示される。

  • [tagName= DIV expr= onDOMNodeInserted(this)]

反省会

  • * { behavior: expression(...) } と body * { text-underline-position: expression(...) } はあんまり違わないように見えるけど、選択肢の一つとして。
  • behavior と比べてかなり重い気がする。う〜む。