Many new things
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import org.openautonomousconnection.luascript.hosts.ClipboardHost;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* JavaFX clipboard host (text only).
|
||||
*/
|
||||
public final class FxClipboardHost implements ClipboardHost {
|
||||
|
||||
@Override
|
||||
public void setText(String text) {
|
||||
String s = text == null ? "" : text;
|
||||
Platform.runLater(() -> {
|
||||
ClipboardContent c = new ClipboardContent();
|
||||
c.putString(s);
|
||||
Clipboard.getSystemClipboard().setContent(c);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
AtomicReference<String> out = new AtomicReference<>("");
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
String s = Clipboard.getSystemClipboard().getString();
|
||||
out.set(s == null ? "" : s);
|
||||
});
|
||||
return out.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.scene.web.WebEngine;
|
||||
import org.openautonomousconnection.luascript.hosts.CssHost;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* CSS host via internal JS bridge.
|
||||
*/
|
||||
public final class FxCssHost implements CssHost {
|
||||
|
||||
private final WebEngine engine;
|
||||
private final FxDomHost dom;
|
||||
|
||||
public FxCssHost(WebEngine engine, FxDomHost dom) {
|
||||
this.engine = Objects.requireNonNull(engine, "engine");
|
||||
this.dom = Objects.requireNonNull(dom, "dom");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComputedStyle(String elementId, String property) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(property, "property");
|
||||
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " var prop=" + FxWebBridge.toJsLiteral(property) + ";"
|
||||
+ " var el=document.getElementById(id);"
|
||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
||||
+ " var cs=getComputedStyle(el);"
|
||||
+ " var v=cs.getPropertyValue(prop) || cs[prop] || '';"
|
||||
+ " return String(v);"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return ret == null ? "" : String.valueOf(ret);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getComputedStyles(String elementId, String[] properties) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(properties, "properties");
|
||||
|
||||
Map<String, String> out = new LinkedHashMap<>();
|
||||
for (String p : properties) {
|
||||
if (p == null || p.isBlank()) continue;
|
||||
out.put(p, getComputedStyle(elementId, p));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInlineStyle(String elementId, String property, String value) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(property, "property");
|
||||
|
||||
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String v = value == null ? "" : value;
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " var prop=" + FxWebBridge.toJsLiteral(property) + ";"
|
||||
+ " var val=" + FxWebBridge.toJsLiteral(v) + ";"
|
||||
+ " var el=document.getElementById(id);"
|
||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
||||
+ " if(!val){ el.style.removeProperty(prop); return null; }"
|
||||
+ " el.style.setProperty(prop, val);"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
|
||||
engine.executeScript(script);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInlineStyle(String elementId, String property) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(property, "property");
|
||||
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " var prop=" + FxWebBridge.toJsLiteral(property) + ";"
|
||||
+ " var el=document.getElementById(id);"
|
||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
||||
+ " var v=el.style.getPropertyValue(prop) || '';"
|
||||
+ " return String(v);"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return ret == null ? "" : String.valueOf(ret);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCssVariable(String elementId, String name, String value) {
|
||||
Objects.requireNonNull(name, "name");
|
||||
if (!name.trim().startsWith("--")) throw new IllegalArgumentException("CSS variable must start with '--': " + name);
|
||||
setInlineStyle(elementId, name.trim(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCssVariable(String elementId, String name) {
|
||||
Objects.requireNonNull(name, "name");
|
||||
if (!name.trim().startsWith("--")) throw new IllegalArgumentException("CSS variable must start with '--': " + name);
|
||||
return getComputedStyle(elementId, name.trim());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.scene.web.WebEngine;
|
||||
import org.openautonomousconnection.luascript.hosts.GeometryHost;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Geometry/scroll host via internal JS bridge.
|
||||
*/
|
||||
public final class FxGeometryHost implements GeometryHost {
|
||||
|
||||
private final WebEngine engine;
|
||||
private final FxDomHost dom;
|
||||
|
||||
public FxGeometryHost(WebEngine engine, FxDomHost dom) {
|
||||
this.engine = Objects.requireNonNull(engine, "engine");
|
||||
this.dom = Objects.requireNonNull(dom, "dom");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getBoundingClientRect(String elementId) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " var el=document.getElementById(id);"
|
||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
||||
+ " var r=el.getBoundingClientRect();"
|
||||
+ " return {"
|
||||
+ " x:r.x, y:r.y, width:r.width, height:r.height,"
|
||||
+ " top:r.top, left:r.left, right:r.right, bottom:r.bottom"
|
||||
+ " };"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return FxWebBridge.toStringObjectMap(ret);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getViewport() {
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " return {"
|
||||
+ " width: window.innerWidth || 0,"
|
||||
+ " height: window.innerHeight || 0,"
|
||||
+ " devicePixelRatio: window.devicePixelRatio || 1,"
|
||||
+ " scrollX: window.scrollX || 0,"
|
||||
+ " scrollY: window.scrollY || 0"
|
||||
+ " };"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return FxWebBridge.toStringObjectMap(ret);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollTo(double x, double y) {
|
||||
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " window.scrollTo(" + FxWebBridge.toJsLiteral(x) + "," + FxWebBridge.toJsLiteral(y) + ");"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollIntoView(String elementId, String align) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
String a = FxWebBridge.normalizeAlign(align);
|
||||
|
||||
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " var el=document.getElementById(id);"
|
||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
||||
+ " el.scrollIntoView({block:" + FxWebBridge.toJsLiteral(a) + ", inline:" + FxWebBridge.toJsLiteral(a) + "});"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.scene.web.WebEngine;
|
||||
import netscape.javascript.JSObject;
|
||||
import org.openautonomousconnection.luascript.hosts.ObserverHost;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Observer host implemented via JS observers calling back into Java.
|
||||
*
|
||||
* <p>Requires JavaScript enabled (bridge only).</p>
|
||||
*/
|
||||
public final class FxObserverHost implements ObserverHost {
|
||||
|
||||
private final WebEngine engine;
|
||||
private final FxDomHost dom;
|
||||
|
||||
private volatile ObserverCallback callback;
|
||||
|
||||
private final ConcurrentHashMap<String, Boolean> mutationObserved = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Boolean> resizeObserved = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Boolean> intersectionObserved = new ConcurrentHashMap<>();
|
||||
|
||||
public FxObserverHost(WebEngine engine, FxDomHost dom) {
|
||||
this.engine = Objects.requireNonNull(engine, "engine");
|
||||
this.dom = Objects.requireNonNull(dom, "dom");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(ObserverCallback callback) {
|
||||
this.callback = callback;
|
||||
FxThreadBridge.runAndWait(this::installBridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void observeMutations(String elementId, boolean subtree, boolean attributes, boolean childList, boolean characterData) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
dom.requireDocument();
|
||||
FxWebBridge.ensureJsEnabled(engine);
|
||||
installBridge();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " var el=document.getElementById(id);"
|
||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
||||
+ " window.__oac_obs = window.__oac_obs || {};"
|
||||
+ " window.__oac_obs.muts = window.__oac_obs.muts || new Map();"
|
||||
+ " if(window.__oac_obs.muts.has(id)) return null;"
|
||||
+ " var cfg={subtree:" + (subtree ? "true" : "false")
|
||||
+ " ,attributes:" + (attributes ? "true" : "false")
|
||||
+ " ,childList:" + (childList ? "true" : "false")
|
||||
+ " ,characterData:" + (characterData ? "true" : "false")
|
||||
+ " };"
|
||||
+ " var mo=new MutationObserver(function(muts){"
|
||||
+ " try{"
|
||||
+ " var payload={count:muts.length};"
|
||||
+ " window.__oac_bridge.emit('mutation', id, JSON.stringify(payload));"
|
||||
+ " }catch(e){}"
|
||||
+ " });"
|
||||
+ " mo.observe(el,cfg);"
|
||||
+ " window.__oac_obs.muts.set(id, mo);"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
engine.setJavaScriptEnabled(false);
|
||||
mutationObserved.put(elementId, true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unobserveMutations(String elementId) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
dom.requireDocument();
|
||||
FxWebBridge.ensureJsEnabled(engine);
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " if(!window.__oac_obs || !window.__oac_obs.muts) return null;"
|
||||
+ " var mo=window.__oac_obs.muts.get(id);"
|
||||
+ " if(mo){ mo.disconnect(); window.__oac_obs.muts.delete(id); }"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
engine.setJavaScriptEnabled(false);
|
||||
mutationObserved.remove(elementId);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void observeResize(String elementId) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
dom.requireDocument();
|
||||
FxWebBridge.ensureJsEnabled(engine);
|
||||
installBridge();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " var el=document.getElementById(id);"
|
||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
||||
+ " if(!('ResizeObserver' in window)) return null;"
|
||||
+ " window.__oac_obs = window.__oac_obs || {};"
|
||||
+ " window.__oac_obs.res = window.__oac_obs.res || new Map();"
|
||||
+ " if(window.__oac_obs.res.has(id)) return null;"
|
||||
+ " var ro=new ResizeObserver(function(entries){"
|
||||
+ " try{"
|
||||
+ " var r=entries && entries[0] && entries[0].contentRect;"
|
||||
+ " var payload=r?{x:r.x,y:r.y,width:r.width,height:r.height}:{count:(entries?entries.length:0)};"
|
||||
+ " window.__oac_bridge.emit('resize', id, JSON.stringify(payload));"
|
||||
+ " }catch(e){}"
|
||||
+ " });"
|
||||
+ " ro.observe(el);"
|
||||
+ " window.__oac_obs.res.set(id, ro);"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
engine.setJavaScriptEnabled(false);
|
||||
resizeObserved.put(elementId, true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unobserveResize(String elementId) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
dom.requireDocument();
|
||||
FxWebBridge.ensureJsEnabled(engine);
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " if(!window.__oac_obs || !window.__oac_obs.res) return null;"
|
||||
+ " var ro=window.__oac_obs.res.get(id);"
|
||||
+ " if(ro){ ro.disconnect(); window.__oac_obs.res.delete(id); }"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
engine.setJavaScriptEnabled(false);
|
||||
resizeObserved.remove(elementId);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void observeIntersection(String elementId, double threshold) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
double t = threshold;
|
||||
if (t < 0.0) t = 0.0;
|
||||
if (t > 1.0) t = 1.0;
|
||||
|
||||
double finalT = t;
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
dom.requireDocument();
|
||||
FxWebBridge.ensureJsEnabled(engine);
|
||||
installBridge();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " var el=document.getElementById(id);"
|
||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
||||
+ " if(!('IntersectionObserver' in window)) return null;"
|
||||
+ " window.__oac_obs = window.__oac_obs || {};"
|
||||
+ " window.__oac_obs.int = window.__oac_obs.int || new Map();"
|
||||
+ " if(window.__oac_obs.int.has(id)) return null;"
|
||||
+ " var io=new IntersectionObserver(function(entries){"
|
||||
+ " try{"
|
||||
+ " var e=entries && entries[0];"
|
||||
+ " var payload=e?{"
|
||||
+ " isIntersecting:!!e.isIntersecting,"
|
||||
+ " ratio:(e.intersectionRatio||0)"
|
||||
+ " }:{count:(entries?entries.length:0)};"
|
||||
+ " window.__oac_bridge.emit('intersection', id, JSON.stringify(payload));"
|
||||
+ " }catch(ex){}"
|
||||
+ " }, {threshold:" + FxWebBridge.toJsLiteral(finalT) + "});"
|
||||
+ " io.observe(el);"
|
||||
+ " window.__oac_obs.int.set(id, io);"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
engine.setJavaScriptEnabled(false);
|
||||
intersectionObserved.put(elementId, true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unobserveIntersection(String elementId) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
dom.requireDocument();
|
||||
FxWebBridge.ensureJsEnabled(engine);
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " if(!window.__oac_obs || !window.__oac_obs.int) return null;"
|
||||
+ " var io=window.__oac_obs.int.get(id);"
|
||||
+ " if(io){ io.disconnect(); window.__oac_obs.int.delete(id); }"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
engine.setJavaScriptEnabled(false);
|
||||
intersectionObserved.remove(elementId);
|
||||
});
|
||||
}
|
||||
|
||||
private void installBridge() {
|
||||
dom.requireDocument();
|
||||
FxWebBridge.ensureJsEnabled(engine);
|
||||
|
||||
JSObject win = (JSObject) engine.executeScript("window");
|
||||
win.setMember("__oac_bridge", new Bridge());
|
||||
}
|
||||
|
||||
/**
|
||||
* Object exposed to JS.
|
||||
*/
|
||||
public final class Bridge {
|
||||
/**
|
||||
* Emits observer events from JS to Java.
|
||||
*
|
||||
* @param type observer type
|
||||
* @param targetId element id
|
||||
* @param json payload JSON string
|
||||
*/
|
||||
public void emit(String type, String targetId, String json) {
|
||||
ObserverCallback cb = callback;
|
||||
if (cb == null) return;
|
||||
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("json", json == null ? "" : json);
|
||||
|
||||
try {
|
||||
cb.onEvent(type == null ? "" : type, targetId == null ? "" : targetId, payload);
|
||||
} catch (RuntimeException ex) {
|
||||
System.err.println("[observer] callback failed: " + ex.getMessage());
|
||||
ex.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Platform;
|
||||
import org.openautonomousconnection.luascript.hosts.SchedulerHost;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* JavaFX-based scheduler providing setTimeout/setInterval/requestAnimationFrame.
|
||||
*/
|
||||
public final class FxSchedulerHost implements SchedulerHost, AutoCloseable {
|
||||
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final AtomicLong seq = new AtomicLong(1);
|
||||
|
||||
private final ConcurrentHashMap<Long, ScheduledFuture<?>> scheduled = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Long, Runnable> rafCallbacks = new ConcurrentHashMap<>();
|
||||
private final Set<Long> canceledRaf = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private final AnimationTimer rafTimer;
|
||||
|
||||
public FxSchedulerHost() {
|
||||
this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "oac-fx-scheduler");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
this.rafTimer = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
// Drain RAF callbacks once per frame.
|
||||
if (rafCallbacks.isEmpty()) return;
|
||||
|
||||
Map<Long, Runnable> snap = new ConcurrentHashMap<>(rafCallbacks);
|
||||
rafCallbacks.clear();
|
||||
|
||||
for (Map.Entry<Long, Runnable> e : snap.entrySet()) {
|
||||
long id = e.getKey();
|
||||
Runnable cb = e.getValue();
|
||||
if (canceledRaf.remove(id)) continue;
|
||||
|
||||
try {
|
||||
cb.run();
|
||||
} catch (RuntimeException ex) {
|
||||
System.err.println("[scheduler.raf] callback failed: " + ex.getMessage());
|
||||
ex.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Platform.runLater(rafTimer::start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long setTimeout(long delayMillis, Runnable callback) {
|
||||
if (delayMillis < 0) delayMillis = 0;
|
||||
Runnable cb = Objects.requireNonNull(callback, "callback");
|
||||
|
||||
long id = seq.getAndIncrement();
|
||||
ScheduledFuture<?> f = scheduler.schedule(() -> safeRun(cb, "timeout"), delayMillis, TimeUnit.MILLISECONDS);
|
||||
scheduled.put(id, f);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long setInterval(long intervalMillis, Runnable callback) {
|
||||
if (intervalMillis <= 0) throw new IllegalArgumentException("intervalMillis must be > 0");
|
||||
Runnable cb = Objects.requireNonNull(callback, "callback");
|
||||
|
||||
long id = seq.getAndIncrement();
|
||||
ScheduledFuture<?> f = scheduler.scheduleAtFixedRate(() -> safeRun(cb, "interval"), intervalMillis, intervalMillis, TimeUnit.MILLISECONDS);
|
||||
scheduled.put(id, f);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clear(long handle) {
|
||||
ScheduledFuture<?> f = scheduled.remove(handle);
|
||||
if (f == null) return false;
|
||||
return f.cancel(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long requestAnimationFrame(Runnable callback) {
|
||||
Runnable cb = Objects.requireNonNull(callback, "callback");
|
||||
long id = seq.getAndIncrement();
|
||||
rafCallbacks.put(id, cb);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelAnimationFrame(long handle) {
|
||||
// If already queued, mark as canceled.
|
||||
boolean existed = rafCallbacks.remove(handle) != null;
|
||||
canceledRaf.add(handle);
|
||||
return existed;
|
||||
}
|
||||
|
||||
private static void safeRun(Runnable cb, String kind) {
|
||||
try {
|
||||
cb.run();
|
||||
} catch (RuntimeException ex) {
|
||||
System.err.println("[scheduler." + kind + "] callback failed: " + ex.getMessage());
|
||||
ex.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
Platform.runLater(rafTimer::stop);
|
||||
} catch (Exception ignored) {
|
||||
// ignore
|
||||
}
|
||||
scheduler.shutdownNow();
|
||||
scheduled.clear();
|
||||
rafCallbacks.clear();
|
||||
canceledRaf.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.scene.web.WebEngine;
|
||||
import org.openautonomousconnection.luascript.hosts.SelectorHost;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* CSS selector host implemented via internal JS bridge.
|
||||
*/
|
||||
public final class FxSelectorHost implements SelectorHost {
|
||||
|
||||
private final WebEngine engine;
|
||||
private final FxDomHost dom;
|
||||
|
||||
public FxSelectorHost(WebEngine engine, FxDomHost dom) {
|
||||
this.engine = Objects.requireNonNull(engine, "engine");
|
||||
this.dom = Objects.requireNonNull(dom, "dom");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String querySelector(String selector) {
|
||||
Objects.requireNonNull(selector, "selector");
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
dom.ensureAllElementsHaveId();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var sel = " + FxWebBridge.toJsLiteral(selector) + ";"
|
||||
+ " var el = document.querySelector(sel);"
|
||||
+ " if(!el) return null;"
|
||||
+ " if(!el.id){"
|
||||
+ " el.id='__auto_' + Math.floor(Math.random()*1e18).toString(36);"
|
||||
+ " }"
|
||||
+ " return el.id;"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return ret == null ? null : String.valueOf(ret);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> querySelectorAll(String selector) {
|
||||
Objects.requireNonNull(selector, "selector");
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
dom.ensureAllElementsHaveId();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var sel=" + FxWebBridge.toJsLiteral(selector) + ";"
|
||||
+ " var els=document.querySelectorAll(sel);"
|
||||
+ " var out=[];"
|
||||
+ " for(var i=0;i<els.length;i++){"
|
||||
+ " var el=els[i];"
|
||||
+ " if(!el.id){ el.id='__auto_' + Math.floor(Math.random()*1e18).toString(36); }"
|
||||
+ " out.push(el.id);"
|
||||
+ " }"
|
||||
+ " return out;"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return FxWebBridge.toStringList(ret);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String elementId, String selector) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(selector, "selector");
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " var sel=" + FxWebBridge.toJsLiteral(selector) + ";"
|
||||
+ " var el=document.getElementById(id);"
|
||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
||||
+ " return !!el.matches(sel);"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return ret instanceof Boolean b ? b : Boolean.parseBoolean(String.valueOf(ret));
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String closest(String elementId, String selector) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(selector, "selector");
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var id=" + FxWebBridge.toJsLiteral(elementId) + ";"
|
||||
+ " var sel=" + FxWebBridge.toJsLiteral(selector) + ";"
|
||||
+ " var el=document.getElementById(id);"
|
||||
+ " if(!el) throw new Error('Unknown element id: '+id);"
|
||||
+ " var c=el.closest(sel);"
|
||||
+ " if(!c) return null;"
|
||||
+ " if(!c.id){ c.id='__auto_' + Math.floor(Math.random()*1e18).toString(36); }"
|
||||
+ " return c.id;"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return ret == null ? null : String.valueOf(ret);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.scene.web.WebEngine;
|
||||
import org.openautonomousconnection.luascript.hosts.StorageHost;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Storage host backed by localStorage/sessionStorage via JS bridge.
|
||||
*/
|
||||
public final class FxStorageHost implements StorageHost {
|
||||
|
||||
private final WebEngine engine;
|
||||
private final FxDomHost dom;
|
||||
|
||||
public FxStorageHost(WebEngine engine, FxDomHost dom) {
|
||||
this.engine = Objects.requireNonNull(engine, "engine");
|
||||
this.dom = Objects.requireNonNull(dom, "dom");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> localKeys() {
|
||||
return keys("localStorage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String localGet(String key) {
|
||||
return get("localStorage", key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localSet(String key, String value) {
|
||||
set("localStorage", key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localRemove(String key) {
|
||||
remove("localStorage", key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localClear() {
|
||||
clear("localStorage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> sessionKeys() {
|
||||
return keys("sessionStorage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sessionGet(String key) {
|
||||
return get("sessionStorage", key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionSet(String key, String value) {
|
||||
set("sessionStorage", key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionRemove(String key) {
|
||||
remove("sessionStorage", key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionClear() {
|
||||
clear("sessionStorage");
|
||||
}
|
||||
|
||||
private List<String> keys(String storageName) {
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
|
||||
+ " if(!s) return [];"
|
||||
+ " var out=[];"
|
||||
+ " for(var i=0;i<s.length;i++){ out.push(String(s.key(i))); }"
|
||||
+ " return out;"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return FxWebBridge.toStringList(ret);
|
||||
}));
|
||||
}
|
||||
|
||||
private String get(String storageName, String key) {
|
||||
Objects.requireNonNull(key, "key");
|
||||
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
|
||||
+ " if(!s) return null;"
|
||||
+ " var v=s.getItem(" + FxWebBridge.toJsLiteral(key) + ");"
|
||||
+ " return v===null?null:String(v);"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return ret == null ? null : String.valueOf(ret);
|
||||
}));
|
||||
}
|
||||
|
||||
private void set(String storageName, String key, String value) {
|
||||
Objects.requireNonNull(key, "key");
|
||||
|
||||
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
if (value == null) {
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
|
||||
+ " if(!s) return null;"
|
||||
+ " s.removeItem(" + FxWebBridge.toJsLiteral(key) + ");"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
return;
|
||||
}
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
|
||||
+ " if(!s) return null;"
|
||||
+ " s.setItem(" + FxWebBridge.toJsLiteral(key) + "," + FxWebBridge.toJsLiteral(value) + ");"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
}));
|
||||
}
|
||||
|
||||
private void remove(String storageName, String key) {
|
||||
Objects.requireNonNull(key, "key");
|
||||
set(storageName, key, null);
|
||||
}
|
||||
|
||||
private void clear(String storageName) {
|
||||
FxThreadBridge.runAndWait(() -> FxWebBridge.runWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var s=window[" + FxWebBridge.toJsLiteral(storageName) + "];"
|
||||
+ " if(!s) return null;"
|
||||
+ " s.clear();"
|
||||
+ " return null;"
|
||||
+ "})();";
|
||||
engine.executeScript(script);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.scene.web.WebEngine;
|
||||
import org.openautonomousconnection.luascript.hosts.UtilHost;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Utility host: Base64, random, URL parsing, query parsing, JSON stringify/normalize via JS.
|
||||
*/
|
||||
public final class FxUtilHost implements UtilHost {
|
||||
|
||||
private final WebEngine engine;
|
||||
private final FxDomHost dom;
|
||||
private final SecureRandom rng = new SecureRandom();
|
||||
|
||||
public FxUtilHost(WebEngine engine, FxDomHost dom) {
|
||||
this.engine = Objects.requireNonNull(engine, "engine");
|
||||
this.dom = Objects.requireNonNull(dom, "dom");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String base64Encode(String text) {
|
||||
String s = text == null ? "" : text;
|
||||
return Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String base64Decode(String base64) {
|
||||
if (base64 == null) return "";
|
||||
byte[] b = Base64.getDecoder().decode(base64);
|
||||
return new String(b, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String randomHex(int numBytes) {
|
||||
if (numBytes <= 0) throw new IllegalArgumentException("numBytes must be > 0");
|
||||
byte[] b = new byte[numBytes];
|
||||
rng.nextBytes(b);
|
||||
StringBuilder sb = new StringBuilder(numBytes * 2);
|
||||
for (byte x : b) sb.append(String.format("%02x", x));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> parseUrl(String url) {
|
||||
Objects.requireNonNull(url, "url");
|
||||
URI u = URI.create(url.trim());
|
||||
|
||||
Map<String, String> out = new LinkedHashMap<>();
|
||||
out.put("scheme", safe(u.getScheme()));
|
||||
out.put("host", safe(u.getHost()));
|
||||
out.put("port", u.getPort() < 0 ? "" : String.valueOf(u.getPort()));
|
||||
out.put("path", safe(u.getPath()));
|
||||
out.put("query", safe(u.getQuery()));
|
||||
out.put("fragment", safe(u.getFragment()));
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> parseQuery(String query) {
|
||||
String q = query == null ? "" : query.trim();
|
||||
if (q.startsWith("?")) q = q.substring(1);
|
||||
|
||||
Map<String, List<String>> out = new LinkedHashMap<>();
|
||||
if (q.isEmpty()) return out;
|
||||
|
||||
for (String part : q.split("&")) {
|
||||
if (part.isEmpty()) continue;
|
||||
String k;
|
||||
String v;
|
||||
int idx = part.indexOf('=');
|
||||
if (idx < 0) {
|
||||
k = decode(part);
|
||||
v = "";
|
||||
} else {
|
||||
k = decode(part.substring(0, idx));
|
||||
v = decode(part.substring(idx + 1));
|
||||
}
|
||||
out.computeIfAbsent(k, __ -> new ArrayList<>()).add(v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String jsonStringifyExpr(String elementId, String jsExpr) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(jsExpr, "jsExpr");
|
||||
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
// NOTE: jsExpr is intentionally raw JS expression; this is a trusted host API.
|
||||
// If you need untrusted usage, wrap/validate upstream.
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var v=(" + jsExpr + ");"
|
||||
+ " return JSON.stringify(v);"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return ret == null ? "null" : String.valueOf(ret);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String jsonNormalize(String elementId, String json) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(json, "json");
|
||||
|
||||
return FxThreadBridge.callAndWait(() -> FxWebBridge.callWithJs(engine, () -> {
|
||||
dom.requireDocument();
|
||||
|
||||
String script = ""
|
||||
+ "(function(){"
|
||||
+ " var s=" + FxWebBridge.toJsLiteral(json) + ";"
|
||||
+ " var obj=JSON.parse(s);"
|
||||
+ " return JSON.stringify(obj);"
|
||||
+ "})();";
|
||||
|
||||
Object ret = engine.executeScript(script);
|
||||
return ret == null ? "null" : String.valueOf(ret);
|
||||
}));
|
||||
}
|
||||
|
||||
private static String safe(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
private static String decode(String s) {
|
||||
return URLDecoder.decode(s, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.openautonomousconnection.luascript.hosts;
|
||||
|
||||
/**
|
||||
* Clipboard access.
|
||||
*/
|
||||
public interface ClipboardHost {
|
||||
|
||||
/**
|
||||
* Sets clipboard text.
|
||||
*
|
||||
* @param text text
|
||||
*/
|
||||
void setText(String text);
|
||||
|
||||
/**
|
||||
* Gets clipboard text.
|
||||
*
|
||||
* @return text or empty string if none
|
||||
*/
|
||||
String getText();
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.openautonomousconnection.luascript.hosts;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CSSOM / computed styles access.
|
||||
*/
|
||||
public interface CssHost {
|
||||
|
||||
/**
|
||||
* Gets computed style value for a property.
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param property CSS property (kebab-case or camelCase accepted by browser)
|
||||
* @return computed value (never null, may be empty)
|
||||
*/
|
||||
String getComputedStyle(String elementId, String property);
|
||||
|
||||
/**
|
||||
* Returns a computed style snapshot of selected properties.
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param properties properties to fetch
|
||||
* @return map property->value
|
||||
*/
|
||||
Map<String, String> getComputedStyles(String elementId, String[] properties);
|
||||
|
||||
/**
|
||||
* Sets an inline style property (style attribute).
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param property CSS property
|
||||
* @param value CSS value (empty => remove)
|
||||
*/
|
||||
void setInlineStyle(String elementId, String property, String value);
|
||||
|
||||
/**
|
||||
* Gets an inline style property (from style attribute).
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param property CSS property
|
||||
* @return inline value (may be empty)
|
||||
*/
|
||||
String getInlineStyle(String elementId, String property);
|
||||
|
||||
/**
|
||||
* Sets a CSS variable (custom property) on an element.
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param name variable name (e.g. "--primary")
|
||||
* @param value value
|
||||
*/
|
||||
void setCssVariable(String elementId, String name, String value);
|
||||
|
||||
/**
|
||||
* Gets a CSS variable (computed) from an element.
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param name variable name (e.g. "--primary")
|
||||
* @return computed var (may be empty)
|
||||
*/
|
||||
String getCssVariable(String elementId, String name);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.openautonomousconnection.luascript.hosts;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Layout and scrolling related accessors.
|
||||
*
|
||||
* <p>All results are plain maps to keep dependencies minimal.</p>
|
||||
*/
|
||||
public interface GeometryHost {
|
||||
|
||||
/**
|
||||
* Returns bounding client rect of an element.
|
||||
*
|
||||
* @param elementId element id
|
||||
* @return map: x,y,width,height,top,left,right,bottom
|
||||
*/
|
||||
Map<String, Object> getBoundingClientRect(String elementId);
|
||||
|
||||
/**
|
||||
* Returns viewport metrics.
|
||||
*
|
||||
* @return map: width,height,devicePixelRatio,scrollX,scrollY
|
||||
*/
|
||||
Map<String, Object> getViewport();
|
||||
|
||||
/**
|
||||
* Scrolls the document to coordinates.
|
||||
*
|
||||
* @param x scroll x
|
||||
* @param y scroll y
|
||||
*/
|
||||
void scrollTo(double x, double y);
|
||||
|
||||
/**
|
||||
* Scrolls an element into view.
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param align one of: "start","center","end","nearest" (null => "nearest")
|
||||
*/
|
||||
void scrollIntoView(String elementId, String align);
|
||||
}
|
||||
@@ -10,62 +10,25 @@ import java.util.Optional;
|
||||
*/
|
||||
public interface HostServices {
|
||||
|
||||
/**
|
||||
* Returns an optional UI host.
|
||||
*
|
||||
* @return ui host
|
||||
*/
|
||||
Optional<UiHost> ui();
|
||||
|
||||
/**
|
||||
* Returns an optional DOM host.
|
||||
*
|
||||
* @return dom host
|
||||
*/
|
||||
Optional<DomHost> dom();
|
||||
|
||||
/**
|
||||
* Returns an optional event host.
|
||||
*
|
||||
* @return event host
|
||||
*/
|
||||
Optional<EventHost> events();
|
||||
|
||||
/**
|
||||
* Returns an optional resource host.
|
||||
*
|
||||
* @return resource host
|
||||
*/
|
||||
Optional<ResourceHost> resources();
|
||||
|
||||
/**
|
||||
* Returns an optional console host.
|
||||
*
|
||||
* @return console host
|
||||
*/
|
||||
Optional<ConsoleHost> console();
|
||||
|
||||
/**
|
||||
* Returns an optional audio host.
|
||||
*
|
||||
* @return audio host
|
||||
*/
|
||||
Optional<AudioHost> audio();
|
||||
|
||||
/**
|
||||
* Returns an optional image host.
|
||||
*
|
||||
* @return image host
|
||||
*/
|
||||
Optional<ImageHost> image();
|
||||
|
||||
/**
|
||||
* Returns an optional video host.
|
||||
*
|
||||
* @return video host
|
||||
*/
|
||||
Optional<VideoHost> video();
|
||||
|
||||
/* NEW */
|
||||
Optional<SchedulerHost> scheduler();
|
||||
Optional<SelectorHost> selector();
|
||||
Optional<GeometryHost> geometry();
|
||||
Optional<CssHost> css();
|
||||
Optional<StorageHost> storage();
|
||||
Optional<UtilHost> util();
|
||||
Optional<ClipboardHost> clipboard();
|
||||
Optional<ObserverHost> observers();
|
||||
|
||||
/**
|
||||
* Simple immutable implementation.
|
||||
*/
|
||||
@@ -80,18 +43,15 @@ public interface HostServices {
|
||||
private final ImageHost imageHost;
|
||||
private final VideoHost videoHost;
|
||||
|
||||
/**
|
||||
* Creates a HostServices container.
|
||||
*
|
||||
* @param ui ui host
|
||||
* @param dom dom host
|
||||
* @param events event host
|
||||
* @param resources resource host
|
||||
* @param console console host
|
||||
* @param audioHost audio host
|
||||
* @param imageHost image host
|
||||
* @param videoHost video host
|
||||
*/
|
||||
private final SchedulerHost schedulerHost;
|
||||
private final SelectorHost selectorHost;
|
||||
private final GeometryHost geometryHost;
|
||||
private final CssHost cssHost;
|
||||
private final StorageHost storageHost;
|
||||
private final UtilHost utilHost;
|
||||
private final ClipboardHost clipboardHost;
|
||||
private final ObserverHost observerHost;
|
||||
|
||||
public Default(
|
||||
UiHost ui,
|
||||
DomHost dom,
|
||||
@@ -100,7 +60,15 @@ public interface HostServices {
|
||||
ConsoleHost console,
|
||||
AudioHost audioHost,
|
||||
ImageHost imageHost,
|
||||
VideoHost videoHost
|
||||
VideoHost videoHost,
|
||||
SchedulerHost schedulerHost,
|
||||
SelectorHost selectorHost,
|
||||
GeometryHost geometryHost,
|
||||
CssHost cssHost,
|
||||
StorageHost storageHost,
|
||||
UtilHost utilHost,
|
||||
ClipboardHost clipboardHost,
|
||||
ObserverHost observerHost
|
||||
) {
|
||||
this.ui = ui;
|
||||
this.dom = dom;
|
||||
@@ -110,47 +78,33 @@ public interface HostServices {
|
||||
this.audioHost = audioHost;
|
||||
this.imageHost = imageHost;
|
||||
this.videoHost = videoHost;
|
||||
this.schedulerHost = schedulerHost;
|
||||
this.selectorHost = selectorHost;
|
||||
this.geometryHost = geometryHost;
|
||||
this.cssHost = cssHost;
|
||||
this.storageHost = storageHost;
|
||||
this.utilHost = utilHost;
|
||||
this.clipboardHost = clipboardHost;
|
||||
this.observerHost = observerHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<UiHost> ui() {
|
||||
return Optional.ofNullable(ui);
|
||||
}
|
||||
@Override public Optional<UiHost> ui() { return Optional.ofNullable(ui); }
|
||||
@Override public Optional<DomHost> dom() { return Optional.ofNullable(dom); }
|
||||
@Override public Optional<EventHost> events() { return Optional.ofNullable(events); }
|
||||
@Override public Optional<ResourceHost> resources() { return Optional.ofNullable(resources); }
|
||||
@Override public Optional<ConsoleHost> console() { return Optional.ofNullable(console); }
|
||||
@Override public Optional<AudioHost> audio() { return Optional.ofNullable(audioHost); }
|
||||
@Override public Optional<ImageHost> image() { return Optional.ofNullable(imageHost); }
|
||||
@Override public Optional<VideoHost> video() { return Optional.ofNullable(videoHost); }
|
||||
|
||||
@Override
|
||||
public Optional<DomHost> dom() {
|
||||
return Optional.ofNullable(dom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<EventHost> events() {
|
||||
return Optional.ofNullable(events);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ResourceHost> resources() {
|
||||
return Optional.ofNullable(resources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ConsoleHost> console() {
|
||||
return Optional.ofNullable(console);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AudioHost> audio() {
|
||||
return Optional.ofNullable(audioHost);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ImageHost> image() {
|
||||
return Optional.ofNullable(imageHost);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<VideoHost> video() {
|
||||
return Optional.ofNullable(videoHost);
|
||||
}
|
||||
@Override public Optional<SchedulerHost> scheduler() { return Optional.ofNullable(schedulerHost); }
|
||||
@Override public Optional<SelectorHost> selector() { return Optional.ofNullable(selectorHost); }
|
||||
@Override public Optional<GeometryHost> geometry() { return Optional.ofNullable(geometryHost); }
|
||||
@Override public Optional<CssHost> css() { return Optional.ofNullable(cssHost); }
|
||||
@Override public Optional<StorageHost> storage() { return Optional.ofNullable(storageHost); }
|
||||
@Override public Optional<UtilHost> util() { return Optional.ofNullable(utilHost); }
|
||||
@Override public Optional<ClipboardHost> clipboard() { return Optional.ofNullable(clipboardHost); }
|
||||
@Override public Optional<ObserverHost> observers() { return Optional.ofNullable(observerHost); }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,11 +113,6 @@ public interface HostServices {
|
||||
final class StdoutConsole implements ConsoleHost {
|
||||
private final String prefix;
|
||||
|
||||
/**
|
||||
* Creates a new stdout console with a prefix.
|
||||
*
|
||||
* @param prefix prefix (may be empty)
|
||||
*/
|
||||
public StdoutConsole(String prefix) {
|
||||
this.prefix = Objects.requireNonNull(prefix, "prefix");
|
||||
}
|
||||
@@ -172,29 +121,10 @@ public interface HostServices {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
System.out.println(prefix + "[info] " + safe(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String message) {
|
||||
System.out.println(prefix + "[log] " + safe(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String message) {
|
||||
System.out.println(prefix + "[warn] " + safe(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message) {
|
||||
System.err.println(prefix + "[error] " + safe(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(String message) {
|
||||
System.err.println(prefix + "[exception] " + safe(message));
|
||||
}
|
||||
@Override public void info(String message) { System.out.println(prefix + "[info] " + safe(message)); }
|
||||
@Override public void log(String message) { System.out.println(prefix + "[log] " + safe(message)); }
|
||||
@Override public void warn(String message) { System.out.println(prefix + "[warn] " + safe(message)); }
|
||||
@Override public void error(String message) { System.err.println(prefix + "[error] " + safe(message)); }
|
||||
@Override public void exception(String message) { System.err.println(prefix + "[exception] " + safe(message)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.openautonomousconnection.luascript.hosts;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Reactive observers: Mutation/Resize/Intersection.
|
||||
*
|
||||
* <p>Observers emit events into Lua via a host-provided callback.</p>
|
||||
*/
|
||||
public interface ObserverHost {
|
||||
|
||||
/**
|
||||
* Callback used by the host to notify observers.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface ObserverCallback {
|
||||
/**
|
||||
* Called on observer events.
|
||||
*
|
||||
* @param type observer type ("mutation","resize","intersection")
|
||||
* @param targetId element id
|
||||
* @param data payload map
|
||||
*/
|
||||
void onEvent(String type, String targetId, Map<String, Object> data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the callback to receive observer events.
|
||||
*
|
||||
* @param callback callback (nullable to disable)
|
||||
*/
|
||||
void setCallback(ObserverCallback callback);
|
||||
|
||||
/**
|
||||
* Observes DOM mutations on an element (subtree).
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param subtree true to include subtree
|
||||
* @param attributes true to observe attributes
|
||||
* @param childList true to observe childList
|
||||
* @param characterData true to observe text changes
|
||||
*/
|
||||
void observeMutations(String elementId, boolean subtree, boolean attributes, boolean childList, boolean characterData);
|
||||
|
||||
/**
|
||||
* Stops mutation observing for an element.
|
||||
*
|
||||
* @param elementId element id
|
||||
*/
|
||||
void unobserveMutations(String elementId);
|
||||
|
||||
/**
|
||||
* Observes resize changes of an element.
|
||||
*
|
||||
* @param elementId element id
|
||||
*/
|
||||
void observeResize(String elementId);
|
||||
|
||||
/**
|
||||
* Stops resize observing for an element.
|
||||
*
|
||||
* @param elementId element id
|
||||
*/
|
||||
void unobserveResize(String elementId);
|
||||
|
||||
/**
|
||||
* Observes intersection changes of an element with viewport.
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param threshold threshold in [0..1]
|
||||
*/
|
||||
void observeIntersection(String elementId, double threshold);
|
||||
|
||||
/**
|
||||
* Stops intersection observing for an element.
|
||||
*
|
||||
* @param elementId element id
|
||||
*/
|
||||
void unobserveIntersection(String elementId);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.openautonomousconnection.luascript.hosts;
|
||||
|
||||
/**
|
||||
* Scheduling primitives comparable to JavaScript timers.
|
||||
*
|
||||
* <p>No networking. This host only provides time-based callbacks.</p>
|
||||
*/
|
||||
public interface SchedulerHost {
|
||||
|
||||
/**
|
||||
* Schedules a one-shot callback after a delay.
|
||||
*
|
||||
* @param delayMillis delay in milliseconds (>= 0)
|
||||
* @param callback callback to run on host-defined thread (typically Lua thread)
|
||||
* @return handle id
|
||||
*/
|
||||
long setTimeout(long delayMillis, Runnable callback);
|
||||
|
||||
/**
|
||||
* Schedules a repeating callback with fixed rate.
|
||||
*
|
||||
* @param intervalMillis interval in milliseconds (> 0)
|
||||
* @param callback callback to run
|
||||
* @return handle id
|
||||
*/
|
||||
long setInterval(long intervalMillis, Runnable callback);
|
||||
|
||||
/**
|
||||
* Cancels a timeout/interval handle.
|
||||
*
|
||||
* @param handle handle id
|
||||
* @return true if canceled
|
||||
*/
|
||||
boolean clear(long handle);
|
||||
|
||||
/**
|
||||
* Schedules a callback for the next animation frame.
|
||||
*
|
||||
* <p>Comparable to requestAnimationFrame. The callback is invoked once.</p>
|
||||
*
|
||||
* @param callback callback to run
|
||||
* @return handle id
|
||||
*/
|
||||
long requestAnimationFrame(Runnable callback);
|
||||
|
||||
/**
|
||||
* Cancels a previously scheduled animation frame.
|
||||
*
|
||||
* @param handle handle id
|
||||
* @return true if canceled
|
||||
*/
|
||||
boolean cancelAnimationFrame(long handle);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.openautonomousconnection.luascript.hosts;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CSS selector based DOM querying/traversal.
|
||||
*
|
||||
* <p>All returned elements are identified by stable element ids.</p>
|
||||
*/
|
||||
public interface SelectorHost {
|
||||
|
||||
/**
|
||||
* Returns the first element matching the selector, or null.
|
||||
*
|
||||
* @param selector CSS selector
|
||||
* @return element id or null
|
||||
*/
|
||||
String querySelector(String selector);
|
||||
|
||||
/**
|
||||
* Returns all elements matching the selector.
|
||||
*
|
||||
* @param selector CSS selector
|
||||
* @return list of element ids (never null)
|
||||
*/
|
||||
List<String> querySelectorAll(String selector);
|
||||
|
||||
/**
|
||||
* Checks if an element matches a selector.
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param selector CSS selector
|
||||
* @return true if matches
|
||||
*/
|
||||
boolean matches(String elementId, String selector);
|
||||
|
||||
/**
|
||||
* Returns the closest ancestor (including itself) matching selector, or null.
|
||||
*
|
||||
* @param elementId element id
|
||||
* @param selector CSS selector
|
||||
* @return closest element id or null
|
||||
*/
|
||||
String closest(String elementId, String selector);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.openautonomousconnection.luascript.hosts;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Storage primitives comparable to localStorage/sessionStorage.
|
||||
*/
|
||||
public interface StorageHost {
|
||||
|
||||
/**
|
||||
* @return keys in localStorage
|
||||
*/
|
||||
List<String> localKeys();
|
||||
|
||||
/**
|
||||
* @param key key
|
||||
* @return value or null
|
||||
*/
|
||||
String localGet(String key);
|
||||
|
||||
/**
|
||||
* @param key key
|
||||
* @param value value (null removes)
|
||||
*/
|
||||
void localSet(String key, String value);
|
||||
|
||||
/**
|
||||
* Removes a key from localStorage.
|
||||
*
|
||||
* @param key key
|
||||
*/
|
||||
void localRemove(String key);
|
||||
|
||||
/**
|
||||
* Clears localStorage.
|
||||
*/
|
||||
void localClear();
|
||||
|
||||
/**
|
||||
* @return keys in sessionStorage
|
||||
*/
|
||||
List<String> sessionKeys();
|
||||
|
||||
/**
|
||||
* @param key key
|
||||
* @return value or null
|
||||
*/
|
||||
String sessionGet(String key);
|
||||
|
||||
/**
|
||||
* @param key key
|
||||
* @param value value (null removes)
|
||||
*/
|
||||
void sessionSet(String key, String value);
|
||||
|
||||
/**
|
||||
* Removes a key from sessionStorage.
|
||||
*
|
||||
* @param key key
|
||||
*/
|
||||
void sessionRemove(String key);
|
||||
|
||||
/**
|
||||
* Clears sessionStorage.
|
||||
*/
|
||||
void sessionClear();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.openautonomousconnection.luascript.hosts;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility helpers commonly available in browsers.
|
||||
*
|
||||
* <p>No networking.</p>
|
||||
*/
|
||||
public interface UtilHost {
|
||||
|
||||
/**
|
||||
* Encodes bytes (UTF-8) as Base64.
|
||||
*
|
||||
* @param text text
|
||||
* @return base64
|
||||
*/
|
||||
String base64Encode(String text);
|
||||
|
||||
/**
|
||||
* Decodes Base64 into UTF-8 text.
|
||||
*
|
||||
* @param base64 base64
|
||||
* @return decoded text
|
||||
*/
|
||||
String base64Decode(String base64);
|
||||
|
||||
/**
|
||||
* Generates cryptographically-strong random bytes and returns as hex string.
|
||||
*
|
||||
* @param numBytes number of bytes (>0)
|
||||
* @return hex string
|
||||
*/
|
||||
String randomHex(int numBytes);
|
||||
|
||||
/**
|
||||
* Parses a URL string into components.
|
||||
*
|
||||
* @param url url string
|
||||
* @return map containing scheme,host,port,path,query,fragment
|
||||
*/
|
||||
Map<String, String> parseUrl(String url);
|
||||
|
||||
/**
|
||||
* Parses a query string into key->list(values).
|
||||
*
|
||||
* @param query query string (with or without leading '?')
|
||||
* @return map key->values
|
||||
*/
|
||||
Map<String, List<String>> parseQuery(String query);
|
||||
|
||||
/**
|
||||
* JSON.stringify via browser engine (returns JSON string).
|
||||
*
|
||||
* @param elementId element id that provides the JS context (ignored by some engines but kept for safety)
|
||||
* @param jsExpr JS expression returning a JSON-serializable value
|
||||
* @return JSON string
|
||||
*/
|
||||
String jsonStringifyExpr(String elementId, String jsExpr);
|
||||
|
||||
/**
|
||||
* JSON.parse via browser engine and returns normalized JSON string (stringify(parse(x))).
|
||||
*
|
||||
* @param elementId element id providing JS context
|
||||
* @param json json string
|
||||
* @return normalized json
|
||||
*/
|
||||
String jsonNormalize(String elementId, String json);
|
||||
}
|
||||
@@ -80,14 +80,27 @@ public final class FxLuaScriptEngine implements AutoCloseable {
|
||||
.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);
|
||||
FxAudioHost audioHost = new FxAudioHost();
|
||||
FxVideoHost videoHost = new FxVideoHost(engine, dom);
|
||||
FxImageHost imageHost = new FxImageHost(engine, dom);
|
||||
HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console, audioHost, imageHost, videoHost);
|
||||
|
||||
HostServices services = new HostServices.Default(
|
||||
new FxUiHost(engine, dom),
|
||||
dom,
|
||||
eventHost,
|
||||
new FxWebViewResourceHost(engine),
|
||||
new HostServices.StdoutConsole("[lua] "),
|
||||
new FxAudioHost(),
|
||||
new FxImageHost(engine, dom),
|
||||
new FxVideoHost(engine, dom),
|
||||
new FxSchedulerHost(),
|
||||
new FxSelectorHost(engine, dom),
|
||||
new FxGeometryHost(engine, dom),
|
||||
new FxCssHost(engine, dom),
|
||||
new FxStorageHost(engine, dom),
|
||||
new FxUtilHost(engine, dom),
|
||||
new FxClipboardHost(),
|
||||
new FxObserverHost(engine, dom)
|
||||
);
|
||||
|
||||
LuaRuntime rt = new LuaRuntime(globals, services, policy);
|
||||
eventHost.setRouter(rt.eventRouter());
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.openautonomousconnection.luascript.tables;
|
||||
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.*;
|
||||
import org.openautonomousconnection.luascript.hosts.ClipboardHost;
|
||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||
|
||||
/**
|
||||
* Lua table: clipboard
|
||||
*
|
||||
* <p>Functions:</p>
|
||||
* <ul>
|
||||
* <li>clipboard.set(text)</li>
|
||||
* <li>clipboard.get() -> text</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class ClipboardTable extends ScriptTable {
|
||||
|
||||
public ClipboardTable() {
|
||||
super("clipboard");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void define(HostServices services) {
|
||||
ClipboardHost host = services.clipboard().orElseThrow(() -> new IllegalStateException("ClipboardHost not provided"));
|
||||
|
||||
table().set("set", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue text) {
|
||||
host.setText(text.isnil() ? "" : text.tojstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("get", new ZeroArgFunction() {
|
||||
@Override
|
||||
public LuaValue call() {
|
||||
return LuaValue.valueOf(host.getText());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.openautonomousconnection.luascript.tables;
|
||||
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.*;
|
||||
import org.openautonomousconnection.luascript.events.JavaToLua;
|
||||
import org.openautonomousconnection.luascript.hosts.CssHost;
|
||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Lua table: css
|
||||
*
|
||||
* <p>Functions:</p>
|
||||
* <ul>
|
||||
* <li>css.computed(id, prop) -> string</li>
|
||||
* <li>css.computedMany(id, {props...}) -> map</li>
|
||||
* <li>css.inlineGet(id, prop) -> string</li>
|
||||
* <li>css.inlineSet(id, prop, value)</li>
|
||||
* <li>css.varGet(id, name) -> string</li>
|
||||
* <li>css.varSet(id, name, value)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class CssTable extends ScriptTable {
|
||||
|
||||
public CssTable() {
|
||||
super("css");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void define(HostServices services) {
|
||||
CssHost host = services.css().orElseThrow(() -> new IllegalStateException("CssHost not provided"));
|
||||
|
||||
table().set("computed", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id, LuaValue prop) {
|
||||
return LuaValue.valueOf(host.getComputedStyle(id.checkjstring(), prop.checkjstring()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("computedMany", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id, LuaValue propsTable) {
|
||||
if (!propsTable.istable()) throw new IllegalArgumentException("props must be a table");
|
||||
int n = propsTable.length();
|
||||
String[] props = new String[n];
|
||||
for (int i = 1; i <= n; i++) {
|
||||
props[i - 1] = propsTable.get(i).checkjstring();
|
||||
}
|
||||
Map<String, String> m = host.getComputedStyles(id.checkjstring(), props);
|
||||
return JavaToLua.coerce(m);
|
||||
}
|
||||
});
|
||||
|
||||
table().set("inlineGet", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id, LuaValue prop) {
|
||||
return LuaValue.valueOf(host.getInlineStyle(id.checkjstring(), prop.checkjstring()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("inlineSet", new ThreeArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id, LuaValue prop, LuaValue value) {
|
||||
host.setInlineStyle(id.checkjstring(), prop.checkjstring(), value.isnil() ? "" : value.tojstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("varGet", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id, LuaValue name) {
|
||||
return LuaValue.valueOf(host.getCssVariable(id.checkjstring(), name.checkjstring()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("varSet", new ThreeArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id, LuaValue name, LuaValue value) {
|
||||
host.setCssVariable(id.checkjstring(), name.checkjstring(), value.isnil() ? "" : value.tojstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.openautonomousconnection.luascript.tables;
|
||||
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.OneArgFunction;
|
||||
import org.luaj.vm2.lib.TwoArgFunction;
|
||||
import org.luaj.vm2.lib.VarArgFunction;
|
||||
import org.luaj.vm2.Varargs;
|
||||
import org.openautonomousconnection.luascript.events.JavaToLua;
|
||||
import org.openautonomousconnection.luascript.hosts.GeometryHost;
|
||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Lua table: geometry
|
||||
*
|
||||
* <p>Functions:</p>
|
||||
* <ul>
|
||||
* <li>geometry.rect(id) -> map</li>
|
||||
* <li>geometry.viewport() -> map</li>
|
||||
* <li>geometry.scrollTo(x,y)</li>
|
||||
* <li>geometry.scrollIntoView(id, align?)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class GeometryTable extends ScriptTable {
|
||||
|
||||
public GeometryTable() {
|
||||
super("geometry");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void define(HostServices services) {
|
||||
GeometryHost host = services.geometry().orElseThrow(() -> new IllegalStateException("GeometryHost not provided"));
|
||||
|
||||
table().set("rect", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id) {
|
||||
Map<String, Object> m = host.getBoundingClientRect(id.checkjstring());
|
||||
return JavaToLua.coerce(m);
|
||||
}
|
||||
});
|
||||
|
||||
table().set("viewport", new org.luaj.vm2.lib.ZeroArgFunction() {
|
||||
@Override
|
||||
public LuaValue call() {
|
||||
return JavaToLua.coerce(host.getViewport());
|
||||
}
|
||||
});
|
||||
|
||||
table().set("scrollTo", new VarArgFunction() {
|
||||
@Override
|
||||
public Varargs invoke(Varargs args) {
|
||||
host.scrollTo(args.arg(1).checkdouble(), args.arg(2).checkdouble());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("scrollIntoView", new VarArgFunction() {
|
||||
@Override
|
||||
public Varargs invoke(Varargs args) {
|
||||
String id = args.arg(1).checkjstring();
|
||||
String align = args.narg() >= 2 && !args.arg(2).isnil() ? args.arg(2).tojstring() : null;
|
||||
host.scrollIntoView(id, align);
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package org.openautonomousconnection.luascript.tables;
|
||||
|
||||
import org.luaj.vm2.LuaFunction;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.Varargs;
|
||||
import org.luaj.vm2.lib.*;
|
||||
import org.openautonomousconnection.luascript.events.JavaToLua;
|
||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||
import org.openautonomousconnection.luascript.hosts.ObserverHost;
|
||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Lua table: observers
|
||||
*
|
||||
* <p>Functions:</p>
|
||||
* <ul>
|
||||
* <li>observers.on(fn(type, targetId, dataTable))</li>
|
||||
* <li>observers.mutationObserve(id, subtree, attributes, childList, characterData)</li>
|
||||
* <li>observers.mutationUnobserve(id)</li>
|
||||
* <li>observers.resizeObserve(id)</li>
|
||||
* <li>observers.resizeUnobserve(id)</li>
|
||||
* <li>observers.intersectionObserve(id, threshold)</li>
|
||||
* <li>observers.intersectionUnobserve(id)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class ObserversTable extends ScriptTable {
|
||||
|
||||
public ObserversTable() {
|
||||
super("observers");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void define(HostServices services) {
|
||||
ObserverHost host = services.observers().orElseThrow(() -> new IllegalStateException("ObserverHost not provided"));
|
||||
|
||||
table().set("on", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue fn) {
|
||||
LuaFunction cb = fn.checkfunction();
|
||||
host.setCallback((type, targetId, data) -> {
|
||||
LuaValue luaData = JavaToLua.coerce(data);
|
||||
cb.call(LuaValue.valueOf(type), LuaValue.valueOf(targetId), luaData);
|
||||
});
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("mutationObserve", new VarArgFunction() {
|
||||
@Override
|
||||
public Varargs invoke(Varargs args) {
|
||||
String id = args.arg(1).checkjstring();
|
||||
boolean subtree = args.arg(2).optboolean(false);
|
||||
boolean attributes = args.arg(3).optboolean(true);
|
||||
boolean childList = args.arg(4).optboolean(true);
|
||||
boolean characterData = args.arg(5).optboolean(false);
|
||||
host.observeMutations(id, subtree, attributes, childList, characterData);
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("mutationUnobserve", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id) {
|
||||
host.unobserveMutations(id.checkjstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("resizeObserve", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id) {
|
||||
host.observeResize(id.checkjstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("resizeUnobserve", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id) {
|
||||
host.unobserveResize(id.checkjstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("intersectionObserve", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id, LuaValue threshold) {
|
||||
host.observeIntersection(id.checkjstring(), threshold.checkdouble());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("intersectionUnobserve", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id) {
|
||||
host.unobserveIntersection(id.checkjstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.openautonomousconnection.luascript.tables;
|
||||
|
||||
import org.luaj.vm2.LuaFunction;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.OneArgFunction;
|
||||
import org.luaj.vm2.lib.TwoArgFunction;
|
||||
import org.luaj.vm2.lib.VarArgFunction;
|
||||
import org.luaj.vm2.Varargs;
|
||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||
import org.openautonomousconnection.luascript.hosts.SchedulerHost;
|
||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||
|
||||
/**
|
||||
* Lua table: scheduler
|
||||
*
|
||||
* <p>Functions:</p>
|
||||
* <ul>
|
||||
* <li>scheduler.timeout(ms, fn) -> id</li>
|
||||
* <li>scheduler.interval(ms, fn) -> id</li>
|
||||
* <li>scheduler.clear(id) -> boolean</li>
|
||||
* <li>scheduler.raf(fn) -> id</li>
|
||||
* <li>scheduler.cancelRaf(id) -> boolean</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class SchedulerTable extends ScriptTable {
|
||||
|
||||
public SchedulerTable() {
|
||||
super("scheduler");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void define(HostServices services) {
|
||||
SchedulerHost host = services.scheduler().orElseThrow(() -> new IllegalStateException("SchedulerHost not provided"));
|
||||
|
||||
table().set("timeout", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue ms, LuaValue fn) {
|
||||
long delay = ms.checklong();
|
||||
LuaFunction cb = fn.checkfunction();
|
||||
long id = host.setTimeout(delay, () -> cb.call());
|
||||
return LuaValue.valueOf(id);
|
||||
}
|
||||
});
|
||||
|
||||
table().set("interval", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue ms, LuaValue fn) {
|
||||
long interval = ms.checklong();
|
||||
LuaFunction cb = fn.checkfunction();
|
||||
long id = host.setInterval(interval, () -> cb.call());
|
||||
return LuaValue.valueOf(id);
|
||||
}
|
||||
});
|
||||
|
||||
table().set("clear", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id) {
|
||||
return LuaValue.valueOf(host.clear(id.checklong()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("raf", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue fn) {
|
||||
LuaFunction cb = fn.checkfunction();
|
||||
long id = host.requestAnimationFrame(() -> cb.call());
|
||||
return LuaValue.valueOf(id);
|
||||
}
|
||||
});
|
||||
|
||||
table().set("cancelRaf", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id) {
|
||||
return LuaValue.valueOf(host.cancelAnimationFrame(id.checklong()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.openautonomousconnection.luascript.tables;
|
||||
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.TwoArgFunction;
|
||||
import org.luaj.vm2.lib.OneArgFunction;
|
||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||
import org.openautonomousconnection.luascript.hosts.SelectorHost;
|
||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Lua table: selector
|
||||
*
|
||||
* <p>Functions:</p>
|
||||
* <ul>
|
||||
* <li>selector.one(css) -> id|nil</li>
|
||||
* <li>selector.all(css) -> { ids... }</li>
|
||||
* <li>selector.matches(id, css) -> boolean</li>
|
||||
* <li>selector.closest(id, css) -> id|nil</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class SelectorTable extends ScriptTable {
|
||||
|
||||
public SelectorTable() {
|
||||
super("selector");
|
||||
}
|
||||
|
||||
private static LuaValue toLuaArray(List<String> values) {
|
||||
LuaTable t = new LuaTable();
|
||||
if (values == null || values.isEmpty()) return t;
|
||||
int i = 1;
|
||||
for (String v : values) {
|
||||
t.set(i++, v == null ? LuaValue.NIL : LuaValue.valueOf(v));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void define(HostServices services) {
|
||||
SelectorHost host = services.selector().orElseThrow(() -> new IllegalStateException("SelectorHost not provided"));
|
||||
|
||||
table().set("one", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue css) {
|
||||
String id = host.querySelector(css.checkjstring());
|
||||
return id == null ? LuaValue.NIL : LuaValue.valueOf(id);
|
||||
}
|
||||
});
|
||||
|
||||
table().set("all", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue css) {
|
||||
return toLuaArray(host.querySelectorAll(css.checkjstring()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("matches", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id, LuaValue css) {
|
||||
return LuaValue.valueOf(host.matches(id.checkjstring(), css.checkjstring()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("closest", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue id, LuaValue css) {
|
||||
String out = host.closest(id.checkjstring(), css.checkjstring());
|
||||
return out == null ? LuaValue.NIL : LuaValue.valueOf(out);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.openautonomousconnection.luascript.tables;
|
||||
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.*;
|
||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||
import org.openautonomousconnection.luascript.hosts.StorageHost;
|
||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Lua table: storage
|
||||
*
|
||||
* <p>Functions:</p>
|
||||
* <ul>
|
||||
* <li>storage.localGet(key) -> string|nil</li>
|
||||
* <li>storage.localSet(key, value|nil)</li>
|
||||
* <li>storage.localKeys() -> {keys...}</li>
|
||||
* <li>storage.localRemove(key)</li>
|
||||
* <li>storage.localClear()</li>
|
||||
* <li>same for session*</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class StorageTable extends ScriptTable {
|
||||
|
||||
public StorageTable() {
|
||||
super("storage");
|
||||
}
|
||||
|
||||
private static LuaValue toLuaArray(List<String> values) {
|
||||
LuaTable t = new LuaTable();
|
||||
if (values == null || values.isEmpty()) return t;
|
||||
int i = 1;
|
||||
for (String v : values) t.set(i++, v == null ? LuaValue.NIL : LuaValue.valueOf(v));
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void define(HostServices services) {
|
||||
StorageHost host = services.storage().orElseThrow(() -> new IllegalStateException("StorageHost not provided"));
|
||||
|
||||
table().set("localKeys", new ZeroArgFunction() {
|
||||
@Override
|
||||
public LuaValue call() {
|
||||
return toLuaArray(host.localKeys());
|
||||
}
|
||||
});
|
||||
|
||||
table().set("localGet", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue key) {
|
||||
String v = host.localGet(key.checkjstring());
|
||||
return v == null ? LuaValue.NIL : LuaValue.valueOf(v);
|
||||
}
|
||||
});
|
||||
|
||||
table().set("localSet", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue key, LuaValue value) {
|
||||
host.localSet(key.checkjstring(), value.isnil() ? null : value.tojstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("localRemove", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue key) {
|
||||
host.localRemove(key.checkjstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("localClear", new ZeroArgFunction() {
|
||||
@Override
|
||||
public LuaValue call() {
|
||||
host.localClear();
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("sessionKeys", new ZeroArgFunction() {
|
||||
@Override
|
||||
public LuaValue call() {
|
||||
return toLuaArray(host.sessionKeys());
|
||||
}
|
||||
});
|
||||
|
||||
table().set("sessionGet", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue key) {
|
||||
String v = host.sessionGet(key.checkjstring());
|
||||
return v == null ? LuaValue.NIL : LuaValue.valueOf(v);
|
||||
}
|
||||
});
|
||||
|
||||
table().set("sessionSet", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue key, LuaValue value) {
|
||||
host.sessionSet(key.checkjstring(), value.isnil() ? null : value.tojstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("sessionRemove", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue key) {
|
||||
host.sessionRemove(key.checkjstring());
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
|
||||
table().set("sessionClear", new ZeroArgFunction() {
|
||||
@Override
|
||||
public LuaValue call() {
|
||||
host.sessionClear();
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.openautonomousconnection.luascript.tables;
|
||||
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.*;
|
||||
import org.openautonomousconnection.luascript.events.JavaToLua;
|
||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||
import org.openautonomousconnection.luascript.hosts.UtilHost;
|
||||
import org.openautonomousconnection.luascript.utils.ScriptTable;
|
||||
|
||||
/**
|
||||
* Lua table: util
|
||||
*
|
||||
* <p>Functions:</p>
|
||||
* <ul>
|
||||
* <li>util.base64Encode(text)</li>
|
||||
* <li>util.base64Decode(b64)</li>
|
||||
* <li>util.randomHex(bytes)</li>
|
||||
* <li>util.parseUrl(url) -> map</li>
|
||||
* <li>util.parseQuery(query) -> map(key->array)</li>
|
||||
* <li>util.jsonStringifyExpr(elementId, jsExpr) -> jsonString</li>
|
||||
* <li>util.jsonNormalize(elementId, json) -> jsonString</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class UtilTable extends ScriptTable {
|
||||
|
||||
public UtilTable() {
|
||||
super("util");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void define(HostServices services) {
|
||||
UtilHost host = services.util().orElseThrow(() -> new IllegalStateException("UtilHost not provided"));
|
||||
|
||||
table().set("base64Encode", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue text) {
|
||||
return LuaValue.valueOf(host.base64Encode(text.isnil() ? "" : text.tojstring()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("base64Decode", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue b64) {
|
||||
return LuaValue.valueOf(host.base64Decode(b64.isnil() ? "" : b64.tojstring()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("randomHex", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue bytes) {
|
||||
return LuaValue.valueOf(host.randomHex(bytes.checkint()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("parseUrl", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue url) {
|
||||
return JavaToLua.coerce(host.parseUrl(url.checkjstring()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("parseQuery", new OneArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue q) {
|
||||
return JavaToLua.coerce(host.parseQuery(q.isnil() ? "" : q.tojstring()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("jsonStringifyExpr", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue elementId, LuaValue jsExpr) {
|
||||
return LuaValue.valueOf(host.jsonStringifyExpr(elementId.checkjstring(), jsExpr.checkjstring()));
|
||||
}
|
||||
});
|
||||
|
||||
table().set("jsonNormalize", new TwoArgFunction() {
|
||||
@Override
|
||||
public LuaValue call(LuaValue elementId, LuaValue json) {
|
||||
return LuaValue.valueOf(host.jsonNormalize(elementId.checkjstring(), json.checkjstring()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.openautonomousconnection.luascript.values;
|
||||
|
||||
import org.luaj.vm2.LuaValue;
|
||||
|
||||
public class LuaObject extends LuaValue {
|
||||
@Override
|
||||
public int type() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typename() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user