Firefox3.1β1 をインストールしてみたけれど createContextualFragment が


2008-10-21 追記 この日記は、こんな流れで修正やら追記がされてます。

  1. Firefox3で動いていたコードがFirefox3.1β1で動かないんが → 3.1でどっか変わった?
  2. 「ちがくね?」と指摘を受ける
  3. 検証し直してみると、実は createContextualFragmentは一部のブラウザ(Opera,IE)以外なら実用的だった ← 俺的新発見
  4. 記事修正。おわり

uupaa.js(ver0.6)では、HTML文字列をノード化するために、

uu.node.insert = function(html, context) {
  var node = document.createRange().createContextualFragment(html);
  var rv = node.firstChild;
  context.appendChild(node);
  return rv;
}
uu.log(uu.node.insert("<span>UU.LAST_CHILD</span>"));

とかやってるのですが、このコードが Firefox3.1β1 だとうまく動きません。

rv の中身を見てみると、

- Firefox3.0.3 は ElementArray@1[(ELEMENT_NODE)/span[1]]
- Firefox3.1β1 は ElementArray@2[(TEXT_NODE)[" "], (ELEMENT_NODE)/span[1]]

が返ってきてます(表示はuupaa.jsのインスペクタのもの)

むぅ

なんか余計なテキストノードが挟まってますね。


そういえば、Opera9.2x の createContextualFragment もダメなんだよなぁ。
Safari3(WebKit)にも createContextualFragment があるけど、Firefox3と挙動が違うためつかえないんだよなぁ(オンザフライでノードが作れない)。
createContextualFragment がこちらの期待通りに動くのは、Firefox3.0ぐらいなもんです。


DOMで標準化(文書化)されてないAPIって、大抵はこんな感じです。
APIが実装されている」と「クロスブラウザで使える」は、別の話なんですよね。
テストケースをだれかが公表しないかぎり、この手の混乱がずっと続くわけです。

当面は

Firefox3.1 でも 旧来の方式(プレースホルダ)で実体化。

uu.node.substance = function(html) {
 // createContextualFragment が使えない環境(Safar3, Firefox2, IE等)では、
 // <div></div>をプレースホルダとしてノードを生成し、中身だけを切り抜いて返す
 var rv, e = _doc.body.appendChild(_doc.createElement("div")); // placeholder
 e.innerHTML = html;
 rv = uu.node.cutdown(e);
 uu.node.remove(e); // remove placeholder
 return rv;
}

2008-10-21 21:05 追記

http://developer.mozilla.org/en/DOM/range.createContextualFragment に記載されているコードを試してみました。
ページを表示するとbody要素に<div>I am a div node</div>が追加されます。
Showボタンをクリックすると、ライブなHTML(JavaScriptで加工した後のHTML)が表示されるようになってます。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>createContextualFragment test</title>
<script src="createContextualFragment.js"></script>
</head>
<body><input type="button" value="Show" onclick="show()" /></body>
</html>
// createContextualFragment.js
function boot() {
  var rv = insert("<div>I am a div node</div>");
  alert(rv);
}
function insert(tagString) {
  var range = document.createRange();
//range.selectNode(document.getElementsByTagName("div").item(0)); ここを修正
  range.selectNode(document.getElementsByTagName("body")[0]);
  var documentFragment = range.createContextualFragment(tagString);
  var rv = documentFragment.firstChild; // ここを追加
  document.body.appendChild(documentFragment)
  return rv; // ここも追加
}
function show() {
  if (/Gecko\//.test(navigator.userAgent)) { // for Firefox2+ add outerHTML prop.
    HTMLElement.prototype.__defineGetter__("outerHTML", function() {
      var r = document.createRange(), tub = document.createElement("div");
      r.selectNode(this);
      tub.appendChild(r.cloneContents());
      return tub.innerHTML;
    });
  }

  var d = window.open().document;
  d.write("<xmp>" + document.body.outerHTML + "</xmp>");
  d.close();
}
window.onload = boot;
結果
Browser alert(rv)
Firefox3.1β1 HTMLDivElement
Firefox3.0.3 HTMLDivElement
Firefox2.0.0.7 HTMLDivElement
Safari3.1.1 HTMLDivElement
Chrome HTMLDivElement
Opera9.27 HTMLDivElement
Opera9.61 HTMLBodyElement
IE8 not impl

Showボタンを押すと、こんな感じのHTMLになります。

Firefox3.1β1, Firefox3.0.3, Firefox2.0.0.7
<body><input value="Show" onclick="show()" type="button"><div>I am a div node</div></body>
Safari3.1.1, Chrome
<body><input type="button" value="Show" onclick="show()">

<div>I am a div node</div></body>
Opera9.27
<BODY><INPUT type="button" value="Show" onclick="show()">

<DIV>I am a div node</DIV>
Opera9.61
<BODY><INPUT type="button" value="Show" onclick="show()">

<BODY><DIV>I am a div node</DIV></BODY></BODY>

どうやら、

  var node = document.createRange().createContextualFragment(html);

といったコードで、Nodeを作成できたのはFirefox3.0のみで、むしろ少数派の挙動だったようです。
逆の可能性について思慮が不足してました。(ありがとうid:javascripterさん)

問題はOpera

Opera9.5βとOpera9.61でためしましたが、<body> でラップして返すように改悪されてます。
細かいところだと、Opera9.27も </body> が提示されないんですよね(これはIEに動作をあわせているからだと思いますが)

勉強になりました。