From 92a80cbd057c06e259a06bb40a85e921ad5aec6a Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Tue, 20 Sep 2011 12:44:49 -0400 Subject: [PATCH 01/18] Adds implementation tests for jQuery.event.propHooks #8789 --- test/unit/event.js | 71 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/test/unit/event.js b/test/unit/event.js index a7a989a56..7efef7522 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -54,7 +54,7 @@ test("Handler changes and .trigger() order", function() { markup.find( "b" ).trigger( "click" ); equals( path, "p div body html ", "Delivered all events" ) - + markup.remove(); }); */ @@ -2332,13 +2332,13 @@ test(".on and .off", function() { // We should have removed all the event handlers ... kinda hacky way to check this var data = jQuery.data[ jQuery( "#onandoff" )[0].expando ] || {}; equals( data.events, undefined, "no events left" ); - + jQuery("#onandoff").remove(); }); test("delegated events quickIs", function() { expect(23); - var markup = jQuery( + var markup = jQuery( '
'+ '

'+ 'deadbeatclub'+ @@ -2349,12 +2349,12 @@ test("delegated events quickIs", function() { '

' ), str, - check = function(el, expect){ + check = function(el, expect){ str = ""; markup.find( el ).trigger( "blink" ); equals( str, expect, "On " + el ); }, - func = function(e){ + func = function(e){ var tag = this.nodeName.toLowerCase(); str += (str && " ") + tag + "|" + e.handleObj.selector; ok( e.handleObj.quick, "Selector "+ e.handleObj.selector + " on " + tag + " is a quickIs case" ); @@ -2379,7 +2379,7 @@ test("delegated events quickIs", function() { check( "p", "p|.D p|:first-child" ); check( "b", "b|[devo=cool] p|.D p|:first-child" ); check( "em", "em|em q|#famous em|em em|em:empty em|em:last-child q|#famous" ); - + markup.find( "b" ).attr( "devo", "NO" ); check( "b", "b|[devo='NO'] p|.D p|:first-child" ); @@ -2460,6 +2460,65 @@ test("delegated events quickIs", function() { })(); +test("jQuery.event.propHooks", function() { + expect( 1 ); + ok( jQuery.event.propHooks, "jQuery.event.propHooks exists" ); +}); + +test("jQuery.event.propHooks as function", function() { + + expect( 2 ); + + jQuery( "
" ).appendTo( "#qunit-fixture" ); + + var $fixture = jQuery( "#hook-fixture" ); + + // Does not exist + $fixture.bind( "click", function( event ) { + ok( !("propC" in event), "event.propC Does not exist" ); + }).trigger( "click" ); + + $fixture.unbind( "click" ); + + // Store as function + jQuery.event.propHooks[ "custom" ] = function( event ) { + // receives the event object for processing + event.propC = true; + return event; + }; + + $fixture.bind( "custom", function( event ) { + ok( event.propC, "event.propC exists" ); + }).trigger( "custom" ); +}); + +test("jQuery.event.propHooks usecase", function() { + + expect( 3 ); + + jQuery( "
" ).appendTo( "#qunit-fixture" ); + + var $fixture = jQuery( "#hook-fixture" ); + + $fixture.bind( "fakedrop", function( event ) { + ok( !("dataTransfer" in event), "event.dataTransfer is not available" ); + }).trigger( "fakedrop" ); + + $fixture.unbind( "fakedrop" ); + + jQuery.event.propHooks[ "fakedrop" ] = function( event ) { + event.dataTransfer = "some val"; + return event; + }; + + $fixture.bind( "fakedrop", function( event ) { + ok( ("dataTransfer" in event), "event.dataTransfer exists, just copied" ); + equal( event.dataTransfer, "some val", "event.dataTransfer equal 'some val'" ); + }).trigger( "fakedrop" ); + + $fixture.unbind( "fakedrop" ); +}); + /* test("event properties", function() { stop(); From 9fbed020a15b0bfe33ce622b6f2fbccc1e2b4739 Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Tue, 20 Sep 2011 12:54:34 -0400 Subject: [PATCH 02/18] Implements jQuery.event.propHooks. Fixes #8789 --- src/event.js | 81 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/src/event.js b/src/event.js index da787ea85..7fdcba5ad 100644 --- a/src/event.js +++ b/src/event.js @@ -7,6 +7,7 @@ var rnamespaces = /\.(.*)$/, rescape = /[^\w\s.|`]/g, rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, rhoverHack = /\bhover(\.\S+)?/, + rmouseEvent = /^(?:mouse)|(?:click)|(contextmenu)/, rquickIs = /^([\w\-]+)?(?:#([\w\-]+))?(?:\.([\w\-]+))?(?:\[([\w+\-]+)=["']?([\w\-]*)["']?\])?(?::(first-child|last-child|empty))?$/, quickPseudoMap = { "empty": "firstChild", @@ -26,8 +27,8 @@ var rnamespaces = /\.(.*)$/, }, quickIs = function( elem, m ) { return ( - (!m[1] || elem.nodeName.toLowerCase() === m[1]) && - (!m[2] || elem.id === m[2]) && + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || elem.id === m[2]) && (!m[3] || m[3].test( elem.className )) && (!m[4] || elem.getAttribute( m[4] ) == m[5]) && (!m[6] || !elem[ m[6] ]) @@ -106,7 +107,7 @@ jQuery.event = { handleObj = jQuery.extend({ type: type, origType: tns[1], - data: data, + data: data, handler: handler, guid: handler.guid, selector: selector, @@ -126,7 +127,7 @@ jQuery.event = { if ( !handlers ) { handlers = events[ type ] = []; handlers.delegateCount = 0; - + // Only use addEventListener/attachEvent if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element @@ -153,7 +154,7 @@ jQuery.event = { } else { handlers.push( handleObj ); } - + // Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true; } @@ -170,7 +171,7 @@ jQuery.event = { var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), t, tns, type, namespaces, origCount, j, events, special, handle, eventType, handleObj; - + if ( !elemData || !(events = elemData.events) ) { return; } @@ -252,7 +253,7 @@ jQuery.event = { jQuery.removeData( elem, [ "events", "handle" ], true ); } }, - + // Events that are safe to short-circuit if no handlers are attached. // Native DOM events should not be added, they may have inline handlers. customEvent: { @@ -361,7 +362,7 @@ jQuery.event = { } addHandlers( doc.defaultView || doc.parentWindow || window, bubbleType ); } - + // Bubble up the DOM tree for ( i = 0; i < eventPath.length; i++ ) { cur = eventPath[ i ]; @@ -402,7 +403,7 @@ jQuery.event = { } } } - + return event.result; }, @@ -410,7 +411,11 @@ jQuery.event = { // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( event || window.event ); - + + if ( jQuery.event.propHooks[ event.type ] ) { + event = jQuery.event.propHooks[ event.type ]( event ); + } + var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []), delegateCount = handlers.delegateCount, args = Array.prototype.slice.call( arguments, 0 ), @@ -447,7 +452,7 @@ jQuery.event = { } } } - + // Copy the remaining (bound) handlers in case they're changed handlers = handlers.slice( delegateCount ); @@ -469,6 +474,8 @@ jQuery.event = { props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + propHooks: {}, + fix: function( event ) { if ( event[ jQuery.expando ] ) { return event; @@ -500,16 +507,6 @@ jQuery.event = { event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - var eventDocument = event.target.ownerDocument || document, - doc = eventDocument.documentElement, - body = eventDocument.body; - - event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); - event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); - } - // Add which for key events if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { event.which = event.charCode != null ? event.charCode : event.keyCode; @@ -520,10 +517,8 @@ jQuery.event = { event.metaKey = event.ctrlKey; } - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && event.button !== undefined ) { - event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + if ( jQuery.event.propHooks[ event.type ] ) { + event = jQuery.event.propHooks[ event.type ]( event, originalEvent ); } return event; @@ -540,7 +535,7 @@ jQuery.event = { // Make sure the ready event is setup setup: jQuery.bindReady }, - + focus: { delegateType: "focusin", trigger: useNativeMethod @@ -580,7 +575,7 @@ function dispatch( target, event, handlers, args ) { // Triggered event must either 1) be non-exclusive and have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { - + // Pass in a reference to the handler function itself // So that we can later remove it event.handler = handleObj.handler; @@ -740,7 +735,7 @@ if ( !jQuery.support.submitBubbles ) { type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : ""; // Do the elem.form check after type to avoid VML-related crash in IE (#9807) - if ( (e.type === "click" && (type === "submit" || type === "image") && elem.form) || + if ( (e.type === "click" && (type === "submit" || type === "image") && elem.form) || (e.type === "keypress" && e.keyCode === 13 && (type === "text" || type === "password") && elem.form) ) { simulate( "submit", this, e ); } @@ -987,7 +982,7 @@ jQuery.fn.extend({ jQuery.event.remove( this, types, fn, selector ); }); }, - + bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }, @@ -1056,7 +1051,7 @@ jQuery.fn.extend({ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { @@ -1073,6 +1068,32 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl if ( jQuery.attrFn ) { jQuery.attrFn[ name ] = true; } + + // Add internal event property hooks to mouse events + if ( rmouseEvent.test( name ) ) { + + jQuery.event.propHooks[ name ] = function( event, original ) { + + var eventDocument, doc, body; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + eventDocument = event.target.ownerDocument || document; + doc = eventDocument.documentElement; + body = eventDocument.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + return event; + }; + } }); })( jQuery ); From 3d39b7d52763d1aa337ccc17482ca54af530fb1f Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Tue, 20 Sep 2011 13:14:59 -0400 Subject: [PATCH 03/18] Updates rmouseEvent --- src/event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event.js b/src/event.js index 7fdcba5ad..2ae41a458 100644 --- a/src/event.js +++ b/src/event.js @@ -7,7 +7,7 @@ var rnamespaces = /\.(.*)$/, rescape = /[^\w\s.|`]/g, rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, rhoverHack = /\bhover(\.\S+)?/, - rmouseEvent = /^(?:mouse)|(?:click)|(contextmenu)/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, rquickIs = /^([\w\-]+)?(?:#([\w\-]+))?(?:\.([\w\-]+))?(?:\[([\w+\-]+)=["']?([\w\-]*)["']?\])?(?::(first-child|last-child|empty))?$/, quickPseudoMap = { "empty": "firstChild", From 0aaa1fae8335c2ce09085c9d73fb1124a90cef49 Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Tue, 20 Sep 2011 14:54:33 -0400 Subject: [PATCH 04/18] Removes pageX pageY from prop list --- src/event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event.js b/src/event.js index 2ae41a458..f60c8e670 100644 --- a/src/event.js +++ b/src/event.js @@ -472,7 +472,7 @@ jQuery.event = { return event.result; }, - props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), propHooks: {}, From 9ef0394fbdec99c6addb14b0127abfb813508ffe Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 10:35:18 -0400 Subject: [PATCH 05/18] jQuery.event.propHooks => this.propHooks where possible --- src/event.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event.js b/src/event.js index f60c8e670..7654d6f27 100644 --- a/src/event.js +++ b/src/event.js @@ -517,8 +517,8 @@ jQuery.event = { event.metaKey = event.ctrlKey; } - if ( jQuery.event.propHooks[ event.type ] ) { - event = jQuery.event.propHooks[ event.type ]( event, originalEvent ); + if ( this.propHooks[ event.type ] ) { + event = this.propHooks[ event.type ]( event, originalEvent ); } return event; From 313bee9de4b2a3ddf9b72c3f3e0c7fd55845e2ee Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 10:43:32 -0400 Subject: [PATCH 06/18] Moves key event fixes to own even prop hook defs --- src/event.js | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/event.js b/src/event.js index 7654d6f27..0986b9294 100644 --- a/src/event.js +++ b/src/event.js @@ -8,6 +8,7 @@ var rnamespaces = /\.(.*)$/, rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, rhoverHack = /\bhover(\.\S+)?/, rmouseEvent = /^(?:mouse|contextmenu)|click/, + rkeyEvent = /^(?:key)/, rquickIs = /^([\w\-]+)?(?:#([\w\-]+))?(?:\.([\w\-]+))?(?:\[([\w+\-]+)=["']?([\w\-]*)["']?\])?(?::(first-child|last-child|empty))?$/, quickPseudoMap = { "empty": "firstChild", @@ -492,6 +493,7 @@ jQuery.event = { } // Fix target property, if necessary + // Removal of this condition will crash IE6,7,8 if ( !event.target ) { // Fixes #1925 where srcElement might not be defined either event.target = event.srcElement || document; @@ -507,16 +509,6 @@ jQuery.event = { event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } - // Add which for key events - if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { - event.which = event.charCode != null ? event.charCode : event.keyCode; - } - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && event.ctrlKey ) { - event.metaKey = event.ctrlKey; - } - if ( this.propHooks[ event.type ] ) { event = this.propHooks[ event.type ]( event, originalEvent ); } @@ -1069,9 +1061,27 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl jQuery.attrFn[ name ] = true; } - // Add internal event property hooks to mouse events - if ( rmouseEvent.test( name ) ) { + // Key Event property hooks + if ( rkeyEvent.test( name ) ) { + jQuery.event.propHooks[ name ] = function( event, original ) { + + // Add which for key events + if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { + event.which = event.charCode != null ? event.charCode : event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + return event; + }; + } + + // Mouse Event property hooks + if ( rmouseEvent.test( name ) ) { jQuery.event.propHooks[ name ] = function( event, original ) { var eventDocument, doc, body; @@ -1093,8 +1103,7 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl } return event; }; - } -}); + }}); })( jQuery ); From 2c903b805c922355409b4d593fbc3206395d008d Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 10:52:29 -0400 Subject: [PATCH 07/18] Adds notes re: crash status of fix conditions --- src/event.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event.js b/src/event.js index 0986b9294..86306d75f 100644 --- a/src/event.js +++ b/src/event.js @@ -493,13 +493,14 @@ jQuery.event = { } // Fix target property, if necessary - // Removal of this condition will crash IE6,7,8 + // Removal will crash IE6,7,8 if ( !event.target ) { // Fixes #1925 where srcElement might not be defined either event.target = event.srcElement || document; } // check if target is a textnode (safari) + // Removal will crash IE6,7,8 if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } From 66202aeae53be1c302922e57f61debd43d6415db Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 10:59:29 -0400 Subject: [PATCH 08/18] Restore this.propHooks => jQuery.event.propHooks for better gzip compression. Thanks gnarf --- src/event.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event.js b/src/event.js index 86306d75f..b9da01832 100644 --- a/src/event.js +++ b/src/event.js @@ -510,8 +510,8 @@ jQuery.event = { event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } - if ( this.propHooks[ event.type ] ) { - event = this.propHooks[ event.type ]( event, originalEvent ); + if ( jQuery.event.propHooks[ event.type ] ) { + event = jQuery.event.propHooks[ event.type ]( event, originalEvent ); } return event; From 7babc7f20393acb44d4fced34fe86fde65f893a0 Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 11:07:16 -0400 Subject: [PATCH 09/18] Cache reference to propHook lookup and result --- src/event.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/event.js b/src/event.js index b9da01832..a9bf19706 100644 --- a/src/event.js +++ b/src/event.js @@ -484,8 +484,11 @@ jQuery.event = { // store a copy of the original event object // and "clone" to set read-only properties - var originalEvent = event; + var originalEvent = event, + propHook; + event = jQuery.Event( originalEvent ); + propHook = jQuery.event.propHooks[ event.type ]; for ( var i = this.props.length, prop; i; ) { prop = this.props[ --i ]; @@ -510,8 +513,8 @@ jQuery.event = { event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } - if ( jQuery.event.propHooks[ event.type ] ) { - event = jQuery.event.propHooks[ event.type ]( event, originalEvent ); + if ( propHook ) { + event = propHook( event, originalEvent ); } return event; From 737820118b0af9c9e0d077481f0630f0f279a2c8 Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 11:12:42 -0400 Subject: [PATCH 10/18] More reference caching --- src/event.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/event.js b/src/event.js index a9bf19706..379dc7906 100644 --- a/src/event.js +++ b/src/event.js @@ -413,8 +413,10 @@ jQuery.event = { // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( event || window.event ); - if ( jQuery.event.propHooks[ event.type ] ) { - event = jQuery.event.propHooks[ event.type ]( event ); + var propHook = jQuery.event.propHooks[ event.type ]; + + if ( propHook ) { + event = propHook( event ); } var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []), From 7dab8981f8868caaa350ea7029929e7e81662fd2 Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 11:14:45 -0400 Subject: [PATCH 11/18] Remove unnec. empty line --- src/event.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/event.js b/src/event.js index 379dc7906..7227e2d65 100644 --- a/src/event.js +++ b/src/event.js @@ -1067,7 +1067,6 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl jQuery.attrFn[ name ] = true; } - // Key Event property hooks if ( rkeyEvent.test( name ) ) { jQuery.event.propHooks[ name ] = function( event, original ) { From dc878ae33bfe44a746d1f24eca7033f80d32517c Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 11:22:56 -0400 Subject: [PATCH 12/18] More ref localization --- src/event.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/event.js b/src/event.js index 7227e2d65..38ed3ae91 100644 --- a/src/event.js +++ b/src/event.js @@ -1071,14 +1071,18 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl if ( rkeyEvent.test( name ) ) { jQuery.event.propHooks[ name ] = function( event, original ) { + var charCode = event.charCode, + keyCode = event.keyCode, + ctrlKey = event.ctrlKey; + // Add which for key events - if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { - event.which = event.charCode != null ? event.charCode : event.keyCode; + if ( event.which == null && (charCode != null || keyCode != null) ) { + event.which = charCode != null ? charCode : keyCode; } // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && event.ctrlKey ) { - event.metaKey = event.ctrlKey; + if ( !event.metaKey && ctrlKey ) { + event.metaKey = ctrlKey; } return event; @@ -1089,13 +1093,14 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl if ( rmouseEvent.test( name ) ) { jQuery.event.propHooks[ name ] = function( event, original ) { - var eventDocument, doc, body; + var eventDoc, doc, body, + button = event.button; // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && event.clientX != null ) { - eventDocument = event.target.ownerDocument || document; - doc = eventDocument.documentElement; - body = eventDocument.body; + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); @@ -1103,8 +1108,8 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl // Add which for click: 1 === left; 2 === middle; 3 === right // Note: button is not normalized, so don't use it - if ( !event.which && event.button !== undefined ) { - event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + if ( !event.which && button !== undefined ) { + event.which = (button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) )); } return event; }; From 14cb04dc947cd9d01c97ec0c4dc487f2cd53c3ac Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 11:30:03 -0400 Subject: [PATCH 13/18] Shortcircuit fix if possible --- src/event.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/event.js b/src/event.js index 38ed3ae91..fec3e6d49 100644 --- a/src/event.js +++ b/src/event.js @@ -492,6 +492,11 @@ jQuery.event = { event = jQuery.Event( originalEvent ); propHook = jQuery.event.propHooks[ event.type ]; + // Determine if an early return is possible + if ( !propHook && ( event.target && event.target.nodeType !== 3 ) && event.relatedTarget ) { + return event; + } + for ( var i = this.props.length, prop; i; ) { prop = this.props[ --i ]; event[ prop ] = originalEvent[ prop ]; From c7f3b6109eabd32ade5085ef81ec2208964122cd Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 19:05:06 -0400 Subject: [PATCH 14/18] Removes early return loop, must copy properties --- src/event.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/event.js b/src/event.js index fec3e6d49..38ed3ae91 100644 --- a/src/event.js +++ b/src/event.js @@ -492,11 +492,6 @@ jQuery.event = { event = jQuery.Event( originalEvent ); propHook = jQuery.event.propHooks[ event.type ]; - // Determine if an early return is possible - if ( !propHook && ( event.target && event.target.nodeType !== 3 ) && event.relatedTarget ) { - return event; - } - for ( var i = this.props.length, prop; i; ) { prop = this.props[ --i ]; event[ prop ] = originalEvent[ prop ]; From c80ad2524ea510fd68d4e33ed4a89752f795828b Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Thu, 22 Sep 2011 20:02:34 -0400 Subject: [PATCH 15/18] current state --- src/event.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/event.js b/src/event.js index 38ed3ae91..edc8ce5e5 100644 --- a/src/event.js +++ b/src/event.js @@ -475,7 +475,7 @@ jQuery.event = { return event.result; }, - props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + props: "altKey attrName bubbles button cancelable charCode ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view which".split(" "), propHooks: {}, @@ -487,13 +487,17 @@ jQuery.event = { // store a copy of the original event object // and "clone" to set read-only properties var originalEvent = event, - propHook; + propHook = jQuery.event.propHooks[ event.type ], + copy = this.props; event = jQuery.Event( originalEvent ); - propHook = jQuery.event.propHooks[ event.type ]; - for ( var i = this.props.length, prop; i; ) { - prop = this.props[ --i ]; + if ( propHook ) { + copy.push.apply( copy, propHook() || [] ); + } + + for ( var i = copy.length, prop; i; ) { + prop = copy[ --i ]; event[ prop ] = originalEvent[ prop ]; } @@ -1093,6 +1097,10 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl if ( rmouseEvent.test( name ) ) { jQuery.event.propHooks[ name ] = function( event, original ) { + if ( !event ) { + return "layerX layerY clientX clientY offsetX offsetY wheelDelta".split(" "); + } + var eventDoc, doc, body, button = event.button; @@ -1111,6 +1119,7 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl if ( !event.which && button !== undefined ) { event.which = (button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) )); } + return event; }; }}); From 87e1c62d8e5a916587217196ee7d1755bc9115ca Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Fri, 23 Sep 2011 15:45:19 -0400 Subject: [PATCH 16/18] Moves mouse properties to mouseProps --- src/event.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event.js b/src/event.js index edc8ce5e5..1ac5a2931 100644 --- a/src/event.js +++ b/src/event.js @@ -10,6 +10,7 @@ var rnamespaces = /\.(.*)$/, rmouseEvent = /^(?:mouse|contextmenu)|click/, rkeyEvent = /^(?:key)/, rquickIs = /^([\w\-]+)?(?:#([\w\-]+))?(?:\.([\w\-]+))?(?:\[([\w+\-]+)=["']?([\w\-]*)["']?\])?(?::(first-child|last-child|empty))?$/, + mouseProps = "layerX layerY clientX clientY offsetX offsetY wheelDelta".split(" "), quickPseudoMap = { "empty": "firstChild", "first-child": "previousSibling", @@ -1098,7 +1099,7 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl jQuery.event.propHooks[ name ] = function( event, original ) { if ( !event ) { - return "layerX layerY clientX clientY offsetX offsetY wheelDelta".split(" "); + return mouseProps; } var eventDoc, doc, body, From b4120a74306e1d98e212c77e5a89c1526a51fc3b Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Sun, 25 Sep 2011 19:56:34 -0400 Subject: [PATCH 17/18] propHooks now an object with `props` array and `filter` function. Use the originalEvent to grab properties in filter functions since they often won't have been copied to event. Mark a few current props in the main jQuery.event.props list as deprecated, they aren't supported across all browsers. --- src/event.js | 140 ++++++++++++++++++++------------------------- test/unit/event.js | 100 +++++++++++--------------------- 2 files changed, 95 insertions(+), 145 deletions(-) diff --git a/src/event.js b/src/event.js index 699b9381e..00b332c5b 100644 --- a/src/event.js +++ b/src/event.js @@ -7,6 +7,8 @@ var rnamespaces = /\.(.*)$/, rescape = /[^\w\s.|`]/g, rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, rhoverHack = /\bhover(\.\S+)?/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, rquickIs = /^([\w\-]+)?(?:#([\w\-]+))?(?:\.([\w\-]+))?(?:\[([\w+\-]+)=["']?([\w\-]*)["']?\])?$/, quickParse = function( selector ) { var quick = rquickIs.exec( selector ); @@ -407,12 +409,6 @@ jQuery.event = { // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( event || window.event ); - var propHook = jQuery.event.propHooks[ event.type ]; - - if ( propHook ) { - event = propHook( event ); - } - var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []), delegateCount = handlers.delegateCount, args = Array.prototype.slice.call( arguments, 0 ), @@ -469,55 +465,89 @@ jQuery.event = { return event.result; }, - props: "altKey attrName bubbles button cancelable charCode ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view which".split(" "), + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp type view which".split(" "), propHooks: {}, + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement layerX layerY offsetX offsetY pageX pageY screenX screenY toElement wheelDelta".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = original.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && original.fromElement ) { + event.relatedTarget = original.fromElement === event.target ? original.toElement : original.fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = (button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) )); + } + + return event; + } + }, + fix: function( event ) { if ( event[ jQuery.expando ] ) { return event; } - // store a copy of the original event object - // and "clone" to set read-only properties + // Create a writable copy of the event object and normalize some properties var originalEvent = event, - propHook = jQuery.event.propHooks[ event.type ], - copy = this.props; + propHook = jQuery.event.propHooks[ event.type ] || {}, + copy = propHook.props? this.props.concat( propHook.props ) : this.props; event = jQuery.Event( originalEvent ); - if ( propHook ) { - copy.push.apply( copy, propHook() || [] ); - } - for ( var i = copy.length, prop; i; ) { prop = copy[ --i ]; event[ prop ] = originalEvent[ prop ]; } - // Fix target property, if necessary - // Removal will crash IE6,7,8 + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) if ( !event.target ) { - // Fixes #1925 where srcElement might not be defined either - event.target = event.srcElement || document; + event.target = originalEvent.srcElement || document; } - // check if target is a textnode (safari) - // Removal will crash IE6,7,8 + // Target should not be a text node (#504, Safari) if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } - // Add relatedTarget, if necessary - if ( !event.relatedTarget && event.fromElement ) { - event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; } - if ( propHook ) { - event = propHook( event, originalEvent ); - } - - return event; + return propHook.filter? propHook.filter( event, originalEvent ) : event; }, // Deprecated, use jQuery.guid instead @@ -1026,58 +1056,14 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl jQuery.attrFn[ name ] = true; } - // Key Event property hooks if ( rkeyEvent.test( name ) ) { - jQuery.event.propHooks[ name ] = function( event, original ) { - - var charCode = event.charCode, - keyCode = event.keyCode, - ctrlKey = event.ctrlKey; - - // Add which for key events - if ( event.which == null && (charCode != null || keyCode != null) ) { - event.which = charCode != null ? charCode : keyCode; - } - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && ctrlKey ) { - event.metaKey = ctrlKey; - } - - return event; - }; + jQuery.event.propHooks[ name ] = jQuery.event.keyHooks; } - // Mouse Event property hooks if ( rmouseEvent.test( name ) ) { - jQuery.event.propHooks[ name ] = function( event, original ) { - - if ( !event ) { - return mouseProps; - } - - var eventDoc, doc, body, - button = event.button; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); - event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = (button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) )); - } - - return event; - }; - }}); + jQuery.event.propHooks[ name ] = jQuery.event.mouseHooks; + } +}); })( jQuery ); diff --git a/test/unit/event.js b/test/unit/event.js index 103b7f00a..319569ab9 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -2383,6 +2383,38 @@ test("delegated events quickIs", function() { markup.remove(); }); +test("propHooks extensions", function() { + expect( 3 ); + + jQuery( "" ).appendTo( "#qunit-fixture" ); + + var $fixture = jQuery( "#hook-fixture" ); + + // Ensure the property doesn't exist + $fixture.bind( "focus", function( event ) { + ok( !("blurrinessLevel" in event), "event.blurrinessLevel does not exist" ); + })[0].focus(); + + // Must blur the link so focus works below + $fixture.unbind( "focus" )[0].blur(); + + // Define a custom property for "focus" events via the filter function + ok( !jQuery.event.propHooks.focus, "We aren't clobbering an existing focus hook" ); + jQuery.event.propHooks.focus = { + filter: function( event, originalEvent ) { + event.blurrinessLevel = 42; + return event; + } + }; + + // Trigger a native focus and ensure the property is set + $fixture.bind( "focus", function( event ) { + equals( event.blurrinessLevel, 42, "event.blurrinessLevel was set" ); + })[0].focus(); + + delete jQuery.event.propHooks.focus; + $fixture.unbind( "focus" ).remove(); +}); (function(){ // This code must be run before DOM ready! @@ -2457,72 +2489,4 @@ test("delegated events quickIs", function() { })(); -test("jQuery.event.propHooks", function() { - expect( 1 ); - ok( jQuery.event.propHooks, "jQuery.event.propHooks exists" ); -}); - -test("jQuery.event.propHooks as function", function() { - - expect( 2 ); - - jQuery( "
" ).appendTo( "#qunit-fixture" ); - - var $fixture = jQuery( "#hook-fixture" ); - - // Does not exist - $fixture.bind( "click", function( event ) { - ok( !("propC" in event), "event.propC Does not exist" ); - }).trigger( "click" ); - - $fixture.unbind( "click" ); - - // Store as function - jQuery.event.propHooks[ "custom" ] = function( event ) { - // receives the event object for processing - event.propC = true; - return event; - }; - - $fixture.bind( "custom", function( event ) { - ok( event.propC, "event.propC exists" ); - }).trigger( "custom" ); -}); - -test("jQuery.event.propHooks usecase", function() { - - expect( 3 ); - - jQuery( "
" ).appendTo( "#qunit-fixture" ); - - var $fixture = jQuery( "#hook-fixture" ); - - $fixture.bind( "fakedrop", function( event ) { - ok( !("dataTransfer" in event), "event.dataTransfer is not available" ); - }).trigger( "fakedrop" ); - - $fixture.unbind( "fakedrop" ); - - jQuery.event.propHooks[ "fakedrop" ] = function( event ) { - event.dataTransfer = "some val"; - return event; - }; - - $fixture.bind( "fakedrop", function( event ) { - ok( ("dataTransfer" in event), "event.dataTransfer exists, just copied" ); - equal( event.dataTransfer, "some val", "event.dataTransfer equal 'some val'" ); - }).trigger( "fakedrop" ); - - $fixture.unbind( "fakedrop" ); -}); - -/* -test("event properties", function() { - stop(); - jQuery("#simon1").click(function(event) { - ok( event.timeStamp, "assert event.timeStamp is present" ); - start(); - }).click(); -}); -*/ From c7838c3607239b0231128b270762609703ae3893 Mon Sep 17 00:00:00 2001 From: Dave Methvin Date: Sun, 25 Sep 2011 22:04:52 -0400 Subject: [PATCH 18/18] Minor cleanups to code. Futile effort to get IE to pass the unit test. --- src/event.js | 9 +++++---- test/unit/event.js | 5 ++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/event.js b/src/event.js index 00b332c5b..9ad8d69b7 100644 --- a/src/event.js +++ b/src/event.js @@ -488,7 +488,8 @@ jQuery.event = { props: "button buttons clientX clientY fromElement layerX layerY offsetX offsetY pageX pageY screenX screenY toElement wheelDelta".split(" "), filter: function( event, original ) { var eventDoc, doc, body, - button = original.button; + button = original.button, + fromElement = original.fromElement; // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && original.clientX != null ) { @@ -501,8 +502,8 @@ jQuery.event = { } // Add relatedTarget, if necessary - if ( !event.relatedTarget && original.fromElement ) { - event.relatedTarget = original.fromElement === event.target ? original.toElement : original.fromElement; + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; } // Add which for click: 1 === left; 2 === middle; 3 === right @@ -523,7 +524,7 @@ jQuery.event = { // Create a writable copy of the event object and normalize some properties var originalEvent = event, propHook = jQuery.event.propHooks[ event.type ] || {}, - copy = propHook.props? this.props.concat( propHook.props ) : this.props; + copy = propHook.props ? this.props.concat( propHook.props ) : this.props; event = jQuery.Event( originalEvent ); diff --git a/test/unit/event.js b/test/unit/event.js index 319569ab9..1ca3e7d78 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -2386,9 +2386,8 @@ test("delegated events quickIs", function() { test("propHooks extensions", function() { expect( 3 ); - jQuery( "" ).appendTo( "#qunit-fixture" ); - - var $fixture = jQuery( "#hook-fixture" ); + // IE requires focusable elements to be visible, so append to body + var $fixture = jQuery( "" ).appendTo( "body" ); // Ensure the property doesn't exist $fixture.bind( "focus", function( event ) {