【发布时间】:2023-04-08 14:15:01
【问题描述】:
将 JRE 版本 1.8.0_66 迁移到 1.8.0_111 后,我遇到了从 JavaScript 向上调用到 JavaFX 的问题。
长话短说:当有一个正在运行的后台线程时,WebView/WebEngine 拒绝进行 JS-to-Java 调用。
我使用 WebView 呈现从域数据模型 (DM) 生成的 HTML 内容。内容包含分配有处理程序的元素,如下所示:
<a href='#' onclick='explainHeadWord(this)'>some_word</a>
JS 部分长这样:
function explainHeadWord(hwElement) {
jsBridge.jsHandleQuery(hwElement.innerHTML);
}
function testBridge() {
jsBridge.jsTest();
}
其中jsBridge 是Controller 的内部Java 类
public class JSBridge {
public void jsHandleQuery(String headWord) {
log("jsBridge: jsHandleQuery: requested %s", headWord);
handleQuery(headWord);
}
public void jsTest() {
log("jsBridge: jsTest: test succeeded ");
}
}
注入如下:
engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
if (newValue == Worker.State.SUCCEEDED) {
engine.setJavaScriptEnabled(true);
JSObject window = (JSObject) engine.executeScript("window");
window.setMember("jsBridge", new JSBridge());
//engine.executeScript("jsTest()");
//engine.executeScript("explainHeadWord(document.getElementsByTagName('a')[0])");
//engine.executeScript("jsBridge.jsHandleQuery(document.getElementsByTagName('a')[0])");
}
除了主要的 DM 之外,我还有一个交叉引用索引,它是从 DM 构建的Map<String, Collection<String>>,以及每次 DM 更改时在后台重建该索引的触发方法。第一种方法(在版本 1.8.0_66 上运行良好)基于 ExecutorService:
private ExecutorService executor = Executors.newCachedThreadPool();
private Future<Boolean> indexer = executor.submit(() -> false);
...
private void rebuildIndex() {
executor.submit(() -> {
indexer.cancel(true);
indexer = executor.submit(() -> {
fullSearchIndex = getIndex();
if (isIndexingAborted()) return false;
return true;
});
try {
if (indexer.get()) {
log("resetIndex: Done");
updateTableView();
}
} catch (InterruptedException e) {
...
}
});
}
正如预期的那样,单击 WebView 中的锚点会导致 JS 调用 jsBridge.jsHandleQuery(hwElement.innerHTML) 并最终导致在 Controller 中实现 handleQuery(headWord) 方法调用。但是在将 JRE 迁移到 1.8.0_111 版本后,WebView 停止响应锚点点击。
我调查了日志,发现注入jsBridge 是成功的,并且执行了window.setMember() 下面代码中注释的测试脚本。单击<a> 元素没有任何结果。但是没有运行测试脚本(已注释),日志中出现了记录:
<record>
<date>2017-01-09T02:00:47</date>
<millis>1483920047169</millis>
<sequence>160</sequence>
<logger>com.sun.webkit.WebPage</logger>
<level>FINE</level>
<class>com.sun.webkit.WebPage</class>
<method>fwkAddMessageToConsole</method>
<thread>11</thread>
<message>fwkAddMessageToConsole(): message = TypeError: jsBridge.jsHandleQuery is not a function. (In 'jsBridge.jsHandleQuery(hwElement.innerHTML)', 'jsBridge.jsHandleQuery' is undefined), lineNumber = 26, sourceId = jar:file:/.../jar.jar!/view.js</message>
</record>
过了一会儿,后台(索引)线程完成并重新加载了 WebView 中的内容,单击 <a> 元素开始再次响应 - jsBridge.jsHandleQuery 已执行。
索引线程执行getIndex() 方法,该方法遍历DM 并从DM 中返回收集到Map 的数据。与 FX 应用程序线程或 WebView 的任何交互均不依赖于 Index。代入fullSearchIndex = getMockIndex();
private Map<String, Collection<String>> getMockIndex() {
try {
Thread.sleep(20000);
} catch (InterruptedException e) { }
return Collections.emptyMap();
}
后台线程不会改变<a>的行为。
下一步是通过利用将后台线程重构为 FX 样式
javafx.concurrent.Service 但结果是一样的。
感谢您指出我做错了什么以及如何解决这个问题。
【问题讨论】:
标签: javascript multithreading javafx webview