Bug Tracker

Opened 8 years ago

Closed 8 years ago

Last modified 5 years ago

#10345 closed bug (patchwelcome)

IE<=8 preventDefault doesn't prevent an element from taking focus

Reported by: ajones754 Owned by: ajones754
Priority: undecided Milestone: None
Component: unfiled Version: 1.6.4
Keywords: Cc:
Blocked by: Blocking:

Description

In IE<=8, an element which makes a preventDefault() call when handling an event does not stop that element from stealing focus from another currently focused element.

Full HTML page code follows. Tested with jQuery 1.6.4 and 1.5.1. giving same result.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!-- http://www.w3.org/QA/2002/04/valid-dtd-list.html -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <meta http-equiv="content-type" content="text/html;charset=utf-8" />
 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
 <style>
 div:focus{background-color:cyan;}
 </style>
 <script type="text/javascript">
 $(document).ready(function(){
 $("#div3").mousedown(function(event){ event.preventDefault(); });
 });
 </script>
</head>
<body>
 <div id="div1" tabindex=1>Div 1 - click me to focus</div>
 <div id="div2" tabindex=2>Div 2 - click me to focus</div>
 <div id="div3">Div 3 - click on me to (erroneously) steal focus in IE<=8. But not in IE9/FF/Chrome.</div>
</body>
</html>


Change History (9)

comment:1 Changed 8 years ago by ajones754

comment:2 Changed 8 years ago by dmethvin

Owner: set to ajones754
Status: newpending

Does the same problem occur if the example is coded in pure DOM functions? If so we probably can't fix it.

comment:3 Changed 8 years ago by ajones754

Status: pendingnew

The problem does seem to occur in pure DOM functions i.e.

<div onmousedown="return false;"></div>

However, I have found a workaround. It appears that the document.activeElement doesn't actually get set by IE until a later stage - so I store it and make a timeout that refocuses it after the mousedown has been handled: http://jsfiddle.net/KQBfP/5/

Can this workaround be intergrated into jQuery? If so, I think the only extra work needed would be to integrate this technique with jQuery's existing event handlers. Basically, all event handlers would have to wait for the originally focused element to be refocused before executing - in case they reference document.activeElement themselves.

Also, the .focus() call in refocus() (see the jsFiddle) would not have to trigger any .focus() event handlers itself.

Of course, all of this is IE<=8 conditional.

comment:4 Changed 8 years ago by dmethvin

Status: newpending

Thanks for doing some research on this. Can you verify that the jQuery(edge) build fixes this problem? If so, it should work fine in 1.7.

http://jsfiddle.net/KQBfP/9/

comment:5 Changed 8 years ago by ajones754

Status: pendingnew

No worries. jQuery(edge) doesn't appear to fix the issue; clicking on div3 can still steal focus from div1 or div2.

comment:6 Changed 8 years ago by dmethvin

Resolution: patchwelcome
Status: newclosed

I agree, 1.7b1 doesn't fix the problem but since it breaks for bare DOM that isn't too surprising.

However, I have found a workaround. It appears that the document.activeElement doesn't actually get set by IE until a later stage - so I store it and make a timeout that refocuses it after the mousedown has been handled

It is very risky to incorporate fixes into core that involve asynchronous actions, since they can cause problems if subsequent (immediate) events would override that delayed action.

I tried looking at special IE events like onbeforeactivate but they don't leave the focus in the right place:

http://msdn.microsoft.com/en-us/library/ms536791%28v=vs.85%29.aspx

This is a pretty obscure edge case so I am going to close it. If there is a way to fix it that doesn't involve asynchronous actions I'd consider it though.

comment:7 Changed 8 years ago by spekary

This bug is currently blocking a fix in jQueryUi for Autocomplete. See https://github.com/scottgonzalez/jquery-ui/compare/autocomplete-syncchange. It is mentioned on the net here: http://stackoverflow.com/questions/6677082/ie-event-propagation-problem-jquery-autocomplete-jscrollpane and also appears to be the cause of this old bug and other similar bugs: http://bugs.jquery.com/ticket/4029 So, its not that obscure of an edge case. For lots of current and future jQueryUI stuff, its pretty important to be able to keep a mousedown from causing a blur event.

comment:8 in reply to:  7 Changed 8 years ago by scottgonzalez

Replying to spekary:

This bug is currently blocking a fix in jQueryUi for Autocomplete.

I don't think that's true. A bug in IE is blocking the fix for autocomplete. IE treats focus/blur asynchronously anyway, so there's nothing jQuery can do to fix the problem.

comment:9 Changed 5 years ago by laughinghan@…

This is 3 years late but I just discovered there is in fact a way to get IE ≤ 8 to do what e.preventDefault() on mousedown does in other browsers (which prevents selection and prevents focus): set the unselectable attribute! http://stackoverflow.com/a/17525223/362030

Note that unlike the workarounds always suggested (including comment:3), which always boil down to setTimeout(function() { thingIDidntWantFocusStolenFrom.focus(); }), this prevents focus from ever being stolen by the mousedown target in the first place!

What's funny about unselectable is that it isn't inherited, so it's often overlooked in favor of the selectstart event (which bubbles, and e.preventDefault() on which prevents selection but doesn't prevent focus), or set on every descendant element with a tree-traversal, but you can actually just set it on event.target on mousedown: http://jsbin.com/yagekiji/1

Unfortunately I can't think of a way to work this into a clean jQuery patch that avoids asynchronous actions, because setting unselectable is a permanent side-effect. The main problem, permanency, that just because e.preventDefault() was called on this mousedown doesn't mean we want selection and focus prevented forever, should be completely solved by listening for the next mousedown and, before running any user-supplied handlers, resetting (unsetting) unselectable (it only blocks selections starting in that element, which must fire mousedown on that element first). The minor problem, that it's a side-effect and the user could theoretically check for unselectable on the DOM element, is kind of well you're peeking at exactly the stuff you asked us to hide from you, what did you expect, but could be mitigated by resetting (unsetting) unselectable in a timeout after the mousedown.

In any case, I think this is an ingenious workaround for the common use case of "I don't want clicking anywhere in this element to steal focus from anywhere, ever"!

Note: See TracTickets for help on using tickets.