From c8c6ab6924b48832bfc2b94d40887b2f1b6c891e Mon Sep 17 00:00:00 2001 From: Oleg Date: Mon, 17 Dec 2012 18:17:39 +0400 Subject: [PATCH] Fix #12569. Improve feature detect for event bubbling. Close gh-1076. --- src/support.js | 112 ++++++++++++++------------------------- test/csp.php | 5 +- test/unit/event.js | 28 ++++++++++ test/unit/support.js | 121 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 180 insertions(+), 86 deletions(-) diff --git a/src/support.js b/src/support.js index 70f5f114d..c99bb3922 100644 --- a/src/support.js +++ b/src/support.js @@ -1,16 +1,6 @@ jQuery.support = (function() { - var support, - all, - a, - select, - opt, - input, - fragment, - eventName, - i, - isSupported, - clickFn, + var support, all, a, select, opt, input, fragment, eventName, isSupported, i, div = document.createElement("div"); // Setup @@ -80,9 +70,6 @@ jQuery.support = (function() { boxModel: document.compatMode === "CSS1Compat", // Will be defined later - submitBubbles: true, - changeBubbles: true, - focusinBubbles: false, deleteExpando: true, noCloneEvent: true, inlineBlockNeedsLayout: false, @@ -101,24 +88,13 @@ jQuery.support = (function() { select.disabled = true; support.optDisabled = !opt.disabled; - // Test to see if it's possible to delete an expando from an element - // Fails in Internet Explorer + // Support: IE<9 try { delete div.test; } catch( e ) { support.deleteExpando = false; } - if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { - div.attachEvent( "onclick", clickFn = function() { - // Cloning a node shouldn't copy over any - // bound event handlers (IE does this) - support.noCloneEvent = false; - }); - div.cloneNode( true ).fireEvent("onclick"); - div.detachEvent( "onclick", clickFn ); - } - // Check if we can trust getAttribute("value") input = document.createElement("input"); input.setAttribute( "value", "" ); @@ -130,49 +106,42 @@ jQuery.support = (function() { support.radioValue = input.value === "t"; // #11217 - WebKit loses check when the name is after the checked attribute - input.setAttribute( "checked", "checked" ); + input.setAttribute( "checked", "t" ); input.setAttribute( "name", "t" ); - div.appendChild( input ); fragment = document.createDocumentFragment(); - fragment.appendChild( div.lastChild ); - - // WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + fragment.appendChild( input ); // Check if a disconnected checkbox will retain its checked // value of true after appended to the DOM (IE6/7) support.appendChecked = input.checked; - fragment.removeChild( input ); - fragment.appendChild( div ); + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; - // Technique from Juriy Zaytsev - // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ - // We only care about the case where non-standard event systems - // are used, namely in IE. Short-circuiting here helps us to - // avoid an eval call (in setAttribute) which can cause CSP - // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() if ( div.attachEvent ) { - for ( i in { - submit: true, - change: true, - focusin: true - }) { - eventName = "on" + i; - isSupported = ( eventName in div ); - if ( !isSupported ) { - div.setAttribute( eventName, "return;" ); - isSupported = ( typeof div[ eventName ] === "function" ); - } - support[ i + "Bubbles" ] = isSupported; - } + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; } // Run tests that need a body at doc ready jQuery(function() { - var container, div, tds, marginDiv, - divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", body = document.getElementsByTagName("body")[0]; if ( !body ) { @@ -181,20 +150,17 @@ jQuery.support = (function() { } container = document.createElement("div"); - container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px"; - body.insertBefore( container, body.firstChild ); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; - // Construct the test element - div = document.createElement("div"); - container.appendChild( div ); + body.appendChild( container ).appendChild( div ); + // Support: IE8 // Check if table cells still have offsetWidth/Height when they are set // to display:none and there are still other visible table cells in a // table row; if so, offsetWidth/Height are not reliable for use when // determining if an element has been hidden directly using // display:none (it is still safe to use offsets if a parent element is // hidden; don safety goggles and see bug #4512 for more information). - // (only IE 8 fails this test) div.innerHTML = "
t
"; tds = div.getElementsByTagName("td"); tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; @@ -203,8 +169,8 @@ jQuery.support = (function() { tds[ 0 ].style.display = ""; tds[ 1 ].style.display = "none"; + // Support: IE8 // Check if empty table cells still have offsetWidth/Height - // (IE <= 8 fail this test) support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); // Check box-sizing and margin behavior @@ -213,39 +179,36 @@ jQuery.support = (function() { support.boxSizing = ( div.offsetWidth === 4 ); support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); - // NOTE: To any future maintainer, we've window.getComputedStyle - // because jsdom on node.js will break without it. + // Use window.getComputedStyle because jsdom on node.js will break without it. if ( window.getComputedStyle ) { support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. For more - // info see bug #3333 + // gets computed margin-right based on width of container. (#3333) // Fails in WebKit before Feb 2011 nightlies // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - marginDiv = document.createElement("div"); + marginDiv = div.appendChild( document.createElement("div") ); marginDiv.style.cssText = div.style.cssText = divReset; marginDiv.style.marginRight = marginDiv.style.width = "0"; div.style.width = "1px"; - div.appendChild( marginDiv ); + support.reliableMarginRight = !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); } if ( typeof div.style.zoom !== "undefined" ) { + // Support: IE<8 // Check if natively block-level elements act like inline-block // elements when setting their display to 'inline' and giving // them layout - // (IE < 8 does this) div.innerHTML = ""; div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + // Support: IE6 // Check if elements with layout shrink-wrap their children - // (IE 6 does this) div.style.display = "block"; - div.style.overflow = "visible"; div.innerHTML = "
"; div.firstChild.style.width = "5px"; support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); @@ -255,14 +218,15 @@ jQuery.support = (function() { body.style.zoom = 1; } - // Null elements to avoid leaks in IE body.removeChild( container ); + + // Null elements to avoid leaks in IE container = div = tds = marginDiv = null; }); // Null elements to avoid leaks in IE - fragment.removeChild( div ); - all = a = select = opt = input = fragment = div = null; + all = select = fragment = opt = a = input = null; return support; })(); + diff --git a/test/csp.php b/test/csp.php index e3245149c..9ab18f392 100644 --- a/test/csp.php +++ b/test/csp.php @@ -1,4 +1,7 @@ - + diff --git a/test/unit/event.js b/test/unit/event.js index 32355c9ad..ff2ea1dd5 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -2601,3 +2601,31 @@ test( "make sure events cloned correctly", 18, function() { clone.find("p:first").click(); // 0 should be fired clone.find("#check1").change(); // 0 events should fire }); + +test( "Check order of focusin/focusout events", 2, function() { + var focus, blur, + input = jQuery("#name"); + + input.on("focus", function() { + focus = true; + + }).on("focusin", function() { + ok( !focus, "Focusin event should fire before focus does" ); + + }).on("blur", function() { + blur = true; + + }).on("focusout", function() { + ok( !blur, "Focusout event should fire before blur does" ); + }); + + // gain focus + input.focus(); + + // then lose it + jQuery("#search").focus(); + + // cleanup + input.off(); +}); + diff --git a/test/unit/support.js b/test/unit/support.js index 8d4ac21bc..94a871ee9 100644 --- a/test/unit/support.js +++ b/test/unit/support.js @@ -7,7 +7,7 @@ test("boxModel", function() { }); if ( jQuery.css ) { - testIframeWithCallback( "body background is not lost if set prior to loading jQuery (#9238)", "support/bodyBackground.html", function( color, support ) { + testIframeWithCallback( "body background is not lost if set prior to loading jQuery (#9239)", "support/bodyBackground.html", function( color, support ) { expect( 2 ); var i, passed = true, @@ -29,6 +29,7 @@ if ( jQuery.css ) { strictEqual( jQuery.support[ i ], support[ i ], "Unexpected property: " + i ); } } + ok( passed, "Same support properties" ); }); } @@ -44,13 +45,12 @@ testIframeWithCallback( "box-sizing does not affect jQuery.support.shrinkWrapBlo }); (function() { - - var userAgent = window.navigator.userAgent, - expected; + var expected, + userAgent = window.navigator.userAgent; // These tests do not have to stay // They are here to help with upcoming support changes for 1.8 - if ( /chrome\/19\.0/i.test(userAgent) ) { + if ( /chrome/i.test( userAgent ) ) { expected = { "leadingWhitespace":true, "tbody":true, @@ -83,7 +83,106 @@ testIframeWithCallback( "box-sizing does not affect jQuery.support.shrinkWrapBlo "cors":true, "doesNotIncludeMarginInBodyOffset":true }; - } else if ( /msie 8\.0/i.test(userAgent) ) { + } else if ( /opera/i.test( userAgent ) ) { + expected = { + "leadingWhitespace":true, + "tbody":true, + "htmlSerialize":true, + "style":true, + "hrefNormalized":true, + "opacity":true, + "cssFloat":true, + "checkOn":true, + "optSelected":true, + "getSetAttribute":true, + "enctype":true, + "html5Clone":true, + "submitBubbles":true, + "changeBubbles":true, + "focusinBubbles":false, + "deleteExpando":true, + "noCloneEvent":true, + "inlineBlockNeedsLayout":false, + "shrinkWrapBlocks":false, + "reliableMarginRight":true, + "noCloneChecked":true, + "optDisabled":true, + "radioValue":false, + "checkClone":true, + "appendChecked":true, + "boxModel":true, + "reliableHiddenOffsets":true, + "ajax":true, + "cors":true, + "doesNotIncludeMarginInBodyOffset":true + }; + } else if ( /msie 10\.0/i.test( userAgent ) ) { + expected = { + "leadingWhitespace":true, + "tbody":true, + "htmlSerialize":true, + "style":true, + "hrefNormalized":true, + "opacity":true, + "cssFloat":true, + "checkOn":true, + "optSelected":false, + "getSetAttribute":true, + "enctype":true, + "html5Clone":true, + "submitBubbles":true, + "changeBubbles":true, + "focusinBubbles":true, + "deleteExpando":true, + "noCloneEvent":true, + "inlineBlockNeedsLayout":false, + "shrinkWrapBlocks":false, + "reliableMarginRight":true, + "noCloneChecked":false, + "optDisabled":true, + "radioValue":false, + "checkClone":true, + "appendChecked":true, + "boxModel":true, + "reliableHiddenOffsets":true, + "ajax":true, + "cors":true, + "doesNotIncludeMarginInBodyOffset":true + }; + } else if ( /msie 9\.0/i.test( userAgent ) ) { + expected = { + "leadingWhitespace":true, + "tbody":true, + "htmlSerialize":true, + "style":true, + "hrefNormalized":true, + "opacity":true, + "cssFloat":true, + "checkOn":true, + "optSelected":false, + "getSetAttribute":true, + "enctype":true, + "html5Clone":true, + "submitBubbles":true, + "changeBubbles":true, + "focusinBubbles":true, + "deleteExpando":true, + "noCloneEvent":true, + "inlineBlockNeedsLayout":false, + "shrinkWrapBlocks":false, + "reliableMarginRight":true, + "noCloneChecked":false, + "optDisabled":true, + "radioValue":false, + "checkClone":true, + "appendChecked":true, + "boxModel":true, + "reliableHiddenOffsets":true, + "ajax":true, + "cors":false, + "doesNotIncludeMarginInBodyOffset":true + }; + } else if ( /msie 8\.0/i.test( userAgent ) ) { expected = { "leadingWhitespace":false, "tbody":true, @@ -116,7 +215,7 @@ testIframeWithCallback( "box-sizing does not affect jQuery.support.shrinkWrapBlo "cors":false, "doesNotIncludeMarginInBodyOffset":true }; - } else if ( /msie 7\.0/i.test(userAgent) ) { + } else if ( /msie 7\.0/i.test( userAgent ) ) { expected = { "ajax": true, "appendChecked": false, @@ -149,7 +248,7 @@ testIframeWithCallback( "box-sizing does not affect jQuery.support.shrinkWrapBlo "tbody": false, "style": false }; - } else if ( /msie 6\.0/i.test(userAgent) ) { + } else if ( /msie 6\.0/i.test( userAgent ) ) { expected = { "leadingWhitespace":false, "tbody":false, @@ -182,7 +281,7 @@ testIframeWithCallback( "box-sizing does not affect jQuery.support.shrinkWrapBlo "cors":false, "doesNotIncludeMarginInBodyOffset":true }; - } else if ( /5\.1\.1 safari/i.test(userAgent) ) { + } else if ( /5\.1\.1 safari/i.test( userAgent ) ) { expected = { "leadingWhitespace":true, "tbody":true, @@ -215,7 +314,7 @@ testIframeWithCallback( "box-sizing does not affect jQuery.support.shrinkWrapBlo "cors":true, "doesNotIncludeMarginInBodyOffset":true }; - } else if ( /firefox\/3\.6/i.test(userAgent) ) { + } else if ( /firefox/i.test( userAgent ) ) { expected = { "leadingWhitespace":true, "tbody":true, @@ -227,7 +326,7 @@ testIframeWithCallback( "box-sizing does not affect jQuery.support.shrinkWrapBlo "checkOn":true, "optSelected":true, "getSetAttribute":true, - "enctype":false, + "enctype":true, "html5Clone":true, "submitBubbles":true, "changeBubbles":true, -- 2.39.5