Skip to content

Commit 4b27ae1

Browse files
committed
Manipulation: Detect sneaky no-content replaceWith input
Fixes gh-2204 Ref 642e9a4 Closes gh-1752 Closes gh-2206
1 parent 1541664 commit 4b27ae1

File tree

2 files changed

+41
-22
lines changed

2 files changed

+41
-22
lines changed

src/manipulation.js

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ jQuery.extend({
201201
return clone;
202202
},
203203

204-
buildFragment: function( elems, context, scripts, selection ) {
204+
buildFragment: function( elems, context, scripts, selection, ignored ) {
205205
var elem, tmp, tag, wrap, contains, j,
206206
fragment = context.createDocumentFragment(),
207207
nodes = [],
@@ -257,9 +257,11 @@ jQuery.extend({
257257
i = 0;
258258
while ( (elem = nodes[ i++ ]) ) {
259259

260-
// #4087 - If origin and destination elements are the same, and this is
261-
// that element, do not do anything
260+
// Skip elements already in the context collection (trac-4087)
262261
if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
262+
if ( ignored ) {
263+
ignored.push( elem );
264+
}
263265
continue;
264266
}
265267

@@ -446,28 +448,28 @@ jQuery.fn.extend({
446448
},
447449

448450
replaceWith: function() {
449-
var arg = arguments[ 0 ];
450-
451-
// Make the changes, replacing each context element with the new content
452-
this.domManip( arguments, function( elem ) {
453-
arg = this.parentNode;
451+
var ignored = [];
454452

455-
jQuery.cleanData( getAll( this ) );
453+
// Make the changes, replacing each non-ignored context element with the new content
454+
return this.domManip( arguments, function( elem ) {
455+
var parent = this.parentNode;
456456

457-
if ( arg ) {
458-
arg.replaceChild( elem, this );
457+
if ( jQuery.inArray( this, ignored ) < 0 ) {
458+
jQuery.cleanData( getAll( this ) );
459+
if ( parent ) {
460+
parent.replaceChild( elem, this );
461+
}
459462
}
460-
});
461463

462-
// Force removal if there was no new content (e.g., from empty arguments)
463-
return arg && (arg.length || arg.nodeType) ? this : this.remove();
464+
// Force callback invocation
465+
}, ignored );
464466
},
465467

466468
detach: function( selector ) {
467469
return this.remove( selector, true );
468470
},
469471

470-
domManip: function( args, callback ) {
472+
domManip: function( args, callback, ignored ) {
471473

472474
// Flatten any nested arrays
473475
args = concat.apply( [], args );
@@ -489,19 +491,20 @@ jQuery.fn.extend({
489491
if ( isFunction ) {
490492
args[ 0 ] = value.call( this, index, self.html() );
491493
}
492-
self.domManip( args, callback );
494+
self.domManip( args, callback, ignored );
493495
});
494496
}
495497

496498
if ( l ) {
497-
fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
499+
fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this, ignored );
498500
first = fragment.firstChild;
499501

500502
if ( fragment.childNodes.length === 1 ) {
501503
fragment = first;
502504
}
503505

504-
if ( first ) {
506+
// Require either new content or an interest in ignored elements to invoke the callback
507+
if ( first || ignored ) {
505508
scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
506509
hasScripts = scripts.length;
507510

test/unit/manipulation.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,22 +1279,38 @@ test( "replaceWith(string) for more than one element", function() {
12791279
equal(jQuery("#foo p").length, 0, "verify that all the three original element have been replaced");
12801280
});
12811281

1282-
test( "Empty replaceWith (#13401; #13596)", 8, function() {
1283-
var $el = jQuery( "<div/>" ),
1282+
test( "Empty replaceWith (trac-13401; trac-13596; gh-2204)", function() {
1283+
1284+
expect( 25 );
1285+
1286+
var $el = jQuery( "<div/><div/>" ).html( "<p>0</p>" ),
1287+
expectedHTML = $el.html(),
12841288
tests = {
12851289
"empty string": "",
12861290
"empty array": [],
1291+
"array of empty string": [ "" ],
12871292
"empty collection": jQuery( "#nonexistent" ),
12881293

1289-
// in case of jQuery(...).replaceWith();
1290-
"empty undefined": undefined
1294+
// in case of jQuery(...).replaceWith();
1295+
"undefined": undefined
12911296
};
12921297

12931298
jQuery.each( tests, function( label, input ) {
12941299
$el.html( "<a/>" ).children().replaceWith( input );
12951300
strictEqual( $el.html(), "", "replaceWith(" + label + ")" );
12961301
$el.html( "<b/>" ).children().replaceWith(function() { return input; });
12971302
strictEqual( $el.html(), "", "replaceWith(function returning " + label + ")" );
1303+
$el.html( "<i/>" ).children().replaceWith(function( i ) { i; return input; });
1304+
strictEqual( $el.html(), "", "replaceWith(other function returning " + label + ")" );
1305+
$el.html( "<p/>" ).children().replaceWith(function( i ) {
1306+
return i ?
1307+
input :
1308+
jQuery( this ).html( i + "" );
1309+
});
1310+
strictEqual( $el.eq( 0 ).html(), expectedHTML,
1311+
"replaceWith(function conditionally returning context)" );
1312+
strictEqual( $el.eq( 1 ).html(), "",
1313+
"replaceWith(function conditionally returning " + label + ")" );
12981314
});
12991315
});
13001316

0 commit comments

Comments
 (0)