Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/main/java/org/htmlunit/javascript/JavaScriptEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@
import org.htmlunit.corejs.javascript.AbstractEcmaObjectOperations;
import org.htmlunit.corejs.javascript.BaseFunction;
import org.htmlunit.corejs.javascript.Callable;
import org.htmlunit.corejs.javascript.ClassDescriptor;
import org.htmlunit.corejs.javascript.Context;
import org.htmlunit.corejs.javascript.ContextAction;
import org.htmlunit.corejs.javascript.ContextFactory;
import org.htmlunit.corejs.javascript.EcmaError;
import org.htmlunit.corejs.javascript.Function;
import org.htmlunit.corejs.javascript.FunctionObject;
import org.htmlunit.corejs.javascript.JSFunction;
import org.htmlunit.corejs.javascript.JavaScriptException;
import org.htmlunit.corejs.javascript.NativeArray;
import org.htmlunit.corejs.javascript.NativeArrayIterator;
Expand All @@ -59,6 +61,7 @@
import org.htmlunit.corejs.javascript.ScriptableObject;
import org.htmlunit.corejs.javascript.StackStyle;
import org.htmlunit.corejs.javascript.Symbol;
import org.htmlunit.corejs.javascript.SymbolKey;
import org.htmlunit.corejs.javascript.TopLevel;
import org.htmlunit.corejs.javascript.VarScope;
import org.htmlunit.corejs.javascript.WithScope;
Expand Down Expand Up @@ -325,6 +328,41 @@ public static void configureGlobalThis(
scopeContructorFunctionObject.setPrototype(ctorPrototypesPerJSName.get(extendedClassName));
}
}
else if (config.getDescriptor() != null) {
// Descriptor-based path (ClassDescriptor API)
final HtmlUnitScriptable classPrototype = config.getHostClass().getDeclaredConstructor().newInstance();
classPrototype.setParentScope(scope);
classPrototype.setClassName(jsClassName);

// buildConstructor wires up all methods/properties, sets proto.__proto__ to Object.prototype,
// and registers the constructor in the scope (which routes to globalThis via TopLevel.put)
final JSFunction ctor = config.getDescriptor().buildConstructor(
Context.getCurrentContext(), scope, classPrototype, false);
ctorPrototypesPerJSName.put(jsClassName, ctor);

// Override proto chain after buildConstructor (it defaults to Object.prototype)
if (extendedClassName != null) {
classPrototype.setPrototype(prototypesPerJSName.get(extendedClassName));
ctor.setPrototype(ctorPrototypesPerJSName.get(extendedClassName));
}

prototypes.put(config.getHostClass(), classPrototype);
prototypesPerJSName.put(jsClassName, classPrototype);

// Add Symbol.toStringTag to match the annotation-based setup in process()
ScriptableObject.defineProperty(classPrototype, SymbolKey.TO_STRING_TAG, jsClassName,
ScriptableObject.DONTENUM | ScriptableObject.READONLY);

if (!config.isJsObject()) {
// buildConstructor registers the ctor in the scope (→ globalThis), so remove it
globalThis.delete(jsClassName);
}

final String alias = config.getJsConstructorAlias();
if (alias != null) {
ScriptableObject.defineProperty(globalThis, alias, ctor, ScriptableObject.DONTENUM);
}
}
else {
final HtmlUnitScriptable classPrototype = configureClass(config, scope);
prototypes.put(config.getHostClass(), classPrototype);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,63 @@ protected AbstractJavaScriptConfiguration(final BrowserVersion browser, final Cl
}
}
}

final SupportedBrowser expectedBrowser = toSupportedBrowser(browser);
for (final HtmlUnitClassDescriptor descriptor : getDescriptors()) {
final org.htmlunit.corejs.javascript.ClassDescriptor classDescriptor =
descriptor.forBrowser(expectedBrowser);
if (classDescriptor != null) {
final ClassConfiguration config = new ClassConfiguration(
descriptor.getHostClass(),
descriptor.getDomClasses(),
descriptor.isJsObject(),
null,
descriptor.getExtendedClassName(),
classDescriptor);
configuration_.add(config);
}
}
}

/**
* @return the classes configured by this configuration
*/
protected abstract Class<? extends HtmlUnitScriptable>[] getClasses();

/**
* Returns the descriptor-based host classes registered by this configuration.
* Subclasses override this to return their migrated descriptors.
*
* @return the descriptors, never {@code null}
*/
protected HtmlUnitClassDescriptor[] getDescriptors() {
return new HtmlUnitClassDescriptor[0];
}

/**
* Maps a {@link BrowserVersion} to the corresponding {@link SupportedBrowser} constant.
*
* @param browserVersion the browser version
* @return the matching {@link SupportedBrowser}
*/
private static SupportedBrowser toSupportedBrowser(final BrowserVersion browserVersion) {
if (browserVersion != null) {
if (browserVersion.isChrome()) {
return CHROME;
}
if (browserVersion.isEdge()) {
return EDGE;
}
if (browserVersion.isFirefoxESR()) {
return FF_ESR;
}
if (browserVersion.isFirefox()) {
return FF;
}
}
return CHROME;
}

/**
* Gets all the configurations.
* @return the class configurations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.List;
import java.util.Map;

import org.htmlunit.corejs.javascript.ClassDescriptor;
import org.htmlunit.corejs.javascript.ScriptableObject;
import org.htmlunit.corejs.javascript.Symbol;
import org.htmlunit.javascript.HtmlUnitScriptable;
Expand Down Expand Up @@ -54,6 +55,7 @@ public final class ClassConfiguration {
private final Class<?>[] domClasses_;
private final boolean jsObject_;
private final String className_;
private final ClassDescriptor descriptor_;

/**
* Constructor.
Expand All @@ -66,6 +68,22 @@ public final class ClassConfiguration {
*/
public ClassConfiguration(final Class<? extends HtmlUnitScriptable> hostClass, final Class<?>[] domClasses,
final boolean jsObject, final String className, final String extendedClassName) {
this(hostClass, domClasses, jsObject, className, extendedClassName, null);
}

/**
* Constructor for descriptor-based host classes.
*
* @param hostClass - the class implementing this functionality
* @param domClasses the DOM classes that this object supports
* @param jsObject boolean flag for if this object is a JavaScript object
* @param className the class name, can be null
* @param extendedClassName the extended class name
* @param descriptor the {@link ClassDescriptor} to use for wiring, or {@code null} for annotation-based setup
*/
public ClassConfiguration(final Class<? extends HtmlUnitScriptable> hostClass, final Class<?>[] domClasses,
final boolean jsObject, final String className, final String extendedClassName,
final ClassDescriptor descriptor) {
hostClass_ = hostClass;
hostClassSimpleName_ = hostClass_.getSimpleName();
jsObject_ = jsObject;
Expand All @@ -77,6 +95,7 @@ public ClassConfiguration(final Class<? extends HtmlUnitScriptable> hostClass, f
className_ = className;
}
extendedClassName_ = extendedClassName;
descriptor_ = descriptor;
}

void setJSConstructor(final String name, final Member jsConstructor) {
Expand Down Expand Up @@ -308,6 +327,16 @@ public String getClassName() {
return className_;
}

/**
* Returns the {@link ClassDescriptor} for this class, or {@code null} if this class
* uses the annotation-based setup.
*
* @return the descriptor, or {@code null}
*/
public ClassDescriptor getDescriptor() {
return descriptor_;
}

/**
* Class used to contain the property information if the property is readable, writable and the
* methods that implement the get and set functions.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2002-2026 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.htmlunit.javascript.configuration;

import org.htmlunit.corejs.javascript.ClassDescriptor;
import org.htmlunit.javascript.HtmlUnitScriptable;

/**
* Descriptor-based registration for a JavaScript host class.
*
* <p>This interface is part of the incremental migration from the annotation-based
* setup ({@code @JsxClass}, {@code @JsxFunction}, etc.) to Rhino's
* {@link ClassDescriptor} API. Both paths can coexist during the transition.</p>
*
* <p>Classes that have been migrated implement this interface as a static
* {@code HTMLUNIT_DESCRIPTOR} constant and no longer carry {@code @Jsx*} annotations.
* They are registered via {@link AbstractJavaScriptConfiguration#getDescriptors()}
* rather than being listed in the {@code getClasses()} array.</p>
*
* @author Ronald Brill
*/
public interface HtmlUnitClassDescriptor {

/**
* Returns the {@link ClassDescriptor} for the given browser, or {@code null}
* if this class is not supported by that browser.
*
* @param browser the target browser
* @return the descriptor, or {@code null} if unsupported
*/
ClassDescriptor forBrowser(SupportedBrowser browser);

/**
* Returns the host class that implements this JavaScript object.
*
* @return the host class
*/
Class<? extends HtmlUnitScriptable> getHostClass();

/**
* Returns the DOM classes that this JavaScript object wraps.
* May be an empty array if the object has no DOM counterpart.
*
* @return the DOM classes, never {@code null}
*/
Class<?>[] getDomClasses();

/**
* Returns whether this object is visible as a named property of the global object
* (i.e. {@code window.ClassName} is defined).
*
* @return {@code true} if the constructor is exposed on the global object
*/
boolean isJsObject();

/**
* Returns the simple name of the parent JavaScript class, or an empty string
* if the class does not extend another HtmlUnit scriptable class.
*
* @return the extended class name, never {@code null}
*/
String getExtendedClassName();

/**
* Returns an additional name under which the constructor should be registered
* on the global object, or {@code null} if no alias is needed.
*
* @return the alias, or {@code null}
*/
default String getJsConstructorAlias() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -544,17 +544,17 @@ public final class JavaScriptConfiguration extends AbstractJavaScriptConfigurati
static final Class<? extends HtmlUnitScriptable>[] CLASSES_ = new Class[] {
// level 1
AbortController.class, AbstractRange.class, Atomics.class, AudioBuffer.class, AudioListener.class,
AudioParam.class, BarProp.class, Blob.class, CSS.class, CSSRule.class, CSSRuleList.class,
AudioParam.class, Blob.class, CSS.class, CSSRule.class, CSSRuleList.class,
CSSStyleDeclaration.class, Cache.class, CacheStorage.class, CanvasGradient.class, CanvasPattern.class,
CanvasRenderingContext2D.class, CaretPosition.class, Credential.class, CredentialsContainer.class, Crypto.class,
CryptoKey.class, CustomElementRegistry.class, DOMError.class, DOMException.class, DOMImplementation.class,
DOMMatrixReadOnly.class, DOMParser.class, DOMPointReadOnly.class, DOMRectList.class, DOMRectReadOnly.class,
DOMStringList.class, DOMStringMap.class, DOMTokenList.class, DataTransfer.class, DataTransferItem.class,
DataTransferItemList.class, Event.class, EventTarget.class, External.class, FileList.class, FileSystem.class,
FileSystemDirectoryReader.class, FileSystemEntry.class, FontFace.class, FormData.class, Gamepad.class,
GamepadButton.class, Geolocation.class, GeolocationCoordinates.class, GeolocationPosition.class,
FileSystemDirectoryReader.class, FileSystemEntry.class, FontFace.class, FormData.class,
Geolocation.class, GeolocationPosition.class,
GeolocationPositionError.class, HTMLOptionsCollection.class, Headers.class, History.class, IDBCursor.class,
IDBFactory.class, IDBIndex.class, IDBKeyRange.class, IDBObjectStore.class, IdleDeadline.class,
IDBFactory.class, IDBIndex.class, IDBKeyRange.class, IDBObjectStore.class,
ImageBitmap.class, ImageBitmapRenderingContext.class, ImageData.class, InputDeviceCapabilities.class,
IntersectionObserver.class, IntersectionObserverEntry.class, KeyframeEffect.class, Location.class,
MIDIInputMap.class, MIDIOutputMap.class, MediaDeviceInfo.class, MediaError.class, MediaKeyStatusMap.class,
Expand All @@ -571,7 +571,7 @@ public final class JavaScriptConfiguration extends AbstractJavaScriptConfigurati
SVGAnimatedLengthList.class, SVGAnimatedNumber.class, SVGAnimatedNumberList.class,
SVGAnimatedPreserveAspectRatio.class, SVGAnimatedRect.class, SVGAnimatedString.class,
SVGAnimatedTransformList.class, SVGLength.class, SVGLengthList.class, SVGMatrix.class, SVGNumber.class,
SVGNumberList.class, SVGPoint.class, SVGPointList.class, SVGPreserveAspectRatio.class, SVGRect.class,
SVGNumberList.class, SVGPoint.class, SVGPointList.class, SVGPreserveAspectRatio.class,
SVGStringList.class, SVGTransform.class, SVGTransformList.class, SVGUnitTypes.class, Selection.class,
SpeechGrammar.class, SpeechGrammarList.class,
SpeechSynthesisVoice.class, Storage.class, StorageManager.class, StyleMedia.class, StyleSheet.class,
Expand Down Expand Up @@ -710,6 +710,18 @@ protected Class<? extends HtmlUnitScriptable>[] getClasses() {
return CLASSES_;
}

@Override
protected HtmlUnitClassDescriptor[] getDescriptors() {
return new HtmlUnitClassDescriptor[] {
BarProp.HTMLUNIT_DESCRIPTOR,
Gamepad.HTMLUNIT_DESCRIPTOR,
GamepadButton.HTMLUNIT_DESCRIPTOR,
GeolocationCoordinates.HTMLUNIT_DESCRIPTOR,
IdleDeadline.HTMLUNIT_DESCRIPTOR,
SVGRect.HTMLUNIT_DESCRIPTOR,
};
}

/**
* @return the configuration of the globalThis class
*/
Expand Down
46 changes: 36 additions & 10 deletions src/main/java/org/htmlunit/javascript/host/BarProp.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,50 @@
*/
package org.htmlunit.javascript.host;

import org.htmlunit.corejs.javascript.ClassDescriptor;
import org.htmlunit.corejs.javascript.Undefined;
import org.htmlunit.javascript.HtmlUnitScriptable;
import org.htmlunit.javascript.configuration.JsxClass;
import org.htmlunit.javascript.configuration.JsxConstructor;
import org.htmlunit.javascript.configuration.HtmlUnitClassDescriptor;
import org.htmlunit.javascript.configuration.SupportedBrowser;

/**
* A JavaScript object for {@code BarProp}.
*
* @author Ahmed Ashour
* @author Ronald Brill
*/
@JsxClass
public class BarProp extends HtmlUnitScriptable {

/**
* JavaScript constructor.
*/
@JsxConstructor
public void jsConstructor() {
// nothing to do
}
/** Descriptor for registering this class with the JavaScript engine. */
public static final HtmlUnitClassDescriptor HTMLUNIT_DESCRIPTOR = new HtmlUnitClassDescriptor() {

private static final ClassDescriptor DESCRIPTOR_ = new ClassDescriptor.Builder("BarProp", 0,
(cx, f, callerObj, scope, thisObj, args) -> Undefined.instance)
.build();

@Override
public ClassDescriptor forBrowser(final SupportedBrowser browser) {
return DESCRIPTOR_;
}

@Override
public Class<? extends HtmlUnitScriptable> getHostClass() {
return BarProp.class;
}

@Override
public Class<?>[] getDomClasses() {
return new Class<?>[0];
}

@Override
public boolean isJsObject() {
return true;
}

@Override
public String getExtendedClassName() {
return "";
}
};
}
Loading