diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/core.js | 24 | ||||
-rw-r--r-- | src/manipulation.js | 374 |
2 files changed, 211 insertions, 187 deletions
diff --git a/src/core.js b/src/core.js index 1b566c479..19246d1d3 100644 --- a/src/core.js +++ b/src/core.js @@ -16,6 +16,7 @@ var _$ = window.$, // Save a reference to some core methods + core_concat = Array.prototype.concat, core_push = Array.prototype.push, core_slice = Array.prototype.slice, core_indexOf = Array.prototype.indexOf, @@ -472,26 +473,31 @@ jQuery.extend({ // data: string of html // context (optional): If specified, the fragment will be created in this context, defaults to document - // scripts (optional): If true, will include scripts passed in the html string - parseHTML: function( data, context, scripts ) { - var parsed; + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { if ( !data || typeof data !== "string" ) { return null; } if ( typeof context === "boolean" ) { - scripts = context; - context = 0; + keepScripts = context; + context = false; } context = context || document; + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + // Single tag - if ( (parsed = rsingleTag.exec( data )) ) { + if ( parsed ) { return [ context.createElement( parsed[1] ) ]; } - parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] ); + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } return jQuery.merge( [], - (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes ); + ( parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment ).childNodes ); }, parseJSON: function( data ) { @@ -744,7 +750,7 @@ jQuery.extend({ } // Flatten any nested arrays - return ret.concat.apply( [], ret ); + return core_concat.apply( [], ret ); }, // A global GUID counter for objects diff --git a/src/manipulation.js b/src/manipulation.js index 473fa5c82..f9e85c9c5 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -26,7 +26,8 @@ var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figca rcheckableType = /^(?:checkbox|radio)$/, // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptType = /\/(java|ecma)script/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g, wrapMap = { option: [ 1, "<select multiple='multiple'>", "</select>" ], @@ -181,13 +182,15 @@ jQuery.fn.extend({ i = 0; for ( ; (elem = this[i]) != null; i++ ) { - if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { if ( !keepData && elem.nodeType === 1 ) { - jQuery.cleanData( elem.getElementsByTagName("*") ); - jQuery.cleanData( [ elem ] ); + jQuery.cleanData( getAll( elem ) ); } if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } elem.parentNode.removeChild( elem ); } } @@ -203,7 +206,7 @@ jQuery.fn.extend({ for ( ; (elem = this[i]) != null; i++ ) { // Remove element nodes and prevent memory leaks if ( elem.nodeType === 1 ) { - jQuery.cleanData( elem.getElementsByTagName("*") ); + jQuery.cleanData( getAll( elem, false ) ); } // Remove any remaining nodes @@ -249,7 +252,7 @@ jQuery.fn.extend({ // Remove element nodes and prevent memory leaks elem = this[i] || {}; if ( elem.nodeType === 1 ) { - jQuery.cleanData( elem.getElementsByTagName( "*" ) ); + jQuery.cleanData( getAll( elem, false ) ); elem.innerHTML = value; } } @@ -311,31 +314,27 @@ jQuery.fn.extend({ domManip: function( args, table, callback ) { // Flatten any nested arrays - args = [].concat.apply( [], args ); + args = core_concat.apply( [], args ); - var results, first, fragment, iNoClone, + var fragment, first, results, scripts, hasScripts, iNoClone, node, doc, i = 0, + l = this.length, value = args[0], - scripts = [], - l = this.length; + isFunction = jQuery.isFunction( value ); // We can't cloneNode fragments that contain checked, in WebKit - if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) { + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { return this.each(function() { - jQuery(this).domManip( args, table, callback ); - }); - } - - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - args[0] = value.call( this, i, table ? self.html() : undefined ); + var self = jQuery( this ); + if ( isFunction ) { + args[0] = value.call( this, i, table ? self.html() : undefined ); + } self.domManip( args, table, callback ); }); } if ( this[0] ) { - results = jQuery.buildFragment( args, this, scripts ); + results = jQuery.buildFragment( args, this ); fragment = results.fragment; first = fragment.firstChild; @@ -345,48 +344,61 @@ jQuery.fn.extend({ if ( first ) { table = table && jQuery.nodeName( first, "tr" ); + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; // Use the original fragment for the last item instead of the first because it can end up // being emptied incorrectly in certain situations (#8070). // Fragments from the fragment cache must always be cloned and never used in place. for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) { + node = fragment; + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } callback.call( table && jQuery.nodeName( this[i], "table" ) ? findOrAppend( this[i], "tbody" ) : this[i], - i === iNoClone ? - fragment : - jQuery.clone( fragment, true, true ) + node ); } - } - // Fix #11809: Avoid leaking memory - fragment = first = null; - - if ( scripts.length ) { - jQuery.each( scripts, function( i, elem ) { - if ( elem.src ) { - if ( jQuery.ajax ) { - jQuery.ajax({ - url: elem.src, - type: "GET", - dataType: "script", - async: false, - global: false, - "throws": true - }); - } else { - jQuery.error("no ajax"); + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery.ajax({ + url: node.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } } - } else { - jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) ); } + } - if ( elem.parentNode ) { - elem.parentNode.removeChild( elem ); - } - }); + // Fix #11809: Avoid leaking memory + fragment = first = null; } } @@ -398,6 +410,31 @@ function findOrAppend( elem, tag ) { return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); } +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + var attr = elem.getAttributeNode("type"); + elem.type = ( attr && attr.specified ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + function cloneCopyEvent( src, dest ) { if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { @@ -448,9 +485,14 @@ function cloneFixAttributes( src, dest ) { nodeName = dest.nodeName.toLowerCase(); - if ( nodeName === "object" ) { - // IE6-10 improperly clones children of object elements using classid. - // IE10 throws NoModificationAllowedError if parent is null, #12132. + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { if ( dest.parentNode ) { dest.outerHTML = src.outerHTML; } @@ -459,7 +501,7 @@ function cloneFixAttributes( src, dest ) { // element in IE9, the outerHTML strategy above is not sufficient. // If the src has innerHTML and the destination does not, // copy the src.innerHTML into the dest.innerHTML. #10324 - if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) { + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { dest.innerHTML = src.innerHTML; } @@ -485,10 +527,6 @@ function cloneFixAttributes( src, dest ) { // cloning other types of input fields } else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; - - // IE blanks contents when cloning scripts - } else if ( nodeName === "script" && dest.text !== src.text ) { - dest.text = src.text; } // Event data gets referenced instead of copied if the expando @@ -569,16 +607,26 @@ jQuery.each({ }; }); -function getAll( elem ) { - if ( typeof elem.getElementsByTagName !== "undefined" ) { - return elem.getElementsByTagName( "*" ); - - } else if ( typeof elem.querySelectorAll !== "undefined" ) { - return elem.querySelectorAll( "*" ); - - } else { - return []; +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== "undefined" ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== "undefined" ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; } // Used in clean, fixes the defaultChecked property @@ -590,10 +638,8 @@ function fixDefaultChecked( elem ) { jQuery.extend({ clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var srcElements, - destElements, - i, - clone; + var destElements, srcElements, node, i, clone, + inPage = jQuery.contains( elem.ownerDocument, elem ); if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { // Break the original-clone style connection in IE9/10 (#8909) @@ -616,191 +662,163 @@ jQuery.extend({ // proprietary methods to clear the events. Thanks to MooTools // guys for this hotness. - cloneFixAttributes( elem, clone ); - - // Using Sizzle here is crazy slow, so we use getElementsByTagName instead - srcElements = getAll( elem ); + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 destElements = getAll( clone ); + srcElements = getAll( elem ); // Weird iteration because IE will replace the length property // with an element if you are cloning the body and one of the // elements on the page has a name or id of "length" - for ( i = 0; srcElements[i]; ++i ) { + for ( i = 0; (node = srcElements[i]) != null; ++i ) { // Ensure that the destination node is not null; Fixes #9587 if ( destElements[i] ) { - cloneFixAttributes( srcElements[i], destElements[i] ); + cloneFixAttributes( node, destElements[i] ); } } } // Copy the events from the original to the clone if ( dataAndEvents ) { - cloneCopyEvent( elem, clone ); - if ( deepDataAndEvents ) { - srcElements = getAll( elem ); destElements = getAll( clone ); + srcElements = getAll( elem ); - for ( i = 0; srcElements[i]; ++i ) { - cloneCopyEvent( srcElements[i], destElements[i] ); + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); } + } else { + cloneCopyEvent( elem, clone ); } } - srcElements = destElements = null; + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; // Return the cloned set return clone; }, clean: function( elems, context, fragment, scripts ) { - var i, j, elem, tag, wrap, depth, parent, div, hasBody, tbody, handleScript, jsTags, - safe = context === document && safeFragment, - ret = []; + var elem, j, tmp, tag, wrap, tbody, + ret = [], + i = 0, + safe = context === document && safeFragment; // Ensure that context is a document if ( !context || typeof context.createDocumentFragment === "undefined" ) { context = document; } - // Use the already-created safe fragment if context permits for ( i = 0; (elem = elems[i]) != null; i++ ) { - if ( typeof elem === "number" ) { - elem += ""; - } + if ( elem || elem === 0 ) { + // Add nodes directly + if ( typeof elem === "object" ) { + jQuery.merge( ret, elem.nodeType ? [ elem ] : elem ); - if ( !elem ) { - continue; - } + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + ret.push( context.createTextNode( elem ) ); - // Convert html string into DOM nodes - if ( typeof elem === "string" ) { - if ( !rhtml.test( elem ) ) { - elem = context.createTextNode( elem ); + // Convert html into DOM nodes } else { - // Ensure a safe container in which to render the html + // Ensure a safe container safe = safe || createSafeFragment( context ); - div = div || safe.appendChild( context.createElement("div") ); + tmp = tmp || safe.appendChild( context.createElement("div") ); - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(rxhtmlTag, "<$1></$2>"); - - // Go to html and back, then peel off extra wrappers + // Deserialize a standard representation tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); wrap = wrapMap[ tag ] || wrapMap._default; - depth = wrap[0]; - div.innerHTML = wrap[1] + addMandatoryAttributes( elem ) + wrap[2]; + tmp.innerHTML = wrap[1] + addMandatoryAttributes( elem.replace( rxhtmlTag, "<$1></$2>" ) ) + wrap[2]; - // Move to the right depth - while ( depth-- ) { - div = div.lastChild; + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + ret.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); } // Remove IE's autoinserted <tbody> from table fragments if ( !jQuery.support.tbody ) { // String was a <table>, *may* have spurious <tbody> - hasBody = rtbody.test(elem); - tbody = tag === "table" && !hasBody ? - div.firstChild && div.firstChild.childNodes : - - // String was a bare <thead> or <tfoot> - wrap[1] === "<table>" && !hasBody ? - div.childNodes : - []; - - for ( j = tbody.length - 1; j >= 0 ; --j ) { - if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { - tbody[ j ].parentNode.removeChild( tbody[ j ] ); + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare <thead> or <tfoot> + wrap[1] === "<table>" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); } } } - // IE completely kills leading whitespace when innerHTML is used - if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); - } + jQuery.merge( ret, tmp.childNodes ); - elem = div.childNodes; - parent = div; + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; - // Remember the top-level container for proper cleanup - div = safe.lastChild; - } - } - - if ( elem.nodeType ) { - ret.push( elem ); - } else { - jQuery.merge( ret, elem ); - - // Fix #12392 - if ( parent ) { - - // for WebKit and IE > 9 - parent.textContent = ""; - - // for oldIE - while ( parent.firstChild ) { - parent.removeChild( parent.firstChild ); + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); } - parent = null; + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; } } } // Fix #11356: Clear elements from safeFragment - if ( div ) { - safe.removeChild( div ); + if ( tmp ) { + safe.removeChild( tmp ); } - elem = div = safe = null; - - // Reset defaultChecked for any radios and checkboxes // about to be appended to the DOM in IE 6/7 (#8060) if ( !jQuery.support.appendChecked ) { - for ( i = 0; (elem = ret[i]) != null; i++ ) { - if ( jQuery.nodeName( elem, "input" ) ) { - fixDefaultChecked( elem ); - } else if ( typeof elem.getElementsByTagName !== "undefined" ) { - jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); - } - } + jQuery.grep( getAll( ret, "input" ), fixDefaultChecked ); } - // Append elements to a provided document fragment if ( fragment ) { - // Special handling of each script element - handleScript = function( elem ) { - // Check if we consider it executable - if ( !elem.type || rscriptType.test( elem.type ) ) { - // Detach the script and store it in the scripts array (if provided) or the fragment - // Return truthy to indicate that it has been handled - return scripts ? - scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) : - fragment.appendChild( elem ); + for ( i = 0; (elem = ret[i]) != null; i++ ) { + safe = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + fragment.appendChild( elem ); + tmp = getAll( elem, "script" ); + + // Preserve script evaluation history + if ( safe ) { + setGlobalEval( tmp ); } - }; - for ( i = 0; (elem = ret[i]) != null; i++ ) { - // Check if we're done after handling an executable script - if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) { - // Append to fragment and handle embedded scripts - fragment.appendChild( elem ); - if ( typeof elem.getElementsByTagName !== "undefined" ) { - // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration - jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript ); - - // Splice the scripts into ret after their former ancestor and advance our index beyond them - ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); - i += jsTags.length; + // Capture executables + if ( scripts ) { + for ( j = 0; (elem = tmp[j]) != null; j++ ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } } } } } + elem = tmp = safe = null; + return ret; }, |