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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/main/java/org/htmlunit/javascript/JavaScriptEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -454,10 +454,7 @@ public static void configureRhino(final WebClient webClient, final BrowserVersio
}

// add Intl
final Intl intl = new Intl();
intl.setParentScope(scope);
globalThis.defineProperty(intl.getClassName(), intl, ScriptableObject.DONTENUM);
intl.defineProperties(scope, browserVersion);
Intl.init(scope, globalThis, browserVersion);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.htmlunit.javascript.configuration.JsxClass;
import org.htmlunit.javascript.configuration.JsxConstructor;
import org.htmlunit.javascript.configuration.JsxFunction;
import org.htmlunit.javascript.configuration.JsxStaticFunction;
import org.htmlunit.javascript.host.Window;
import org.htmlunit.util.StringUtils;

Expand Down Expand Up @@ -305,6 +306,18 @@ public Scriptable resolvedOptions() {
return options;
}

/**
* Returns an array containing those of the provided locales that are supported
* without having to fall back to the default locale.
* @param localesArgument A string with a BCP 47 language tag, or an array of such strings
* @param options unused
* @return an array containing supported locales
*/
@JsxStaticFunction
public static Scriptable supportedLocalesOf(final Scriptable localesArgument, final Scriptable options) {
return Intl.supportedLocalesOf(localesArgument);
}

/**
* Helper.
*/
Expand Down
160 changes: 153 additions & 7 deletions src/main/java/org/htmlunit/javascript/host/intl/Intl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,64 @@

import static org.htmlunit.BrowserVersionFeatures.JS_INTL_V8_BREAK_ITERATOR;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.IllformedLocaleException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.LocaleUtils;
import org.htmlunit.BrowserVersion;
import org.htmlunit.corejs.javascript.Context;
import org.htmlunit.corejs.javascript.Function;
import org.htmlunit.corejs.javascript.FunctionObject;
import org.htmlunit.corejs.javascript.NativeArray;
import org.htmlunit.corejs.javascript.Scriptable;
import org.htmlunit.corejs.javascript.ScriptableObject;
import org.htmlunit.corejs.javascript.TopLevel;
import org.htmlunit.javascript.HtmlUnitScriptable;
import org.htmlunit.javascript.JavaScriptEngine;
import org.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration;
import org.htmlunit.javascript.configuration.ClassConfiguration;
import org.htmlunit.javascript.configuration.JsxClass;
import org.htmlunit.javascript.configuration.JsxStaticFunction;

/**
* A JavaScript object for {@code Intl}.
*
* @author Ahmed Ashour
* @author Lai Quang Duong
*/
@JsxClass
public class Intl extends HtmlUnitScriptable {

/**
* Define needed properties.
* @param scope the scope
* Initialize the Intl object and register it on the global scope.
* @param scope the top-level scope
* @param globalThis the global object
* @param browserVersion the browser version
*/
public void defineProperties(final Scriptable scope, final BrowserVersion browserVersion) {
public static void init(final Scriptable scope, final ScriptableObject globalThis,
final BrowserVersion browserVersion) {
final Intl intl = new Intl();
intl.setParentScope(scope);
intl.defineProperties(scope, browserVersion);

// Configure static functions (getCanonicalLocales)
final ClassConfiguration intlConfig = AbstractJavaScriptConfiguration.getClassConfiguration(Intl.class, browserVersion);

Check failure on line 65 in src/main/java/org/htmlunit/javascript/host/intl/Intl.java

View workflow job for this annotation

GitHub Actions / CheckStyle

[checkstyle] reported by reviewdog 🐶 Line is longer than 120 characters (found 128). Raw Output: /home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/intl/Intl.java:65:0: error: Line is longer than 120 characters (found 128). (com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck)
if (intlConfig != null) {
defineStaticFunctions(intlConfig, intl, intl);
}

globalThis.defineProperty(intl.getClassName(), intl, ScriptableObject.DONTENUM);
}

private void defineProperties(final Scriptable scope, final BrowserVersion browserVersion) {
define(scope, Collator.class, browserVersion);
define(scope, DateTimeFormat.class, browserVersion);
define(scope, Locale.class, browserVersion);
define(scope, NumberFormat.class, browserVersion);
if (browserVersion.hasFeature(JS_INTL_V8_BREAK_ITERATOR)) {
define(scope, V8BreakIterator.class, browserVersion);
Expand All @@ -51,13 +85,125 @@
try {
final ClassConfiguration config = AbstractJavaScriptConfiguration.getClassConfiguration(c, browserVersion);
final HtmlUnitScriptable prototype = JavaScriptEngine.configureClass(config, scope);
final FunctionObject functionObject =
new FunctionObject(config.getJsConstructor().getKey(),
config.getJsConstructor().getValue(), this);
functionObject.addAsConstructor(this, prototype, ScriptableObject.DONTENUM);
final FunctionObject constructorFn = new FunctionObject(config.getJsConstructor().getKey(),
config.getJsConstructor().getValue(), this);
constructorFn.addAsConstructor(this, prototype, ScriptableObject.DONTENUM);

defineStaticFunctions(config, this, constructorFn);
}
catch (final Exception e) {
throw JavaScriptEngine.throwAsScriptRuntimeEx(e);
}
}

private static void defineStaticFunctions(final ClassConfiguration config,
final Scriptable scope, final ScriptableObject target) {
final Map<String, Method> staticFunctionMap = config.getStaticFunctionMap();
if (staticFunctionMap != null) {
for (final Map.Entry<String, Method> entry : staticFunctionMap.entrySet()) {
final FunctionObject fn = new FunctionObject(entry.getKey(), entry.getValue(), scope);
target.defineProperty(entry.getKey(), fn, ScriptableObject.EMPTY);
}
}
}

/**
* Returns an array containing the canonical locale names.
* Duplicates will be omitted and elements will be validated as structurally valid language tags.
*
* @param cx the current context
* @param thisObj the scriptable this
* @param args the arguments
* @param funObj the function object
* @return an array of canonical locale names
*
* @see <a href="https://tc39.es/ecma402/#sec-intl.getcanonicallocales">spec</a>
*/
@JsxStaticFunction
public static Object getCanonicalLocales(final Context cx, final Scriptable thisObj,
final Object[] args, final Function funObj) {
if (args.length == 0 || JavaScriptEngine.isUndefined(args[0])) {
return cx.newArray(TopLevel.getTopLevelScope(thisObj), new Object[0]);
}

final Object localesArgument = args[0];
if (localesArgument == null) {
throw JavaScriptEngine.typeError("Cannot convert null to object");
}

final List<String> languageTags = new ArrayList<>();
if (localesArgument instanceof String s) {
languageTags.add(s);
}
else if (localesArgument instanceof Scriptable scriptable) {
if ("String".equals(scriptable.getClassName()) || scriptable instanceof Locale) {
languageTags.add(scriptable.toString());
}
else if (scriptable instanceof NativeArray array) {

Check warning on line 142 in src/main/java/org/htmlunit/javascript/host/intl/Intl.java

View workflow job for this annotation

GitHub Actions / PMD

[PMD] reported by reviewdog 🐶 Avoid using implementation types like 'NativeArray'; use the interface instead Raw Output: {"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:///home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/intl/Intl.java"},"region":{"endColumn":55,"endLine":142,"startColumn":44,"startLine":142}}}],"message":{"text":"Avoid using implementation types like 'NativeArray'; use the interface instead"},"ruleId":"LooseCoupling","ruleIndex":167}
for (int i = 0; i < array.getLength(); i++) {
final Object elem = array.get(i);
if (elem instanceof String s) {
languageTags.add(s);
}
else if (elem instanceof Locale) {
languageTags.add(elem.toString());
}
else if (elem instanceof ScriptableObject) {
languageTags.add(JavaScriptEngine.toString(elem));
}
else {
throw JavaScriptEngine.typeError("Invalid element in locales argument");
}
}
}
else {
languageTags.add(JavaScriptEngine.toString(localesArgument));
}
}

final Set<String> canonicalLocales = new LinkedHashSet<>(languageTags.size());
for (final String tag : languageTags) {
try {
canonicalLocales.add(
new java.util.Locale.Builder().setLanguageTag(tag).build().toLanguageTag());
}
catch (final IllformedLocaleException e) {
throw JavaScriptEngine.rangeError("Invalid language tag: " + tag);

Check warning on line 171 in src/main/java/org/htmlunit/javascript/host/intl/Intl.java

View workflow job for this annotation

GitHub Actions / PMD

[PMD] reported by reviewdog 🐶 Thrown exception does not preserve the stack trace of exception 'e' on all code paths Raw Output: {"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:///home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/intl/Intl.java"},"region":{"endColumn":82,"endLine":171,"startColumn":23,"startLine":171}}}],"message":{"text":"Thrown exception does not preserve the stack trace of exception 'e' on all code paths"},"ruleId":"PreserveStackTrace","ruleIndex":33}
}
}

return cx.newArray(TopLevel.getTopLevelScope(thisObj), canonicalLocales.toArray());
}

/**
* Shared utility for {@code supportedLocalesOf} implementations.
* @param localesArgument the locales argument
* @return a Scriptable array of supported locale strings
*/
static Scriptable supportedLocalesOf(final Scriptable localesArgument) {
final String[] locales;
if (localesArgument instanceof NativeArray array) {

Check warning on line 185 in src/main/java/org/htmlunit/javascript/host/intl/Intl.java

View workflow job for this annotation

GitHub Actions / PMD

[PMD] reported by reviewdog 🐶 Avoid using implementation types like 'NativeArray'; use the interface instead Raw Output: {"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:///home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/intl/Intl.java"},"region":{"endColumn":51,"endLine":185,"startColumn":40,"startLine":185}}}],"message":{"text":"Avoid using implementation types like 'NativeArray'; use the interface instead"},"ruleId":"LooseCoupling","ruleIndex":167}
locales = new String[(int) array.getLength()];
for (int i = 0; i < locales.length; i++) {
locales[i] = JavaScriptEngine.toString(array.get(i));
}
}
else {
locales = new String[] {JavaScriptEngine.toString(localesArgument)};
}

final List<String> supportedLocales = new ArrayList<>();
for (final String locale : locales) {
if (locale.isEmpty()) {
throw JavaScriptEngine.rangeError("Invalid language tag: " + locale);
}
final java.util.Locale l = java.util.Locale.forLanguageTag(locale);
if (LocaleUtils.isAvailableLocale(l)) {
supportedLocales.add(locale);
}
}

return Context.getCurrentContext().newArray(
TopLevel.getTopLevelScope(localesArgument), supportedLocales.toArray());
}
}
Loading
Loading