Skip to main content

Bug Tracker

Side navigation

#10345 closed bug (patchwelcome)

Opened September 26, 2011 02:47PM UTC

Closed October 07, 2011 01:18AM UTC

Last modified April 30, 2014 07:13PM UTC

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>


Attachments (0)
Change History (9)

Changed September 26, 2011 02:51PM UTC by ajones754 comment:1

Changed September 26, 2011 09:49PM UTC by dmethvin comment:2

owner: → 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.

Changed September 28, 2011 04:54PM UTC by ajones754 comment:3

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.

Changed September 28, 2011 06:01PM UTC by dmethvin comment:4

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/

Changed September 28, 2011 06:43PM UTC by ajones754 comment:5

status: pendingnew

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

Changed October 07, 2011 01:18AM UTC by dmethvin comment:6

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.

Changed October 19, 2011 04:28AM UTC by spekary comment:7

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.

Changed October 19, 2011 12:01PM UTC by scottgonzalez comment:8

Replying to [comment:7 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.

Changed April 30, 2014 07:13PM UTC by laughinghan@gmail.com comment:9

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"!