Skip to content

Commit 7a951ef

Browse files
committed
Widget: Optimize attachment of the _untrackClassesElement listener
jQuery UI 1.13.0 changed the logic attaching the `_untrackClassesElement` listener in the `_classes` widget method; one of the side effects was calling `this._on` for each node that needed the listener. That caused a severe performance degradation for large comboboxes as each `_on` jQuery UI call causes a jQuery `add` call that calls Sizzle's `uniqueSort` underneath. Instead, collect the nodes that need the listener and then, outside of the loop, create a jQuery object out of them and attach the listener once. That's still slower than the jQuery 1.12 version but only slightly: 936 ms to 1.03s on a very large list on a recent MacBook Pro, compared to ~30 seconds before this patch. Fixes gh-2014
1 parent 933ce5d commit 7a951ef

File tree

4 files changed

+112
-3
lines changed

4 files changed

+112
-3
lines changed

tests/unit/autocomplete/core.js

+23
Original file line numberDiff line numberDiff line change
@@ -443,4 +443,27 @@ QUnit.test( "Close on click outside when focus remains", function( assert ) {
443443
} );
444444
} );
445445

446+
QUnit.test( "extra listeners created during typing (trac-15082, trac-15095)", function( assert ) {
447+
assert.expect( 2 );
448+
449+
var origRemoveListenersCount;
450+
var element = $( "#autocomplete" ).autocomplete( {
451+
source: [ "java", "javascript" ],
452+
delay: 0
453+
} );
454+
455+
element.val( "j" ).autocomplete( "search", "j" );
456+
origRemoveListenersCount = jQuery._data( element[ 0 ], "events" ).remove.length;
457+
458+
element.val( "ja" ).autocomplete( "search", "ja" );
459+
assert.equal( jQuery._data( element[ 0 ], "events" ).remove.length,
460+
origRemoveListenersCount,
461+
"No extra listeners after typing multiple letters" );
462+
463+
element.val( "jav" ).autocomplete( "search", "jav" );
464+
assert.equal( jQuery._data( element[ 0 ], "events" ).remove.length,
465+
origRemoveListenersCount,
466+
"No extra listeners after typing multiple letters" );
467+
} );
468+
446469
} );

tests/unit/selectmenu/core.js

+30
Original file line numberDiff line numberDiff line change
@@ -404,4 +404,34 @@ QUnit.test( "Options with hidden attribute should not be rendered", function( as
404404
} );
405405
} );
406406

407+
QUnit.test( "extra listeners created after selection (trac-15078, trac-15152)", function( assert ) {
408+
assert.expect( 3 );
409+
410+
var origRemoveListenersCount;
411+
var element = $( "#speed" ).selectmenu();
412+
var menu = element.selectmenu( "widget" );
413+
414+
element.val( "Slow" );
415+
element.selectmenu( "refresh" );
416+
origRemoveListenersCount = jQuery._data( menu[ 0 ], "events" ).remove.length;
417+
418+
element.val( "Fast" );
419+
element.selectmenu( "refresh" );
420+
assert.equal( jQuery._data( menu[ 0 ], "events" ).remove.length,
421+
origRemoveListenersCount,
422+
"No extra listeners after typing multiple letters" );
423+
424+
element.val( "Faster" );
425+
element.selectmenu( "refresh" );
426+
assert.equal( jQuery._data( menu[ 0 ], "events" ).remove.length,
427+
origRemoveListenersCount,
428+
"No extra listeners after typing multiple letters" );
429+
430+
element.val( "Slow" );
431+
element.selectmenu( "refresh" );
432+
assert.equal( jQuery._data( menu[ 0 ], "events" ).remove.length,
433+
origRemoveListenersCount,
434+
"No extra listeners after typing multiple letters" );
435+
} );
436+
407437
} );

tests/unit/tabs/core.js

+52
Original file line numberDiff line numberDiff line change
@@ -675,4 +675,56 @@ QUnit.test( "#4033 - IE expands hash to full url and misinterprets tab as ajax",
675675
state( assert, element, 1 );
676676
} );
677677

678+
679+
QUnit.test( "extra listeners created when tabs are added/removed (trac-15136)", function( assert ) {
680+
assert.expect( 3 );
681+
682+
var origRemoveListenersCount;
683+
var element = $( "#tabs1" ).tabs();
684+
var tabCounter = 10;
685+
686+
function addTab() {
687+
var label = "Tab " + tabCounter;
688+
var id = "tabs-" + tabCounter;
689+
var li = $(
690+
"<li>" +
691+
" <a href='#" + id + "'>" + label + "</a> " +
692+
" <span class='ui-icon ui-icon-close' role='presentation'>Remove Tab</span>" +
693+
"</li>"
694+
);
695+
var tabContentHtml = "Tab " + tabCounter + " content.";
696+
697+
element.find( ".ui-tabs-nav" ).append( li );
698+
element.append( "<div id='" + id + "'><p>" + tabContentHtml + "</p></div>" );
699+
element.tabs( "refresh" );
700+
tabCounter++;
701+
}
702+
703+
function removeLastTab() {
704+
element.find( ".ui-icon-close" ).last().trigger( "click" );
705+
}
706+
707+
origRemoveListenersCount = jQuery._data( element[ 0 ], "events" ).remove.length;
708+
709+
addTab();
710+
assert.equal( jQuery._data( element[ 0 ], "events" ).remove.length,
711+
origRemoveListenersCount,
712+
"No extra listeners after adding a new tab" );
713+
714+
addTab();
715+
addTab();
716+
addTab();
717+
assert.equal( jQuery._data( element[ 0 ], "events" ).remove.length,
718+
origRemoveListenersCount,
719+
"No extra listeners after adding multiple tabs" );
720+
721+
removeLastTab();
722+
removeLastTab();
723+
removeLastTab();
724+
removeLastTab();
725+
assert.equal( jQuery._data( element[ 0 ], "events" ).remove.length,
726+
origRemoveListenersCount,
727+
"No extra listeners after removing all the extra tabs" );
728+
} );
729+
678730
} );

ui/widget.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,8 @@ $.Widget.prototype = {
499499
}, options );
500500

501501
function bindRemoveEvent() {
502+
var nodesToBind = [];
503+
502504
options.element.each( function( _, element ) {
503505
var isTracked = $.map( that.classesElementLookup, function( elements ) {
504506
return elements;
@@ -508,11 +510,13 @@ $.Widget.prototype = {
508510
} );
509511

510512
if ( !isTracked ) {
511-
that._on( $( element ), {
512-
remove: "_untrackClassesElement"
513-
} );
513+
nodesToBind.push( element );
514514
}
515515
} );
516+
517+
that._on( $( nodesToBind ), {
518+
remove: "_untrackClassesElement"
519+
} );
516520
}
517521

518522
function processClassString( classes, checkOption ) {

0 commit comments

Comments
 (0)