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
This commit is contained in:
Michał Gołębiowski-Owczarek 2022-10-10 18:15:34 +02:00 committed by GitHub
parent aa231cd214
commit ee6e874075
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 113 additions and 107 deletions

View File

@ -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,

View File

@ -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;

View File

@ -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() );