Manipulation: Detect sneaky no-content replaceWith input

Fixes gh-2204
Ref 642e9a4557
Closes gh-1752
Closes gh-2206

(cherry picked from commit 4b27ae16a2)

Conflicts:
	src/manipulation.js
	test/unit/manipulation.js
This commit is contained in:
Richard Gibson 2015-04-13 16:05:48 -04:00
parent 1e7a2f3674
commit 4cafb58ba4
2 changed files with 41 additions and 23 deletions

View File

@ -274,14 +274,13 @@ jQuery.extend({
return clone; return clone;
}, },
buildFragment: function( elems, context, scripts, selection ) { buildFragment: function( elems, context, scripts, selection, ignored ) {
var j, elem, contains, var j, elem, contains,
tmp, tag, wrap, tmp, tag, wrap,
l = elems.length, l = elems.length,
// Ensure a safe fragment // Ensure a safe fragment
safe = createSafeFragment( context ), safe = createSafeFragment( context ),
nodes = [], nodes = [],
i = 0; i = 0;
@ -343,9 +342,11 @@ jQuery.extend({
i = 0; i = 0;
while ( (elem = nodes[ i++ ]) ) { while ( (elem = nodes[ i++ ]) ) {
// #4087 - If origin and destination elements are the same, and this is // Skip elements already in the context collection (trac-4087)
// that element, do not do anything
if ( selection && jQuery.inArray( elem, selection ) > -1 ) { if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
if ( ignored ) {
ignored.push( elem );
}
continue; continue;
} }
@ -573,28 +574,28 @@ jQuery.fn.extend({
}, },
replaceWith: function() { replaceWith: function() {
var arg = arguments[ 0 ]; var ignored = [];
// Make the changes, replacing each context element with the new content // Make the changes, replacing each non-ignored context element with the new content
this.domManip( arguments, function( elem ) { return this.domManip( arguments, function( elem ) {
arg = this.parentNode; var parent = this.parentNode;
jQuery.cleanData( getAll( this ) ); if ( jQuery.inArray( this, ignored ) < 0 ) {
jQuery.cleanData( getAll( this ) );
if ( arg ) { if ( parent ) {
arg.replaceChild( elem, this ); parent.replaceChild( elem, this );
}
} }
});
// Force removal if there was no new content (e.g., from empty arguments) // Force callback invocation
return arg && (arg.length || arg.nodeType) ? this : this.remove(); }, ignored );
}, },
detach: function( selector ) { detach: function( selector ) {
return this.remove( selector, true ); return this.remove( selector, true );
}, },
domManip: function( args, callback ) { domManip: function( args, callback, ignored ) {
// Flatten any nested arrays // Flatten any nested arrays
args = concat.apply( [], args ); args = concat.apply( [], args );
@ -617,19 +618,20 @@ jQuery.fn.extend({
if ( isFunction ) { if ( isFunction ) {
args[0] = value.call( this, index, self.html() ); args[0] = value.call( this, index, self.html() );
} }
self.domManip( args, callback ); self.domManip( args, callback, ignored );
}); });
} }
if ( l ) { if ( l ) {
fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this, ignored );
first = fragment.firstChild; first = fragment.firstChild;
if ( fragment.childNodes.length === 1 ) { if ( fragment.childNodes.length === 1 ) {
fragment = first; fragment = first;
} }
if ( first ) { // Require either new content or an interest in ignored elements to invoke the callback
if ( first || ignored ) {
scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
hasScripts = scripts.length; hasScripts = scripts.length;

View File

@ -1329,15 +1329,20 @@ test( "replaceWith(string) for more than one element", function() {
equal(jQuery("#foo p").length, 0, "verify that all the three original element have been replaced"); equal(jQuery("#foo p").length, 0, "verify that all the three original element have been replaced");
}); });
test( "Empty replaceWith (#13401; #13596)", 8, function() { test( "Empty replaceWith (trac-13401; trac-13596; gh-2204)", function() {
var $el = jQuery( "<div/>" ),
expect( 25 );
var $el = jQuery( "<div/><div/>" ).html( "<p>0</p>" ),
expectedHTML = $el.html(),
tests = { tests = {
"empty string": "", "empty string": "",
"empty array": [], "empty array": [],
"array of empty string": [ "" ],
"empty collection": jQuery( "#nonexistent" ), "empty collection": jQuery( "#nonexistent" ),
// in case of jQuery(...).replaceWith(); // in case of jQuery(...).replaceWith();
"empty undefined": undefined "undefined": undefined
}; };
jQuery.each( tests, function( label, input ) { jQuery.each( tests, function( label, input ) {
@ -1345,6 +1350,17 @@ test( "Empty replaceWith (#13401; #13596)", 8, function() {
strictEqual( $el.html(), "", "replaceWith(" + label + ")" ); strictEqual( $el.html(), "", "replaceWith(" + label + ")" );
$el.html( "<b/>" ).children().replaceWith(function() { return input; }); $el.html( "<b/>" ).children().replaceWith(function() { return input; });
strictEqual( $el.html(), "", "replaceWith(function returning " + label + ")" ); strictEqual( $el.html(), "", "replaceWith(function returning " + label + ")" );
$el.html( "<i/>" ).children().replaceWith(function( i ) { i; return input; });
strictEqual( $el.html(), "", "replaceWith(other function returning " + label + ")" );
$el.html( "<p/>" ).children().replaceWith(function( i ) {
return i ?
input :
jQuery( this ).html( i + "" );
});
strictEqual( $el.eq( 0 ).html(), expectedHTML,
"replaceWith(function conditionally returning context)" );
strictEqual( $el.eq( 1 ).html(), "",
"replaceWith(function conditionally returning " + label + ")" );
}); });
}); });