Skip to content

Commit 4cafb58

Browse files
committed
Manipulation: Detect sneaky no-content replaceWith input
Fixes gh-2204 Ref 642e9a4 Closes gh-1752 Closes gh-2206 (cherry picked from commit 4b27ae1) Conflicts: src/manipulation.js test/unit/manipulation.js
1 parent 1e7a2f3 commit 4cafb58

File tree

2 files changed

+41
-23
lines changed

2 files changed

+41
-23
lines changed

src/manipulation.js

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -274,14 +274,13 @@ jQuery.extend({
274274
return clone;
275275
},
276276

277-
buildFragment: function( elems, context, scripts, selection ) {
277+
buildFragment: function( elems, context, scripts, selection, ignored ) {
278278
var j, elem, contains,
279279
tmp, tag, wrap,
280280
l = elems.length,
281281

282282
// Ensure a safe fragment
283283
safe = createSafeFragment( context ),
284-
285284
nodes = [],
286285
i = 0;
287286

@@ -343,9 +342,11 @@ jQuery.extend({
343342
i = 0;
344343
while ( (elem = nodes[ i++ ]) ) {
345344

346-
// #4087 - If origin and destination elements are the same, and this is
347-
// that element, do not do anything
345+
// Skip elements already in the context collection (trac-4087)
348346
if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
347+
if ( ignored ) {
348+
ignored.push( elem );
349+
}
349350
continue;
350351
}
351352

@@ -573,28 +574,28 @@ jQuery.fn.extend({
573574
},
574575

575576
replaceWith: function() {
576-
var arg = arguments[ 0 ];
577-
578-
// Make the changes, replacing each context element with the new content
579-
this.domManip( arguments, function( elem ) {
580-
arg = this.parentNode;
577+
var ignored = [];
581578

582-
jQuery.cleanData( getAll( this ) );
579+
// Make the changes, replacing each non-ignored context element with the new content
580+
return this.domManip( arguments, function( elem ) {
581+
var parent = this.parentNode;
583582

584-
if ( arg ) {
585-
arg.replaceChild( elem, this );
583+
if ( jQuery.inArray( this, ignored ) < 0 ) {
584+
jQuery.cleanData( getAll( this ) );
585+
if ( parent ) {
586+
parent.replaceChild( elem, this );
587+
}
586588
}
587-
});
588589

589-
// Force removal if there was no new content (e.g., from empty arguments)
590-
return arg && (arg.length || arg.nodeType) ? this : this.remove();
590+
// Force callback invocation
591+
}, ignored );
591592
},
592593

593594
detach: function( selector ) {
594595
return this.remove( selector, true );
595596
},
596597

597-
domManip: function( args, callback ) {
598+
domManip: function( args, callback, ignored ) {
598599

599600
// Flatten any nested arrays
600601
args = concat.apply( [], args );
@@ -617,19 +618,20 @@ jQuery.fn.extend({
617618
if ( isFunction ) {
618619
args[0] = value.call( this, index, self.html() );
619620
}
620-
self.domManip( args, callback );
621+
self.domManip( args, callback, ignored );
621622
});
622623
}
623624

624625
if ( l ) {
625-
fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
626+
fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this, ignored );
626627
first = fragment.firstChild;
627628

628629
if ( fragment.childNodes.length === 1 ) {
629630
fragment = first;
630631
}
631632

632-
if ( first ) {
633+
// Require either new content or an interest in ignored elements to invoke the callback
634+
if ( first || ignored ) {
633635
scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
634636
hasScripts = scripts.length;
635637

test/unit/manipulation.js

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

1332-
test( "Empty replaceWith (#13401; #13596)", 8, function() {
1333-
var $el = jQuery( "<div/>" ),
1332+
test( "Empty replaceWith (trac-13401; trac-13596; gh-2204)", function() {
1333+
1334+
expect( 25 );
1335+
1336+
var $el = jQuery( "<div/><div/>" ).html( "<p>0</p>" ),
1337+
expectedHTML = $el.html(),
13341338
tests = {
13351339
"empty string": "",
13361340
"empty array": [],
1341+
"array of empty string": [ "" ],
13371342
"empty collection": jQuery( "#nonexistent" ),
13381343

1339-
// in case of jQuery(...).replaceWith();
1340-
"empty undefined": undefined
1344+
// in case of jQuery(...).replaceWith();
1345+
"undefined": undefined
13411346
};
13421347

13431348
jQuery.each( tests, function( label, input ) {
13441349
$el.html( "<a/>" ).children().replaceWith( input );
13451350
strictEqual( $el.html(), "", "replaceWith(" + label + ")" );
13461351
$el.html( "<b/>" ).children().replaceWith(function() { return input; });
13471352
strictEqual( $el.html(), "", "replaceWith(function returning " + label + ")" );
1353+
$el.html( "<i/>" ).children().replaceWith(function( i ) { i; return input; });
1354+
strictEqual( $el.html(), "", "replaceWith(other function returning " + label + ")" );
1355+
$el.html( "<p/>" ).children().replaceWith(function( i ) {
1356+
return i ?
1357+
input :
1358+
jQuery( this ).html( i + "" );
1359+
});
1360+
strictEqual( $el.eq( 0 ).html(), expectedHTML,
1361+
"replaceWith(function conditionally returning context)" );
1362+
strictEqual( $el.eq( 1 ).html(), "",
1363+
"replaceWith(function conditionally returning " + label + ")" );
13481364
});
13491365
});
13501366

0 commit comments

Comments
 (0)