Skip to content

Commit 6cca44f

Browse files
committed
HTMLElement#moveBefore() implemented
1 parent ff801c8 commit 6cca44f

File tree

4 files changed

+935
-0
lines changed

4 files changed

+935
-0
lines changed

src/changes/changes.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77
</properties>
88

99
<body>
10+
<release version="4.20.0" date="December xx, 2025" description="Bugfixes">
11+
<action type="add" dev="rbri" issue="#1053">
12+
HTMLElement#moveBefore() implemented.
13+
</action>
14+
<action type="update" dev="rbri">
15+
neko: code cleanup and warning fixes.
16+
</action>
17+
</release>
18+
19+
1020
<release version="4.19.0" date="November 23, 2025" description="Chrome/Edge 142, Firefox 145, support spread in arrays, neko improvements, Bugfixes">
1121
<action type="update" dev="rbri">
1222
neko: code quality and minor performance improvements.

src/main/java/org/htmlunit/html/DomNode.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,52 @@ protected void onAddedToDocumentFragment() {
13181318
}
13191319
}
13201320

1321+
/**
1322+
* Add a DOM node as a child to this node before the referenced node.
1323+
* If the referenced node is null, append to the end.
1324+
* @param movedDomNode the node to move
1325+
* @param referenceDomNode the node to move before
1326+
* @throws DOMException in case of problems
1327+
*/
1328+
public void moveBefore(final DomNode movedDomNode, final DomNode referenceDomNode) {
1329+
if (movedDomNode == referenceDomNode) {
1330+
return;
1331+
}
1332+
1333+
// If moving to the same position (node is already right before referenceNode), no operation needed
1334+
if (movedDomNode.getNextSibling() == referenceDomNode) {
1335+
return;
1336+
}
1337+
1338+
if (movedDomNode.isAncestorOf(this)) {
1339+
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
1340+
"The new child element contains the parent.");
1341+
}
1342+
1343+
if (referenceDomNode != null && !this.isAncestorOf(referenceDomNode)) {
1344+
throw new DOMException(DOMException.NOT_FOUND_ERR,
1345+
"The node before which the new node is to be inserted is not a child of this node.");
1346+
}
1347+
1348+
if (referenceDomNode != null && referenceDomNode.isAttachedToPage() && !movedDomNode.isAttachedToPage()) {
1349+
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
1350+
"State-preserving atomic move cannot be performed on nodes participating in an invalid hierarchy.");
1351+
}
1352+
1353+
// Remove the node from its current position
1354+
movedDomNode.basicRemove();
1355+
1356+
// Insert at the new position
1357+
if (referenceDomNode == null) {
1358+
// Move to the end
1359+
basicAppend(movedDomNode);
1360+
}
1361+
else {
1362+
// Insert before the reference node
1363+
referenceDomNode.basicInsertBefore(movedDomNode);
1364+
}
1365+
}
1366+
13211367
/**
13221368
* @return an {@link Iterable} over the children of this node
13231369
*/

src/main/java/org/htmlunit/javascript/host/Element.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,61 @@ public void insertAdjacentHTML(final String position, final String text) {
826826
parseHtmlSnippet(proxyDomNode, text);
827827
}
828828

829+
/**
830+
* Moves a given Node inside the invoking node as a direct child, before a given reference node.
831+
*
832+
* @param context the JavaScript context
833+
* @param scope the scope
834+
* @param thisObj the scriptable
835+
* @param args the arguments passed into the method
836+
* @param function the function
837+
*/
838+
@JsxFunction({CHROME, EDGE, FF})
839+
public static void moveBefore(final Context context, final Scriptable scope,
840+
final Scriptable thisObj, final Object[] args, final Function function) {
841+
((HTMLElement) thisObj).moveBeforeImpl(args);
842+
}
843+
844+
/**
845+
* Add a DOM node as a child to this node before the referenced node.
846+
* If the referenced node is null, append to the end.
847+
* @param args the arguments
848+
* @throws DOMException in case of problems
849+
*/
850+
protected void moveBeforeImpl(final Object[] args) throws org.w3c.dom.DOMException {
851+
if (args.length < 2) {
852+
throw JavaScriptEngine.typeError(
853+
"Failed to execute 'moveBefore' on 'Element': 2 arguments required, but only 0 present.");
854+
}
855+
856+
final Object movedNodeObject = args[0];
857+
if (!(movedNodeObject instanceof Node)) {
858+
throw JavaScriptEngine.typeError(
859+
"Failed to execute 'moveBefore' on 'Element': parameter 1 is not of type 'Node'.");
860+
}
861+
final Object referenceNodeObject = args[1];
862+
if (referenceNodeObject != null && !(referenceNodeObject instanceof Node)) {
863+
throw JavaScriptEngine.typeError(
864+
"Failed to execute 'moveBefore' on 'Element': parameter 2 is not of type 'Node'.");
865+
}
866+
867+
try {
868+
if (referenceNodeObject == null) {
869+
getDomNodeOrDie().moveBefore(((Node) movedNodeObject).getDomNodeOrDie(), null);
870+
return;
871+
}
872+
873+
getDomNodeOrDie().moveBefore(
874+
((Node) movedNodeObject).getDomNodeOrDie(), ((Node) referenceNodeObject).getDomNodeOrDie());
875+
}
876+
catch (final org.w3c.dom.DOMException e) {
877+
throw JavaScriptEngine.asJavaScriptException(
878+
getWindow(),
879+
"Failed to execute 'moveChild' on '" + this + ": " + e.getMessage(),
880+
e.code);
881+
}
882+
}
883+
829884
/**
830885
* Parses the specified HTML source code, appending the resulting content at the specified target location.
831886
* @param target the node indicating the position at which the parsed content should be placed
@@ -1687,6 +1742,7 @@ public boolean toggleAttribute(final String name, final Object force) {
16871742
/**
16881743
* Inserts a set of Node objects or string objects after the last child of the Element.
16891744
* String objects are inserted as equivalent Text nodes.
1745+
*
16901746
* @param context the context
16911747
* @param scope the scope
16921748
* @param thisObj this object
@@ -1706,6 +1762,7 @@ public static void append(final Context context, final Scriptable scope,
17061762
/**
17071763
* Inserts a set of Node objects or string objects before the first child of the Element.
17081764
* String objects are inserted as equivalent Text nodes.
1765+
*
17091766
* @param context the context
17101767
* @param scope the scope
17111768
* @param thisObj this object
@@ -1725,6 +1782,7 @@ public static void prepend(final Context context, final Scriptable scope,
17251782
/**
17261783
* Replaces the existing children of a Node with a specified new set of children.
17271784
* These can be string or Node objects.
1785+
*
17281786
* @param context the context
17291787
* @param scope the scope
17301788
* @param thisObj this object

0 commit comments

Comments
 (0)