CSPACE 3304 - dismissing popup areas on IE8

Antranig Basman antranig.basman at colorado.edu
Thu Dec 16 08:10:45 UTC 2010


This mail contains far more detail than most readers would want to go into, but more than anything provides 
a repository for future reference and analysis should we ever run into an issue similar to this again (or 
indeed, as seems likely, continue running into *this* issue :P)


Over the last few of days, Yura and I have been investigating issues involving popup areas (in particular 
NumberPatternChooser and Autocomplete) that have been behaving incorrectly under IE8. This involves tracking 
of focus and blur events in a way that is consistent across all browsers and is usually unrewardingly 
intricate. We have two main components in CSpace which deal with this, according to different strategies. 
Actually until yesterday I wasn't clear that these were really aimed at essentially the *same* purpose. The 
first, "GlobalDismissal", is something fairly quick & simple I put in during my first month on CSpace in 
August to deal with cases where various dialogs (date widget, confirmation etc.) were not being dismissed 
correctly when the user clicked outside them. This initial implementation only tracked mouse events and was 
not extended to deal with general focus loss. One idea we had during the attempt to fix CSPACE-3304 was to 
extend "GlobalDismissal" by using the new jQuery synthetic event "focusin" which promises to allow 
registering of focus handlers which bubble up through the document (browsers historically have treated this 
bubbling inconsistently)


"GlobalDismissal" approach

In this approach we register a global "focusin" handler on the entire document base. In this case the popup 
opens and stays open, but unfortunately on IE8 closes on receiving the first down arrow keystroke (on all 
other browsers it behaves correctly). The following trace shows the sequence of focus and blur events.

*1) LOG: 17:54:20.375:  Invoking blur on TR#fluid-id-334.csc-numberPatternChooser-patternRow cs-selecting
*2) LOG: 17:54:20.390:  received focusin on BODY#acquisition
*3) LOG: 17:54:31.906:  Invoking focus on TR#fluid-id-335.csc-numberPatternChooser-patternRow
*4) LOG: 17:54:32.172:  received focusin on BODY#acquisition

The transition between *1 and *2 is apparently synchronous. This means that with the "globalDismissal" 
approach the popup will already have been closed by the time the focus event arrives (these were actually 
from two different keystrokes, please ignore the intervening gap of 11 seconds)

The initial "blur" results from the behaviour of the jquery.keyboard-a11y plugin which attempts to ensure 
consistency by matching synthetic blur events to focus events. Unfortunately IE8 *synchronously* interprets 
the blur event without waiting for a further focus on the same event stack, and immediately synthesises its 
own focus based on the blur which focuses "nothing in particular", in fact the entire document body. This 
implies that any "stateless" approach to tracking blur on IE8 cannot work (unless the a11y plugin is 
rewritten to not generate blurs particularly on IE8 - they are unfortunately necessary on at least two other 
modern browsers, FF 3.6 and Safari 4) since this intervening event will *always* be interpreted as a signal 
that the contents of the popup has lost focus.

"Dead Man's Blur" approach

This is a utility which has been unofficially part of the Fluid framework for a couple of releases but in 
the current 1.3 release is being upgraded to core status - especially in regard of its usage in several 
places around CSpace. This registers a "stateful event processor" which, on receiving a blur on some number 
of "core elements" starts a timer, waiting to see whether the reason for the blur is a shortly forthcoming 
focus on ANOTHER of these elements. If this focus is received, the forthcoming blur handler (probably 
dismissing a dialog or popup of some kind) is cancelled. If however the timer expires without such an event, 
the originally queued blur handler is executed. In the following trace, the default expiration timer is set 
to 150ms:

*01) LOG: 19:17:01.250:  received focusin on DIV#secondary-nav-menu.csc-tabs-container
*02) LOG: 19:17:02.812:  received focusin on INPUT#.csc-acquisition-numberPatternChooser-reference-number
*03) LOG: 19:17:04.109:  Cancelled by focus on INPUT.csc-numberPatternChooser-button
*04) LOG: 19:17:04.109:  received focusin on INPUT.csc-numberPatternChooser-button
*05) LOG: 19:17:05.250:  focusing list
*06) LOG: 19:17:05.250:  received focusin on TABLE.csc-numberPatternChooser-list
*07) LOG: 19:17:05.250:  Cancelled by focus on INPUT.csc-numberPatternChooser-button
*08) LOG: 19:17:05.265:  Beginning timer for blur on INPUT.csc-numberPatternChooser-button
*09) LOG: 19:17:05.281:  Container focus handler executing
*10) LOG: 19:17:05.281:  focusing item TR.csc-numberPatternChooser-patternRow
*11) LOG: 19:17:05.422:  Timer expired, blurring <=== POPUP CLOSED HERE
*12) LOG: 19:17:05.422:  received focusin on DIV.csc-numberPatternChooser cs-numberPatternChooser
*13) LOG: 19:17:05.422:  Beginning timer for blur on TR.csc-numberPatternChooser-patternRow cs-selecting
*14) LOG: 19:17:05.578:  Timer expired, blurring

*01), *02) General startup, mouse click on the entry field
*03) TAB key to tbutton
*05) SPACE activates button - list is focused

*07), *08) Anomalous *REFOCUS* and blur of button by IE event handling machinery which cannot really be 
explained, but *should* be essentially harmless, if it were not for:

*09), *10), *11) Container focus handler executes for selectable row elements - which *SHOULD* have resulted 
in a focusin event after *10) resulting in cancellation of the blur handler. Instead it simply expires, 
closing the dialog.

*12), *13), *14) After the disaster of inappropriately closing the popup, various other inappropriate events 
are generated by IE8, including the processing of an "out-of-order" focus followed by blur.

The core issue appears to be the "failure of faithfulness" in IE8 of the jQuery focus() call executed on the 
pattern row element. One would expect this either synchronously or within a short period of time to give 
rise to an event which would be caught by jQuery's own "focusin" handler but it does not. This is an issue 
encountered in various test cases in Fluid but was until now dealt with in an "ad hoc" manner to make the 
tests pass correctly under IE8. This is presumably a jQuery framework bug, but in the meantime it appears 
that bypassing jQuery for programmatic focus and blur calls does indeed give rise to the expected 
"cancellation event" required by dead man's blur:

*01) LOG: 19:51:26.750:  received focusin on DIV.csc-numberPatternChooser cs-numberPatternChooser
*02) LOG: 19:51:27.828:  received focusin on INPUT#.csc-acquisition-numberPatternChooser-reference-number
*03) LOG: 19:51:29.297:  Cancelled by focus on INPUT.csc-numberPatternChooser-button
*04) LOG: 19:51:29.297:  received focusin on INPUT.csc-numberPatternChooser-button
*05) LOG: 19:51:30.640:  focusing list
*06) LOG: 19:51:30.640:  received focusin on TABLE.csc-numberPatternChooser-list
*07) LOG: 19:51:30.640:  Cancelled by focus on INPUT.csc-numberPatternChooser-button
*08) LOG: 19:51:30.656:  Beginning timer for blur on INPUT.csc-numberPatternChooser-button
*09) LOG: 19:51:30.656:  Container focus handler executing
*10) LOG: 19:51:30.656:  focusing item TR.csc-numberPatternChooser-patternRow
*11) LOG: 19:51:30.656:  Cancelled by focus on TR.csc-numberPatternChooser-patternRow (SUCCESSFUL FOCUSIN)
*12) LOG: 19:51:30.656:  received focusin on TR.csc-numberPatternChooser-patternRow
*13) LOG: 19:51:30.812:  Timer expired, blurring
<==== DOWN ARROW PRESSED HERE
*14) LOG: 19:51:35.562:  Invoking blur on TR.csc-numberPatternChooser-patternRow cs-selecting
*15) LOG: 19:51:35.562:  received focusin on BODY#acquisition
*16) LOG: 19:51:35.562:  Invoking focus on TR.csc-numberPatternChooser-patternRow
*17) LOG: 19:51:35.562:  Cancelled by focus on TR.csc-numberPatternChooser-patternRow
*18) LOG: 19:51:35.562:  received focusin on TR.csc-numberPatternChooser-patternRow
*19) LOG: 19:51:35.578:  Beginning timer for blur on TR.csc-numberPatternChooser-patternRow
*20) LOG: 19:51:35.734:  Timer expired, blurring
*21) LOG: 19:51:35.734:  received focusin on DIV.csc-numberPatternChooser cs-numberPatternChooser
*22) LOG: 19:51:35.734:  Beginning timer for blur on TR.csc-numberPatternChooser-patternRow cs-selecting
*23) LOG: 19:51:35.890:  Timer expired, blurring

Unfortunately, to add insult to injury, this trace reveals a FURTHER problem. Starting the further train of 
events after *14), the user presses the down arrow to change the selection of the number pattern. This 
schedules a "blur" train which as seen in our first trace causes a synchronous blur and then focus on the 
further element. Unfortunately, as *18), *19) reveal, the ORIGINAL blur event continues to propagate 
asynchronously, not setting off the timer in deadMan'sBlur until *AFTER* the focus event which might cancel 
it is received.

Dealing with this requires an awkward "proleptic" extension to dead man's blur. It needs to keep track of 
timestamps for cancellation events and match them up with blurs occuring in time windows BOTH BEFORE AND 
AFTER the blur handler starts to execute.

Implementing this extension allows NumberPatternChooser to work....

However, for Autocomplete, there is a yet further insult:

LOG: 23:59:02.844:  Applying raw focus to LI#matchItem:.cs-autocomplete-matchItem
is followed on expiry of the timer by
LOG: 23:59:02.969:  New value
In this case we have met our match.... even raw triggering of the native "focus" event on IE fails to catch 
the global "focusin" handler. The only way out is to abandon direct manipulation of "focus" events entirely 
and declare our own totally fresh events "fluid-focus" and "fluid-blur" which we ensure to fire IN ADDITION 
to the standard jQuery events, but which are guaranteed to propagate up the DOM tree reliably by virtue of 
the standard jQuery event mechanism. We then listen to these events in addition to the DOM standard ones.




IN SUMMARY, we identify 3 bugs in the IE8/jQuery system:

BUG 1: IE8 will fire a synchronous FOCUS event (momentarily targetted at the docuemtn) on being given a 
synthetic blur event
BUG 2: IE8 will not notify HANDLERS to the blur event synchronously, but wait until the FOCUS event handler 
stack returns, this allowing observers to see "out-of-order" service of focus and blur events.
BUG 3: Causing synthetic focus with jQuery's "focus" call is not "faithful" on IE and will not give rise to 
any kind of focus handler notification at all (looks like neither focus nor focusin). Only a call to the raw 
DOM node's "focus" method will achieve this (and in practice, this has also now been found unreliable).

Fixing bug 3 requires patching the keyboard-a11y plugin to apply raw DOM methods (now - new synthetic focus 
event via fluid.focus()) for synthetic focus wherever it can.

The presence of bug 1 implies that "globalDismissal" cannot be used since it is insufficiently context aware.

Fixing bug 2 requires creation of a "proleptic dead man's blur" which will allow blur handlers to be 
preemptively cancelled by focus events registered in a short time window BEFORE the blur handler itself 
starts to execute.



These fixes are now attached to the base FLUID JIRA
http://issues.fluidproject.org/browse/FLUID-3487

The patch version "i" was applied to trunk yesterday and will be part of the Infusion 1.3 final release. 
This appeared stable and passed many test cases, but turned out not to be sufficient to resolve CSPACE-3304.
Patch version "j" is required to support the behaviour described above on IE8.

The fixes on the CollectionSpace side are attached to the original 
http://issues.collectionspace.org/browse/CSPACE-3304 as "CSPACE-3304-proleptic-DMB.patch"

These patches are in a "nearly done" state but still include a lot of logging statements to aid debugging on 
other machines and platforms. Hopefully during the course of the day they will be checked and verified by 
others.



More information about the fluid-work mailing list