Event fixes

This commit is contained in:
UnlegitDqrk
2026-02-27 23:54:33 +01:00
parent aed0f35c34
commit cda595907f
6 changed files with 75 additions and 36 deletions

10
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>org.openautonomousconnection</groupId> <groupId>org.openautonomousconnection</groupId>
<artifactId>LuaScript</artifactId> <artifactId>LuaScript</artifactId>
<version>1.0.0-BETA.1.3</version> <version>0.0.0-STABLE.1.3</version>
<organization> <organization>
<name>Open Autonomous Connection</name> <name>Open Autonomous Connection</name>
@@ -65,6 +65,10 @@
</licenses> </licenses>
<repositories> <repositories>
<repository>
<id>unlegitdqrk</id>
<url>https://repo.unlegitdqrk.dev/api/packages/UnlegitDqrk/maven</url>
</repository>
<repository> <repository>
<id>oac</id> <id>oac</id>
<url>https://repo.open-autonomous-connection.org/api/packages/open-autonomous-connection/maven</url> <url>https://repo.open-autonomous-connection.org/api/packages/open-autonomous-connection/maven</url>
@@ -72,10 +76,6 @@
<enabled>true</enabled> <enabled>true</enabled>
</snapshots> </snapshots>
</repository> </repository>
<repository>
<id>unlegitdqrk</id>
<url>https://repo.unlegitdqrk.dev/api/packages/UnlegitDqrk/maven</url>
</repository>
</repositories> </repositories>
<dependencies> <dependencies>

View File

@@ -11,6 +11,8 @@ import org.w3c.dom.events.EventTarget;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** /**
* EventHost implementation for JavaFX WebView using W3C DOM EventTarget listeners. * EventHost implementation for JavaFX WebView using W3C DOM EventTarget listeners.
@@ -22,19 +24,31 @@ public final class FxEventHost implements EventHost {
private static final String GLOBAL_TARGET_ID = "__global__"; private static final String GLOBAL_TARGET_ID = "__global__";
private final FxDomHost dom; private final FxDomHost dom;
private final LuaEventRouter router; private volatile LuaEventRouter router;
private final ConcurrentHashMap<String, EventListener> elementListeners = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, EventListener> elementListeners = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, EventListener> globalListeners = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, EventListener> globalListeners = new ConcurrentHashMap<>();
private final ExecutorService dispatchExecutor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "oac-fx-event-dispatch");
t.setDaemon(true);
return t;
});
/** /**
* Creates a new FxEventHost. * Creates a new FxEventHost.
* *
* @param dom fx dom host * @param dom fx dom host
*/
public FxEventHost(FxDomHost dom) {
this.dom = Objects.requireNonNull(dom, "dom");
}
/**
* Sets the active Lua router for this host.
*
* @param router lua router * @param router lua router
*/ */
public FxEventHost(FxDomHost dom, LuaEventRouter router) { public void setRouter(LuaEventRouter router) {
this.dom = Objects.requireNonNull(dom, "dom");
this.router = Objects.requireNonNull(router, "router"); this.router = Objects.requireNonNull(router, "router");
} }
@@ -49,7 +63,7 @@ public final class FxEventHost implements EventHost {
elementListeners.computeIfAbsent(key, k -> { elementListeners.computeIfAbsent(key, k -> {
EventListener listener = ev -> { EventListener listener = ev -> {
Map<String, Object> payload = FxEventPayloadExtractor.extract(ev); Map<String, Object> payload = FxEventPayloadExtractor.extract(ev);
router.emit(elementId, evt, payload); dispatchExecutor.execute(() -> emitSafely(elementId, evt, payload));
}; };
FxThreadBridge.runAndWait(() -> { FxThreadBridge.runAndWait(() -> {
@@ -87,7 +101,7 @@ public final class FxEventHost implements EventHost {
globalListeners.computeIfAbsent(evt, k -> { globalListeners.computeIfAbsent(evt, k -> {
EventListener listener = ev -> { EventListener listener = ev -> {
Map<String, Object> payload = FxEventPayloadExtractor.extract(ev); Map<String, Object> payload = FxEventPayloadExtractor.extract(ev);
router.emit(GLOBAL_TARGET_ID, evt, payload); dispatchExecutor.execute(() -> emitSafely(GLOBAL_TARGET_ID, evt, payload));
}; };
FxThreadBridge.runAndWait(() -> { FxThreadBridge.runAndWait(() -> {
@@ -113,4 +127,18 @@ public final class FxEventHost implements EventHost {
((EventTarget) doc).removeEventListener(evt, listener, false); ((EventTarget) doc).removeEventListener(evt, listener, false);
}); });
} }
private void emitSafely(String elementId, String eventName, Map<String, Object> payload) {
try {
LuaEventRouter activeRouter = router;
if (activeRouter == null) {
System.err.println("[oac.event] Dropping '" + eventName + "' for '" + elementId + "': router not initialized");
return;
}
activeRouter.emit(elementId, eventName, payload);
} catch (RuntimeException ex) {
System.err.println("[oac.event] Failed to dispatch '" + eventName + "' for '" + elementId + "': " + ex.getMessage());
ex.printStackTrace(System.err);
}
}
} }

View File

@@ -82,23 +82,13 @@ public final class FxLuaScriptEngine implements AutoCloseable {
.sandbox(true) .sandbox(true)
); );
// Create runtime first (router lives inside it).
HostServices.StdoutConsole console = new HostServices.StdoutConsole("[lua] "); HostServices.StdoutConsole console = new HostServices.StdoutConsole("[lua] ");
FxUiHost uiHost = new FxUiHost(engine, dom); FxUiHost uiHost = new FxUiHost(engine, dom);
FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine); FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine);
FxEventHost eventHost = new FxEventHost(dom);
// runtime depends on services; events depends on runtime router.
// We'll create eventHost after runtime, then build HostServices with it.
LuaRuntime rt = new LuaRuntime(globals, new HostServices.Default(uiHost, dom, null, resourceHost, console), policy);
FxEventHost eventHost = new FxEventHost(dom, rt.eventRouter());
// Rebuild services including eventHost and reinstall tables.
HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console); HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console);
LuaRuntime rt = new LuaRuntime(globals, services, policy);
// Replace runtime with correct services (clean and deterministic). eventHost.setRouter(rt.eventRouter());
rt.close();
rt = new LuaRuntime(globals, services, policy);
rt.installStdTables(true); rt.installStdTables(true);
rt.bootstrapFromDom(); rt.bootstrapFromDom();

View File

@@ -24,6 +24,6 @@ public record LuaExecutionPolicy(Duration timeout, long instructionLimit, int ho
* @return policy * @return policy
*/ */
public static LuaExecutionPolicy uiDefault() { public static LuaExecutionPolicy uiDefault() {
return new LuaExecutionPolicy(Duration.ofMillis(50), 200_000, 5_000); return new LuaExecutionPolicy(Duration.ofSeconds(5), 200_000, 5_000);
} }
} }

View File

@@ -1,8 +1,8 @@
package org.openautonomousconnection.luascript.security; package org.openautonomousconnection.luascript.security;
import org.luaj.vm2.*; import org.luaj.vm2.*;
import org.luaj.vm2.lib.DebugLib;
import org.luaj.vm2.lib.VarArgFunction; import org.luaj.vm2.lib.VarArgFunction;
import org.openautonomousconnection.luascript.utils.LuaGlobalsFactory;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.*; import java.util.concurrent.*;
@@ -16,6 +16,7 @@ import java.util.concurrent.atomic.AtomicLong;
public final class LuaSecurityManager implements AutoCloseable { public final class LuaSecurityManager implements AutoCloseable {
private final ExecutorService executor; private final ExecutorService executor;
private final ConcurrentHashMap<Globals, LuaValue> debugSetHooks = new ConcurrentHashMap<>();
/** /**
* Creates a new LuaSecurityManager that runs all Lua code on a single dedicated daemon thread. * Creates a new LuaSecurityManager that runs all Lua code on a single dedicated daemon thread.
@@ -28,8 +29,8 @@ public final class LuaSecurityManager implements AutoCloseable {
}); });
} }
private static Varargs callWithHook(Globals globals, LuaFunction function, Varargs args, LuaExecutionPolicy policy) { private Varargs callWithHook(Globals globals, LuaFunction function, Varargs args, LuaExecutionPolicy policy) {
final LuaValue sethook = resolveAndHideDebugSetHook(globals); final LuaValue sethook = resolveDebugSetHook(globals);
final long deadlineMillis = System.currentTimeMillis() + policy.timeout().toMillis(); final long deadlineMillis = System.currentTimeMillis() + policy.timeout().toMillis();
final AtomicLong ticks = new AtomicLong(0); final AtomicLong ticks = new AtomicLong(0);
@@ -84,13 +85,19 @@ public final class LuaSecurityManager implements AutoCloseable {
return resumed; return resumed;
} }
private static LuaValue resolveAndHideDebugSetHook(Globals globals) { private LuaValue resolveDebugSetHook(Globals globals) {
// Ensure DebugLib exists for hooks. LuaValue cached = debugSetHooks.get(globals);
globals.load(new DebugLib()); if (cached != null) return cached;
LuaValue internal = globals.get(LuaGlobalsFactory.INTERNAL_DEBUG_SETHOOK_KEY);
if (!internal.isnil() && internal.isfunction()) {
debugSetHooks.put(globals, internal);
return internal;
}
LuaValue debugTable = globals.get("debug"); LuaValue debugTable = globals.get("debug");
if (debugTable.isnil() || !debugTable.istable()) { if (debugTable.isnil() || !debugTable.istable()) {
throw new IllegalStateException("Debug library not available (debug table missing)"); throw new IllegalStateException("Debug library not available (debug table missing and no internal sethook cached)");
} }
LuaValue sethook = debugTable.get("sethook"); LuaValue sethook = debugTable.get("sethook");
@@ -100,6 +107,7 @@ public final class LuaSecurityManager implements AutoCloseable {
// Hide debug from scripts. // Hide debug from scripts.
globals.set("debug", LuaValue.NIL); globals.set("debug", LuaValue.NIL);
debugSetHooks.put(globals, sethook);
return sethook; return sethook;
} }

View File

@@ -22,6 +22,8 @@ import java.util.function.Consumer;
*/ */
public final class LuaGlobalsFactory { public final class LuaGlobalsFactory {
public static final String INTERNAL_DEBUG_SETHOOK_KEY = "__oac_internal_debug_sethook";
private LuaGlobalsFactory() { private LuaGlobalsFactory() {
} }
@@ -35,12 +37,13 @@ public final class LuaGlobalsFactory {
Objects.requireNonNull(options, "options"); Objects.requireNonNull(options, "options");
Globals g = JsePlatform.standardGlobals(); Globals g = JsePlatform.standardGlobals();
g.load(new DebugLib());
cacheInternalDebugSethook(g);
if (options.enableDebug) { if (options.enableDebug) {
// Ensure debug functions are available (depending on defaults). // Keep debug table visible to scripts.
g.load(new DebugLib());
} else { } else {
// If sandbox is not enabled but you still want no debug, explicitly remove it. // Keep hook support internally, but do not expose debug to scripts.
g.set("debug", LuaValue.NIL); g.set("debug", LuaValue.NIL);
} }
@@ -55,6 +58,16 @@ public final class LuaGlobalsFactory {
return g; return g;
} }
private static void cacheInternalDebugSethook(Globals g) {
LuaValue debugTable = g.get("debug");
if (debugTable.isnil() || !debugTable.istable()) return;
LuaValue sethook = debugTable.get("sethook");
if (!sethook.isnil() && sethook.isfunction()) {
g.set(INTERNAL_DEBUG_SETHOOK_KEY, sethook);
}
}
/** /**
* Applies basic sandbox hardening to the given globals. * Applies basic sandbox hardening to the given globals.
* *