首先这个问题hax也有相关分析,受益良多。
问题描述(如果你只想看结论,可以跳过此部分)
近些时候发现一个bug:在IE下,当ExtJs中的菜单处于显示状态时,点击TextField或TextArea,发现它的emptyText未移除。(emptyText应该在只有为空,并未激活时显示)
然后对此bug进行了调试追踪,发现TextField并未收到focus事件。
继续制作简单模拟场景以定位排除,最终发现:
当在mousedown事件中改变页面焦点目标时,就会发生问题:界面光标已经在text input中了,但并未发出focus事件;当前的document.activeElement不为text input,而是JS中修改的值。
Ext中Menu会自动将焦点设置为自己(实际上是它下面的一个a元素),然后点击页面其它位置时,它会隐藏。
而隐藏的元素是无法获得焦点的,从而导致焦点变动。
这是简单的bug重现案例:
<html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <title>测试 - oldFocusTarget判断</title> </head> <body> <input type="text" id="test" value="init value" /> <a id="sucks" href="#">sucks</a> <script type="text/javascript"> var text = document.getElementById("test"), a = document.getElementById("sucks"); // 监听text focus状态 var handleFocus = function(){ document.title = "focus! "+new Date(); text.value = ""; }; var handleBlur = function(){ document.title = "blur! "+new Date(); text.value = "empty text"; }; text.attachEvent("onfocus", handleFocus); text.attachEvent("onblur", handleBlur); // text点击时更改focus对象 text.attachEvent("onmousedown", function(){ a.focus(); }); // 最终,text有输入框,但焦点在a上,并且text未触发focus </script> </body> </html>
简单理解
开始一直不能理解,看完hax的分析后才恍然大悟,其实就是焦点状态不同步。
当点击鼠标时(mousedown),IE的UI控件(即hax所说的windows基础控件所模拟的)会获得焦点,但在Dom层(即hax所说的html层)如果有在mousedown事件中修改焦点目标,会使得它与UI控件层失去同步。
这时,UI控件层的焦点是点击的控件,而Dom层则是JS操作聚焦后的元素。
就出现了文本框内有输入符,但Dom层焦点又不在它身上的bug。
如果不在mousedown中处理焦点,是没有任何问题的,为什么修改后就会失去同步呢?
探索深层原因
这里尝试分析IE对UI控件层与Dom层焦点同步的逻辑,目前有了一个猜测:
1. 当鼠标点击时,先记录Dom层焦点(document.activeElement)为oldFocusTarget
2. UI控件层内部处理点击行为 (注:2、3不区分先后次序)
3. Dom层发出mousedown事件,并执行相关事件句柄
4. 如果现在的Dom层焦点与oldFocusTarget一致,则修改Dom层焦点为当前UI控件。
这是一个比较给力的证明(IE6下测试通过):
<html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <title>测试 - oldFocusTarget判断</title> </head> <body> <input type="text" id="test" value="init value" /> <a id="sucks" href="#">sucks</a> <a id="f_uck" href="#">f_uck</a> <script type="text/javascript"> var text = document.getElementById("test"), a = document.getElementById("sucks"), b = document.getElementById("f_uck"); // 初始focus对象设置为a a.focus(); // text点击时更改focus对象,但最终改回来 text.attachEvent("onmousedown", function(){ b.focus(); a.focus(); }); // text最终还是focus // 监听事件触发次序,以对照。因为IE中事件句柄后注册先触发,所以这里在最后注册 var sequence = []; function record(eventName, domName){ return function(){ sequence.push(domName + ' ' + eventName); } } var toTest = ["mousedown", "mouseup", "focus", "blur"], i, n; for(i=0; i<toTest.length; i++){ n = toTest[i]; text.attachEvent("on"+n, record(n, "text")); a.attachEvent("on"+n, record(n, "a")); b.attachEvent("on"+n, record(n, "b")); } // 5秒后弹出记录下来的次序 setTimeout(function(){ alert(sequence.join("\n")); }, 5000); </script> </body> </html>
在这个案例中,初始focus对象为a(注意IE中按F5刷新会保留上次focus目标,所以请F6+回车进行刷新)
然后,点击text input后依次触发:
(点击) text mousedown
(b.focus) a blur, b focus
(a.focus) b blur, a focus
(mousedown完成,对比焦点通过) a blur, text focus
可以看到最后还是触发了text focus,而如果将以上代码的初始focus设置(a.focus())注释掉,就会发现text focus不会发出了。
结束
IE sucks...