Problem in brief
The HTML spec defines two remove behaviors for HTMLSelectElement:
remove() — inherited from ChildNode, removes the element from the DOM
remove(long index) — removes the option at the given index
Currently, HtmlUnit always dispatches remove() to remove(int) with index = 0, removing the first option instead of the element itself.
Reproducing
var select = document.getElementById('mySelect');
// select has 3 options and is inside a <form>
console.log(select.options.length); // 3
console.log(select.parentNode); // [object HTMLFormElement]
select.remove();
// Expected:
console.log(select.options.length); // 3
console.log(select.parentNode); // null
// Actual (HtmlUnit):
console.log(select.options.length); // 2 (first option removed!)
console.log(select.parentNode); // [object HTMLFormElement]
Root cause
The @JsxFunction HTMLSelectElement.remove(int index) shadows the inherited Element.remove().
When Rhino dispatches select.remove() with no arguments, it calls remove(int) and coerces the missing argument to 0.
Suggested fix
Change the parameter type to Object and check for Undefined to detect the no-argument case:
@JsxFunction
- public void remove(final int index) {
+ public void remove(final Object index) {
+ if (JavaScriptEngine.isUndefined(index)) {
+ super.remove();
+ return;
+ }
+
+ final int i = (int) JavaScriptEngine.toNumber(index);
- if (index < 0 && getBrowserVersion().hasFeature(JS_SELECT_REMOVE_IGNORE_IF_INDEX_OUTSIDE)) {
+ if (i < 0 && getBrowserVersion().hasFeature(JS_SELECT_REMOVE_IGNORE_IF_INDEX_OUTSIDE)) {
return;
}
// ...
}
Note: This fix has a minor edge case — select.remove(undefined) should remove the option at index 0
(undefined coerced to 0), but with this fix it behaves the same as select.remove() (removes from DOM).
This is because Rhino currently cannot distinguish no-argument calls from explicit undefined arguments — see the notes section below.
Notes on Rhino's handling of missing arguments vs explicit undefined
FunctionObject pads missing arguments with Undefined.instance before calling convertArg:
Object arg = (i < argsLength) ? args[i] : Undefined.instance;
This means both select.remove() (no args) and select.remove(undefined) (explicit undefined) arrive
at the Java method as Undefined.instance, making them indistinguishable when the parameter type is Object.
Changing the parameter type to Scriptable does not help either — convertArg for JAVA_SCRIPTABLE_TYPE
calls ScriptRuntime.toObjectOrNull(), which coerces both null and undefined to null.
It would make more sense for undefined to be coerced to Undefined.SCRIPTABLE_UNDEFINED rather than null.
This way, Java code could distinguish a missing argument from an explicit undefined argument.
A complete fix would require addressing this issue in htmlunit-core-js.
Problem in brief
The HTML spec defines two
removebehaviors forHTMLSelectElement:remove()— inherited fromChildNode, removes the element from the DOMremove(long index)— removes the option at the given indexCurrently, HtmlUnit always dispatches
remove()toremove(int)withindex = 0, removing the first option instead of the element itself.Reproducing
Root cause
The
@JsxFunctionHTMLSelectElement.remove(int index)shadows the inheritedElement.remove().When
Rhinodispatchesselect.remove()with no arguments, it callsremove(int)and coerces the missing argument to0.Suggested fix
Change the parameter type to
Objectand check forUndefinedto detect the no-argument case:Note: This fix has a minor edge case —
select.remove(undefined)should remove the option at index 0(
undefinedcoerced to0), but with this fix it behaves the same asselect.remove()(removes from DOM).This is because Rhino currently cannot distinguish no-argument calls from explicit
undefinedarguments — see the notes section below.Notes on Rhino's handling of missing arguments vs explicit
undefinedFunctionObjectpads missing arguments withUndefined.instancebefore callingconvertArg:This means both
select.remove()(no args) andselect.remove(undefined)(explicitundefined) arriveat the Java method as
Undefined.instance, making them indistinguishable when the parameter type isObject.Changing the parameter type to
Scriptabledoes not help either —convertArgforJAVA_SCRIPTABLE_TYPEcalls
ScriptRuntime.toObjectOrNull(), which coerces bothnullandundefinedtonull.It would make more sense for
undefinedto be coerced toUndefined.SCRIPTABLE_UNDEFINEDrather thannull.This way, Java code could distinguish a missing argument from an explicit
undefinedargument.A complete fix would require addressing this issue in
htmlunit-core-js.