From a74320fca8bb0a2190f6e1fdb71a73733b6986e4 Mon Sep 17 00:00:00 2001 From: Timmy Willison Date: Tue, 5 May 2015 11:44:55 -0700 Subject: [PATCH] Manipulation: privatize buildFragment() function Fixes gh-2224 --- src/core/parseHTML.js | 10 +- src/manipulation.js | 164 +++------------------------- src/manipulation/buildFragment.js | 101 +++++++++++++++++ src/manipulation/getAll.js | 20 ++++ src/manipulation/setGlobalEval.js | 20 ++++ src/manipulation/var/rscriptType.js | 3 + src/manipulation/var/rtagName.js | 3 + src/manipulation/wrapMap.js | 35 ++++++ 8 files changed, 199 insertions(+), 157 deletions(-) create mode 100644 src/manipulation/buildFragment.js create mode 100644 src/manipulation/getAll.js create mode 100644 src/manipulation/setGlobalEval.js create mode 100644 src/manipulation/var/rscriptType.js create mode 100644 src/manipulation/var/rtagName.js create mode 100644 src/manipulation/wrapMap.js diff --git a/src/core/parseHTML.js b/src/core/parseHTML.js index 519600a63..e7d7c63c8 100644 --- a/src/core/parseHTML.js +++ b/src/core/parseHTML.js @@ -2,13 +2,11 @@ define([ "../core", "../var/document", "./var/rsingleTag", + "../manipulation/buildFragment", // This is the only module that needs core/support - "./support", - - // buildFragment - "../manipulation" -], function( jQuery, document, rsingleTag, support ) { + "./support" +], function( jQuery, document, rsingleTag, buildFragment, support ) { // data: string of html // context (optional): If specified, the fragment will be created in this context, @@ -36,7 +34,7 @@ jQuery.parseHTML = function( data, context, keepScripts ) { return [ context.createElement( parsed[1] ) ]; } - parsed = jQuery.buildFragment( [ data ], context, scripts ); + parsed = buildFragment( [ data ], context, scripts ); if ( scripts && scripts.length ) { jQuery( scripts ).remove(); diff --git a/src/manipulation.js b/src/manipulation.js index d085755a3..b6326a3ae 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -4,7 +4,14 @@ define([ "./var/push", "./core/access", "./manipulation/var/rcheckableType", + "./manipulation/var/rtagName", + "./manipulation/var/rscriptType", + "./manipulation/wrapMap", + "./manipulation/getAll", + "./manipulation/setGlobalEval", + "./manipulation/buildFragment", "./manipulation/support", + "./data/var/dataPriv", "./data/var/dataUser", @@ -13,49 +20,18 @@ define([ "./traversing", "./selector", "./event" -], function( jQuery, concat, push, access, rcheckableType, support, dataPriv, dataUser ) { +], function( jQuery, concat, push, access, + rcheckableType, rtagName, rscriptType, + wrapMap, getAll, setGlobalEval, buildFragment, support, + dataPriv, dataUser ) { var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi, - rtagName = /<([\w:-]+)/, - rhtml = /<|&#?\w+;/, rnoInnerhtml = /<(?:script|style|link)/i, // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptType = /^$|\/(?:java|ecma)script/i, rscriptTypeMasked = /^true\/(.*)/, - rcleanScript = /^\s*\s*$/g, - - // We have to close these tags to support XHTML (#13200) - wrapMap = { - - // Support: IE9 - option: [ 1, "" ], - - thead: [ 1, "", "
" ], - - // Some of the following wrappers are not fully defined, because - // their parent elements (except for "table" element) could be omitted - // since browser parsers are smart enough to auto-insert them - - // Support: Android 2.3 - // Android browser doesn't auto-insert colgroup - col: [ 2, "", "
" ], - - // Auto-insert "tbody" element - tr: [ 2, "", "
" ], - - // Auto-insert "tbody" and "tr" elements - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] - }; - -// Support: IE9 -wrapMap.optgroup = wrapMap.option; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; + rcleanScript = /^\s*\s*$/g; function manipulationTarget( elem, content ) { if ( jQuery.nodeName( elem, "table" ) && @@ -84,18 +60,6 @@ function restoreScript( elem ) { return elem; } -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], "globalEval", !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - function cloneCopyEvent( src, dest ) { var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; @@ -130,20 +94,6 @@ function cloneCopyEvent( src, dest ) { } } -function getAll( context, tag ) { - // Support: IE9-11+ - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( tag || "*" ) : - typeof context.querySelectorAll !== "undefined" ? - context.querySelectorAll( tag || "*" ) : - []; - - return tag === undefined || tag && jQuery.nodeName( context, tag ) ? - jQuery.merge( [ context ], ret ) : - ret; -} - // Fix IE bugs, see support tests function fixInput( src, dest ) { var nodeName = dest.nodeName.toLowerCase(); @@ -205,94 +155,6 @@ jQuery.extend({ return clone; }, - buildFragment: function( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - // Support: Android<4.1, PhantomJS<2 - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement("div") ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android<4.1, PhantomJS<2 - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( (elem = nodes[ i++ ]) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( (elem = tmp[ j++ ]) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; - }, - cleanData: function( elems ) { var data, elem, type, special = jQuery.event.special, @@ -500,7 +362,7 @@ jQuery.fn.extend({ } if ( l ) { - fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this, ignored ); + fragment = buildFragment( args, this[ 0 ].ownerDocument, false, this, ignored ); first = fragment.firstChild; if ( fragment.childNodes.length === 1 ) { diff --git a/src/manipulation/buildFragment.js b/src/manipulation/buildFragment.js new file mode 100644 index 000000000..d5cb4062a --- /dev/null +++ b/src/manipulation/buildFragment.js @@ -0,0 +1,101 @@ +define([ + "../core", + "./var/rtagName", + "./var/rscriptType", + "./wrapMap", + "./getAll", + "./setGlobalEval" +], function( jQuery, rtagName, rscriptType, wrapMap, getAll, setGlobalEval ) { + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + // Support: Android<4.1, PhantomJS<2 + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android<4.1, PhantomJS<2 + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + +return buildFragment; +}); diff --git a/src/manipulation/getAll.js b/src/manipulation/getAll.js new file mode 100644 index 000000000..d08470b48 --- /dev/null +++ b/src/manipulation/getAll.js @@ -0,0 +1,20 @@ +define([ + "../core" +], function( jQuery ) { + +function getAll( context, tag ) { + // Support: IE9-11+ + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== "undefined" ? + context.querySelectorAll( tag || "*" ) : + []; + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], ret ) : + ret; +} + +return getAll; +}); diff --git a/src/manipulation/setGlobalEval.js b/src/manipulation/setGlobalEval.js new file mode 100644 index 000000000..2eab9cf53 --- /dev/null +++ b/src/manipulation/setGlobalEval.js @@ -0,0 +1,20 @@ +define([ + "../data/var/dataPriv" +], function( dataPriv ) { + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + +return setGlobalEval; +}); diff --git a/src/manipulation/var/rscriptType.js b/src/manipulation/var/rscriptType.js new file mode 100644 index 000000000..60ef70ac8 --- /dev/null +++ b/src/manipulation/var/rscriptType.js @@ -0,0 +1,3 @@ +define(function() { + return ( /^$|\/(?:java|ecma)script/i ); +}); diff --git a/src/manipulation/var/rtagName.js b/src/manipulation/var/rtagName.js new file mode 100644 index 000000000..cd0b768e0 --- /dev/null +++ b/src/manipulation/var/rtagName.js @@ -0,0 +1,3 @@ +define(function() { + return ( /<([\w:-]+)/ ); +}); diff --git a/src/manipulation/wrapMap.js b/src/manipulation/wrapMap.js new file mode 100644 index 000000000..7080bf996 --- /dev/null +++ b/src/manipulation/wrapMap.js @@ -0,0 +1,35 @@ +define(function() { + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE9 + option: [ 1, "" ], + + thead: [ 1, "", "
" ], + + // Some of the following wrappers are not fully defined, because + // their parent elements (except for "table" element) could be omitted + // since browser parsers are smart enough to auto-insert them + + // Support: Android 2.3 + // Android browser doesn't auto-insert colgroup + col: [ 2, "", "
" ], + + // Auto-insert "tbody" element + tr: [ 2, "", "
" ], + + // Auto-insert "tbody" and "tr" elements + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +// Support: IE9 +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +return wrapMap; +}); -- 2.39.5