Switched to JavaFX and added builtin Support
This commit is contained in:
@@ -1,232 +0,0 @@
|
||||
package org.openautonomousconnection.luascript.dom.jsoup;
|
||||
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.openautonomousconnection.luascript.hosts.DomHost;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* DomHost implementation backed by a jsoup Document.
|
||||
*
|
||||
* <p>Element identity is the HTML attribute {@code id}. This implementation auto-assigns ids to all
|
||||
* elements that do not have one, making them addressable.</p>
|
||||
*/
|
||||
public final class JsoupDomHost implements DomHost {
|
||||
|
||||
private final Document document;
|
||||
private final AtomicLong autoIdSeq = new AtomicLong(1);
|
||||
|
||||
public JsoupDomHost(Document document) {
|
||||
this.document = Objects.requireNonNull(document, "document");
|
||||
ensureAllElementsHaveId();
|
||||
}
|
||||
|
||||
public Document document() {
|
||||
return document;
|
||||
}
|
||||
|
||||
public void ensureAllElementsHaveId() {
|
||||
Elements all = document.getAllElements();
|
||||
for (Element el : all) {
|
||||
if (el == document) continue;
|
||||
if (!hasUsableId(el)) {
|
||||
el.attr("id", generateUniqueId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllElementIds() {
|
||||
Elements all = document.getAllElements();
|
||||
List<String> ids = new ArrayList<>(all.size());
|
||||
for (Element el : all) {
|
||||
if (el == document) continue;
|
||||
String id = el.id();
|
||||
if (id != null && !id.isBlank()) ids.add(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes(String elementId) {
|
||||
Element el = byId(elementId);
|
||||
Map<String, String> out = new LinkedHashMap<>();
|
||||
el.attributes().forEach(a -> out.put(a.getKey(), a.getValue()));
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTagName(String elementId) {
|
||||
return byId(elementId).tagName().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTextContent(String elementId) {
|
||||
return byId(elementId).text();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextContent(String elementId, String text) {
|
||||
byId(elementId).text(text == null ? "" : text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String elementId, String name) {
|
||||
Element el = byId(elementId);
|
||||
String n = normalizeAttr(name);
|
||||
if (!el.hasAttr(n)) return null;
|
||||
return el.attr(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String elementId, String name, String value) {
|
||||
Element el = byId(elementId);
|
||||
el.attr(normalizeAttr(name), value == null ? "" : value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String elementId, String name) {
|
||||
byId(elementId).removeAttr(normalizeAttr(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentId(String elementId) {
|
||||
Element el = byId(elementId);
|
||||
Element p = el.parent();
|
||||
if (p == null || p == document) return null;
|
||||
if (!hasUsableId(p)) p.attr("id", generateUniqueId());
|
||||
return p.id();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getChildrenIds(String elementId) {
|
||||
Element el = byId(elementId);
|
||||
List<String> out = new ArrayList<>();
|
||||
for (Element child : el.children()) {
|
||||
if (!hasUsableId(child)) child.attr("id", generateUniqueId());
|
||||
out.add(child.id());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createElement(String tagName, String requestedId) {
|
||||
String tag = normalizeTag(tagName);
|
||||
String id = normalizeOrGenerateId(requestedId);
|
||||
if (exists(id)) throw new IllegalArgumentException("Element id already exists: " + id);
|
||||
|
||||
Element el = document.createElement(tag);
|
||||
el.attr("id", id);
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeElement(String elementId) {
|
||||
byId(elementId).remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendChild(String parentId, String childId) {
|
||||
Element parent = byId(parentId);
|
||||
Element child = byId(childId);
|
||||
child.remove();
|
||||
parent.appendChild(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertBefore(String parentId, String childId, String beforeChildId) {
|
||||
Element parent = byId(parentId);
|
||||
Element child = byId(childId);
|
||||
Element before = byId(beforeChildId);
|
||||
|
||||
if (before.parent() == null || !Objects.equals(before.parent(), parent)) {
|
||||
throw new IllegalArgumentException("beforeChildId is not a direct child of parentId: " + beforeChildId);
|
||||
}
|
||||
|
||||
child.remove();
|
||||
before.before(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String id) {
|
||||
if (id == null || id.isBlank()) return false;
|
||||
return document.getElementById(id) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> queryByTag(String tagName) {
|
||||
String tag = normalizeTag(tagName);
|
||||
Elements els = document.getElementsByTag(tag);
|
||||
|
||||
List<String> out = new ArrayList<>(els.size());
|
||||
for (Element el : els) {
|
||||
if (!hasUsableId(el)) el.attr("id", generateUniqueId());
|
||||
out.add(el.id());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> queryByClass(String className) {
|
||||
String cls = normalizeCssIdent(className);
|
||||
Elements els = document.getElementsByClass(cls);
|
||||
|
||||
List<String> out = new ArrayList<>(els.size());
|
||||
for (Element el : els) {
|
||||
if (!hasUsableId(el)) el.attr("id", generateUniqueId());
|
||||
out.add(el.id());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private Element byId(String id) {
|
||||
if (id == null || id.isBlank()) throw new IllegalArgumentException("elementId is blank");
|
||||
Element el = document.getElementById(id);
|
||||
if (el == null) throw new IllegalArgumentException("Unknown element id: " + id);
|
||||
return el;
|
||||
}
|
||||
|
||||
private boolean hasUsableId(Element el) {
|
||||
String id = el.id();
|
||||
return id != null && !id.isBlank();
|
||||
}
|
||||
|
||||
private String generateUniqueId() {
|
||||
while (true) {
|
||||
String id = "__auto_" + autoIdSeq.getAndIncrement();
|
||||
if (!exists(id)) return id;
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeTag(String tagName) {
|
||||
if (tagName == null) throw new IllegalArgumentException("tagName is null");
|
||||
String t = tagName.trim().toLowerCase(Locale.ROOT);
|
||||
if (t.isEmpty()) throw new IllegalArgumentException("tagName is empty");
|
||||
return t;
|
||||
}
|
||||
|
||||
private String normalizeAttr(String name) {
|
||||
if (name == null) throw new IllegalArgumentException("attribute name is null");
|
||||
String n = name.trim();
|
||||
if (n.isEmpty()) throw new IllegalArgumentException("attribute name is empty");
|
||||
return n;
|
||||
}
|
||||
|
||||
private String normalizeCssIdent(String s) {
|
||||
if (s == null) throw new IllegalArgumentException("identifier is null");
|
||||
String v = s.trim();
|
||||
if (v.isEmpty()) throw new IllegalArgumentException("identifier is empty");
|
||||
return v;
|
||||
}
|
||||
|
||||
private String normalizeOrGenerateId(String requestedId) {
|
||||
if (requestedId != null) {
|
||||
String id = requestedId.trim();
|
||||
if (!id.isEmpty()) return id;
|
||||
}
|
||||
return generateUniqueId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.scene.web.WebEngine;
|
||||
import org.openautonomousconnection.luascript.fx.FxThreadBridge;
|
||||
import org.openautonomousconnection.luascript.hosts.DomHost;
|
||||
import org.w3c.dom.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* DomHost implementation backed by JavaFX WebView's W3C DOM (WebEngine#getDocument()).
|
||||
*
|
||||
* <p>No jsoup and no JavaScript. All operations are performed via W3C DOM APIs.</p>
|
||||
*
|
||||
* <p>Element identity is the {@code id} attribute. This host auto-assigns stable ids to elements that
|
||||
* do not have one, ensuring addressability for Lua bindings and event routing.</p>
|
||||
*/
|
||||
public final class FxDomHost implements DomHost {
|
||||
|
||||
private final WebEngine engine;
|
||||
private final AtomicLong autoIdSeq = new AtomicLong(1);
|
||||
|
||||
/**
|
||||
* Creates a new FxDomHost.
|
||||
*
|
||||
* @param engine JavaFX WebEngine
|
||||
*/
|
||||
public FxDomHost(WebEngine engine) {
|
||||
this.engine = Objects.requireNonNull(engine, "engine");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures every element has a stable id.
|
||||
*/
|
||||
public void ensureAllElementsHaveId() {
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
Document doc = requireDocument();
|
||||
NodeList all = doc.getElementsByTagName("*");
|
||||
for (int i = 0; i < all.getLength(); i++) {
|
||||
Node n = all.item(i);
|
||||
if (n instanceof Element el) {
|
||||
if (!hasUsableId(el)) {
|
||||
el.setAttribute("id", generateUniqueId(doc));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllElementIds() {
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Document doc = requireDocument();
|
||||
NodeList all = doc.getElementsByTagName("*");
|
||||
List<String> ids = new ArrayList<>(all.getLength());
|
||||
for (int i = 0; i < all.getLength(); i++) {
|
||||
Node n = all.item(i);
|
||||
if (n instanceof Element el) {
|
||||
String id = el.getAttribute("id");
|
||||
if (id != null && !id.isBlank()) ids.add(id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes(String elementId) {
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Element el = byId(elementId);
|
||||
NamedNodeMap nnm = el.getAttributes();
|
||||
Map<String, String> out = new LinkedHashMap<>();
|
||||
for (int i = 0; i < nnm.getLength(); i++) {
|
||||
Node a = nnm.item(i);
|
||||
if (a == null) continue;
|
||||
out.put(a.getNodeName(), a.getNodeValue());
|
||||
}
|
||||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTagName(String elementId) {
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Element el = byId(elementId);
|
||||
String tag = el.getTagName();
|
||||
return tag == null ? "" : tag.trim().toLowerCase(Locale.ROOT);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTextContent(String elementId) {
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Element el = byId(elementId);
|
||||
String t = el.getTextContent();
|
||||
return t == null ? "" : t;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextContent(String elementId, String text) {
|
||||
FxThreadBridge.runAndWait(() -> byId(elementId).setTextContent(text == null ? "" : text));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String elementId, String name) {
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Element el = byId(elementId);
|
||||
String n = normalizeAttr(name);
|
||||
if (!el.hasAttribute(n)) return null;
|
||||
String v = el.getAttribute(n);
|
||||
return v == null ? "" : v;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String elementId, String name, String value) {
|
||||
FxThreadBridge.runAndWait(() -> byId(elementId).setAttribute(normalizeAttr(name), value == null ? "" : value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String elementId, String name) {
|
||||
FxThreadBridge.runAndWait(() -> byId(elementId).removeAttribute(normalizeAttr(name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentId(String elementId) {
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Document doc = requireDocument();
|
||||
Element el = byId(elementId);
|
||||
Node p = el.getParentNode();
|
||||
if (!(p instanceof Element pe)) return null;
|
||||
|
||||
if (!hasUsableId(pe)) pe.setAttribute("id", generateUniqueId(doc));
|
||||
String id = pe.getAttribute("id");
|
||||
return (id == null || id.isBlank()) ? null : id;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getChildrenIds(String elementId) {
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Document doc = requireDocument();
|
||||
Element el = byId(elementId);
|
||||
NodeList children = el.getChildNodes();
|
||||
|
||||
List<String> out = new ArrayList<>();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node n = children.item(i);
|
||||
if (n instanceof Element ce) {
|
||||
if (!hasUsableId(ce)) ce.setAttribute("id", generateUniqueId(doc));
|
||||
out.add(ce.getAttribute("id"));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createElement(String tagName, String requestedId) {
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Document doc = requireDocument();
|
||||
|
||||
String tag = normalizeTag(tagName);
|
||||
String id = normalizeOrGenerateId(requestedId, doc);
|
||||
if (doc.getElementById(id) != null) throw new IllegalArgumentException("Element id already exists: " + id);
|
||||
|
||||
Element el = doc.createElement(tag);
|
||||
el.setAttribute("id", id);
|
||||
|
||||
// Make it immediately addressable by putting it into a hidden staging container.
|
||||
ensureStagingContainer(doc).appendChild(el);
|
||||
return id;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeElement(String elementId) {
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
Element el = byId(elementId);
|
||||
Node p = el.getParentNode();
|
||||
if (p != null) p.removeChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendChild(String parentId, String childId) {
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
Element parent = byId(parentId);
|
||||
Element child = byId(childId);
|
||||
|
||||
Node old = child.getParentNode();
|
||||
if (old != null) old.removeChild(child);
|
||||
|
||||
parent.appendChild(child);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertBefore(String parentId, String childId, String beforeChildId) {
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
Element parent = byId(parentId);
|
||||
Element child = byId(childId);
|
||||
Element before = byId(beforeChildId);
|
||||
|
||||
Node beforeParent = before.getParentNode();
|
||||
if (beforeParent == null || !beforeParent.isSameNode(parent)) {
|
||||
throw new IllegalArgumentException("beforeChildId is not a direct child of parentId: " + beforeChildId);
|
||||
}
|
||||
|
||||
Node old = child.getParentNode();
|
||||
if (old != null) old.removeChild(child);
|
||||
|
||||
parent.insertBefore(child, before);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String id) {
|
||||
if (id == null || id.isBlank()) return false;
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Document doc = engine.getDocument();
|
||||
return doc != null && doc.getElementById(id) != null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> queryByTag(String tagName) {
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Document doc = requireDocument();
|
||||
String tag = normalizeTag(tagName);
|
||||
NodeList els = doc.getElementsByTagName(tag);
|
||||
|
||||
List<String> out = new ArrayList<>(els.getLength());
|
||||
for (int i = 0; i < els.getLength(); i++) {
|
||||
Node n = els.item(i);
|
||||
if (n instanceof Element el) {
|
||||
if (!hasUsableId(el)) el.setAttribute("id", generateUniqueId(doc));
|
||||
out.add(el.getAttribute("id"));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> queryByClass(String className) {
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Document doc = requireDocument();
|
||||
String cls = normalizeCssIdent(className);
|
||||
|
||||
NodeList all = doc.getElementsByTagName("*");
|
||||
List<String> out = new ArrayList<>();
|
||||
for (int i = 0; i < all.getLength(); i++) {
|
||||
Node n = all.item(i);
|
||||
if (n instanceof Element el) {
|
||||
String c = el.getAttribute("class");
|
||||
if (c == null || c.isBlank()) continue;
|
||||
if (hasClassToken(c, cls)) {
|
||||
if (!hasUsableId(el)) el.setAttribute("id", generateUniqueId(doc));
|
||||
out.add(el.getAttribute("id"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the current document (FX thread access required by callers).
|
||||
*
|
||||
* @return document
|
||||
*/
|
||||
public Document requireDocument() {
|
||||
Document doc = engine.getDocument();
|
||||
if (doc == null) throw new IllegalStateException("WebEngine document is not available yet (page not loaded?)");
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes element lookup for FX event host / ui host (FX thread access required by callers).
|
||||
*
|
||||
* @param id element id
|
||||
* @return element
|
||||
*/
|
||||
public Element byId(String id) {
|
||||
if (id == null || id.isBlank()) throw new IllegalArgumentException("elementId is blank");
|
||||
Document doc = requireDocument();
|
||||
Element el = doc.getElementById(id);
|
||||
if (el == null) throw new IllegalArgumentException("Unknown element id: " + id);
|
||||
return el;
|
||||
}
|
||||
|
||||
private static boolean hasUsableId(Element el) {
|
||||
String id = el.getAttribute("id");
|
||||
return id != null && !id.isBlank();
|
||||
}
|
||||
|
||||
private String generateUniqueId(Document doc) {
|
||||
while (true) {
|
||||
String id = "__auto_" + autoIdSeq.getAndIncrement();
|
||||
if (doc.getElementById(id) == null) return id;
|
||||
}
|
||||
}
|
||||
|
||||
private static Element ensureStagingContainer(Document doc) {
|
||||
Element body = (Element) doc.getElementsByTagName("body").item(0);
|
||||
if (body == null) throw new IllegalStateException("No <body> element available");
|
||||
|
||||
Element staging = doc.getElementById("__oac_staging");
|
||||
if (staging != null) return staging;
|
||||
|
||||
staging = doc.createElement("div");
|
||||
staging.setAttribute("id", "__oac_staging");
|
||||
staging.setAttribute("style", "display:none !important;");
|
||||
body.appendChild(staging);
|
||||
return staging;
|
||||
}
|
||||
|
||||
private static boolean hasClassToken(String classAttr, String cls) {
|
||||
String[] parts = classAttr.trim().split("\\s+");
|
||||
for (String p : parts) {
|
||||
if (p.equals(cls)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String normalizeTag(String tagName) {
|
||||
if (tagName == null) throw new IllegalArgumentException("tagName is null");
|
||||
String t = tagName.trim().toLowerCase(Locale.ROOT);
|
||||
if (t.isEmpty()) throw new IllegalArgumentException("tagName is empty");
|
||||
return t;
|
||||
}
|
||||
|
||||
private static String normalizeAttr(String name) {
|
||||
if (name == null) throw new IllegalArgumentException("attribute name is null");
|
||||
String n = name.trim();
|
||||
if (n.isEmpty()) throw new IllegalArgumentException("attribute name is empty");
|
||||
return n;
|
||||
}
|
||||
|
||||
private static String normalizeCssIdent(String s) {
|
||||
if (s == null) throw new IllegalArgumentException("identifier is null");
|
||||
String v = s.trim();
|
||||
if (v.isEmpty()) throw new IllegalArgumentException("identifier is empty");
|
||||
return v;
|
||||
}
|
||||
|
||||
private String normalizeOrGenerateId(String requestedId, Document doc) {
|
||||
if (requestedId != null) {
|
||||
String id = requestedId.trim();
|
||||
if (!id.isEmpty()) return id;
|
||||
}
|
||||
return generateUniqueId(doc);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import org.openautonomousconnection.luascript.fx.FxDomHost;
|
||||
import org.openautonomousconnection.luascript.events.UiEventRegistry;
|
||||
import org.openautonomousconnection.luascript.fx.FxThreadBridge;
|
||||
import org.openautonomousconnection.luascript.hosts.EventHost;
|
||||
import org.openautonomousconnection.luascript.runtime.LuaEventRouter;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.events.EventListener;
|
||||
import org.w3c.dom.events.EventTarget;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* EventHost implementation for JavaFX WebView using W3C DOM EventTarget listeners.
|
||||
*
|
||||
* <p>No JavaScript required. Events are forwarded to Lua via {@link LuaEventRouter}.</p>
|
||||
*/
|
||||
public final class FxEventHost implements EventHost {
|
||||
|
||||
private static final String GLOBAL_TARGET_ID = "__global__";
|
||||
|
||||
private final FxDomHost dom;
|
||||
private final LuaEventRouter router;
|
||||
|
||||
private final ConcurrentHashMap<String, EventListener> elementListeners = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, EventListener> globalListeners = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new FxEventHost.
|
||||
*
|
||||
* @param dom fx dom host
|
||||
* @param router lua router
|
||||
*/
|
||||
public FxEventHost(FxDomHost dom, LuaEventRouter router) {
|
||||
this.dom = Objects.requireNonNull(dom, "dom");
|
||||
this.router = Objects.requireNonNull(router, "router");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(String elementId, String eventName) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(eventName, "eventName");
|
||||
|
||||
String evt = UiEventRegistry.normalize(eventName);
|
||||
String key = elementId + "\n" + evt;
|
||||
|
||||
elementListeners.computeIfAbsent(key, k -> {
|
||||
EventListener listener = ev -> {
|
||||
Map<String, Object> payload = FxEventPayloadExtractor.extract(ev);
|
||||
router.emit(elementId, evt, payload);
|
||||
};
|
||||
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
Element el = dom.byId(elementId);
|
||||
((EventTarget) el).addEventListener(evt, listener, false);
|
||||
});
|
||||
|
||||
return listener;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(String elementId, String eventName) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(eventName, "eventName");
|
||||
|
||||
String evt = UiEventRegistry.normalize(eventName);
|
||||
String key = elementId + "\n" + evt;
|
||||
|
||||
EventListener listener = elementListeners.remove(key);
|
||||
if (listener == null) return;
|
||||
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
Element el = dom.byId(elementId);
|
||||
((EventTarget) el).removeEventListener(evt, listener, false);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGlobalListener(String eventName) {
|
||||
Objects.requireNonNull(eventName, "eventName");
|
||||
|
||||
String evt = UiEventRegistry.normalize(eventName);
|
||||
|
||||
globalListeners.computeIfAbsent(evt, k -> {
|
||||
EventListener listener = ev -> {
|
||||
Map<String, Object> payload = FxEventPayloadExtractor.extract(ev);
|
||||
router.emit(GLOBAL_TARGET_ID, evt, payload);
|
||||
};
|
||||
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
Document doc = dom.requireDocument();
|
||||
((EventTarget) doc).addEventListener(evt, listener, false);
|
||||
});
|
||||
|
||||
return listener;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGlobalListener(String eventName) {
|
||||
Objects.requireNonNull(eventName, "eventName");
|
||||
|
||||
String evt = UiEventRegistry.normalize(eventName);
|
||||
|
||||
EventListener listener = globalListeners.remove(evt);
|
||||
if (listener == null) return;
|
||||
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
Document doc = dom.requireDocument();
|
||||
((EventTarget) doc).removeEventListener(evt, listener, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import com.sun.webkit.dom.KeyboardEventImpl;
|
||||
import org.w3c.dom.events.Event;
|
||||
import org.w3c.dom.events.MouseEvent;
|
||||
import org.w3c.dom.events.UIEvent;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Extracts common DOM event fields into a plain map for Lua.
|
||||
*/
|
||||
public final class FxEventPayloadExtractor {
|
||||
|
||||
private FxEventPayloadExtractor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an event payload.
|
||||
*
|
||||
* @param ev W3C DOM event
|
||||
* @return map payload (never null)
|
||||
*/
|
||||
public static Map<String, Object> extract(Event ev) {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
if (ev == null) return data;
|
||||
|
||||
data.put("type", ev.getType());
|
||||
|
||||
if (ev instanceof UIEvent uiev) {
|
||||
data.put("detail", uiev.getDetail());
|
||||
}
|
||||
|
||||
if (ev instanceof MouseEvent mev) {
|
||||
data.put("clientX", (int) mev.getClientX());
|
||||
data.put("clientY", (int) mev.getClientY());
|
||||
data.put("button", (int) mev.getButton());
|
||||
data.put("altKey", mev.getAltKey());
|
||||
data.put("ctrlKey", mev.getCtrlKey());
|
||||
data.put("shiftKey", mev.getShiftKey());
|
||||
data.put("metaKey", mev.getMetaKey());
|
||||
}
|
||||
|
||||
if (ev instanceof KeyboardEventImpl kev) {
|
||||
data.put("key", kev.getKeyIdentifier());
|
||||
data.put("keyCode", (int) kev.getKeyCode());
|
||||
data.put("altKey", kev.getAltKey());
|
||||
data.put("ctrlKey", kev.getCtrlKey());
|
||||
data.put("shiftKey", kev.getShiftKey());
|
||||
data.put("metaKey", kev.getMetaKey());
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
/**
|
||||
* Executes work on the JavaFX Application Thread and waits synchronously for completion.
|
||||
*/
|
||||
public final class FxThreadBridge {
|
||||
|
||||
private FxThreadBridge() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given task on the FX thread and blocks until completion.
|
||||
*
|
||||
* @param task task to execute
|
||||
* @param <T> result type
|
||||
* @return task result
|
||||
*/
|
||||
public static <T> T callAndWait(Callable<T> task) {
|
||||
Objects.requireNonNull(task, "task");
|
||||
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
try {
|
||||
return task.call();
|
||||
} catch (Exception e) {
|
||||
throw rethrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
FutureTask<T> ft = new FutureTask<>(task);
|
||||
Platform.runLater(ft);
|
||||
|
||||
try {
|
||||
return ft.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Interrupted while waiting for FX thread", e);
|
||||
} catch (ExecutionException e) {
|
||||
throw rethrow(e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given runnable on the FX thread and blocks until completion.
|
||||
*
|
||||
* @param runnable runnable
|
||||
*/
|
||||
public static void runAndWait(Runnable runnable) {
|
||||
callAndWait(() -> {
|
||||
runnable.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static RuntimeException rethrow(Throwable t) {
|
||||
if (t instanceof RuntimeException re) return re;
|
||||
if (t instanceof Error err) throw err;
|
||||
return new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.scene.web.WebEngine;
|
||||
import org.openautonomousconnection.luascript.fx.FxDomHost;
|
||||
import org.openautonomousconnection.luascript.fx.FxThreadBridge;
|
||||
import org.openautonomousconnection.luascript.hosts.UiHost;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* UiHost implementation for JavaFX WebView (no JavaScript).
|
||||
*
|
||||
* <p>Operations are implemented via W3C DOM attributes/text, and best-effort behavior for
|
||||
* value/style/class using standard attributes.</p>
|
||||
*/
|
||||
public final class FxUiHost implements UiHost {
|
||||
|
||||
private final WebEngine engine;
|
||||
private final FxDomHost dom;
|
||||
|
||||
/**
|
||||
* Creates a new UI host.
|
||||
*
|
||||
* @param engine web engine
|
||||
* @param dom dom host
|
||||
*/
|
||||
public FxUiHost(WebEngine engine, FxDomHost dom) {
|
||||
this.engine = Objects.requireNonNull(engine, "engine");
|
||||
this.dom = Objects.requireNonNull(dom, "dom");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void alert(String message) {
|
||||
// No JS: use simple JavaFX dialog-less fallback (log-style). You can replace with real Dialogs later.
|
||||
// Keeping it deterministic and non-blocking for now.
|
||||
System.out.println("[ui.alert] " + (message == null ? "" : message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirm(String message) {
|
||||
// No JS: deterministic default (false). Replace with JavaFX dialogs if you want UI interaction.
|
||||
System.out.println("[ui.confirm] " + (message == null ? "" : message));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prompt(String message, String defaultValue) {
|
||||
// No JS: deterministic default.
|
||||
System.out.println("[ui.prompt] " + (message == null ? "" : message));
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(String elementId, String text) {
|
||||
dom.setTextContent(elementId, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(String elementId) {
|
||||
return dom.getTextContent(elementId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHtml(String elementId, String html) {
|
||||
// Without JS, safest is to set textContent (prevents HTML parsing).
|
||||
// If you need real HTML injection, we must extend DomHost with fragment parsing (not in current API).
|
||||
dom.setTextContent(elementId, html);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHtml(String elementId) {
|
||||
// Without JS, best-effort: return textContent.
|
||||
return dom.getTextContent(elementId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String elementId, String value) {
|
||||
// Input/textarea value is commonly reflected as attribute "value".
|
||||
dom.setAttribute(elementId, "value", value == null ? "" : value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String elementId) {
|
||||
String v = dom.getAttribute(elementId, "value");
|
||||
return v == null ? "" : v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(String elementId, boolean enabled) {
|
||||
if (enabled) dom.removeAttribute(elementId, "disabled");
|
||||
else dom.setAttribute(elementId, "disabled", "disabled");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(String elementId, boolean visible) {
|
||||
// Best-effort via style attribute
|
||||
String style = dom.getAttribute(elementId, "style");
|
||||
style = style == null ? "" : style;
|
||||
|
||||
style = removeCssProp(style, "display");
|
||||
if (!visible) {
|
||||
style = style.trim();
|
||||
if (!style.isEmpty() && !style.endsWith(";")) style += ";";
|
||||
style += "display:none;";
|
||||
}
|
||||
|
||||
dom.setAttribute(elementId, "style", style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addClass(String elementId, String className) {
|
||||
String cls = Objects.requireNonNull(className, "className").trim();
|
||||
if (cls.isEmpty()) return;
|
||||
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
Element el = dom.byId(elementId);
|
||||
String c = el.getAttribute("class");
|
||||
c = (c == null) ? "" : c.trim();
|
||||
|
||||
if (c.isEmpty()) {
|
||||
el.setAttribute("class", cls);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasClassToken(c, cls)) {
|
||||
el.setAttribute("class", c + " " + cls);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClass(String elementId, String className) {
|
||||
String cls = Objects.requireNonNull(className, "className").trim();
|
||||
if (cls.isEmpty()) return;
|
||||
|
||||
FxThreadBridge.runAndWait(() -> {
|
||||
Element el = dom.byId(elementId);
|
||||
String c = el.getAttribute("class");
|
||||
c = (c == null) ? "" : c.trim();
|
||||
if (c.isEmpty()) return;
|
||||
|
||||
String[] parts = c.split("\\s+");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String p : parts) {
|
||||
if (p.equals(cls)) continue;
|
||||
if (!sb.isEmpty()) sb.append(' ');
|
||||
sb.append(p);
|
||||
}
|
||||
el.setAttribute("class", sb.toString());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean toggleClass(String elementId, String className) {
|
||||
if (hasClass(elementId, className)) {
|
||||
removeClass(elementId, className);
|
||||
return false;
|
||||
}
|
||||
addClass(elementId, className);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasClass(String elementId, String className) {
|
||||
String cls = Objects.requireNonNull(className, "className").trim();
|
||||
if (cls.isEmpty()) return false;
|
||||
|
||||
return FxThreadBridge.callAndWait(() -> {
|
||||
Element el = dom.byId(elementId);
|
||||
String c = el.getAttribute("class");
|
||||
c = (c == null) ? "" : c.trim();
|
||||
return !c.isEmpty() && hasClassToken(c, cls);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStyle(String elementId, String property, String value) {
|
||||
String prop = Objects.requireNonNull(property, "property").trim().toLowerCase();
|
||||
if (prop.isEmpty()) return;
|
||||
|
||||
String style = dom.getAttribute(elementId, "style");
|
||||
style = style == null ? "" : style;
|
||||
|
||||
style = removeCssProp(style, prop);
|
||||
String v = value == null ? "" : value.trim();
|
||||
|
||||
if (!v.isEmpty()) {
|
||||
style = style.trim();
|
||||
if (!style.isEmpty() && !style.endsWith(";")) style += ";";
|
||||
style += prop + ":" + v + ";";
|
||||
}
|
||||
dom.setAttribute(elementId, "style", style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStyle(String elementId, String property) {
|
||||
// Best-effort parsing from style attribute.
|
||||
String prop = Objects.requireNonNull(property, "property").trim().toLowerCase();
|
||||
if (prop.isEmpty()) return "";
|
||||
|
||||
String style = dom.getAttribute(elementId, "style");
|
||||
style = style == null ? "" : style;
|
||||
|
||||
for (String part : style.split(";")) {
|
||||
String p = part.trim();
|
||||
if (p.isEmpty()) continue;
|
||||
int idx = p.indexOf(':');
|
||||
if (idx <= 0) continue;
|
||||
|
||||
String k = p.substring(0, idx).trim().toLowerCase();
|
||||
if (k.equals(prop)) return p.substring(idx + 1).trim();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String elementId, String name, String value) {
|
||||
dom.setAttribute(elementId, name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String elementId, String name) {
|
||||
return dom.getAttribute(elementId, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String elementId, String name) {
|
||||
dom.removeAttribute(elementId, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focus(String elementId) {
|
||||
// Without JS, focus control is limited. Best-effort via attribute.
|
||||
dom.setAttribute(elementId, "autofocus", "autofocus");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blur(String elementId) {
|
||||
// No-op without JS.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollIntoView(String elementId) {
|
||||
// No JS => no reliable scrollIntoView. No-op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public int viewportWidth() {
|
||||
// Without JS, no real viewport query. Return -1 (unknown).
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int viewportHeight() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nowMillis() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private static boolean hasClassToken(String classAttr, String cls) {
|
||||
String[] parts = classAttr.trim().split("\\s+");
|
||||
for (String p : parts) {
|
||||
if (p.equals(cls)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String removeCssProp(String style, String propLower) {
|
||||
if (style == null || style.isBlank()) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (String part : style.split(";")) {
|
||||
String p = part.trim();
|
||||
if (p.isEmpty()) continue;
|
||||
int idx = p.indexOf(':');
|
||||
if (idx <= 0) continue;
|
||||
|
||||
String k = p.substring(0, idx).trim().toLowerCase();
|
||||
if (k.equals(propLower)) continue;
|
||||
|
||||
if (!sb.isEmpty()) sb.append(';');
|
||||
sb.append(p);
|
||||
}
|
||||
|
||||
String out = sb.toString().trim();
|
||||
if (!out.isEmpty() && !out.endsWith(";")) out += ";";
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.openautonomousconnection.luascript.fx;
|
||||
|
||||
import javafx.scene.web.WebEngine;
|
||||
import org.openautonomousconnection.luascript.fx.FxThreadBridge;
|
||||
import org.openautonomousconnection.luascript.hosts.ResourceHost;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* ResourceHost that loads script src via URLConnection.
|
||||
*
|
||||
* <p>Relative URLs are resolved against the current WebEngine location.</p>
|
||||
*/
|
||||
public final class FxWebViewResourceHost implements ResourceHost {
|
||||
|
||||
private final WebEngine engine;
|
||||
|
||||
/**
|
||||
* Creates a new resource host.
|
||||
*
|
||||
* @param engine web engine
|
||||
*/
|
||||
public FxWebViewResourceHost(WebEngine engine) {
|
||||
this.engine = Objects.requireNonNull(engine, "engine");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String src) throws Exception {
|
||||
Objects.requireNonNull(src, "src");
|
||||
String trimmed = src.trim();
|
||||
if (trimmed.isEmpty()) throw new IllegalArgumentException("src is empty");
|
||||
|
||||
String base = FxThreadBridge.callAndWait(engine::getLocation);
|
||||
URL url = (base == null || base.isBlank())
|
||||
? new URL(trimmed)
|
||||
: new URL(new URL(base), trimmed);
|
||||
|
||||
URLConnection con = url.openConnection();
|
||||
con.setUseCaches(false);
|
||||
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
StringBuilder sb = new StringBuilder(4096);
|
||||
char[] buf = new char[8192];
|
||||
int r;
|
||||
while ((r = br.read(buf)) >= 0) {
|
||||
sb.append(buf, 0, r);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
package org.openautonomousconnection.luascript.hosts;
|
||||
|
||||
import javafx.scene.web.WebEngine;
|
||||
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.runtime.LuaRuntime;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -24,6 +31,38 @@ public final class HostServices {
|
||||
this.resources = b.resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a JavaFX WebView preset of HostServices (DOM + events + resources + UI).
|
||||
*
|
||||
* <p>Important: This method creates the {@link EventHost} using the provided {@link LuaRuntime}'s
|
||||
* {@link org.openautonomousconnection.luascript.runtime.LuaEventRouter}. Therefore, the runtime must already
|
||||
* be constructed before calling this method.</p>
|
||||
*
|
||||
* <p>Note: You should call {@link FxDomHost#ensureAllElementsHaveId()} after the WebEngine finished loading.</p>
|
||||
*
|
||||
* @param engine JavaFX WebEngine
|
||||
* @param runtime Lua runtime used for event routing
|
||||
* @param console optional console host (may be null)
|
||||
* @return HostServices preset for JavaFX WebView
|
||||
*/
|
||||
public static HostServices fxPreset(WebEngine engine, LuaRuntime runtime, ConsoleHost console) {
|
||||
Objects.requireNonNull(engine, "engine");
|
||||
Objects.requireNonNull(runtime, "runtime");
|
||||
|
||||
FxDomHost dom = new FxDomHost(engine);
|
||||
FxWebViewResourceHost resources = new FxWebViewResourceHost(engine);
|
||||
FxUiHost ui = new FxUiHost(engine, dom);
|
||||
FxEventHost events = new FxEventHost(dom, runtime.eventRouter());
|
||||
|
||||
Builder b = builder().dom(dom).events(events).resources(resources).ui(ui);
|
||||
|
||||
if (console != null) {
|
||||
b.console(console);
|
||||
}
|
||||
|
||||
return b.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return builder
|
||||
*/
|
||||
@@ -124,4 +163,3 @@ public final class HostServices {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,21 @@ import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Bootstrap that:
|
||||
* 1) Executes all Lua scripts found in DOM
|
||||
* * 2) Scans DOM for on:* handlers and binds them automatically.
|
||||
* 1) Executes ALL scripts found in DOM as Lua: <script> ... or <script src="...">
|
||||
* 2) Scans DOM for on:* handlers and binds them automatically.
|
||||
*/
|
||||
public final class LuaScriptBootstrap {
|
||||
|
||||
private LuaScriptBootstrap() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstraps Lua from DOM.
|
||||
*
|
||||
* @param globals globals
|
||||
* @param services host services
|
||||
* @param dispatcher event dispatcher
|
||||
*/
|
||||
public static void bootstrap(Globals globals, HostServices services, LuaEventDispatcher dispatcher) {
|
||||
Objects.requireNonNull(globals, "globals");
|
||||
Objects.requireNonNull(services, "services");
|
||||
@@ -41,20 +48,16 @@ public final class LuaScriptBootstrap {
|
||||
Map<String, String> attrs = dom.getAttributes(elementId);
|
||||
if (attrs == null) continue;
|
||||
|
||||
String type = safeLower(attrs.getOrDefault("type", ""));
|
||||
String src = attrs.get("src");
|
||||
|
||||
boolean isLuaByType = "lua".equals(type) || "text/lua".equals(type) || "application/lua".equals(type);
|
||||
if (!isLuaByType) continue; // IMPORTANT: only run scripts explicitly marked as Lua
|
||||
|
||||
boolean hasSrc = src != null && !src.trim().isEmpty();
|
||||
|
||||
if (hasSrc) {
|
||||
String path = src.trim();
|
||||
try {
|
||||
String code = resources.readText(path);
|
||||
LuaScriptExecutor.execute(globals, code, path);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to load lua script src='" + path + "': " + ex.getMessage(), ex);
|
||||
throw new IllegalStateException("Failed to load script src='" + path + "': " + ex.getMessage(), ex);
|
||||
}
|
||||
} else {
|
||||
String inline = dom.getTextContent(elementId);
|
||||
|
||||
Reference in New Issue
Block a user