XPathの"@checked"と CSS2の":checked", setAttribute()は控えめに。

CSS2の擬似クラスセレクタに、":checked" というものがあります。
<input type="radio" checked="checked" /> や <input type="checkbox" checked="checked" /> などがヒットします。

何も知らないから XPath で表現してみたかった。

uupaa.jsは、CSSセレクタ(Level1〜3 + jQuery独自のやつ)を、内部でXPathに変換しています。
最近は、XPathスキーなので、":checked" を '//input[@checked and @checked="checked"]' として実装してみました…が、なんか、ちゃんと動かないんです。
不思議なことに動くブラウザもあるし、どうやら状況依存な気配も感じます。

何で?

結論としては、XPathのchecked属性("@checked")は、getAttribute("checked") 的なもの(DOM - Attr)であり、JavaScriptで操作可能なプロパティ(input.checked)とは、住む世界が異なるため動かなくて当然。

ブラウザにとって、setAttribute()の結果を画面に反映させる義務は無い(らしい)ので、見栄えに関する属性をsetAttribute()してしまうと、描画結果は不定となる。

ほんと?

elm.checked = true;

とすると、チェックボックスがチェックされます。当然ですね。
でも…

elm.setAttribute("checked", "checked");

これで、チェックされてしまうブラウザがあり、

elm.removeAttribute("checked");

とすると、アンチェックされるブラウザと、チェックされたままになるブラウザがあります。
さらにいうと、

elm.checked = false;
elm.checked = true;
elm.checked = false;

の後で、

elm.setAttribute("checked", "checked");

しても、チェックされないブラウザがあることに気が付いたのは、丸2日を浪費した後でした。

悔しいから無理矢理まとめてみた。

状況により、elm.checked の値と、elm.getAttribute("checked")の値にずれが生じ、XPathの"@"が役立たずになるという表です。
elm.getAttribute("checked")の結果とelm.checked の値が一致していれば、整合性が"○"、不一致なら"×"になります。

ブラウザ 直前の操作 アクション チェック状態 elm.checkedの値 getの値 整合性
Firefox2 リロード [set] □→× false→true null→checked
リロード [set] + [remove] □→×→□ false→true→false null→checked→null
[change] x 2 [set] falseのまま null→checked ×
[change] x 2 [set] + [remove] falseのまま null→checked→null ×
IE6 リロード [set] □→× false→true null→checked
リロード [set] + [remove] □→×→× false→true false→true
[change] x 2 [set] □→× false→true null→checked
[change] x 2 [set] + [remove] □→×→× false→true false→true
Safari3.1 リロード [set] □→× false→true null→checked
リロード [set] + [remove] □→×→□ false→true→false null→checked→null
[change] x 2 [set] falseのまま null→checked ×
[change] x 2 [set] + [remove] falseのまま null→checked→null ×
Opera9.5 リロード [set] □→× false→true null→checked
リロード [set] + [remove] □→×→□ false→true→false null→checked→null
[change] x 2 [set] □→× false→true null→checked
[change] x 2 [set] + [remove] □→×→□ false→true→false null→checked→null

[change]ボタンは、elm.checked = true; と elm.checked = false を繰り返すボタンです。
[set]ボタンは、elm.setAttribute("checked", "checked"); を実行するボタンです。
[remove]ボタンは、elm.removeAttribute("checked"); を実行するボタンです。
getは elm.getAttribute("checked"); の実行結果です。

そろそろ総括。

IEシリーズは、getAttribute, setAttribute の実装が、そもそもシンタックスシュガーなので、整合性が取れているように見えるだけ。
FirefoxSafariは、チェックボックスにユーザが触れるか、JavaScriptでcheckedプロパティの値を変更するまでは、整合性を保障する仕組みが入っているように見えるが、JavaScriptでプロパティを変更するかマウスでチェックボックスをクリックした後は、(属性値を参照する順番が変わるため?)整合性が無くなり、XPathで取れる値と実際の画面の状態にズレが生じる。
Operaは整合性を維持する仕組みが実装されている(IEの動作を真似る必然性からか)。Operaは見えないところで頑張ってる。

SafariFirefoxは表で見る限り同じに見えるが、細かな挙動が異なる。

setAttribute()で見栄えに関する属性をいじくりまわされると、XPathが使えなくなるようです。

テストコード

<html><head><title>Pseudo Selector</title><style>
#frame { position: absolute; top: 100px; left: 100px;
         width: 28em; height: 8em; border: 1px solid gray; }
</style></head><body>
<div id="frame">
  <input id="checkbox1" type="checkbox" value="1" onclick="review()" />
    <label for="checkbox1">checkbox1</label>
  <p id="screen"></p>
</div>
<div>
  <input type="button" value="change" onclick="change()" />
  <input type="button" value="set" onclick="set()" />
  <input type="button" value="remove" onclick="remove()" />
</div>
<script>
window.onload = function() { review(); }
function elm() { return document.getElementById("checkbox1"); }
function change() {
  var e = elm();
  e.checked = !e.checked;
  review();
}
function set() {
  elm().setAttribute("checked", "checked");
  review();
}
function remove() {
  elm().removeAttribute("checked");
  review();
}
function review() {
  var e = elm(), rv = [];
  rv.push("elm.checked = ", (e.checked) ? "true" : false, "<br />",
          "elm.getAttribute(\"checked\") = ", e.getAttribute("checked"), "<br />");
  if (e.hasAttribute) {
    rv.push("e.hasAttribute(\"checked\") = ", e.hasAttribute("checked"));
  } else {
    rv.push("e.hasAttribute = function undefined");
  }
  document.getElementById("screen").innerHTML = rv.join("");
}
</script></body></html>