読者です 読者をやめる 読者になる 読者になる

RollOutをください

mouseleave/mouseenter, rollOut/rollOver

Flex には確か rollover/rollout と mouseover/mouseout が別にあって、IE にも mouseleave/mouseenter と mouseover/mouseout が別にあって、要するにそれが無くて mouseover/mouseout だけだとどうなるのかというと、

  • 自分の子どもにマウスが乗ったら自分の mouseOut 発射
  • (そんで次の瞬間)自分の子どもの mouseOver がバブって来て再度発射

という、みなさんも一度は体験なさっているであろう、プルダウンメニューとか小さいポップアップとか作ってるときに「ファシャーーーッ!貴様ーーーッ!」と激高して不健康になる大変な悪が出現するわけですね。

で、無いのが FirefoxSafari (と Opera)。SafariJavaScript にはクソも期待していないし、Opera ファンの一人として「動かないことになれている」のでいいのですが、Firefox はなんとかして欲しい。Firefox ユーザは、なぜか自分たちを十字軍か何かと勘違いしているので、動かないときーきーうるさい。でも、何とかして欲しくて実は何とかなってるんじゃないのかと思い、ppk 兄さんのところを見たら

THE OTHER BROWSERS SHOULD IMPLEMENT THESE EVENTS AS SOON AS POSSIBLE.

と、兄さんも珍しく声を荒げてお怒りの様子である。だよなあ。

とにかく、ネイティブ実装無しにこれを普通に書くとどうなるかというと、onmouseout あたりに

if ( event.type == "mouseout" && event.relatedTarget ) {
	if ( event.relatedTarget == [] ) return;
	else {
		var eventTarget = event.target;
		var parent = event.relatedTarget.parentNode;
		while ( parent ) {
			if ( parent == eventTarget ) return;
			parent = parent.parentNode;
		}
	}
}

ファッキンなコードですけど、話としてこういうことを書かないとならな…、い、ですよねえ…?。イベントの relatedTarget が自分の子どもだったら「あ、無しで」っていう処理。でも違うのかなあ…。なんかあるのかなあ…。ちなみに IE だと HTMLElement か Node みたいなところに contains ってメソッドがあるんですけど、Gecko ないのかなあ。

やっぱだめなような…

探す方向で自分の限界を感じたので、じゃあ、フックして自分で作ればいいかと思ったら FF とSafari なら結構作るには作れそう。

function mouseleaveHook(event) {
    var evt = document.createEvent("Event");
    evt.initEvent("mouseleave", false, true);

// ここで上の判別をごにょごにょっとやって、OKなら

    this.dispatchEvent(evt);
}

[].addEventListener("mouseout",mouseleaveHook,false);
[].addEventListener("mouseleave",function(){alert("yeah")},false);

でもこれ、なんかイヤ。さらに HTML の属性としてのイベントハンドラだと、うまく動かない。

※Firefoxで
<div id="[私]" onmouseleave="alert('無視ですか');" onclick="alert('こっちは動く(あたりまえ)');">test</div>

属性イベントハンドラって、いつだれが JS と繋いでんのさ!

ということは、属性イベントハンドラは、addEventListener のラッパではないらしい。じゃあいつどうやってできるのさ、と思って色々調べてみても、なんかある瞬間にできる、としか思えない。上の例で行くと、

// Firefoxで
[].onclick !== undefined // onclick メソッドがある
[].onmouseleave === undefined

[].constructor.prototype.onclick === undefined // コンストラクタ(この場合 HTMLほにゃららElement)の prototype にはない
[].constructor.prototype.onmouseleave === undefined

って感じで、

typeof [].attributes.onclick.nodeValue === "string"
typeof [].attributes.onmouseleave.nodeValue === "string"
// 属性の値自体は同じく文字列

であり、Firebug で見ても onclick は属性としては別段変わったプロパティを持っていたりはしなかった。constructor.prototype(__proto__)には無いし、HTMLElement の JS API を見てもそれっぽいものは見あたらない。つまり属性は属性でそのままにしておいて、JS じゃない誰かが、この属性を利用して JavaScript の、しかも prototype じゃないインスタンスのメソッドをダイナミックに作ってるんじゃあ…?

ということで MSDN は得意な僕ですが、MDC 当たりをあたっても、あんまり普段見慣れてないところだからよくわからない…。検索したりなどしていると結局 Firefox のソースにご案内されたので、じっと見つめてみるとどうやら nsGenericHTMLElement の AfterSetAttr メソッドが臭い。nsContentUtils::IsEventAttributeName ってのを使って属性がイベントなのか調べてるし、こいつがさらに AddScriptEventListener ってのをやって、その先で CompileEventHandler とか BindCompiledEventHandler とかぷんぷんしまくることをやってるっぽい。

AfterSetAttr って、じっと見ると SetAttr の最後で実行されてて、どうやら SetAttr って XPCOM がうんにゃらかんにゃらで nsIDOMElement::setAttributeJavaScriptElement.setAttribute から動かしてるのかなあ、とか妄想。

そこで IsEventAttributeName の nsContentUtils を見てみると、わーお、めっちゃイベント名直書き。こりゃあかん。これ使って生成されてる sEventTable ってのを JavaScript から動的に変えられる手法でもなけりゃ、勝手につくった HTML の属性から(勝手に作っちゃったらもうHTMLじゃないけど…) JavaScript に繋ぐのは無理なのかあ!

ぬわー!

明日考えよう。