Event fixes
This commit is contained in:
10
pom.xml
10
pom.xml
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -131,4 +121,4 @@ public final class FxLuaScriptEngine implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,4 +148,4 @@ public final class LuaSecurityManager implements AutoCloseable {
|
|||||||
public void close() {
|
public void close() {
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user