From ee6e874075ba1fcd8f9e62cd1ee5c04f6518b6d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82=C4=99biowski-Owczarek?= Date: Mon, 10 Oct 2022 18:15:34 +0200 Subject: [PATCH] Manipulation: Extract domManip to a separate file We've already had `buildFragment` extracted to a separate file long ago. `domManip` is quite a complex & crucial API and so far it has existed within the `manipulation.js` module. Extracting it makes the module shorter and easier to understand. A few comments / messages in tests have also been updated to not suggest there's a public `jQuery.domManip` API - it's been private since 3.0.0. Closes gh-5138 --- src/manipulation.js | 105 +-------------------------------- src/manipulation/domManip.js | 109 +++++++++++++++++++++++++++++++++++ test/unit/ajax.js | 6 +- 3 files changed, 113 insertions(+), 107 deletions(-) create mode 100644 src/manipulation/domManip.js diff --git a/src/manipulation.js b/src/manipulation.js index cad13bd92..d3ee1130f 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -1,19 +1,16 @@ import jQuery from "./core.js"; import isAttached from "./core/isAttached.js"; -import flat from "./var/flat.js"; import isIE from "./var/isIE.js"; import push from "./var/push.js"; import access from "./core/access.js"; import rtagName from "./manipulation/var/rtagName.js"; -import rscriptType from "./manipulation/var/rscriptType.js"; import wrapMap from "./manipulation/wrapMap.js"; import getAll from "./manipulation/getAll.js"; +import domManip from "./manipulation/domManip.js"; import setGlobalEval from "./manipulation/setGlobalEval.js"; -import buildFragment from "./manipulation/buildFragment.js"; import dataPriv from "./data/var/dataPriv.js"; import dataUser from "./data/var/dataUser.js"; import acceptData from "./data/var/acceptData.js"; -import DOMEval from "./core/DOMEval.js"; import nodeName from "./core/nodeName.js"; import "./core/init.js"; @@ -38,21 +35,6 @@ function manipulationTarget( elem, content ) { return elem; } -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - function cloneCopyEvent( src, dest ) { var i, l, type, pdataOld, udataOld, udataCur, events; @@ -85,91 +67,6 @@ function cloneCopyEvent( src, dest ) { } } -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = flat( args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = typeof value === "function"; - - if ( valueIsFunction ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - args[ 0 ] = value.call( this, index, self.html() ); - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = 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 ); - 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 (trac-8070). - for ( ; 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( collection[ i ], node, i ); - } - - 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 || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce, - crossOrigin: node.crossOrigin - }, doc ); - } - } else { - DOMEval( node.textContent, node, doc ); - } - } - } - } - } - } - - return collection; -} - function remove( elem, selector, keepData ) { var node, nodes = selector ? jQuery.filter( selector, elem ) : elem, diff --git a/src/manipulation/domManip.js b/src/manipulation/domManip.js new file mode 100644 index 000000000..f8dded566 --- /dev/null +++ b/src/manipulation/domManip.js @@ -0,0 +1,109 @@ +import jQuery from "../core.js"; +import flat from "../var/flat.js"; +import rscriptType from "./var/rscriptType.js"; +import getAll from "./getAll.js"; +import buildFragment from "./buildFragment.js"; +import dataPriv from "../data/var/dataPriv.js"; +import DOMEval from "../core/DOMEval.js"; + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = typeof value === "function"; + + if ( valueIsFunction ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + args[ 0 ] = value.call( this, index, self.html() ); + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = 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 ); + 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 (trac-8070). + for ( ; 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( collection[ i ], node, i ); + } + + 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 || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce, + crossOrigin: node.crossOrigin + }, doc ); + } + } else { + DOMEval( node.textContent, node, doc ); + } + } + } + } + } + } + + return collection; +} + +export default domManip; diff --git a/test/unit/ajax.js b/test/unit/ajax.js index e4d0efca5..05dd7d36c 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -2502,9 +2502,9 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re } ); } ); -//----------- jQuery.domManip() +//----------- domManip() - QUnit.test( "trac-11264 - jQuery.domManip() - no side effect because of ajaxSetup or global events", function( assert ) { + QUnit.test( "trac-11264 - domManip() - no side effect because of ajaxSetup or global events", function( assert ) { assert.expect( 1 ); jQuery.ajaxSetup( { @@ -2558,7 +2558,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re ); QUnit.test( - "trac-11402 - jQuery.domManip() - script in comments are properly evaluated", + "trac-11402 - domManip() - script in comments are properly evaluated", function( assert ) { assert.expect( 2 ); jQuery( "#qunit-fixture" ).load( baseURL + "cleanScript.html", assert.async() );