Skip to content
Permalink
Browse files

Make sure that checked state is cloned properly. Based upon the patch…

… by Michael, required better test cases and in doing so found more edge cases. Introduced a new check into jQuery.support as a result. Fixes #5929.
  • Loading branch information
mmonteleone authored and jeresig committed Jan 25, 2010
1 parent 390186b commit bed759c95ca6d796125653b540e8611dc63b38bb
Showing with 41 additions and 5 deletions.
  1. +12 −3 src/manipulation.js
  2. +10 −1 src/support.js
  3. +19 −1 test/unit/manipulation.js
@@ -5,6 +5,7 @@ var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
rtagName = /<([\w:]+)/,
rtbody = /<tbody/i,
rhtml = /<|&\w+;/,
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, // checked="checked" or checked (html5)
fcloseTag = function( all, front, tag ) {
return rselfClosing.test( tag ) ?
all :
@@ -35,7 +36,7 @@ jQuery.fn.extend({
if ( jQuery.isFunction(text) ) {
return this.each(function(i) {
var self = jQuery(this);
return self.text( text.call(this, i, self.text()) );
self.text( text.call(this, i, self.text()) );
});
}

@@ -251,11 +252,18 @@ jQuery.fn.extend({
domManip: function( args, table, callback ) {
var results, first, value = args[0], scripts = [];

// We can't cloneNode fragments that contain checked, in WebKit
if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
return this.each(function() {
jQuery(this).domManip( args, table, callback, true );
});
}

if ( jQuery.isFunction(value) ) {
return this.each(function(i) {
var self = jQuery(this);
args[0] = value.call(this, i, table ? self.html() : undefined);
return self.domManip( args, table, callback );
self.domManip( args, table, callback );
});
}

@@ -326,7 +334,8 @@ function cloneCopyEvent(orig, ret) {
function buildFragment( args, nodes, scripts ) {
var fragment, cacheable, cacheresults, doc;

if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && args[0].indexOf("<option") < 0 ) {
// webkit does not clone 'checked' attribute of radio inputs on cloneNode, so don't cache if string has a checked
if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && args[0].indexOf("<option") < 0 && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
cacheable = true;
cacheresults = jQuery.fragments[ args[0] ];
if ( cacheresults ) {
@@ -57,6 +57,7 @@
optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected,

// Will be defined later
checkClone: false,
scriptEval: false,
noCloneEvent: true,
boxModel: null
@@ -89,9 +90,17 @@
div.cloneNode(true).fireEvent("onclick");
}

div = document.createElement("div");
div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>";

var fragment = document.createDocumentFragment();
fragment.appendChild( div.firstChild );

// WebKit doesn't clone checked state correctly in fragments
jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked;

// Figure out if the W3C box model works as expected
// document.body must exist before we can do this
// TODO: This timeout is temporary until I move ready into core.js.
jQuery(function() {
var div = document.createElement("div");
div.style.width = div.style.paddingLeft = "1px";
@@ -197,7 +197,7 @@ test("unwrap()", function() {
});

var testAppend = function(valueObj) {
expect(22);
expect(37);
var defaultText = 'Try them out:'
var result = jQuery('#first').append(valueObj('<b>buga</b>'));
equals( result.text(), defaultText + 'buga', 'Check if text appending works' );
@@ -230,6 +230,24 @@ var testAppend = function(valueObj) {
ok( jQuery("#sap").append(valueObj( [] )), "Check for appending an empty array." );
ok( jQuery("#sap").append(valueObj( "" )), "Check for appending an empty string." );
ok( jQuery("#sap").append(valueObj( document.getElementsByTagName("foo") )), "Check for appending an empty nodelist." );

reset();
jQuery("form").append(valueObj('<input name="radiotest" type="radio" checked="checked" />'));
jQuery("form input[name=radiotest]").each(function(){
ok( jQuery(this).is(':checked'), "Append checked radio");
}).remove();

reset();
jQuery("form").append(valueObj('<input name="radiotest" type="radio" checked = \'checked\' />'));
jQuery("form input[name=radiotest]").each(function(){
ok( jQuery(this).is(':checked'), "Append alternately formated checked radio");
}).remove();

reset();
jQuery("form").append(valueObj('<input name="radiotest" type="radio" checked />'));
jQuery("form input[name=radiotest]").each(function(){
ok( jQuery(this).is(':checked'), "Append HTML5-formated checked radio");
}).remove();

reset();
jQuery("#sap").append(valueObj( document.getElementById('form') ));

4 comments on commit bed759c

@mmonteleone

This comment has been minimized.

Copy link
Contributor Author

@mmonteleone mmonteleone replied Jan 26, 2010

Thanks for fixing up my crummy regex and including this, John.

And good call on adding support.checkClone. I hadn't considered that in what I saw as an all-or-nothing decision to be dirty and explicitly check browser.webkit, or always test the regex and be slow.

@jeresig

This comment has been minimized.

Copy link
Member

@jeresig jeresig replied Jan 26, 2010

Hey Michael, Thanks for the patch. Note that I ended up having to change a bunch of things: I couldn't get the test cases to fail so I rewrote them into a failing state. In doing so I ran across another set of problems (buildFragment isn't the only place where a fragment can be cloned, it can also happen in domManip - hence the new block of code there). It ended up being a surprisingly complex problem - especially building support.checkClone - because it turns out this only happens when you clone an already cloned fragment (a strange case, to be sure). Anyway, thanks for your help, I appreciate it!

@mmonteleone

This comment has been minimized.

Copy link
Contributor Author

@mmonteleone mmonteleone replied Jan 26, 2010

Ah, you're right. I wrongly committed "cleaned up" tests at the last minute without re-verifying they still failed against unpatched 1.4.0. And I also totally missed domManip. Thanks for not blindly accepting patches from strangers! :)

Not that it's of much consequence now, but I do think Webkit's issue is not related to duplicate clones nor does jQuery require duplicate calls to cloneNode to establish support.checkClone. I can reproduce Webkit's issue with only a single call to cloneNode. http://michaelmonteleone.net/files/jq14Webkit/Webkitclone.html (Reproducible in Safari 4.0.4 and Chromium nightly).

var div = document.createElement("div");
div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>";
console.log("should be true: " + div.lastChild.checked);
console.log("should be true (but is false in Webkit): " + div.cloneNode(true).lastChild.checked);

which matches with my original experience in 1.4.0 where there is theoretically only a single clone occurring in the following http://michaelmonteleone.net/files/jq14Webkit/jq140clone.html:

// this is the only code present.  returns false in Webkit
var checked = $("<input type='radio' name='radiotest' checked='checked'/>").is(":checked");

So changing to jQuery.support.checkClone = fragment.cloneNode(true).lastChild.checked; still seems to do the job.

And just to add one more big of Webkit weirdness: Webkit only exhibits this issue when the input has a name attribute.

But yes, I clouded the issue with my broken tests. And I will be sure to help test more while still in pre-release next time. Thank you again for your thoroughness and improvements to the original patch.

@mmonteleone

This comment has been minimized.

Copy link
Contributor Author

@mmonteleone mmonteleone replied Jan 26, 2010

That second link without the HTTP 300 inducing trailing colon: http://michaelmonteleone.net/files/jq14Webkit/jq140clone.html

Please sign in to comment.
You can’t perform that action at this time.