Fixes #7340. Use a single capturing handler to simulate bubbling focusin/focusout...
[jquery.git] / src / event.js
index f3a5d9f..61c8a93 100644 (file)
@@ -23,17 +23,22 @@ jQuery.event = {
                        return;
                }
 
-               // For whatever reason, IE has trouble passing the window object
-               // around, causing it to be cloned in the process
-               if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
-                       elem = window;
+               // TODO :: Use a try/catch until it's safe to pull this out (likely 1.6)
+               // Minor release fix for bug #8018
+               try {
+                       // For whatever reason, IE has trouble passing the window object
+                       // around, causing it to be cloned in the process
+                       if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
+                               elem = window;
+                       }
                }
+               catch ( e ) {}
 
                if ( handler === false ) {
                        handler = returnFalse;
                } else if ( !handler ) {
                        // Fixes bug #7229. Fix recommended by jdalton
-                 return;
+                       return;
                }
 
                var handleObjIn, handleObj;
@@ -65,10 +70,10 @@ jQuery.event = {
                }
 
                if ( !eventHandle ) {
-                       elemData.handle = eventHandle = function() {
+                       elemData.handle = eventHandle = function( e ) {
                                // Handle the second event of a trigger and when
                                // an event is called after a page has unloaded
-                               return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
+                               return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
                                        jQuery.event.handle.apply( eventHandle.elem, arguments ) :
                                        undefined;
                        };
@@ -375,7 +380,7 @@ jQuery.event = {
                                                        target[ "on" + targetType ] = null;
                                                }
 
-                                               jQuery.event.triggered = true;
+                                               jQuery.event.triggered = event.type;
                                                target[ targetType ]();
                                        }
 
@@ -386,7 +391,7 @@ jQuery.event = {
                                        target[ "on" + targetType ] = old;
                                }
 
-                               jQuery.event.triggered = false;
+                               jQuery.event.triggered = undefined;
                        }
                }
        },
@@ -653,6 +658,12 @@ var withinElement = function( event ) {
        // Firefox sometimes assigns relatedTarget a XUL element
        // which we cannot access the parentNode property of
        try {
+
+               // Chrome does something similar, the parentNode property
+               // can be accessed but is null.
+               if ( parent !== document && !parent.parentNode ) {
+                       return;
+               }
                // Traverse up the tree
                while ( parent && parent !== this ) {
                        parent = parent.parentNode;
@@ -703,8 +714,7 @@ if ( !jQuery.support.submitBubbles ) {
                                                type = elem.type;
 
                                        if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
-                                               e.liveFired = undefined;
-                                               return trigger( "submit", this, arguments );
+                                               trigger( "submit", this, arguments );
                                        }
                                });
 
@@ -713,8 +723,7 @@ if ( !jQuery.support.submitBubbles ) {
                                                type = elem.type;
 
                                        if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
-                                               e.liveFired = undefined;
-                                               return trigger( "submit", this, arguments );
+                                               trigger( "submit", this, arguments );
                                        }
                                });
 
@@ -777,7 +786,7 @@ if ( !jQuery.support.changeBubbles ) {
                if ( data != null || val ) {
                        e.type = "change";
                        e.liveFired = undefined;
-                       return jQuery.event.trigger( e, arguments[1], elem );
+                       jQuery.event.trigger( e, arguments[1], elem );
                }
        };
 
@@ -791,7 +800,7 @@ if ( !jQuery.support.changeBubbles ) {
                                var elem = e.target, type = elem.type;
 
                                if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
-                                       return testChange.call( this, e );
+                                       testChange.call( this, e );
                                }
                        },
 
@@ -803,7 +812,7 @@ if ( !jQuery.support.changeBubbles ) {
                                if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
                                        (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
                                        type === "select-multiple" ) {
-                                       return testChange.call( this, e );
+                                       testChange.call( this, e );
                                }
                        },
 
@@ -842,26 +851,50 @@ if ( !jQuery.support.changeBubbles ) {
 }
 
 function trigger( type, elem, args ) {
-       args[0].type = type;
-       return jQuery.event.handle.apply( elem, args );
+       // Piggyback on a donor event to simulate a different one.
+       // Fake originalEvent to avoid donor's stopPropagation, but if the
+       // simulated event prevents default then we do the same on the donor.
+       // Don't pass args or remember liveFired; they apply to the donor event.
+       var event = jQuery.extend( {}, args[ 0 ] );
+       event.type = type;
+       event.originalEvent = {};
+       event.liveFired = undefined;
+       jQuery.event.handle.call( elem, event );
+       if ( event.isDefaultPrevented() ) {
+               args[ 0 ].preventDefault();
+       }
 }
 
 // Create "bubbling" focus and blur events
 if ( document.addEventListener ) {
        jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+       
+               // Attach a single capturing handler while someone wants focusin/focusout
+               var attaches = 0;
+               
                jQuery.event.special[ fix ] = {
                        setup: function() {
-                               this.addEventListener( orig, handler, true );
+                               if ( attaches++ === 0 ) {
+                                       document.addEventListener( orig, handler, true );
+                               }
                        },
                        teardown: function() {
-                               this.removeEventListener( orig, handler, true );
+                               if ( --attaches === 0 ) {
+                                       document.removeEventListener( orig, handler, true );
+                               }
                        }
                };
 
-               function handler( e ) {
-                       e = jQuery.event.fix( e );
+               function handler( donor ) {
+                       // Donor event is always a native one; fix it and switch its type.
+                       // Let focusin/out handler cancel the donor focus/blur event.
+                       var e = jQuery.event.fix( donor );
                        e.type = fix;
-                       return jQuery.event.handle.call( this, e );
+                       e.originalEvent = {};
+                       jQuery.event.trigger( e, null, e.target );
+                       if ( e.isDefaultPrevented() ) {
+                               donor.preventDefault();
+                       }
                }
        });
 }
@@ -1080,7 +1113,7 @@ function liveHandler( event ) {
                for ( j = 0; j < live.length; j++ ) {
                        handleObj = live[j];
 
-                       if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) {
+                       if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) {
                                elem = close.elem;
                                related = null;