127 lines
4.1 KiB
Java
127 lines
4.1 KiB
Java
package org.openautonomousconnection.luascript.runtime;
|
|
|
|
import javafx.concurrent.Worker;
|
|
import javafx.scene.web.WebEngine;
|
|
import org.luaj.vm2.Globals;
|
|
import org.openautonomousconnection.luascript.fx.FxDomHost;
|
|
import org.openautonomousconnection.luascript.fx.FxEventHost;
|
|
import org.openautonomousconnection.luascript.fx.FxUiHost;
|
|
import org.openautonomousconnection.luascript.fx.FxWebViewResourceHost;
|
|
import org.openautonomousconnection.luascript.hosts.AudioHost;
|
|
import org.openautonomousconnection.luascript.hosts.HostServices;
|
|
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
|
|
import org.openautonomousconnection.luascript.utils.LuaGlobalsFactory;
|
|
|
|
import java.util.Objects;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
/**
|
|
* JavaFX WebView integration entry point for LuaScript (no JavaScript).
|
|
*
|
|
* <p>Hard rule: every HTML script tag is treated as Lua.</p>
|
|
*/
|
|
public final class FxLuaScriptEngine implements AutoCloseable {
|
|
|
|
private final WebEngine engine;
|
|
private final LuaExecutionPolicy policy;
|
|
|
|
private final AtomicBoolean bootstrapped = new AtomicBoolean(false);
|
|
|
|
private LuaRuntime runtime;
|
|
|
|
/**
|
|
* Creates an integration engine with default UI execution policy.
|
|
*
|
|
* @param engine web engine
|
|
*/
|
|
public FxLuaScriptEngine(WebEngine engine) {
|
|
this(engine, LuaExecutionPolicy.uiDefault());
|
|
}
|
|
|
|
/**
|
|
* Creates an integration engine with a custom execution policy.
|
|
*
|
|
* @param engine web engine
|
|
* @param policy execution policy
|
|
*/
|
|
public FxLuaScriptEngine(WebEngine engine, LuaExecutionPolicy policy) {
|
|
this.engine = Objects.requireNonNull(engine, "engine");
|
|
this.policy = Objects.requireNonNull(policy, "policy");
|
|
}
|
|
|
|
/**
|
|
* Installs a load hook that bootstraps Lua when a page finished loading.
|
|
*/
|
|
public void install() {
|
|
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
|
|
if (newState == Worker.State.SUCCEEDED) {
|
|
bootstrapped.set(false);
|
|
bootstrap();
|
|
} else if (newState == Worker.State.CANCELLED || newState == Worker.State.FAILED) {
|
|
bootstrapped.set(false);
|
|
closeRuntimeQuietly();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Bootstraps Lua for the currently loaded document.
|
|
*/
|
|
public void bootstrap() {
|
|
if (!bootstrapped.compareAndSet(false, true)) return;
|
|
|
|
closeRuntimeQuietly();
|
|
|
|
// DOM host must exist before event/UI tables, and must ensure stable ids.
|
|
FxDomHost dom = new FxDomHost(engine);
|
|
dom.ensureAllElementsHaveId();
|
|
|
|
// Create per-page globals; harden sandbox in production.
|
|
Globals globals = LuaGlobalsFactory.create(
|
|
new LuaGlobalsFactory.Options()
|
|
.enableDebug(false)
|
|
.sandbox(true)
|
|
);
|
|
|
|
HostServices.StdoutConsole console = new HostServices.StdoutConsole("[lua] ");
|
|
FxUiHost uiHost = new FxUiHost(engine, dom);
|
|
FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine);
|
|
FxEventHost eventHost = new FxEventHost(dom);
|
|
// TODO: Default implementation or parameter for "audioHost"
|
|
HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console, audioHost);
|
|
LuaRuntime rt = new LuaRuntime(globals, services, policy);
|
|
eventHost.setRouter(rt.eventRouter());
|
|
|
|
rt.installStdTables(true);
|
|
rt.bootstrapFromDom();
|
|
|
|
this.runtime = rt;
|
|
}
|
|
|
|
/**
|
|
* Returns active runtime or null if not bootstrapped.
|
|
*
|
|
* @return runtime or null
|
|
*/
|
|
public LuaRuntime runtimeOrNull() {
|
|
return runtime;
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
closeRuntimeQuietly();
|
|
}
|
|
|
|
private void closeRuntimeQuietly() {
|
|
LuaRuntime rt = this.runtime;
|
|
this.runtime = null;
|
|
if (rt != null) {
|
|
try {
|
|
rt.close();
|
|
} catch (Exception ignored) {
|
|
// Best-effort shutdown.
|
|
}
|
|
}
|
|
}
|
|
}
|