From 5c2d08704e289dd2745bcb0557b35a9c0e6af4a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82=C4=99biowski-Owczarek?= Date: Mon, 19 Oct 2020 21:17:51 +0200 Subject: [PATCH] Event: Don't crash if an element is removed on blur In Chrome, if an element having a `focusout` handler is blurred by clicking outside of it, it invokes the handler synchronously. If that handler calls `.remove()` on the element, the data is cleared, leaving private data undefined. We're reading a property from that data so we need to guard against this. Fixes gh-4417 Closes gh-4799 --- src/event.js | 8 +++++++- test/unit/event.js | 27 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/event.js b/src/event.js index b8c5e96fe..4418fbbbf 100644 --- a/src/event.js +++ b/src/event.js @@ -558,7 +558,13 @@ function leverageNative( el, type, expectSync ) { // Cancel the outer synthetic event event.stopImmediatePropagation(); event.preventDefault(); - return result.value; + + // Support: Chrome 86+ + // In Chrome, if an element having a focusout handler is blurred by + // clicking outside of it, it invokes the handler synchronously. If + // that handler calls `.remove()` on the element, the data is cleared, + // leaving `result` undefined. We need to guard against this. + return result && result.value; } // If this is an inner synthetic event for an event with a bubbling surrogate diff --git a/test/unit/event.js b/test/unit/event.js index 771283c2f..90318f8fe 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -2630,6 +2630,33 @@ QUnit.test( "focusin on document & window", function( assert ) { jQuery( document ).off( "focusout", increment ); } ); +QUnit.test( "element removed during focusout (gh-4417)", function( assert ) { + assert.expect( 1 ); + + var button = jQuery( "" ); + + button.appendTo( "#qunit-fixture" ); + + button.on( "click", function() { + button.trigger( "blur" ); + assert.ok( true, "Removing the element didn't crash" ); + } ); + + // Support: Chrome 86+ + // In Chrome, if an element having a focusout handler is blurred by + // clicking outside of it, it invokes the handler synchronously. However, + // if the click happens programmatically, the invocation is asynchronous. + // As we have no way to simulate real user input in unit tests, simulate + // this behavior by calling `jQuery.cleanData` & removing the element using + // native APIs. + button[ 0 ].blur = function() { + jQuery.cleanData( [ this ] ); + this.parentNode.removeChild( this ); + }; + + button[ 0 ].click(); +} ); + testIframe( "jQuery.ready promise", "event/promiseReady.html",