Many new things

This commit is contained in:
UnlegitDqrk
2026-02-28 17:39:42 +01:00
parent a84c626416
commit a9b0ccb8a7
30 changed files with 2490 additions and 150 deletions

View File

@@ -0,0 +1,227 @@
package org.openautonomousconnection.luascript.fx;
import javafx.scene.web.WebEngine;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
/**
* Shared utilities for WebEngine JS bridging.
*/
public final class FxWebBridge {
private FxWebBridge() {
}
/**
* Ensures JavaScript is enabled for the engine.
*
* @param engine engine
*/
public static void ensureJsEnabled(WebEngine engine) {
Objects.requireNonNull(engine, "engine");
if (!engine.isJavaScriptEnabled()) {
engine.setJavaScriptEnabled(true);
}
}
/**
* Executes bridge code with JavaScript temporarily enabled.
*
* @param engine engine
* @param action action
*/
public static void runWithJs(WebEngine engine, Runnable action) {
Objects.requireNonNull(action, "action");
callWithJs(engine, () -> {
action.run();
return null;
});
}
/**
* Executes bridge code with JavaScript temporarily enabled and restores the prior state.
*
* @param engine engine
* @param action action
* @param <T> result type
* @return action result
*/
public static <T> T callWithJs(WebEngine engine, Supplier<T> action) {
Objects.requireNonNull(engine, "engine");
Objects.requireNonNull(action, "action");
boolean enabledBefore = engine.isJavaScriptEnabled();
if (!enabledBefore) {
engine.setJavaScriptEnabled(true);
}
try {
return action.get();
} finally {
if (!enabledBefore) {
engine.setJavaScriptEnabled(false);
}
}
}
/**
* Converts a Java value into a safe JavaScript literal.
*
* <p>Supported: null, String, Boolean, Number (finite).</p>
*
* @param v value
* @return JS literal
*/
public static String toJsLiteral(Object v) {
if (v == null) return "null";
if (v instanceof String s) {
return "'" + escapeJsSingleQuotedString(s) + "'";
}
if (v instanceof Boolean b) {
return b ? "true" : "false";
}
if (v instanceof Number n) {
double d = n.doubleValue();
if (!Double.isFinite(d)) {
throw new IllegalArgumentException("Non-finite number is not supported for JS literal: " + d);
}
return Double.toString(d);
}
throw new IllegalArgumentException("Unsupported value type for JS literal: " + v.getClass().getName());
}
/**
* Builds a JavaScript array literal from Java values.
*
* @param values values
* @return array literal
*/
public static String toJsArrayLiteral(Object[] values) {
if (values == null || values.length == 0) return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 0; i < values.length; i++) {
if (i > 0) sb.append(',');
sb.append(toJsLiteral(values[i]));
}
sb.append(']');
return sb.toString();
}
/**
* Restricts input to a plain JavaScript identifier (prevents injection).
*
* @param s value
* @param label label
* @return identifier
*/
public static String requireJsIdentifier(String s, String label) {
if (s == null) throw new IllegalArgumentException(label + " is null");
String v = s.trim();
if (v.isEmpty()) throw new IllegalArgumentException(label + " is blank");
if (!v.matches("^[A-Za-z_$][A-Za-z0-9_$]*$")) {
throw new IllegalArgumentException(label + " must be a JS identifier: " + v);
}
return v;
}
/**
* Normalizes alignment options for scrollIntoView.
*
* @param align input
* @return normalized
*/
public static String normalizeAlign(String align) {
if (align == null) return "nearest";
String a = align.trim().toLowerCase(Locale.ROOT);
return switch (a) {
case "start", "center", "end", "nearest" -> a;
default -> "nearest";
};
}
/**
* Converts a JS array-like object into a Java string list.
*
* @param jsValue raw JS value
* @return list of string values
*/
public static List<String> toStringList(Object jsValue) {
if (jsValue == null) return List.of();
if (jsValue instanceof List<?> list) {
List<String> out = new ArrayList<>(list.size());
for (Object value : list) out.add(value == null ? null : String.valueOf(value));
return out;
}
try {
Class<?> jsObj = Class.forName("netscape.javascript.JSObject");
if (jsObj.isInstance(jsValue)) {
Object lenObj = jsObj.getMethod("getMember", String.class).invoke(jsValue, "length");
int len = Integer.parseInt(String.valueOf(lenObj));
List<String> out = new ArrayList<>(len);
for (int i = 0; i < len; i++) {
Object value = jsObj.getMethod("getSlot", int.class).invoke(jsValue, i);
out.add(value == null ? null : String.valueOf(value));
}
return out;
}
} catch (Exception ignored) {
// best-effort fallback below
}
return List.of(String.valueOf(jsValue));
}
/**
* Converts a JS object into a Java string-object map when possible.
*
* @param jsValue raw JS value
* @return mapped values
*/
public static Map<String, Object> toStringObjectMap(Object jsValue) {
if (jsValue == null) return Map.of();
if (jsValue instanceof Map<?, ?> map) {
Map<String, Object> out = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getKey() == null) continue;
out.put(String.valueOf(entry.getKey()), entry.getValue());
}
return out;
}
return Map.of("value", jsValue);
}
private static String escapeJsSingleQuotedString(String s) {
if (s == null || s.isEmpty()) return "";
StringBuilder out = new StringBuilder(s.length() + 16);
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '\'' -> out.append("\\'");
case '\\' -> out.append("\\\\");
case '\n' -> out.append("\\n");
case '\r' -> out.append("\\r");
case '\t' -> out.append("\\t");
case '\u0000' -> out.append("\\0");
case '\u2028' -> out.append("\\u2028");
case '\u2029' -> out.append("\\u2029");
default -> {
if (c < 0x20) out.append(String.format("\\u%04x", (int) c));
else out.append(c);
}
}
}
return out.toString();
}
}