From 63c1414a54188685a0bf15ef000cbf5ad75626a6 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 | 213 ++------------------- src/manipulation/buildFragment.js | 118 ++++++++++++ src/manipulation/createSafeFragment.js | 20 ++ src/manipulation/getAll.js | 33 ++++ src/manipulation/setGlobalEval.js | 19 ++ src/manipulation/var/nodeNames.js | 5 + src/manipulation/var/rleadingWhitespace.js | 3 + src/manipulation/var/rscriptType.js | 3 + src/manipulation/var/rtagName.js | 3 + src/manipulation/wrapMap.js | 40 ++++ 11 files changed, 265 insertions(+), 202 deletions(-) create mode 100644 src/manipulation/buildFragment.js create mode 100644 src/manipulation/createSafeFragment.js create mode 100644 src/manipulation/getAll.js create mode 100644 src/manipulation/setGlobalEval.js create mode 100644 src/manipulation/var/nodeNames.js create mode 100644 src/manipulation/var/rleadingWhitespace.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 1e41e50e8..68dd6cf8d 100644 --- a/src/core/parseHTML.js +++ b/src/core/parseHTML.js @@ -1,13 +1,11 @@ define([ "../core", "./var/rsingleTag", + "../manipulation/buildFragment", // This is the only module that needs core/support - "./support", - - // buildFragment - "../manipulation" -], function( jQuery, rsingleTag, support ) { + "./support" +], function( jQuery, rsingleTag, buildFragment, support ) { // data: string of html // context (optional): If specified, the fragment will be created in this context, @@ -35,7 +33,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 564620368..0b068aff0 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -4,7 +4,17 @@ define([ "./var/push", "./var/deletedIds", "./core/access", + "./manipulation/var/rcheckableType", + "./manipulation/var/rtagName", + "./manipulation/var/rscriptType", + "./manipulation/var/rleadingWhitespace", + "./manipulation/var/nodeNames", + "./manipulation/createSafeFragment", + "./manipulation/wrapMap", + "./manipulation/getAll", + "./manipulation/setGlobalEval", + "./manipulation/buildFragment", "./manipulation/support", "./core/init", @@ -12,96 +22,22 @@ define([ "./traversing", "./selector", "./event" -], function( jQuery, concat, push, deletedIds, access, rcheckableType, support ) { +], function( jQuery, concat, push, deletedIds, access, + rcheckableType, rtagName, rscriptType, rleadingWhitespace, nodeNames, + createSafeFragment, wrapMap, getAll, setGlobalEval, + buildFragment, support ) { -function createSafeFragment( document ) { - var list = nodeNames.split( "|" ), - safeFrag = document.createDocumentFragment(); - - if ( safeFrag.createElement ) { - while ( list.length ) { - safeFrag.createElement( - list.pop() - ); - } - } - return safeFrag; -} - -var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|" + - "details|dialog|figcaption|figure|footer|header|hgroup|main|" + - "mark|meter|nav|output|picture|progress|section|summary|template|time|video", - rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, - rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), - rleadingWhitespace = /^\s+/, +var rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp( "<(?:" + nodeNames + ")[\\s/>]", "i" ), 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 = { - option: [ 1, "" ], - - // Support: IE8 - param: [ 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, "", "
" ], - - // IE8 can't serialize link, script, style, or any html5 (NoScope) tags, - // unless wrapped in a div with non-breaking characters in front of it. - _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] - }, safeFragment = createSafeFragment( document ), fragmentDiv = safeFragment.appendChild( document.createElement("div") ); -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -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; -} - // Manipulating tables requires a tbody function manipulationTarget( elem, content ) { if ( jQuery.nodeName( elem, "table" ) && @@ -128,21 +64,7 @@ function restoreScript( elem ) { 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 ) ) { return; } @@ -278,107 +200,6 @@ jQuery.extend({ return clone; }, - buildFragment: function( elems, context, scripts, selection, ignored ) { - var j, elem, contains, - tmp, tag, wrap, - l = elems.length, - - // Ensure a safe fragment - safe = createSafeFragment( context ), - nodes = [], - i = 0; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - 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 || safe.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; - } - - // Manually add leading whitespace removed by IE - if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); - } - - jQuery.merge( nodes, tmp.childNodes ); - - // Fix #12392 for WebKit and IE > 9 - tmp.textContent = ""; - - // Fix #12392 for oldIE - while ( tmp.firstChild ) { - tmp.removeChild( tmp.firstChild ); - } - - // Remember the top-level container for proper cleanup - tmp = safe.lastChild; - } - } - } - - // Fix #11356: Clear elements from fragment - if ( tmp ) { - safe.removeChild( tmp ); - } - - 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( safe.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 ); - } - } - } - } - - tmp = null; - - return safe; - }, - cleanData: function( elems, /* internal */ acceptData ) { var elem, type, id, data, i = 0, @@ -626,7 +447,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..2ef165ef7 --- /dev/null +++ b/src/manipulation/buildFragment.js @@ -0,0 +1,118 @@ +define([ + "../core", + "./var/rtagName", + "./var/rscriptType", + "./var/rleadingWhitespace", + "./createSafeFragment", + "./wrapMap", + "./getAll", + "./setGlobalEval", + "./support" +], function( jQuery, rtagName, rscriptType, rleadingWhitespace, + createSafeFragment, wrapMap, getAll, setGlobalEval, support ) { + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var j, elem, contains, + tmp, tag, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + 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 || safe.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; + } + + // Manually add leading whitespace removed by IE + if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + 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( safe.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 ); + } + } + } + } + + tmp = null; + + return safe; +} + +return buildFragment; +}); diff --git a/src/manipulation/createSafeFragment.js b/src/manipulation/createSafeFragment.js new file mode 100644 index 000000000..dd12e66fd --- /dev/null +++ b/src/manipulation/createSafeFragment.js @@ -0,0 +1,20 @@ +define([ + "./var/nodeNames" +], function( nodeNames ) { + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +return createSafeFragment; +}); diff --git a/src/manipulation/getAll.js b/src/manipulation/getAll.js new file mode 100644 index 000000000..d9a31830d --- /dev/null +++ b/src/manipulation/getAll.js @@ -0,0 +1,33 @@ +define([ + "../core" +], function( jQuery ) { + +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; +} + +return getAll; +}); diff --git a/src/manipulation/setGlobalEval.js b/src/manipulation/setGlobalEval.js new file mode 100644 index 000000000..e0c3197c7 --- /dev/null +++ b/src/manipulation/setGlobalEval.js @@ -0,0 +1,19 @@ +define([ + "../core" +], function( jQuery ) { + +// 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" ) + ); + } +} + +return setGlobalEval; +}); diff --git a/src/manipulation/var/nodeNames.js b/src/manipulation/var/nodeNames.js new file mode 100644 index 000000000..7ef6bb0e5 --- /dev/null +++ b/src/manipulation/var/nodeNames.js @@ -0,0 +1,5 @@ +define(function() { + return "abbr|article|aside|audio|bdi|canvas|data|datalist|" + + "details|dialog|figcaption|figure|footer|header|hgroup|main|" + + "mark|meter|nav|output|picture|progress|section|summary|template|time|video"; +}); diff --git a/src/manipulation/var/rleadingWhitespace.js b/src/manipulation/var/rleadingWhitespace.js new file mode 100644 index 000000000..210ccb913 --- /dev/null +++ b/src/manipulation/var/rleadingWhitespace.js @@ -0,0 +1,3 @@ +define(function() { + return ( /^\s+/ ); +}); 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..8202e7d93 --- /dev/null +++ b/src/manipulation/wrapMap.js @@ -0,0 +1,40 @@ +define([ + "./support" +], function( support ) { + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + option: [ 1, "" ], + + // Support: IE8 + param: [ 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, "", "
" ], + + // IE8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] +}; + +// Support: IE8-IE9 +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +return wrapMap; +});