diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSNumber.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSNumber.java
index f4983570a4f1..9cd131153c5e 100644
--- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSNumber.java
+++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSNumber.java
@@ -119,4 +119,140 @@ public boolean equals(Object that) {
public int hashCode() {
return javaDouble().hashCode();
}
+
+ @JS.Coerce
+ @JS(value = "return isFinite(number);")
+ public static native boolean isFinite(JSNumber number);
+
+ @JS.Coerce
+ @JS(value = "return isFinite(number);")
+ public static native boolean isFinite(double number);
+
+ @JS.Coerce
+ @JS(value = "return Number.isInteger(number);")
+ public static native boolean isInteger(JSNumber number);
+
+ @JS.Coerce
+ @JS(value = "return Number.isInteger(number);")
+ public static native boolean isInteger(double number);
+
+ @JS.Coerce
+ @JS(value = "return isNaN(number);")
+ public static native boolean isNaN(JSNumber number);
+
+ @JS.Coerce
+ @JS(value = "return isNaN(number);")
+ public static native boolean isNaN(double number);
+
+ @JS.Coerce
+ @JS(value = "return Number.isSafeInteger(number);")
+ public static native boolean isSafeInteger(JSNumber number);
+
+ @JS.Coerce
+ @JS(value = "return Number.isSafeInteger(number);")
+ public static native boolean isSafeInteger(double number);
+
+ @JS.Coerce
+ @JS(value = "return Number.parseFloat(number);")
+ public static native double parseFloat(String number);
+
+ @JS.Coerce
+ @JS(value = "return Number.parseInt(number);")
+ private static native double parseIntRaw(String number);
+
+ @JS.Coerce
+ @JS(value = "return Number.parseInt(number, radix);")
+ private static native double parseIntRaw(String number, int radix);
+
+ public static long parseInt(String number) {
+ double result = parseIntRaw(number);
+ if (Double.isNaN(result)) {
+ throw new IllegalArgumentException("Invalid integer: " + number);
+ }
+ return (long) result;
+ }
+
+ public static long parseInt(String number, int radix) {
+ double result = parseIntRaw(number, radix);
+ if (Double.isNaN(result)) {
+ throw new IllegalArgumentException("Invalid integer: " + number + " with radix " + radix);
+ }
+ return (long) result;
+ }
+
+ @JS.Coerce
+ @JS(value = "return Number.EPSILON;")
+ public static native double epsilon();
+
+ @JS.Coerce
+ @JS(value = "return Number.MAX_SAFE_INTEGER;")
+ public static native long maxSafeInteger();
+
+ @JS.Coerce
+ @JS(value = "return Number.MAX_VALUE;")
+ public static native double maxValue();
+
+ @JS.Coerce
+ @JS(value = "return Number.MIN_SAFE_INTEGER;")
+ public static native long minSafeInteger();
+
+ @JS.Coerce
+ @JS(value = "return Number.MIN_VALUE;")
+ public static native double minValue();
+
+ @JS.Coerce
+ @JS(value = "return Number.NaN;")
+ public static native double nan();
+
+ @JS.Coerce
+ @JS(value = "return Number.NEGATIVE_INFINITY;")
+ public static native double negativeInfinity();
+
+ @JS.Coerce
+ @JS(value = "return Number.POSITIVE_INFINITY;")
+ public static native double positiveInfinity();
+
+ @JS.Coerce
+ @JS(value = "return this.toExponential();")
+ public native String toExponential();
+
+ @JS.Coerce
+ @JS(value = "return this.toExponential(fractionDigits);")
+ public native String toExponential(int fractionDigits);
+
+ @JS.Coerce
+ @JS(value = "return this.toFixed();")
+ public native String toFixed();
+
+ @JS.Coerce
+ @JS(value = "return this.toFixed(digits);")
+ public native String toFixed(int digits);
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleString();")
+ public native String toLocaleString();
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleString(locales);")
+ public native String toLocaleString(String locales);
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleString(locales, options);")
+ public native String toLocaleString(String locales, JSObject options);
+
+ @JS.Coerce
+ @JS(value = "return this.toPrecision();")
+ public native String toPrecision();
+
+ @JS.Coerce
+ @JS(value = "return this.toPrecision(precision);")
+ public native String toPrecision(int precision);
+
+ @JS.Coerce
+ @JS(value = "return this.toString(radix);")
+ public native String toString(int radix);
+
+ @JS.Coerce
+ @JS(value = "return this.valueOf();")
+ public native double valueOf();
}
diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSObject.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSObject.java
index cbae872ab70e..9f693f68577b 100644
--- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSObject.java
+++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSObject.java
@@ -49,14 +49,14 @@
* wrapped into a JSObject instance. The JSObject allows the Java code to
* access the fields of the underlying JavaScript object using the get and
* set methods.
- *
+ *
* The Java {@link JSObject} instance that corresponds to the JavaScript object is called a Java * mirror. Vice versa, the JavaScript instance is a JavaScript mirror for that * {@link JSObject} instance. * * *
* Here are a few examples of creating and modifying anonymous JavaScript objects: * *
@@ -68,7 +68,7 @@
* System.out.println(pair.get("x"));
* System.out.println(pair.get("y"));
*
- *
+ *
* In this example, using the {@code JSObject} methods, the user can access the x and
* y fields.
*
@@ -76,32 +76,32 @@
* pair.set("x", 5.4);
* System.out.println(pair.get("x"));
*
- *
+ *
* The code above sets a new value for the x field, and then prints the new value.
*
*
*
* A {@code JSObject} can be a Java-side wrapper for a JavaScript {@code Function} object. The * JavaScript {@code Function} value can be returned by the JavaScript code of the method annotated * with the {@link JS} annotation. - * + *
* The Java program can then call the underlying function by calling the * {@link JSObject#call(Object, Object...)} method. If the underlying JavaScript object is not * callable, then calling {@code call} will result in an exception. - * + *
* The {@code call} method has the following signature: * *
* public Object call(Object thisArgument, Object... arguments); *- * + *
* The {@code invoke} method has the following signature: * *
* public Object invoke(Object... arguments); *- * + *
* The difference is that the method {@code call} takes an object for specifying {@code this} of the * JavaScript function, while {@code invoke} uses the underlying {@code JSObject} as the value of * {@code this}. @@ -130,13 +130,13 @@ * * *
* The second purpose of {@link JSObject} is to declare JavaScript classes as classes in Java code, * in a way that makes the Java objects look-and-feel like native JavaScript objects. Users should * subclass the {@link JSObject} class when they need to define a JavaScript class whose fields and * methods can be accessed conveniently from Java, without the {@link JSObject#get(Object)} and * {@link JSObject#set(Object, Object)} methods. - * + *
* Directly exposing a Java object to JavaScript code means that the JavaScript code is able to * manipulate the data within the object (e.g. mutate fields, add new fields, or redefine existing * fields), which is not allowed by default for regular Java classes. Extending {@link JSObject} @@ -144,7 +144,7 @@ * One of the use-cases for these functionalities are JavaScript frameworks that redefine properties * of JavaScript objects with custom getters and setters, with the goal of enabling data-binding or * reactive updates. - * + *
* In a subclass of {@link JSObject}, every JavaScript property directly corresponds to the Java * field of the same name. Consequently, all these properties point to native JavaScript values * rather than Java values, so bridge methods are generated that are called for each property access @@ -154,7 +154,7 @@ * corresponding Java field. For this reason, the bridge methods also generate check-casts on every * access: if the JavaScript property that corresponds to the Java field does not contain a * compatible value, a {@link ClassCastException} is thrown. - * + *
* There are several restrictions imposed on {@link JSObject} subclasses: *
* The preceding Java class is effectively translated to the corresponding JavaScript class: * *
@@ -194,7 +194,7 @@ * } * } *- * + *
* The {@code Point} class can be used from Java as if it were a normal Java class: * *
@@ -203,7 +203,7 @@ * System.out.println(p.y); * System.out.println(p.absolute()); *- * + *
* All accesses to the fields {@code x} and {@code y} are rewritten to accesses on the corresponding * JavaScript properties. JavaScript code may assign values to these properties that violate the * type of the corresponding Java fields, but a subsequent Java read of such a field will result in @@ -219,7 +219,7 @@ * * *
* When an object of the {@link JSObject} subclass is passed from Java to JavaScript code using the * {@link JS} annotation, the object is converted from its Java representation to its JavaScript * representation. Changes in the JavaScript representation are reflected in the Java representation @@ -236,7 +236,7 @@ * reset(p0, 0.0, 0.0); * System.out.println(p0.x + ", " + p0.y); * - * + *
* A {@link Class} object that represents {@link JSObject} can also be passed to JavaScript code. * The {@link Class} object is wrapped in a proxy, which can be used inside a {@code new} expression * to instantiate the object of the corresponding class from JavaScript. @@ -250,14 +250,14 @@ * Point p1 = create(Point.class, 1.25, 0.5); * System.out.println(p1.x + ", " + p1.y); * - * + *
* Note that creating an object in JavaScript and passing it to Java several times does not * guarantee that the same mirror instance is returned each time -- each time a JavaScript object * becomes a Java mirror, a different instance of the mirror may be returned. * * *
* The {@link JSObject} class allows access to properties of any JavaScript object using the * {@link JSObject#get(Object)} and {@link JSObject#set(Object, Object)} methods. In situations * where the programmer knows the relevant properties of a JavaScript object (for example, when @@ -265,14 +265,14 @@ * "imported" to Java. To do this, the user declares a {@link JSObject} subclass that serves as a * facade to the JavaScript object. This subclass must be annotated with the {@link JS.Import} * annotation. - * + *
* The name of the declared class Java must match the name of the JavaScript class that is being * imported. The package name of the Java class is not taken into account -- the same JavaScript * class can be imported multiple times from within separate packages. - * + *
* The exposed JavaScript fields must be declared as {@code protected} or {@code public}. The * constructor parameters must match those of the JavaScript class, and its body must be empty. - * + *
* Here is an example of a class declared in JavaScript: * *
@@ -283,7 +283,7 @@ * } * } *- * + *
* To import this class in Java code, we need the following declaration in Java: * *
@@ -296,19 +296,19 @@ * } * } *- * + *
* The fields declared in the {@code Rectangle} class are directly mapped to the properties of the * underlying JavaScript object. If the type of the property of the underlying JavaScript object * does not match the type of the field declared in Java, then a field-read in Java will throw a * {@link ClassCastException}. - * + *
* The {@code Rectangle} class can be instantiated from Java as follows: * *
* Rectangle r = new Rectangle(640, 480); * System.out.println(r.width + "x" + r.height); *- * + *
* A JavaScript object whose {@code constructor} property matches the imported JavaScript class can * be converted to the declared Java class when the JavaScript code passes a value to Java. Here is * a code example that creates the {@code Rectangle} object in JavaScript, and passes it to Java: @@ -317,13 +317,13 @@ * @JS("return new Rectangle(width, height);") * Rectangle createRectangle(int width, int height); * - * + *
* Another way to convert a JavaScript object to a Java facade is to call the * {@link JSObject#as(Class)} method to cast the {@link JSObject} instance to the proper subtype. * * *
* The users can annotate the exported classes with the {@link JS.Export} annotation to denote that * the {@link JSObject} subclass should be made available to JavaScript code. Exported classes can * be accessed using the JavaScript VM-instance API, using the `exports` property. @@ -344,7 +344,7 @@ * } * } * - * + *
* The exported class can then be used from JavaScript code as follows: * *
@@ -395,7 +395,7 @@ public String typeof() {
/**
* Sets the value of the key passed as the argument in the JavaScript object.
*
- * @param key the object under which the value should be placed in the JavaScript object
+ * @param key the object under which the value should be placed in the JavaScript object
* @param newValue the value that should be placed under the given key in the JavaScript object
*/
@JS("this[key] = newValue;")
@@ -413,7 +413,7 @@ public String typeof() {
* Invoke the underlying JavaScript function, if this object is callable.
*
* @param args The array of Java arguments, which is converted to JavaScript and passed to the
- * underlying JavaScript function
+ * underlying JavaScript function
* @return The result of the JavaScript function, converted to the corresponding Java value
*/
@JS("return this.apply(this, conversion.extractJavaScriptArray(args[runtime.symbol.javaNative]));")
@@ -424,8 +424,8 @@ public String typeof() {
* in the function, if this object is callable.
*
* @param thisArg The value for the binding of {@code this} inside the JavaScript function
- * @param args The array of Java arguments, which is converted to JavaScript and passed to the
- * underlying JavaScript function
+ * @param args The array of Java arguments, which is converted to JavaScript and passed to the
+ * underlying JavaScript function
* @return The result of the JavaScript function, converted to the corresponding Java value
*/
@JS("return this.apply(thisArg, conversion.extractJavaScriptArray(args[runtime.symbol.javaNative]));")
@@ -547,4 +547,120 @@ public T as(Class cls) {
public boolean equalsJavaScript(JSObject that) {
return referenceEquals(this, that).asBoolean();
}
+
+ @JS.Coerce
+ @JS(value = "return Object.create(proto);")
+ public static native JSObject create(JSObject proto);
+
+ @JS.Coerce
+ @JS(value = "return Object.create(proto, properties);")
+ public static native JSObject create(JSObject proto, JSObject properties);
+
+ @JS.Coerce
+ @JS(value = "return Object.defineProperties(obj, props);")
+ public static native JSObject defineProperties(JSObject obj, JSObject props);
+
+ @JS.Coerce
+ @JS(value = "return Object.defineProperty(obj, prop, descriptor);")
+ public static native JSObject defineProperty(JSObject obj, JSString prop, JSObject descriptor);
+
+ @JS.Coerce
+ @JS(value = "return Object.defineProperty(obj, prop, descriptor);")
+ public static native JSObject defineProperty(JSObject obj, String prop, JSObject descriptor);
+
+ @JS.Coerce
+ @JS(value = "return Object.entries(obj);")
+ public static native JSObject entries(JSObject obj);
+
+ @JS.Coerce
+ @JS(value = "return Object.freeze(obj);")
+ public static native JSObject freeze(JSObject obj);
+
+ @JS.Coerce
+ @JS(value = "return Object.fromEntries(iterable);")
+ public static native JSObject fromEntries(JSObject iterable);
+
+ @JS.Coerce
+ @JS(value = "return Object.getOwnPropertyDescriptor(obj, prop);")
+ public static native JSObject getOwnPropertyDescriptor(JSObject obj, String prop);
+
+ @JS.Coerce
+ @JS(value = "return Object.getOwnPropertyNames(obj);")
+ public static native JSObject getOwnPropertyNames(JSObject obj);
+
+ @JS.Coerce
+ @JS(value = "return Object.groupBy(items, callback);")
+ public static native JSObject groupBy(JSObject items, JSValue callback);
+
+ @JS.Coerce
+ @JS(value = "return Object.hasOwn(obj, prop);")
+ public static native boolean hasOwn(JSObject obj, String prop);
+
+ @JS.Coerce
+ @JS(value = "return Object.hasOwn(obj, prop);")
+ public static native boolean hasOwn(JSObject obj, JSString prop);
+
+ @JS.Coerce
+ @JS(value = "return Object.hasOwn(obj, prop);")
+ public static native boolean hasOwn(JSObject obj, JSSymbol prop);
+
+ @JS.Coerce
+ @JS(value = "return Object.is(value1, value2);")
+ public static native boolean is(JSValue value1, JSValue value2);
+
+ @JS.Coerce
+ @JS(value = "return Object.isExtensible(obj);")
+ public static native boolean isExtensible(JSObject obj);
+
+ @JS.Coerce
+ @JS(value = "return Object.isFrozen(obj);")
+ public static native boolean isFrozen(JSObject obj);
+
+ @JS.Coerce
+ @JS(value = "return Object.isSealed(obj);")
+ public static native boolean isSealed(JSObject obj);
+
+ @JS.Coerce
+ @JS(value = "return Object.keys(obj);")
+ public static native JSObject keys(JSObject obj);
+
+ @JS.Coerce
+ @JS(value = "return Object.preventExtensions(obj);")
+ public static native JSObject preventExtensions(JSObject obj);
+
+ @JS.Coerce
+ @JS(value = "return Object.seal(obj);")
+ public static native JSObject seal(JSObject obj);
+
+ @JS.Coerce
+ @JS(value = "return Object.setPrototypeOf(obj, proto);")
+ public static native JSObject setPrototypeOf(JSObject obj, JSObject proto);
+
+ @JS.Coerce
+ @JS(value = "return Object.values(obj);")
+ public static native JSObject values(JSObject obj);
+
+ @JS.Coerce
+ @JS(value = "return this.isPrototypeOf(object);")
+ public native boolean isPrototypeOf(JSObject object);
+
+ @JS.Coerce
+ @JS(value = "return this.propertyIsEnumerable(prop);")
+ public native boolean propertyIsEnumerable(String prop);
+
+ @JS.Coerce
+ @JS(value = "return this.propertyIsEnumerable(prop);")
+ public native boolean propertyIsEnumerable(JSString prop);
+
+ @JS.Coerce
+ @JS(value = "return this.propertyIsEnumerable(prop);")
+ public native boolean propertyIsEnumerable(JSSymbol prop);
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleString();")
+ public native String toLocaleString();
+
+ @JS.Coerce
+ @JS(value = "return this.valueOf();")
+ public native JSValue valueOf();
}
diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSString.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSString.java
index e7ba442568c2..1fa152efa620 100644
--- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSString.java
+++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSString.java
@@ -82,4 +82,306 @@ public boolean equals(Object that) {
public int hashCode() {
return javaString().hashCode();
}
+
+ @JS.Coerce
+ @JS(value = "return String.fromCharCode(...codeUnits);")
+ public static native JSString fromCharCode(int... codeUnits);
+
+ @JS.Coerce
+ @JS(value = "return String.fromCharCode(...codeUnits);")
+ public static native JSString fromCharCode(char... codeUnits);
+
+
+ @JS.Coerce
+ @JS(value = "return String.fromCodePoint(...codePoints);")
+ public static native JSString fromCodePoint(int... codePoints);
+
+ @JS(value = "return String.raw(template, ...Array.from(substitutions));")
+ public static native JSString raw(JSObject template, Object... substitutions);
+
+ @JS.Coerce
+ @JS(value = "return this.at(index);")
+ public native JSValue at(int index);
+
+ @JS.Coerce
+ @JS(value = "return this.charAt(index);")
+ public native JSString charAt(int index);
+
+ @JS.Coerce
+ @JS(value = """
+ const code = this.charCodeAt(index);
+ return Number.isNaN(code) ? -1 : code;
+ """)
+ public native int charCodeAt(int index);
+
+ @JS.Coerce
+ @JS(value = """
+ const code = this.codePointAt(index);
+ return code === undefined ? -1 : code;
+ """)
+ public native int codePointAt(int index);
+
+ @JS.Coerce
+ @JS(value = "return this.concat.apply(this, strings);")
+ public native JSString concat(JSString... strings);
+
+ @JS.Coerce
+ @JS(value = "return this.endsWith(searchString);")
+ public native boolean endsWith(String searchString);
+
+ @JS.Coerce
+ @JS(value = "return this.endsWith(searchString, endPosition);")
+ public native boolean endsWith(String searchString, int endPosition);
+
+ @JS.Coerce
+ @JS(value = "return this.endsWith(searchString);")
+ public native boolean endsWith(JSString searchString);
+
+ @JS.Coerce
+ @JS(value = "return this.endsWith(searchString, endPosition);")
+ public native boolean endsWith(JSString searchString, int endPosition);
+
+ @JS.Coerce
+ @JS(value = "return this.includes(searchString);")
+ public native boolean includes(String searchString);
+
+ @JS.Coerce
+ @JS(value = "return this.includes(searchString, position);")
+ public native boolean includes(String searchString, int position);
+
+ @JS.Coerce
+ @JS(value = "return this.includes(searchString);")
+ public native boolean includes(JSString searchString);
+
+ @JS.Coerce
+ @JS(value = "return this.includes(searchString, position);")
+ public native boolean includes(JSString searchString, int position);
+
+ @JS.Coerce
+ @JS(value = "return this.indexOf(searchString);")
+ public native int indexOf(String searchString);
+
+ @JS.Coerce
+ @JS(value = "return this.indexOf(searchString, position);")
+ public native int indexOf(String searchString, int position);
+
+ @JS.Coerce
+ @JS(value = "return this.indexOf(searchString);")
+ public native int indexOf(JSString searchString);
+
+ @JS.Coerce
+ @JS(value = "return this.indexOf(searchString, position);")
+ public native int indexOf(JSString searchString, int position);
+
+ @JS.Coerce
+ @JS(value = "return this.isWellFormed();")
+ public native boolean isWellFormed();
+
+ @JS.Coerce
+ @JS(value = "return this.lastIndexOf(searchString);")
+ public native int lastIndexOf(String searchString);
+
+ @JS.Coerce
+ @JS(value = "return this.lastIndexOf(searchString, position);")
+ public native int lastIndexOf(String searchString, int position);
+
+ @JS.Coerce
+ @JS(value = "return this.lastIndexOf(searchString);")
+ public native int lastIndexOf(JSString searchString);
+
+ @JS.Coerce
+ @JS(value = "return this.lastIndexOf(searchString, position);")
+ public native int lastIndexOf(JSString searchString, int position);
+
+ @JS.Coerce
+ @JS(value = "return this.localeCompare(compareString);")
+ public native int localeCompare(String compareString);
+
+ @JS.Coerce
+ @JS(value = "return this.localeCompare(compareString, locales);")
+ public native int localeCompare(String compareString, String locales);
+
+ @JS.Coerce
+ @JS(value = "return this.localeCompare(compareString, locales, options);")
+ public native int localeCompare(String compareString, String locales, JSObject options);
+
+ @JS.Coerce
+ @JS(value = "return this.localeCompare(compareString);")
+ public native int localeCompare(JSString compareString);
+
+ @JS.Coerce
+ @JS(value = "return this.localeCompare(compareString, locales);")
+ public native int localeCompare(JSString compareString, JSString locales);
+
+ @JS.Coerce
+ @JS(value = "return this.localeCompare(compareString, locales, options);")
+ public native int localeCompare(JSString compareString, JSString locales, JSObject options);
+
+ @JS.Coerce
+ @JS(value = "return this.match(regexp);")
+ public native JSObject match(String regexp);
+
+ @JS.Coerce
+ @JS(value = "return this.match(regexp);")
+ public native JSObject match(JSObject regexp);
+
+ @JS.Coerce
+ @JS(value = "return this.match(regexp);")
+ public native JSObject match(JSString regexp);
+
+ @JS.Coerce
+ @JS(value = "return this.matchAll(regexp);")
+ public native JSObject matchAll(JSObject regexp);
+
+ @JS.Coerce
+ @JS(value = "return this.normalize();")
+ public native JSString normalize();
+
+ @JS.Coerce
+ @JS(value = "return this.normalize(form);")
+ public native JSString normalize(String form);
+
+ @JS.Coerce
+ @JS(value = "return this.padEnd(targetLength);")
+ public native JSString padEnd(int targetLength);
+
+ @JS.Coerce
+ @JS(value = "return this.padEnd(targetLength, padString);")
+ public native JSString padEnd(int targetLength, String padString);
+
+ @JS.Coerce
+ @JS(value = "return this.padEnd(targetLength, padString);")
+ public native JSString padEnd(int targetLength, JSString padString);
+
+ @JS.Coerce
+ @JS(value = "return this.padStart(targetLength);")
+ public native JSString padStart(int targetLength);
+
+ @JS.Coerce
+ @JS(value = "return this.padStart(targetLength, padString);")
+ public native JSString padStart(int targetLength, String padString);
+
+ @JS.Coerce
+ @JS(value = "return this.padStart(targetLength, padString);")
+ public native JSString padStart(int targetLength, JSString padString);
+
+ @JS.Coerce
+ @JS(value = "return this.repeat(count);")
+ public native JSString repeat(int count);
+
+ @JS.Coerce
+ @JS(value = "return this.replace(pattern, replacement);")
+ public native JSString replace(JSValue pattern, JSValue replacement);
+
+ @JS.Coerce
+ @JS(value = "return this.replaceAll(pattern, replacement);")
+ public native JSString replaceAll(JSValue pattern, JSValue replacement);
+
+ @JS.Coerce
+ @JS(value = "return this.search(regexp);")
+ public native int search(String regexp);
+
+ @JS.Coerce
+ @JS(value = "return this.search(regexp);")
+ public native int search(JSValue regexp);
+
+ @JS.Coerce
+ @JS(value = "return this.slice(indexStart);")
+ public native JSString slice(int indexStart);
+
+ @JS.Coerce
+ @JS(value = "return this.slice(indexStart, indexEnd);")
+ public native JSString slice(int indexStart, int indexEnd);
+
+ @JS.Coerce
+ @JS(value = "return this.split(separator);")
+ public native JSObject split(String separator);
+
+ @JS.Coerce
+ @JS(value = "return this.split(separator);")
+ public native JSObject split(JSValue separator);
+
+ @JS.Coerce
+ @JS(value = "return this.split(separator, limit);")
+ public native JSObject split(String separator, int limit);
+
+ @JS.Coerce
+ @JS(value = "return this.split(separator, limit);")
+ public native JSObject split(JSValue separator, int limit);
+
+ @JS.Coerce
+ @JS(value = "return this.startsWith(searchString);")
+ public native boolean startsWith(String searchString);
+
+ @JS.Coerce
+ @JS(value = "return this.startsWith(searchString);")
+ public native boolean startsWith(JSString searchString);
+
+ @JS.Coerce
+ @JS(value = "return this.startsWith(searchString, position);")
+ public native boolean startsWith(String searchString, int position);
+
+ @JS.Coerce
+ @JS(value = "return this.startsWith(searchString, position);")
+ public native boolean startsWith(JSString searchString, int position);
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleLowerCase();")
+ public native JSString toLocaleLowerCase();
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleLowerCase(locales);")
+ public native JSString toLocaleLowerCase(String locales);
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleLowerCase(locales);")
+ public native JSString toLocaleLowerCase(JSString locales);
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleUpperCase();")
+ public native JSString toLocaleUpperCase();
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleUpperCase(locales);")
+ public native JSString toLocaleUpperCase(String locales);
+
+ @JS.Coerce
+ @JS(value = "return this.toLocaleUpperCase(locales);")
+ public native JSString toLocaleUpperCase(JSString locales);
+
+ @JS.Coerce
+ @JS(value = "return this.toLowerCase();")
+ public native JSString toLowerCase();
+
+ @JS.Coerce
+ @JS(value = "return this.toUpperCase();")
+ public native JSString toUpperCase();
+
+ @JS.Coerce
+ @JS(value = "return this.toWellFormed();")
+ public native JSString toWellFormed();
+
+ @JS.Coerce
+ @JS(value = "return this.trim();")
+ public native JSString trim();
+
+ @JS.Coerce
+ @JS(value = "return this.trimEnd();")
+ public native JSString trimEnd();
+
+ @JS.Coerce
+ @JS(value = "return this.trimStart();")
+ public native JSString trimStart();
+
+ @JS.Coerce
+ @JS(value = "return this.length;")
+ public native int length();
+
+ @JS.Coerce
+ @JS(value = "return this.substring(indexStart);")
+ public native JSString substring(int indexStart);
+
+ @JS.Coerce
+ @JS(value = "return this.substring(indexStart, indexEnd);")
+ public native JSString substring(int indexStart, int indexEnd);
}
diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSSymbol.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSSymbol.java
index 135ad522c429..2e8bca9680a6 100644
--- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSSymbol.java
+++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSSymbol.java
@@ -71,10 +71,6 @@ protected String stringValue() {
return javaString();
}
- public String description() {
- return javaString();
- }
-
@Override
public boolean equals(Object that) {
if (that instanceof JSSymbol) {
@@ -87,4 +83,84 @@ public boolean equals(Object that) {
public int hashCode() {
return javaString().hashCode();
}
+
+ @JS(value = "return Symbol.for(key);")
+ public static native JSSymbol forKey(String key);
+
+ @JS.Coerce
+ @JS(value = "return Symbol.keyFor(sym);")
+ private static native JSValue keyForRaw(JSSymbol sym);
+
+ public static String keyFor(JSSymbol sym) {
+ JSValue result = keyForRaw(sym);
+ return JSValue.isUndefined(result) ? null : result.asString();
+ }
+
+ @JS.Coerce
+ @JS(value = "return Symbol.asyncDispose;")
+ public static native JSSymbol asyncDispose();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.asyncIterator;")
+ public static native JSSymbol asyncIterator();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.dispose;")
+ public static native JSSymbol dispose();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.hasInstance;")
+ public static native JSSymbol hasInstance();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.isConcatSpreadable;")
+ public static native JSSymbol isConcatSpreadable();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.iterator;")
+ public static native JSSymbol iterator();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.match;")
+ public static native JSSymbol match();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.matchAll;")
+ public static native JSSymbol matchAll();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.replace;")
+ public static native JSSymbol replace();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.search;")
+ public static native JSSymbol search();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.species;")
+ public static native JSSymbol species();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.split;")
+ public static native JSSymbol split();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.toPrimitive;")
+ public static native JSSymbol toPrimitive();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.toStringTag;")
+ public static native JSSymbol toStringTag();
+
+ @JS.Coerce
+ @JS(value = "return Symbol.unscopables;")
+ public static native JSSymbol unscopables();
+
+ @JS.Coerce
+ @JS(value = "return a === b;")
+ public static native boolean isSameSymbol(JSSymbol a, JSSymbol b);
+
+ @JS.Coerce
+ @JS(value = "return this.description;")
+ public native String description();
}
diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSValue.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSValue.java
index 5787460aa37c..27e611701b3b 100644
--- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSValue.java
+++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSValue.java
@@ -45,7 +45,7 @@
/**
* Java representation of a JavaScript value.
- *
+ *
* The subclasses of this class represent JavaScript's six primitive data types and the object data
* type. The JavaScript {@code Null} data type does not have a special representation -- the
* JavaScript {@code null} value is directly mapped to the Java {@code null} value.
@@ -55,6 +55,36 @@ public abstract class JSValue {
JSValue() {
}
+ /**
+ * Attempts to coerce a given value to the specified Java type.
+ *
+ * If the value is a {@link JSValue}, it will be converted using its {@code as(...)} method.
+ * Otherwise, the value is cast directly.
+ *
+ * @param value the value to coerce, which may be a {@code JSValue} or a native Java object
+ * @param cls the target Java class to coerce to
+ * @param the return type
+ * @return the coerced value as an instance of {@code cls}
+ * @throws ClassCastException if the coercion fails or is unsupported
+ */
+ @SuppressWarnings("unchecked")
+ public static R checkedCoerce(Object value, Class cls) {
+ if (value instanceof JSValue jsResult) {
+ return jsResult.as(cls);
+ }
+ return (R) value;
+ }
+
+ /**
+ * Checks whether the given JSValue is the JavaScript 'undefined' value.
+ *
+ * @param value the JSValue to check
+ * @return true if the value is an instance of JSUndefined, false otherwise
+ */
+ public static boolean isUndefined(JSValue value) {
+ return value instanceof JSUndefined;
+ }
+
public static JSUndefined undefined() {
return JSUndefined.instance();
}
diff --git a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSError.java b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/ThrownFromJavaScript.java
similarity index 94%
rename from sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSError.java
rename to sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/ThrownFromJavaScript.java
index 52d810bd5366..25d92dd8be8a 100644
--- a/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/JSError.java
+++ b/sdk/src/org.graalvm.webimage.api/src/org/graalvm/webimage/api/ThrownFromJavaScript.java
@@ -45,10 +45,10 @@
* Represents the error value thrown in JavaScript.
*
* Must not pass a {@link Throwable} instance, these should be thrown directly instead of being
- * wrapped in a {@link JSError}.
+ * wrapped in a {@link ThrownFromJavaScript}.
*/
@SuppressWarnings("serial")
-public final class JSError extends RuntimeException {
+public final class ThrownFromJavaScript extends RuntimeException {
private static final long serialVersionUID = -2343564169271174471L;
@@ -57,7 +57,7 @@ public final class JSError extends RuntimeException {
*/
private final Object thrownObject;
- public JSError(Object thrownObject) {
+ public ThrownFromJavaScript(Object thrownObject) {
super(thrownObject.toString());
this.thrownObject = thrownObject;
assert !(thrownObject instanceof Throwable) : "Tried creating JSError for a throwable: " + thrownObject;
diff --git a/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/spec/JS_JTT_JSAnnotation.java b/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/spec/JS_JTT_JSAnnotation.java
index a70c0532dffe..9afe627b9c71 100644
--- a/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/spec/JS_JTT_JSAnnotation.java
+++ b/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/spec/JS_JTT_JSAnnotation.java
@@ -27,6 +27,10 @@
import java.nio.file.Path;
+import com.oracle.svm.webimage.jtt.api.JSNumberTest;
+import com.oracle.svm.webimage.jtt.api.JSObjectTest;
+import com.oracle.svm.webimage.jtt.api.JSStringTest;
+import com.oracle.svm.webimage.jtt.api.JSSymbolTest;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -131,4 +135,23 @@ public void htmlApiExamplesTest() {
testFileAgainstNoBuild(HtmlApiExamplesTest.OUTPUT, HtmlApiExamplesTest.class.getName());
}
+ @Test
+ public void jsNumberTest() {
+ testFileAgainstNoBuild(JSNumberTest.class.getName());
+ }
+
+ @Test
+ public void jsStringTest() {
+ testFileAgainstNoBuild(JSStringTest.class.getName());
+ }
+
+ @Test
+ public void jsSymbolTest() {
+ testFileAgainstNoBuild(JSSymbolTest.class.getName());
+ }
+
+ @Test
+ public void jsObjectTest() {
+ testFileAgainstNoBuild(JSObjectTest.class.getName());
+ }
}
diff --git a/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/util/JTTTestSuite.java b/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/util/JTTTestSuite.java
index 33ce19029df0..60b1fc9bbb61 100644
--- a/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/util/JTTTestSuite.java
+++ b/web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/util/JTTTestSuite.java
@@ -200,6 +200,13 @@ protected static void testFileAgainstNoBuild(String[] expected, String... args)
testFileAgainstNoBuild(0, new WebImageTestUtil.RunResult(expected), args);
}
+ /**
+ * Runs the currently compiled JS image.
+ */
+ protected static void testFileAgainstNoBuild(String... args) {
+ runJS(args, 0);
+ }
+
/**
* Runs the currently compiled JS image and, asserts the given {@code exitCode} and checks its
* output using the {@code lineChecker} consumer.
@@ -226,7 +233,7 @@ protected static void testClassNoBuild(Class> c, String... args) {
/**
* Runs the currently compiled JS image and compares its result against the given class run in a
* Java runtime.
- *
+ *
* It also checks the expected exit code.
*/
protected static void testClassNoBuildWithExitCode(int exitCode, Class> c, String... args) {
@@ -244,7 +251,7 @@ protected static void testClassNoBuild(Class> c, String[] args, BiConsumer
* It also checks the expected exit code.
*/
protected static void testClassNoBuildWithExitCode(int exitCode, Class> c, String[] args, BiConsumer lineChecker) {
diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSErrorsTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSErrorsTest.java
index cd035d3c4aee..ef9710519966 100644
--- a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSErrorsTest.java
+++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSErrorsTest.java
@@ -26,7 +26,7 @@
package com.oracle.svm.webimage.jtt.api;
import org.graalvm.webimage.api.JS;
-import org.graalvm.webimage.api.JSError;
+import org.graalvm.webimage.api.ThrownFromJavaScript;
public class JSErrorsTest {
/**
@@ -43,7 +43,7 @@ public class JSErrorsTest {
public static void main(String[] args) {
try {
typeError();
- } catch (JSError e) {
+ } catch (ThrownFromJavaScript e) {
System.out.println(e.getMessage());
}
diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSNumberTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSNumberTest.java
new file mode 100644
index 000000000000..3d1c41dfba98
--- /dev/null
+++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSNumberTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.webimage.jtt.api;
+
+import org.graalvm.webimage.api.JSNumber;
+import org.graalvm.webimage.api.JSObject;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class JSNumberTest {
+
+ static final double DOUBLE_SMALL = 3.14159;
+ static final double DOUBLE_BIG = 123.456789;
+ static final double SMALL = 0.00001234;
+ static final double SMALL_EXTENDED = 0.0000123456789;
+ static final double LARGE = 987654321.123;
+ static final double SAFE_INT = 9007199254740991L;
+ static final double UNSAFE_INT = 9007199254740992L;
+ static final double POS_INF = Double.POSITIVE_INFINITY;
+ static final double NEG_INF = Double.NEGATIVE_INFINITY;
+ static final double NAN = Double.NaN;
+ static final int INT = 42;
+ static final int NEG_INT = -42;
+ static final int HEX = 255;
+ static final double DELTA = 0.0;
+
+ public static void main(String[] args) {
+ testIsFinite();
+ testIsInteger();
+ testIsNaN();
+ testIsSafeInteger();
+ testConstants();
+ testParseFloat();
+ testParseInt();
+ testToExponential();
+ testToFixed();
+ testToLocaleString();
+ testToPrecision();
+ testToStringRadix();
+ testValueOf();
+ }
+
+ public static void testIsFinite() {
+ assertTrue(JSNumber.isFinite(JSNumber.of(INT)));
+ assertFalse(JSNumber.isFinite(JSNumber.of(POS_INF)));
+ assertFalse(JSNumber.isFinite(JSNumber.of(NAN)));
+ assertTrue(JSNumber.isFinite(JSNumber.of(DOUBLE_BIG)));
+ assertFalse(JSNumber.isFinite(JSNumber.of(NEG_INF)));
+ }
+
+ public static void testIsInteger() {
+ assertTrue(JSNumber.isInteger(JSNumber.of(INT)));
+ assertFalse(JSNumber.isInteger(JSNumber.of(DOUBLE_BIG)));
+ assertFalse(JSNumber.isInteger(JSNumber.of(NAN)));
+ assertFalse(JSNumber.isInteger(JSNumber.of(POS_INF)));
+ assertFalse(JSNumber.isInteger(JSNumber.of(NEG_INF)));
+ }
+
+ public static void testIsNaN() {
+ assertFalse(JSNumber.isNaN(JSNumber.of(INT)));
+ assertTrue(JSNumber.isNaN(JSNumber.of(NAN)));
+ assertFalse(JSNumber.isNaN(JSNumber.of(POS_INF)));
+ assertFalse(JSNumber.isNaN(JSNumber.of(DOUBLE_BIG)));
+ assertFalse(JSNumber.isNaN(JSNumber.of(NEG_INF)));
+ }
+
+ public static void testIsSafeInteger() {
+ assertTrue(JSNumber.isSafeInteger(JSNumber.of(SAFE_INT)));
+ assertFalse(JSNumber.isSafeInteger(JSNumber.of(UNSAFE_INT)));
+ assertFalse(JSNumber.isSafeInteger(JSNumber.of(DOUBLE_BIG)));
+ assertFalse(JSNumber.isSafeInteger(JSNumber.of(NAN)));
+ assertFalse(JSNumber.isSafeInteger(JSNumber.of(1e100)));
+ }
+
+ public static void testConstants() {
+ assertEquals(Math.ulp(1.0), JSNumber.epsilon(), DELTA);
+ assertEquals(9007199254740991L, JSNumber.maxSafeInteger(), DELTA);
+ assertEquals(Double.MAX_VALUE, JSNumber.maxValue(), DELTA);
+ assertEquals(-9007199254740991L, JSNumber.minSafeInteger(), DELTA);
+ assertEquals(Double.MIN_VALUE, JSNumber.minValue(), DELTA);
+ assertEquals(Double.NaN, JSNumber.nan(), DELTA);
+ assertEquals(Double.NEGATIVE_INFINITY, JSNumber.negativeInfinity(), DELTA);
+ assertEquals(Double.POSITIVE_INFINITY, JSNumber.positiveInfinity(), DELTA);
+
+ }
+
+ public static void testParseFloat() {
+ assertEquals(3.14, JSNumber.parseFloat("3.14abc"), DELTA);
+ assertEquals(NAN, JSNumber.parseFloat("abc"), DELTA);
+ }
+
+ public static void testParseInt() {
+ assertEquals(123L, JSNumber.parseInt("123"));
+ assertEquals(123L, JSNumber.parseInt("123.456"));
+ assertEquals(10L, JSNumber.parseInt("1010", 2));
+ assertEquals(255L, JSNumber.parseInt("FF", 16));
+ assertEquals(63L, JSNumber.parseInt("77", 8));
+ try {
+ JSNumber.parseInt("abc");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertEquals("Invalid integer: abc", expected.getMessage());
+ }
+ try {
+ JSNumber.parseInt("not-a-number", 10);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertEquals("Invalid integer: not-a-number with radix 10", expected.getMessage());
+ }
+ }
+
+ public static void testToExponential() {
+ JSNumber small = JSNumber.of(SMALL);
+ JSNumber big = JSNumber.of(987654321);
+ JSNumber pi = JSNumber.of(DOUBLE_SMALL);
+
+ assertEquals("1.234e-5", small.toExponential());
+ assertEquals("9.87654321e+8", big.toExponential());
+ assertEquals("3.14159e+0", pi.toExponential());
+ assertEquals("1.23e-5", small.toExponential(2));
+ assertEquals("9.8765e+8", big.toExponential(4));
+ assertEquals("3.141590e+0", pi.toExponential(6));
+ }
+
+ public static void testToFixed() {
+ JSNumber pi = JSNumber.of(DOUBLE_SMALL);
+ JSNumber big = JSNumber.of(DOUBLE_BIG);
+ JSNumber small = JSNumber.of(SMALL);
+
+ assertEquals("3", pi.toFixed());
+ assertEquals("123", big.toFixed());
+ assertEquals("0", small.toFixed());
+ assertEquals("3.14", pi.toFixed(2));
+ assertEquals("123.4568", big.toFixed(4));
+ assertEquals("0.00001234", small.toFixed(8));
+ }
+
+ public static void testToLocaleString() {
+ JSNumber number = JSNumber.of(1234567.89);
+ JSObject currencyOpts = JSObject.create();
+ currencyOpts.set("style", "currency");
+ currencyOpts.set("currency", "EUR");
+ JSObject fractionOpts = JSObject.create();
+ fractionOpts.set("minimumFractionDigits", JSNumber.of(4));
+ fractionOpts.set("maximumFractionDigits", JSNumber.of(4));
+
+ assertTrue(number.toLocaleString().matches("\\d[,.]\\d{3}[,.]\\d{3}[,.]\\d{2}"));
+ assertEquals("1\u00A0234\u00A0567,89", number.toLocaleString("de-AT"));
+ assertEquals("1,234,567.89", number.toLocaleString("en-US"));
+ assertEquals("\u20AC\u00A01.234.567,89", number.toLocaleString("de-AT", currencyOpts));
+ assertEquals("1,234,567.8900", number.toLocaleString("en-US", fractionOpts));
+ }
+
+ public static void testToPrecision() {
+ JSNumber big = JSNumber.of(DOUBLE_BIG);
+ JSNumber extended = JSNumber.of(SMALL_EXTENDED);
+ JSNumber large = JSNumber.of(LARGE);
+
+ assertEquals("123.456789", big.toPrecision());
+ assertEquals("0.0000123456789", extended.toPrecision());
+ assertEquals("987654321.123", large.toPrecision());
+ assertEquals("123.5", big.toPrecision(4));
+ assertEquals("0.0000123", extended.toPrecision(3));
+ assertEquals("9.87654e+8", large.toPrecision(6));
+ }
+
+ public static void testToStringRadix() {
+ JSNumber hex = JSNumber.of(HEX);
+ JSNumber pi = JSNumber.of(DOUBLE_SMALL);
+ JSNumber neg = JSNumber.of(NEG_INT);
+
+ assertEquals("JavaScript", hex.toString());
+ assertEquals("JavaScript", pi.toString());
+ assertEquals("JavaScript", neg.toString());
+ assertEquals("11111111", hex.toString(2));
+ assertEquals("ff", hex.toString(16));
+ assertEquals("377", hex.toString(8));
+ assertEquals("-132", neg.toString(5));
+ }
+
+ public static void testValueOf() {
+ assertEquals(42.0, JSNumber.of(INT).valueOf(), DELTA);
+ assertEquals(DOUBLE_SMALL, JSNumber.of(DOUBLE_SMALL).valueOf(), DELTA);
+ assertEquals(NAN, JSNumber.of(NAN).valueOf(), DELTA);
+ }
+}
diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSObjectTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSObjectTest.java
new file mode 100644
index 000000000000..185ba7e01288
--- /dev/null
+++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSObjectTest.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.webimage.jtt.api;
+
+import org.graalvm.webimage.api.JS;
+import org.graalvm.webimage.api.JSBoolean;
+import org.graalvm.webimage.api.JSNumber;
+import org.graalvm.webimage.api.JSObject;
+import org.graalvm.webimage.api.JSString;
+import org.graalvm.webimage.api.JSSymbol;
+import org.graalvm.webimage.api.JSValue;
+import org.graalvm.webimage.api.ThrownFromJavaScript;
+
+import java.util.function.Function;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class JSObjectTest {
+
+ public static void main(String[] args) {
+ testPrototypeInheritance();
+ testCreateWithProperties();
+ testDefineProperties();
+ testDefinePropertyVariants();
+ testEntries();
+ testFreeze();
+ testFromEntries();
+ testGetOwnPropertyDescriptor();
+ testGetOwnPropertyNames();
+ testGroupBy();
+ testHasOwn();
+ testIsEquality();
+ testIsExtensibleAndPreventExtensions();
+ testIsFrozenAndFreeze();
+ testPrototypeChain();
+ testSealAndMutation();
+ testKeysAndValues();
+ testPreventExtensions();
+ testPropertyIsEnumerable();
+ testPrototypeMethodBinding();
+ testToLocaleString();
+ testValueOf();
+ testValues();
+ }
+
+ public static void testPrototypeInheritance() {
+ JSObject proto = JSObject.create();
+ proto.set("greet", fromJavaFunction((String name) -> "Hello, " + name));
+
+ JSObject obj = JSObject.create(proto);
+ JSValue fun = (JSValue) obj.get("greet");
+ String greeting = apply(fun, null, "Alice");
+
+ assertEquals("Hello, Alice", greeting);
+ }
+
+ public static void testCreateWithProperties() {
+ JSObject proto = JSObject.create();
+ proto.set("greet", fromJavaFunction((String name) -> "Hello, " + name));
+ JSObject nameDescriptor = JSObject.create();
+ set(nameDescriptor, JSString.of("Bob"), false, true);
+ JSObject properties = JSObject.create();
+ properties.set("name", nameDescriptor);
+
+ JSObject obj = JSObject.create(proto, properties);
+
+ assertThrows(ThrownFromJavaScript.class, () -> obj.set("name", "Alice"));
+ }
+
+ public static void testDefineProperties() {
+ JSObject target = JSObject.create();
+ JSObject descriptors = JSObject.create();
+ JSObject nameDescriptor = JSObject.create();
+ set(nameDescriptor, JSString.of("Alice"), true, true);
+ descriptors.set("name", nameDescriptor);
+ JSObject versionDescriptor = JSObject.create();
+ set(versionDescriptor, JSNumber.of(26), false, false);
+ descriptors.set("age", versionDescriptor);
+
+ JSObject result = JSObject.defineProperties(target, descriptors);
+ String name1 = JSValue.checkedCoerce(result.get("name"), String.class);
+ int age = JSValue.checkedCoerce(result.get("age"), Integer.class);
+ result.set("name", JSString.of("Bob"));
+
+ assertEquals("Alice", name1);
+ assertEquals(26, age);
+ assertThrows(ThrownFromJavaScript.class, () -> result.set("age", JSNumber.of(21)));
+ assertEquals("Bob", JSValue.checkedCoerce(result.get("name"), String.class));
+ }
+
+ public static void testDefinePropertyVariants() {
+ JSObject obj1 = JSObject.create();
+ JSObject obj2 = JSObject.create();
+ JSObject descriptor = JSObject.create();
+ set(descriptor, JSString.of("Alice"), false, true);
+
+ JSObject.defineProperty(obj1, JSString.of("name"), descriptor);
+ JSObject.defineProperty(obj2, "name", descriptor);
+
+ assertThrows(ThrownFromJavaScript.class, () -> obj1.set("name", "NotAlice"));
+ assertThrows(ThrownFromJavaScript.class, () -> obj2.set("name", "StillNotAlice"));
+ assertEquals("Alice", JSValue.checkedCoerce(obj1.get("name"), String.class));
+ assertEquals("Alice", JSValue.checkedCoerce(obj2.get("name"), String.class));
+ }
+
+ public static void testEntries() {
+ JSObject obj = JSObject.create();
+ obj.set("language", "JavaScript");
+ obj.set("version", "ES2025");
+
+ String result = objToString(JSObject.entries(obj));
+
+ assertEquals("language,JavaScript,version,ES2025", result);
+ }
+
+ public static void testFreeze() {
+ JSObject obj = JSObject.create();
+ obj.set("name", "Alice");
+
+ JSObject.freeze(obj);
+
+ assertThrows(ThrownFromJavaScript.class, () -> obj.set("name", "Changed"));
+ assertEquals("Alice", JSValue.checkedCoerce(obj.get("name"), String.class));
+ }
+
+ public static void testFromEntries() {
+ JSObject entries1 = createArray("framework", "GraalVM");
+ JSObject entries2 = createArray("mode", "native");
+ JSObject entries = createArray(entries1, entries2);
+
+ JSObject result = JSObject.fromEntries(entries);
+ String keys = objToString(result.keys());
+
+ assertEquals("framework,mode", keys);
+ assertEquals("GraalVM", result.get("framework"));
+ assertEquals("native", result.get("mode"));
+ }
+
+ public static void testGetOwnPropertyDescriptor() {
+ JSObject obj = JSObject.create();
+ obj.set("name", "Alice");
+
+ JSObject descriptor = JSObject.getOwnPropertyDescriptor(obj, "name");
+
+ assertEquals("Alice", JSValue.checkedCoerce(descriptor.get("value"), String.class));
+ assertTrue(JSValue.checkedCoerce(descriptor.get("writable"), Boolean.class));
+ assertTrue(JSValue.checkedCoerce(descriptor.get("enumerable"), Boolean.class));
+ assertTrue(JSValue.checkedCoerce(descriptor.get("configurable"), Boolean.class));
+ }
+
+ public static void testGetOwnPropertyNames() {
+ JSObject obj = JSObject.create();
+ obj.set("x", 1);
+ obj.set("y", 2);
+
+ String result = objToString(JSObject.getOwnPropertyNames(obj));
+ assertEquals("x,y", result);
+ }
+
+ public static void testGroupBy() {
+ JSObject item1 = createItem("asparagus", "vegetable", 5);
+ JSObject item2 = createItem("bananas", "fruit", 0);
+ JSObject item3 = createItem("cherries", "fruit", 5);
+ JSObject item4 = createItem("goat", "meat", 23);
+ JSObject item5 = createItem("fish", "meat", 22);
+ JSObject items = createArray(item1, item2, item3, item4, item5);
+
+ JSValue groupByType = fromJavaFunction((JSObject item) -> item.get("type"));
+ JSObject grouped = JSObject.groupBy(items, groupByType);
+
+ assertEquals("asparagus", JSValue.checkedCoerce(JSValue.checkedCoerce(grouped.get("vegetable"), JSObject.class).get(0), JSObject.class).get("name"));
+ assertEquals("bananas", JSValue.checkedCoerce(JSValue.checkedCoerce(grouped.get("fruit"), JSObject.class).get(0), JSObject.class).get("name"));
+ assertEquals("cherries", JSValue.checkedCoerce(JSValue.checkedCoerce(grouped.get("fruit"), JSObject.class).get(1), JSObject.class).get("name"));
+ assertEquals("goat", JSValue.checkedCoerce(JSValue.checkedCoerce(grouped.get("meat"), JSObject.class).get(0), JSObject.class).get("name"));
+ assertEquals("fish", JSValue.checkedCoerce(JSValue.checkedCoerce(grouped.get("meat"), JSObject.class).get(1), JSObject.class).get("name"));
+ }
+
+ public static void testHasOwn() {
+ JSObject obj = JSObject.create();
+ obj.set("x", 10);
+ JSSymbol sym = JSSymbol.forKey("x");
+ obj.set(sym, 42);
+ JSSymbol missing = JSSymbol.forKey("y");
+
+ assertTrue(JSObject.hasOwn(obj, "x"));
+ assertFalse(JSObject.hasOwn(obj, "y"));
+ assertTrue(JSObject.hasOwn(obj, JSString.of("x")));
+ assertFalse(JSObject.hasOwn(obj, JSString.of("y")));
+ assertTrue(JSObject.hasOwn(obj, sym));
+ assertFalse(JSObject.hasOwn(obj, missing));
+ }
+
+ public static void testIsEquality() {
+ JSObject obj1 = JSObject.create();
+ obj1.set("value", 1);
+ JSObject obj2 = JSObject.create();
+ obj2.set("value", 1);
+
+ assertTrue(JSObject.is(JSString.of("hello"), JSString.of("hello")));
+ assertFalse(JSObject.is(JSString.of("5"), JSNumber.of(5)));
+ assertTrue(JSObject.is(JSNumber.of(Double.NaN), JSNumber.of(Double.NaN)));
+ assertFalse(JSObject.is(JSNumber.of(0.0), JSNumber.of(-0.0)));
+ assertTrue(JSObject.is(JSBoolean.of(true), JSBoolean.of(true)));
+ assertFalse(JSObject.is(obj1, obj2));
+ assertTrue(JSObject.is(obj1, obj1));
+ }
+
+ public static void testIsExtensibleAndPreventExtensions() {
+ JSObject obj = createTestObject();
+
+ boolean result1 = JSObject.isExtensible(obj);
+ JSObject.preventExtensions(obj);
+ boolean result2 = JSObject.isExtensible(obj);
+
+ assertTrue(result1);
+ assertFalse(result2);
+ assertThrows(ThrownFromJavaScript.class, () -> obj.set("newProp", "test"));
+ assertFalse(JSObject.hasOwn(obj, "newProp"));
+ }
+
+ public static void testIsFrozenAndFreeze() {
+ JSObject obj = createTestObject();
+
+ boolean result1 = JSObject.isFrozen(obj);
+ JSObject.freeze(obj);
+ boolean result2 = JSObject.isFrozen(obj);
+
+ assertFalse(result1);
+ assertTrue(result2);
+ assertThrows(ThrownFromJavaScript.class, () -> obj.set("name", "Bob"));
+ assertThrows(ThrownFromJavaScript.class, () -> obj.set("newProp", "test"));
+ assertEquals("Alice", obj.get("name"));
+ assertFalse(JSObject.hasOwn(obj, "newProp"));
+ }
+
+ public static void testPrototypeChain() {
+ JSObject proto = JSObject.create();
+ JSObject obj = JSObject.create();
+ JSObject.setPrototypeOf(obj, proto);
+
+ assertTrue(proto.isPrototypeOf(obj));
+ assertFalse(obj.isPrototypeOf(proto));
+ }
+
+ public static void testSealAndMutation() {
+ JSObject obj = createTestObject();
+
+ boolean result1 = JSObject.isSealed(obj);
+ JSObject.seal(obj);
+ boolean result2 = JSObject.isSealed(obj);
+ obj.set("name", "Bob");
+
+ assertFalse(result1);
+ assertTrue(result2);
+ assertThrows(ThrownFromJavaScript.class, () -> obj.set("newProp", "test"));
+ assertEquals("Bob", JSValue.checkedCoerce(obj.get("name"), String.class));
+ assertFalse(JSObject.hasOwn(obj, "newProp"));
+ }
+
+ public static void testKeysAndValues() {
+ JSObject obj = createTestObject();
+ obj.set("age", "27");
+ obj.set("active", "true");
+
+ String keys = objToString(JSObject.keys(obj));
+ String values = objToString(JSObject.values(obj));
+
+ assertEquals("name,age,active", keys);
+ assertEquals("Alice,27,true", values);
+ }
+
+ public static void testPreventExtensions() {
+ JSObject obj = createTestObject();
+
+ boolean result1 = JSObject.isExtensible(obj);
+ JSObject.preventExtensions(obj);
+ boolean result2 = JSObject.isExtensible(obj);
+
+ assertTrue(result1);
+ assertFalse(result2);
+ assertThrows(ThrownFromJavaScript.class, () -> obj.set("newProp", "test"));
+ assertFalse(JSObject.hasOwn(obj, "newProp"));
+ }
+
+ public static void testPropertyIsEnumerable() {
+ JSObject obj = JSObject.create();
+ obj.set("visible", "yes");
+ JSObject descriptors = JSObject.create();
+ JSObject hiddenDescriptor = JSObject.create();
+ hiddenDescriptor.set("value", "no");
+ hiddenDescriptor.set("enumerable", JSBoolean.of(false));
+ descriptors.set("hidden", hiddenDescriptor);
+ JSObject.defineProperties(obj, descriptors);
+ JSSymbol sym = JSSymbol.forKey("secret");
+ obj.set(sym, "classified");
+ JSObject symbolDescriptor = JSObject.create();
+ symbolDescriptor.set("value", "classified");
+ symbolDescriptor.set("enumerable", JSBoolean.of(false));
+ JSObject symbolDescriptors = JSObject.create();
+ symbolDescriptors.set(sym, symbolDescriptor);
+ JSObject.defineProperties(obj, symbolDescriptors);
+
+ assertTrue(obj.propertyIsEnumerable("visible"));
+ assertFalse(obj.propertyIsEnumerable("hidden"));
+ assertFalse(obj.propertyIsEnumerable("missing"));
+ assertTrue(obj.propertyIsEnumerable(JSString.of("visible")));
+ assertFalse(obj.propertyIsEnumerable(JSString.of("hidden")));
+ assertFalse(obj.propertyIsEnumerable(JSString.of("missing")));
+ assertFalse(obj.propertyIsEnumerable(sym));
+ }
+
+ public static void testPrototypeMethodBinding() {
+ JSObject obj = createTestObject();
+
+ JSObject proto = JSObject.create();
+ proto.set("describe", fromJSArgs("return 'I am ' + String(this.name);"));
+ JSObject result = JSObject.setPrototypeOf(obj, proto);
+ JSValue describeFn = (JSValue) result.get("describe");
+ String description = JSValue.checkedCoerce(apply(describeFn, result), String.class);
+
+ assertEquals("I am Alice", description);
+ }
+
+ public static void testToLocaleString() {
+ JSObject obj = createTestObject();
+ obj.set("region", "Austria");
+
+ assertEquals("[object Object]", obj.toLocaleString());
+ }
+
+ public static void testValueOf() {
+ JSObject obj = JSObject.create();
+ obj.set("id", 42);
+
+ JSObject result = JSValue.checkedCoerce(obj.valueOf(), JSObject.class);
+ int value = JSValue.checkedCoerce(result.get("id"), Integer.class);
+
+ assertEquals(42, value);
+ }
+
+ public static void testValues() {
+ JSObject obj = createTestObject();
+ obj.set("age", "27");
+ obj.set("active", "true");
+
+ String values = objToString(JSObject.values(obj));
+
+ assertEquals("Alice,27,true", values);
+ }
+
+ private static void set(JSObject obj, JSValue value, boolean writable, boolean configurable) {
+ obj.set("value", value);
+ obj.set("writable", JSBoolean.of(writable));
+ obj.set("enumerable", JSBoolean.of(true));
+ obj.set("configurable", JSBoolean.of(configurable));
+ }
+
+ private static JSObject createItem(String name, String type, int quantity) {
+ JSObject obj = JSObject.create();
+ obj.set("name", name);
+ obj.set("type", type);
+ obj.set("quantity", quantity);
+ return obj;
+ }
+
+ private static JSObject createTestObject() {
+ JSObject obj = JSObject.create();
+ obj.set("name", "Alice");
+ return obj;
+ }
+
+ @JS.Coerce
+ @JS(value = "return function(args) { return javaFunc.apply(args); }")
+ public static native JSValue fromJavaFunction(Function javaFunc);
+
+ @JS.Coerce
+ @JS(value = "return Function.apply(null, args);")
+ public static native JSValue fromJSArgs(String... args);
+
+ @SafeVarargs
+ @JS.Coerce
+ @JS(value = "return fun.apply(thisArg, args);")
+ public static native R apply(JSValue fun, Q thisArg, T... args);
+
+ @JS.Coerce
+ @JS("return it.toString();")
+ private static native String objToString(Object it);
+
+ @JS(value = "return Array.from(values);")
+ public static native JSObject createArray(Object... values);
+}
diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSStringTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSStringTest.java
new file mode 100644
index 000000000000..6136c0cb7bfb
--- /dev/null
+++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSStringTest.java
@@ -0,0 +1,544 @@
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.webimage.jtt.api;
+
+import org.graalvm.webimage.api.JS;
+import org.graalvm.webimage.api.JSNumber;
+import org.graalvm.webimage.api.JSObject;
+import org.graalvm.webimage.api.JSString;
+import org.graalvm.webimage.api.JSUndefined;
+import org.graalvm.webimage.api.JSValue;
+
+import java.util.function.Function;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class JSStringTest {
+
+ public static final String[] OUTPUT = new String[]{};
+
+ private static final String HELLO_STRING = "Hello";
+ private static final String WORLD_STRING = "World";
+ private static final String HELLO_WORLD_STRING = "Hello World";
+ private static final String JAVASCRIPT_STRING = "JavaScript";
+ private static final int[] ARROWS_CODE_POINTS = new int[]{0x2190, 0x2191, 0x2192, 0x2193};
+ private static final int[] MATH_CODE_POINTS = new int[]{0x2211, 0x221A, 0x03C0, 0x221E};
+ private static final int[] CURRENCY_CODE_POINTS = new int[]{0x20AC, 0x00A5, 0x20B9, 0x0024};
+ private static final String LONG_TEXT = "The quick brown fox jumps over the lazy dog.";
+
+ public static void main(String[] args) {
+ testAt();
+ testCharAt();
+ testCharCodeAt();
+ testCodePointAt();
+ testConcat();
+ testEndsWith();
+ testIncludes();
+ testFromCharCode();
+ testFromCodePoint();
+ testIndexOf();
+ testLastIndexOf();
+ testIsWellFormed();
+ testLength();
+ testLocaleCompare();
+ testMatchAll();
+ testMatch();
+ testNormalize();
+ testPadEnd();
+ testPadStart();
+ testRaw();
+ testRepeat();
+ testReplace();
+ testReplaceAll();
+ testSearch();
+ testSlice();
+ testSplit();
+ testStartsWith();
+ testToLocaleLowerCase();
+ testToLocaleUpperCase();
+ testToLowerCase();
+ testToUpperCase();
+ testToWellFormed();
+ testTrim();
+ testSubstring();
+ }
+
+ public static void testAt() {
+ JSString helloString = JSString.of(HELLO_STRING);
+ JSString javaScriptString = JSString.of(JAVASCRIPT_STRING);
+ JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS);
+ JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS);
+ JSString currencyString = JSString.fromCodePoint(CURRENCY_CODE_POINTS);
+
+ assertEquals("H", helloString.at(0).asString());
+ assertEquals("o", helloString.at(4).asString());
+ assertEquals(JSUndefined.undefined(), JSValue.checkedCoerce(helloString.at(10), JSUndefined.class));
+ assertEquals("o", helloString.at(-1).asString());
+ assertEquals("S", javaScriptString.at(4).asString());
+ assertEquals("i", javaScriptString.at(-3).asString());
+ assertEquals("\u2191", arrowsString.at(1).asString());
+ assertEquals("\u2192", arrowsString.at(-2).asString());
+ assertEquals("\u2211", mathString.at(0).asString());
+ assertEquals("\u221E", mathString.at(3).asString());
+ assertEquals("\u20B9", currencyString.at(2).asString());
+ assertEquals("\u0024", currencyString.at(-1).asString());
+ }
+
+ public static void testCharAt() {
+ JSString helloString = JSString.of(HELLO_STRING);
+ JSString javaScriptString = JSString.of(JAVASCRIPT_STRING);
+ JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS);
+ JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS);
+ JSString currencyString = JSString.fromCodePoint(CURRENCY_CODE_POINTS);
+
+ assertEquals("H", helloString.charAt(0).asString());
+ assertEquals("o", helloString.charAt(4).asString());
+ assertEquals("", helloString.charAt(-1).asString());
+ assertEquals("S", javaScriptString.charAt(4).asString());
+ assertEquals("t", javaScriptString.charAt(9).asString());
+ assertEquals("", javaScriptString.charAt(10).asString());
+ assertEquals("\u2190", arrowsString.charAt(0).asString());
+ assertEquals("\u2192", arrowsString.charAt(2).asString());
+ assertEquals("\u221A", mathString.charAt(1).asString());
+ assertEquals("\u221E", mathString.charAt(3).asString());
+ assertEquals("\u20B9", currencyString.charAt(2).asString());
+ assertEquals("\u0024", currencyString.charAt(3).asString());
+ }
+
+ public static void testCharCodeAt() {
+ JSString helloString = JSString.of(HELLO_STRING);
+ JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS);
+ JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS);
+ JSString currencyString = JSString.fromCodePoint(CURRENCY_CODE_POINTS);
+
+ assertEquals(72, helloString.charCodeAt(0));
+ assertEquals(111, helloString.charCodeAt(4));
+ assertEquals(-1, helloString.charCodeAt(-1));
+ assertEquals(-1, helloString.charCodeAt(10));
+ assertEquals(8592, arrowsString.charCodeAt(0));
+ assertEquals(8594, arrowsString.charCodeAt(2));
+ assertEquals(8730, mathString.charCodeAt(1));
+ assertEquals(8734, mathString.charCodeAt(3));
+ assertEquals(8377, currencyString.charCodeAt(2));
+ assertEquals(36, currencyString.charCodeAt(3));
+ }
+
+ public static void testCodePointAt() {
+ JSString helloString = JSString.of(HELLO_STRING);
+ JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS);
+ JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS);
+ JSString currencyString = JSString.fromCodePoint(CURRENCY_CODE_POINTS);
+
+ assertEquals(72, helloString.codePointAt(0));
+ assertEquals(111, helloString.codePointAt(4));
+ assertEquals(-1, helloString.codePointAt(-1));
+ assertEquals(-1, helloString.codePointAt(10));
+ assertEquals(8592, arrowsString.codePointAt(0));
+ assertEquals(8594, arrowsString.codePointAt(2));
+ assertEquals(8730, mathString.codePointAt(1));
+ assertEquals(8734, mathString.codePointAt(3));
+ assertEquals(8377, currencyString.codePointAt(2));
+ assertEquals(36, currencyString.codePointAt(3));
+ }
+
+ public static void testConcat() {
+ JSString comma = JSString.of(", ");
+ JSString exclaim = JSString.of("!");
+ JSString label = JSString.of("Arrows: ");
+ JSString mathLabel = JSString.of("Math: ");
+ JSString helloString = JSString.of(HELLO_STRING);
+ JSString worldString = JSString.of(WORLD_STRING);
+ JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS);
+ JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS);
+
+ assertEquals("Hello, World!", helloString.concat(comma, worldString, exclaim).asString());
+ assertEquals("Arrows: \u2190\u2191\u2192\u2193", label.concat(arrowsString).asString());
+ assertEquals("Math: \u2211\u221A\u03C0\u221E", mathLabel.concat(mathString).asString());
+ }
+
+ public static void testEndsWith() {
+ JSString worldString = JSString.of(WORLD_STRING);
+ JSString helloWorldString = JSString.of(HELLO_WORLD_STRING);
+ JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS);
+ JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS);
+
+ assertTrue(helloWorldString.endsWith(worldString));
+ assertFalse(helloWorldString.endsWith("world"));
+ assertFalse(helloWorldString.endsWith(HELLO_STRING));
+ assertTrue(helloWorldString.endsWith(HELLO_STRING, 5));
+ assertTrue(arrowsString.endsWith(JSString.fromCodePoint(0x2192, 0x2193)));
+ assertTrue(arrowsString.endsWith(JSString.fromCodePoint(0x2191), 2));
+ assertTrue(mathString.endsWith(JSString.fromCodePoint(0x03C0, 0x221E)));
+ assertTrue(mathString.endsWith(JSString.fromCodePoint(0x221A), 2));
+ assertFalse(mathString.endsWith(JSString.fromCodePoint(0x221E), 3));
+ }
+
+ public static void testIncludes() {
+ JSString worldString = JSString.of(WORLD_STRING);
+ JSString helloWorldString = JSString.of(HELLO_WORLD_STRING);
+ JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS);
+ JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS);
+
+ assertTrue(helloWorldString.includes(worldString));
+ assertFalse(helloWorldString.includes("world"));
+ assertTrue(helloWorldString.includes("lo"));
+ assertFalse(helloWorldString.includes("lo", 5));
+ assertTrue(arrowsString.includes(JSString.fromCodePoint(0x2191)));
+ assertTrue(arrowsString.includes(JSString.fromCodePoint(0x2191), 1));
+ assertTrue(mathString.includes(JSString.fromCodePoint(0x221A)));
+ assertTrue(mathString.includes(JSString.fromCodePoint(0x03C0), 2));
+ }
+
+ public static void testFromCharCode() {
+ assertEquals("", JSString.fromCharCode().asString());
+ assertEquals("A", JSString.fromCharCode(65).asString());
+ assertEquals("Hello", JSString.fromCharCode(72, 101, 108, 108, 111).asString());
+ assertEquals("\u0024\u00A9\u00AE", JSString.fromCharCode(36, 169, 174).asString());
+ assertEquals("\uD83D\uDE00", JSString.fromCharCode(0xD83D, 0xDE00).asString());
+ }
+
+ public static void testFromCodePoint() {
+ assertEquals("", JSString.fromCodePoint().asString());
+ assertEquals("A", JSString.fromCodePoint(65).asString());
+ assertEquals("Hello", JSString.fromCodePoint(72, 101, 108, 108, 111).asString());
+ assertEquals("\u0024\u00A9\u00AE", JSString.fromCodePoint(36, 169, 174).asString());
+ assertEquals("\uD83D\uDE00", JSString.fromCodePoint(0x1F600).asString());
+ assertEquals("\u03A9\uD83D\uDE80", JSString.fromCodePoint(0x03A9, 0x1F680).asString());
+ }
+
+ public static void testIndexOf() {
+ JSString helloWorldString = JSString.of(HELLO_WORLD_STRING);
+ JSString arrowsString = JSString.fromCodePoint(ARROWS_CODE_POINTS);
+ JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS);
+
+ assertEquals(6, helloWorldString.indexOf(WORLD_STRING));
+ assertEquals(-1, helloWorldString.indexOf("world"));
+ assertEquals(2, helloWorldString.indexOf("l"));
+ assertEquals(9, helloWorldString.indexOf("l", 4));
+ assertEquals(2, helloWorldString.indexOf("l"), -4);
+ assertEquals(1, arrowsString.indexOf(JSString.fromCodePoint(0x2191)));
+ assertEquals(2, arrowsString.indexOf(JSString.fromCodePoint(0x2192), 2));
+ assertEquals(2, mathString.indexOf(JSString.fromCodePoint(0x03C0)));
+ assertEquals(1, mathString.indexOf(JSString.fromCodePoint(0x221A), 1));
+ }
+
+ public static void testLastIndexOf() {
+ JSString phrase1 = JSString.of(HELLO_STRING + " " + HELLO_STRING);
+ JSString phrase2 = JSString.fromCodePoint(0x2190, 0x2191, 0x2192, 0x2193, 0x2190, 0x2191, 0x2192, 0x2193);
+
+ assertEquals(6, phrase1.lastIndexOf(HELLO_STRING));
+ assertEquals(0, phrase1.lastIndexOf(HELLO_STRING, 5));
+ assertEquals(0, phrase1.lastIndexOf(HELLO_STRING, -5));
+ assertEquals(9, phrase1.lastIndexOf("lo"));
+ assertEquals(6, phrase2.lastIndexOf(JSString.fromCodePoint(0x2192)));
+ assertEquals(6, phrase2.lastIndexOf(JSString.fromCodePoint(0x2192), 6));
+ }
+
+ public static void testIsWellFormed() {
+ JSString helloWorldString = JSString.of(HELLO_WORLD_STRING);
+ JSString mathString = JSString.fromCodePoint(MATH_CODE_POINTS);
+
+ JSString highPlusAscii = JSString.fromCharCode(0xD800, 0x0041);
+ JSString lowPlusAscii = JSString.fromCharCode(0xDC00, 0x0042);
+ JSString reversedPair = JSString.fromCharCode(0xDC00, 0xD800);
+
+ assertTrue(helloWorldString.isWellFormed());
+ assertTrue(mathString.isWellFormed());
+ assertFalse(highPlusAscii.isWellFormed());
+ assertFalse(lowPlusAscii.isWellFormed());
+ assertFalse(reversedPair.isWellFormed());
+ }
+
+ public static void testLength() {
+ JSString text = JSString.of("Life, the universe and everything. Answer:");
+
+ assertEquals(42, text.length());
+ }
+
+ public static void testLocaleCompare() {
+ JSString a = JSString.of("a");
+ JSString a2 = JSString.of("A");
+ JSString a3 = JSString.of("\u00E4");
+ JSString a4 = JSString.of("ae");
+ JSObject caseSensitive = JSObject.create();
+ caseSensitive.set("sensitivity", "case");
+
+ JSObject accentSensitive = JSObject.create();
+ accentSensitive.set("sensitivity", "accent");
+
+ assertEquals(1, a2.localeCompare("a"));
+ assertEquals(-1, a.localeCompare("A"));
+ assertEquals(0, a2.localeCompare("A"));
+ assertEquals(-1, a3.localeCompare("ae", "de"));
+ assertEquals(-1, a.localeCompare("A", "en", caseSensitive));
+ assertEquals(1, a2.localeCompare("a", "en", caseSensitive));
+ assertEquals(-1, a.localeCompare(a2));
+ assertEquals(-1, a3.localeCompare(a4, JSString.of("sv")));
+ assertEquals(-1, a3.localeCompare("a", "en", accentSensitive));
+ assertEquals(1, a.localeCompare(a3, JSString.of("en"), accentSensitive));
+ assertEquals(0, a3.localeCompare("\u00E4", "en", accentSensitive));
+ }
+
+ public static void testMatchAll() {
+ JSString phrase = JSString.of("Price: \u002412, Discount: \u00245, Tax: \u00242");
+ JSObject iterator = phrase.matchAll(eval("/\\\u0024(\\d+)/g"));
+ String result = iteratorToString(iterator);
+
+ assertEquals("\u002412,12,\u00245,5,\u00242,2", result);
+ }
+
+ public static void testMatch() {
+ JSString phrase = JSString.of("Hello 123 World 456");
+ JSString mixed = JSString.of("Hello 123 World ABC xyz");
+
+ String result1 = objToString(phrase.match("World"));
+ String result2 = objToString(mixed.match(eval("/[A-Z]/g")));
+ String result3 = objToString(phrase.match(JSString.of("\\d+")));
+ String result4 = objToString(phrase.match(eval("/\\d+/g")));
+
+ assertEquals("World", result1);
+ assertEquals("H,W,A,B,C", result2);
+ assertEquals("123", result3);
+ assertEquals("123,456", result4);
+ assertNull(phrase.match("XYZ"));
+ }
+
+ public static void testNormalize() {
+ JSString composed = JSString.fromCodePoint(0x00E9);
+ JSString decomposed = JSString.fromCodePoint(0x0065, 0x0301);
+ JSString fullWidth = JSString.fromCodePoint(0xFF21);
+
+ assertEquals("\u00E9", composed.asString());
+ assertEquals("e\u0301", decomposed.asString());
+ assertEquals("\u00E9", decomposed.normalize().asString());
+ assertEquals("e\u0301", decomposed.normalize("NFD").asString());
+ assertEquals("e\u0301", composed.normalize("NFD").asString());
+ assertEquals("\u00E9", composed.normalize("NFC").asString());
+ assertEquals("\u00E9", decomposed.normalize("NFC").asString());
+ assertEquals("\uFF21", fullWidth.asString());
+ assertEquals("A", fullWidth.normalize("NFKC").asString());
+ }
+
+ public static void testPadEnd() {
+ JSString base = JSString.of("Hi");
+
+ assertEquals("Hi ", base.padEnd(5).asString());
+ assertEquals("Hi***", base.padEnd(5, "*").asString());
+ assertEquals("Hi*****", base.padEnd(7, JSString.of("*")).asString());
+ }
+
+ public static void testPadStart() {
+ JSString base = JSString.of("Hi");
+
+ assertEquals(" Hi", base.padStart(5).asString());
+ assertEquals("---Hi", base.padStart(5, "-").asString());
+ assertEquals("-----Hi", base.padStart(7, JSString.of("-")).asString());
+ }
+
+ public static void testRaw() {
+ JSObject template = JSObject.create();
+ template.set("raw", new String[]{"Line1\n", "Line2\t", "End"});
+
+ assertEquals("Line1\nLine2\tEnd", JSString.raw(template).asString());
+ assertEquals("Line1\nALine2\tBEnd", JSString.raw(template, "A", "B").asString());
+ }
+
+ public static void testRepeat() {
+ JSString base = JSString.of("Echo");
+ JSString helloString = JSString.of(HELLO_STRING);
+
+ assertEquals("EchoEchoEcho", base.repeat(3).asString());
+ assertEquals("HelloHelloHelloHello", helloString.repeat(4).asString());
+ }
+
+ public static void testReplace() {
+ JSString phrase = JSString.of("foo bar foo");
+ JSString digits = JSString.of("Price: 42");
+ JSString helloWorldString = JSString.of(HELLO_WORLD_STRING);
+ JSObject replacer1 = eval("(match) => '[' + match + ']'");
+ JSValue replacer2 = fromJavaFunction((JSString match) -> JSString.of("(" + match.asString() + ")"));
+
+ assertEquals("baz bar foo", phrase.replace(JSString.of("foo"), JSString.of("baz")).asString());
+ assertEquals("baz bar foo", phrase.replace(eval("/foo/"), JSString.of("baz")).asString());
+ assertEquals("World, Hello", helloWorldString.replace(eval("/(\\w+) (\\w+)/"), JSString.of("\u00242, \u00241")).asString());
+ assertEquals("Price: [42]", digits.replace(eval("/\\d+/"), replacer1).asString());
+ assertEquals("Price: (42)", digits.replace(eval("/\\d+/"), replacer2).asString());
+ assertEquals("foo bar foo", phrase.replace(JSString.of("xyz"), JSString.of("baz")).asString());
+ assertEquals("123 bar foo", phrase.replace(JSString.of("foo"), JSNumber.of(123)).asString());
+
+ JSString multi = JSString.of("foo bar foo");
+ assertEquals("baz bar baz", multi.replace(eval("/foo/g"), JSString.of("baz")).asString());
+ }
+
+ public static void testReplaceAll() {
+ JSString multi = JSString.of("foo bar foo");
+
+ assertEquals("baz bar baz", multi.replace(eval("/foo/g"), JSString.of("baz")).asString());
+ }
+
+ public static void testSearch() {
+ JSString text = JSString.of("Find 42 here");
+
+ assertEquals(5, text.search(eval("/\\d+/")));
+ assertEquals(0, text.search("Find"));
+ assertEquals(-1, text.search(eval("/find/")));
+ assertEquals(0, text.search(eval("/find/i")));
+ assertEquals(-1, text.search(eval("/^42/")));
+ assertEquals(-1, text.search("XYZ"));
+ }
+
+ public static void testSlice() {
+ JSString longText = JSString.of(LONG_TEXT);
+
+ assertEquals("the lazy dog.", longText.slice(31).asString());
+ assertEquals("dog.", longText.slice(-4).asString());
+ assertEquals("quick brown fox", longText.slice(4, 19).asString());
+ assertEquals("lazy", longText.slice(-9, -5).asString());
+ }
+
+ public static void testSplit() {
+ JSString csv = JSString.of("red,green,blue,yellow");
+ JSObject regexObject = eval("/,/");
+
+ String result1 = objToString(csv.split(","));
+ String result2 = objToString(csv.split(",", 2));
+ String result3 = objToString(csv.split(regexObject));
+ String result4 = objToString(csv.split(regexObject, 2));
+ String result5 = objToString(csv.split(""));
+
+ assertEquals("red,green,blue,yellow", result1);
+ assertEquals("red,green", result2);
+ assertEquals("red,green,blue,yellow", result3);
+ assertEquals("red,green", result4);
+ assertEquals("r,e,d,,,g,r,e,e,n,,,b,l,u,e,,,y,e,l,l,o,w", result5);
+ }
+
+ public static void testStartsWith() {
+ JSString text = JSString.of("To be, or not to be, that is the question.");
+
+ assertTrue(text.startsWith("To be"));
+ assertFalse(text.startsWith("to be"));
+ assertTrue(text.startsWith(JSString.of("To be")));
+ assertFalse(text.startsWith(JSString.of("question")));
+ assertTrue(text.startsWith("not", 10));
+ assertFalse(text.startsWith("To", 3));
+ assertTrue(text.startsWith(JSString.of("not"), 10));
+ assertFalse(text.startsWith(JSString.of("To"), 3));
+ assertFalse(text.startsWith("To", 100));
+ assertFalse(text.startsWith(JSString.of("To"), 100));
+ }
+
+ public static void testToLocaleLowerCase() {
+ JSString turkish = JSString.fromCodePoint(0x0130).concat(JSString.of("stanbul"));
+ JSString english = JSString.of("HELLO WORLD");
+
+ assertEquals("i\u0307stanbul", turkish.toLocaleLowerCase().asString());
+ assertEquals("istanbul", turkish.toLocaleLowerCase("tr").asString());
+ assertEquals("istanbul", turkish.toLocaleLowerCase(JSString.of("tr")).asString());
+ assertEquals("hello world", english.toLocaleLowerCase().asString());
+ assertEquals("hello world", english.toLocaleLowerCase("en").asString());
+ assertEquals("hello world", english.toLocaleLowerCase(JSString.of("en")).asString());
+ }
+
+ public static void testToLocaleUpperCase() {
+ JSString turkish = JSString.fromCodePoint(0x0069).concat(JSString.of("stanbul"));
+ JSString english = JSString.of("hello world");
+
+ assertEquals("ISTANBUL", turkish.toLocaleUpperCase().asString());
+ assertEquals("\u0130STANBUL", turkish.toLocaleUpperCase("tr").asString());
+ assertEquals("\u0130STANBUL", turkish.toLocaleUpperCase(JSString.of("tr")).asString());
+ assertEquals("HELLO WORLD", english.toLocaleUpperCase().asString());
+ assertEquals("HELLO WORLD", english.toLocaleUpperCase("en").asString());
+ assertEquals("HELLO WORLD", english.toLocaleUpperCase(JSString.of("en")).asString());
+ }
+
+ public static void testToLowerCase() {
+ JSString longText = JSString.of(LONG_TEXT);
+
+ assertEquals("the quick brown fox jumps over the lazy dog.", longText.toLowerCase().asString());
+ }
+
+ public static void testToUpperCase() {
+ JSString longText = JSString.of(LONG_TEXT);
+
+ assertEquals("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.", longText.toUpperCase().asString());
+ }
+
+ public static void testToWellFormed() {
+ JSString str1 = JSString.of("ab").concat(JSString.fromCodePoint(0xD800));
+ JSString str2 = str1.concat(JSString.of("c"));
+ JSString str3 = JSString.fromCodePoint(0xDFFF).concat(JSString.of("ab"));
+ JSString str4 = JSString.of("c").concat(JSString.fromCodePoint(0xDFFF)).concat(JSString.of("ab"));
+ JSString str5 = JSString.of("abc");
+ JSString str6 = JSString.of("ab").concat(JSString.fromCodePoint(0x1F604)).concat(JSString.of("c"));
+
+ assertEquals("ab\uFFFD", str1.toWellFormed().asString());
+ assertEquals("ab\uFFFDc", str2.toWellFormed().asString());
+ assertEquals("\uFFFDab", str3.toWellFormed().asString());
+ assertEquals("c\uFFFDab", str4.toWellFormed().asString());
+ assertEquals("abc", str5.toWellFormed().asString());
+ assertEquals("ab\uD83D\uDE04c", str6.toWellFormed().asString());
+ }
+
+ public static void testTrim() {
+ JSString padded = JSString.of(" To be, or not to be ");
+
+ assertEquals("To be, or not to be", padded.trim().asString());
+ assertEquals("To be, or not to be ", padded.trimStart().asString());
+ assertEquals(" To be, or not to be", padded.trimEnd().asString());
+ }
+
+ public static void testSubstring() {
+ JSString text = JSString.of("JavaScript");
+
+ assertEquals("Java", text.substring(0, 4).asString());
+ assertEquals("Script", text.substring(4).asString());
+ assertEquals("Java", text.substring(4, 0).asString());
+ assertEquals("Script", text.substring(4, 100).asString());
+ assertEquals("", text.substring(100, 200).asString());
+ assertEquals("Java", text.substring(-4, 4).asString());
+ assertEquals("JavaScript", text.substring(-10, 100).asString());
+ }
+
+ @JS.Coerce
+ @JS(value = "return eval(script);")
+ private static native JSObject eval(String script);
+
+ @JS.Coerce
+ @JS("return Array.from(it).toString();")
+ private static native String iteratorToString(Object it);
+
+ @JS.Coerce
+ @JS("return it.toString();")
+ private static native String objToString(Object it);
+
+ @JS.Coerce
+ @JS(value = "return function(args) { return javaFunc.apply(args); }")
+ public static native JSValue fromJavaFunction(Function javaFunc);
+}
diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSSymbolTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSSymbolTest.java
new file mode 100644
index 000000000000..b236a1eab82a
--- /dev/null
+++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JSSymbolTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.svm.webimage.jtt.api;
+
+import org.graalvm.webimage.api.JS;
+import org.graalvm.webimage.api.JSSymbol;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class JSSymbolTest {
+
+ public static void main(String[] args) {
+ testForKey();
+ testEquality();
+ testIsSameSymbol();
+ testKeyFor();
+ }
+
+ public static void testForKey() {
+ JSSymbol sym = JSSymbol.forKey("alpha");
+
+ assertEquals("JavaScript", sym.toString());
+ }
+
+ public static void testEquality() {
+ JSSymbol sym1 = JSSymbol.forKey("shared");
+ JSSymbol sym2 = JSSymbol.forKey("shared");
+ JSSymbol sym3 = JSSymbol.forKey("unique");
+
+ assertEquals(sym1, sym2);
+ assertNotEquals(sym1, sym3);
+ }
+
+ public static void testIsSameSymbol() {
+ JSSymbol sym1 = JSSymbol.forKey("shared");
+ JSSymbol sym2 = JSSymbol.forKey("shared");
+ JSSymbol sym3 = JSSymbol.forKey("unique");
+
+ assertTrue(JSSymbol.isSameSymbol(sym1, sym2));
+ assertFalse(JSSymbol.isSameSymbol(sym1, sym3));
+ }
+
+ public static void testKeyFor() {
+ JSSymbol shared1 = JSSymbol.forKey("alpha");
+ JSSymbol shared2 = JSSymbol.forKey("beta");
+ String result1 = JSSymbol.keyFor(shared1);
+ String result2 = JSSymbol.keyFor(shared2);
+ JSSymbol local = createLocalSymbol("gamma");
+ String result3 = JSSymbol.keyFor(local);
+
+ assertEquals("alpha", result1);
+ assertEquals("beta", result2);
+ assertNull(result3);
+ }
+
+ @JS.Coerce
+ @JS(value = "return Symbol(desc);")
+ private static native JSSymbol createLocalSymbol(String desc);
+}
diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JavaProxyTest.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JavaProxyTest.java
index 32978311ccdb..01e7dbd88878 100644
--- a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JavaProxyTest.java
+++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/api/JavaProxyTest.java
@@ -28,7 +28,7 @@
import java.util.function.Function;
import org.graalvm.webimage.api.JS;
-import org.graalvm.webimage.api.JSError;
+import org.graalvm.webimage.api.ThrownFromJavaScript;
import org.graalvm.webimage.api.JSValue;
import com.oracle.svm.core.NeverInline;
@@ -123,7 +123,7 @@ private static void expectJSError(Runnable r, String name) {
try {
r.run();
System.out.println("ERROR: Expected JS error for " + name);
- } catch (JSError jsError) {
+ } catch (ThrownFromJavaScript thrownFromJavaScript) {
System.out.println("Caught JS error for " + name);
}
}
diff --git a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/testdispatcher/JSAnnotationTests.java b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/testdispatcher/JSAnnotationTests.java
index 627efa5958ea..473047b36eb4 100644
--- a/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/testdispatcher/JSAnnotationTests.java
+++ b/web-image/src/com.oracle.svm.webimage.jtt/src/com/oracle/svm/webimage/jtt/testdispatcher/JSAnnotationTests.java
@@ -29,10 +29,14 @@
import com.oracle.svm.webimage.jtt.api.CoercionConversionTest;
import com.oracle.svm.webimage.jtt.api.HtmlApiExamplesTest;
import com.oracle.svm.webimage.jtt.api.JSErrorsTest;
+import com.oracle.svm.webimage.jtt.api.JSNumberTest;
import com.oracle.svm.webimage.jtt.api.JSObjectConversionTest;
import com.oracle.svm.webimage.jtt.api.JSObjectSubclassTest;
+import com.oracle.svm.webimage.jtt.api.JSObjectTest;
import com.oracle.svm.webimage.jtt.api.JSPrimitiveConversionTest;
import com.oracle.svm.webimage.jtt.api.JSRawCallTest;
+import com.oracle.svm.webimage.jtt.api.JSStringTest;
+import com.oracle.svm.webimage.jtt.api.JSSymbolTest;
import com.oracle.svm.webimage.jtt.api.JavaDocExamplesTest;
import com.oracle.svm.webimage.jtt.api.JavaProxyConversionTest;
import com.oracle.svm.webimage.jtt.api.JavaProxyTest;
@@ -61,6 +65,14 @@ public static void main(String[] args) {
JSErrorsTest.main(remainingArgs);
} else if (checkClass(HtmlApiExamplesTest.class, className)) {
HtmlApiExamplesTest.main(remainingArgs);
+ } else if (checkClass(JSNumberTest.class, className)) {
+ JSNumberTest.main(remainingArgs);
+ } else if (checkClass(JSStringTest.class, className)) {
+ JSStringTest.main(remainingArgs);
+ } else if (checkClass(JSSymbolTest.class, className)) {
+ JSSymbolTest.main(remainingArgs);
+ } else if (checkClass(JSObjectTest.class, className)) {
+ JSObjectTest.main(remainingArgs);
} else {
throw new IllegalArgumentException("unexpected class name");
}
diff --git a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/JSExceptionSupport.java b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/JSExceptionSupport.java
index 01de3eaf12be..c07015b4f35f 100644
--- a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/JSExceptionSupport.java
+++ b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/JSExceptionSupport.java
@@ -28,7 +28,7 @@
import static com.oracle.svm.webimage.substitute.system.Target_java_lang_Throwable_Web.CAUSE_CAPTION;
import static com.oracle.svm.webimage.substitute.system.Target_java_lang_Throwable_Web.SUPPRESSED_CAPTION;
-import org.graalvm.webimage.api.JSError;
+import org.graalvm.webimage.api.ThrownFromJavaScript;
import org.graalvm.webimage.api.JSObject;
import org.graalvm.webimage.api.JSString;
import org.graalvm.webimage.api.JSValue;
@@ -121,8 +121,8 @@ public static void printStackTrace(Target_java_lang_Throwable_Web t, Printer s,
* If the throwable is a JSError, the thrown JS object is implicitly a cause (if it is an
* Error object). In that case, we also print the JS stack trace if available.
*/
- if (SubstrateUtil.cast(t, Throwable.class) instanceof JSError jsError) {
- Object thrownObject = jsError.getThrownObject();
+ if (SubstrateUtil.cast(t, Throwable.class) instanceof ThrownFromJavaScript thrownFromJavaScript) {
+ Object thrownObject = thrownFromJavaScript.getThrownObject();
if (thrownObject instanceof JSObject jsObject && jsObject.get("stack") instanceof JSValue stack) {
s.println(prefix + "Caused by JS Error: " + t.getMessage());
if (stack instanceof JSString jsString) {
diff --git a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/functionintrinsics/JSConversion.java b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/functionintrinsics/JSConversion.java
index 647adab05891..5802a15e9f67 100644
--- a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/functionintrinsics/JSConversion.java
+++ b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/functionintrinsics/JSConversion.java
@@ -33,7 +33,7 @@
import org.graalvm.webimage.api.JS;
import org.graalvm.webimage.api.JSBigInt;
import org.graalvm.webimage.api.JSBoolean;
-import org.graalvm.webimage.api.JSError;
+import org.graalvm.webimage.api.ThrownFromJavaScript;
import org.graalvm.webimage.api.JSNumber;
import org.graalvm.webimage.api.JSObject;
import org.graalvm.webimage.api.JSString;
@@ -674,11 +674,11 @@ public static T coerceJavaScriptToJava(Object x, Class target) {
* Ensures that objects that are thrown in {@link JS}-annotated methods are a subclass of
* {@link Throwable}. If a {@link Throwable} exception is thrown, it gets simply rethrown.
* Otherwise, the exception object is converted to Java according ot the conversion rules
- * specified by {@link JS} and wrapped into a {@link JSError}.
+ * specified by {@link JS} and wrapped into a {@link ThrownFromJavaScript}.
*
* @param excp thrown object. Due to JavaScript semantics this can be an arbitrary type.
- * @throws Throwable the original {@link Throwable} or {@link JSError} which warps the converted
- * thrown JavaScript object
+ * @throws Throwable the original {@link Throwable} or {@link ThrownFromJavaScript} which warps
+ * the converted thrown JavaScript object
*/
public static void handleJSError(Object excp) throws Throwable {
if (JSExceptionSupport.isThrowable(excp)) {
@@ -692,7 +692,7 @@ public static void handleJSError(Object excp) throws Throwable {
throw t;
}
- throw new JSError(obj);
+ throw new ThrownFromJavaScript(obj);
}
}
}
diff --git a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/wasmgc/WasmGCJSConversion.java b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/wasmgc/WasmGCJSConversion.java
index a013c5aa3a21..2bde9cd8d584 100644
--- a/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/wasmgc/WasmGCJSConversion.java
+++ b/web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/wasmgc/WasmGCJSConversion.java
@@ -26,7 +26,7 @@
package com.oracle.svm.webimage.wasmgc;
import org.graalvm.nativeimage.Platforms;
-import org.graalvm.webimage.api.JSError;
+import org.graalvm.webimage.api.ThrownFromJavaScript;
import org.graalvm.webimage.api.JSValue;
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
@@ -175,7 +175,7 @@ public static void throwExceptionFromJS(Object thrownObject) throws Throwable {
throw t;
}
- throw new JSError(thrownObject);
+ throw new ThrownFromJavaScript(thrownObject);
}
@Override