Switched to JavaFX and added builtin Support
This commit is contained in:
7
.idea/discord.xml
generated
Normal file
7
.idea/discord.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
4
.idea/misc.xml
generated
4
.idea/misc.xml
generated
@@ -1,9 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="ASK" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
|
||||
21
pom.xml
21
pom.xml
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.openautonomousconnection</groupId>
|
||||
<artifactId>LuaScript</artifactId>
|
||||
<version>1.0.0-BETA.1.0</version>
|
||||
<version>1.0.0-BETA.1.1</version>
|
||||
|
||||
<organization>
|
||||
<name>Open Autonomous Connection</name>
|
||||
@@ -73,6 +73,10 @@
|
||||
<name>projectlombok</name>
|
||||
<url>https://github.com/projectlombok/lombok?tab=License-1-ov-file</url>
|
||||
</license>
|
||||
<license>
|
||||
<name>MIT - LuaJ</name>
|
||||
<url>https://github.com/luaj/luaj</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<repositories>
|
||||
@@ -89,7 +93,7 @@
|
||||
<dependency>
|
||||
<groupId>dev.unlegitdqrk</groupId>
|
||||
<artifactId>unlegitlibrary</artifactId>
|
||||
<version>1.6.9</version>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.luaj</groupId>
|
||||
@@ -103,9 +107,16 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.22.1</version>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx</artifactId>
|
||||
<version>26-ea+22</version>
|
||||
<type>pom</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<version>26-ea+22</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
jsoup License
|
||||
|
||||
The jsoup code-base (including source and compiled packages) are distributed under the open source MIT license as described below.
|
||||
The MIT License
|
||||
|
||||
Copyright © 2009 - 2025 Jonathan Hedley (https://jsoup.org/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Reference in New Issue
Block a user