From 9d76c0b163675505d1a901e5fe5249a2c55609bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82=C4=99biowski-Owczarek?= Date: Mon, 2 Mar 2020 23:02:42 +0100 Subject: [PATCH] Data:Event:Manipulation: Prevent collisions with Object.prototype Make sure events & data keys matching Object.prototype properties work. A separate fix for such events on cloned elements was added as well. Fixes gh-3256 Closes gh-4603 --- src/data/Data.js | 2 +- src/event.js | 6 ++++-- src/event/trigger.js | 4 +++- src/manipulation.js | 8 +++----- test/unit/data.js | 15 +++++++++++++++ test/unit/event.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/data/Data.js b/src/data/Data.js index 556f4e39c..a5eb09089 100644 --- a/src/data/Data.js +++ b/src/data/Data.js @@ -18,7 +18,7 @@ Data.prototype = { // If not, create one if ( !value ) { - value = {}; + value = Object.create( null ); // We can accept data for non-element nodes in modern browsers, // but we should not, see #8335. diff --git a/src/event.js b/src/event.js index f6dd709dc..c7812508b 100644 --- a/src/event.js +++ b/src/event.js @@ -133,7 +133,7 @@ jQuery.event = { // Init the element's event structure and main handler, if this is the first if ( !( events = elemData.events ) ) { - events = elemData.events = {}; + events = elemData.events = Object.create( null ); } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { @@ -294,7 +294,9 @@ jQuery.event = { // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( nativeEvent ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event diff --git a/src/event/trigger.js b/src/event/trigger.js index aba4c7edd..0d1cd4c56 100644 --- a/src/event/trigger.js +++ b/src/event/trigger.js @@ -99,7 +99,9 @@ jQuery.extend( jQuery.event, { special.bindType || type; // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && dataPriv.get( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); diff --git a/src/manipulation.js b/src/manipulation.js index 08b17a5bb..9ad36a20a 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -60,7 +60,7 @@ function restoreScript( elem ) { } function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + var i, l, type, pdataOld, udataOld, udataCur, events; if ( dest.nodeType !== 1 ) { return; @@ -68,13 +68,11 @@ function cloneCopyEvent( src, dest ) { // 1. Copy private data: events, handlers, etc. if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); + pdataOld = dataPriv.get( src ); events = pdataOld.events; if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; + dataPriv.remove( dest, "handle events" ); for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { diff --git a/test/unit/data.js b/test/unit/data.js index 154ea5eeb..cba519e9e 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -998,3 +998,18 @@ QUnit.test( ".data(prop) does not create expando", function( assert ) { } } } ); + +QUnit.test( "keys matching Object.prototype properties (gh-3256)", function( assert ) { + assert.expect( 2 ); + + var div = jQuery( "
" ); + + assert.strictEqual( div.data( "hasOwnProperty" ), undefined, + "hasOwnProperty not matched (before forced data creation)" ); + + // Force the creation of a data object for this element. + div.data( { foo: "bar" } ); + + assert.strictEqual( div.data( "hasOwnProperty" ), undefined, + "hasOwnProperty not matched (after forced data creation)" ); +} ); diff --git a/test/unit/event.js b/test/unit/event.js index cecad3f34..938c66876 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -1796,6 +1796,49 @@ QUnit.test( "jQuery.off using dispatched jQuery.Event", function( assert ) { .remove(); } ); +QUnit.test( "events with type matching an Object.prototype property (gh-3256)", function( assert ) { + assert.expect( 1 ); + + var elem = jQuery( "
" ), + eventFired = false; + + elem.appendTo( "#qunit-fixture" ); + + try { + elem + .one( "hasOwnProperty", function() { + eventFired = true; + } ) + .trigger( "hasOwnProperty" ); + } finally { + assert.strictEqual( eventFired, true, "trigger fired without crashing" ); + } +} ); + +QUnit.test( "events with type matching an Object.prototype property, cloned element (gh-3256)", + function( assert ) { + assert.expect( 1 ); + + var elem = jQuery( "
" ), + eventFired = false; + + elem.appendTo( "#qunit-fixture" ); + + try { + // Make sure the original element has some event data. + elem.on( "click", function() {} ); + + elem + .clone( true ) + .one( "hasOwnProperty", function() { + eventFired = true; + } ) + .trigger( "hasOwnProperty" ); + } finally { + assert.strictEqual( eventFired, true, "trigger fired without crashing" ); + } +} ); + // selector-native does not support scope-fixing in delegation QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "delegated event with delegateTarget-relative selector", function( assert ) { assert.expect( 3 );