使用 WebEngine 执行多个脚本(或依赖于另一个脚本的脚本)
Executing multiple scripts with WebEngine (or a script that depends on another script)
我想使用(JavaFX 的 WebEngine)在网页上执行一些 Java脚本(和 jQuery)并处理结果(使用 Java代码)。
我对自己执行某些脚本的回调函数有问题。
为了尽可能简单地说明我的问题,我做了一个最小化的代码来显示不需要的结果(它只是一个更大项目的一部分)。
所以,我创建了三个 classes:
- Browser - 一个 class 包装了 WebEngine 并用作加载网页和执行脚本的浏览器。
- JQueryFunction - class 列出了几个与 jQuery 回调函数一起使用的函数,可以通过多种方式实现。 (基本上它们应该是用户按照自己的方式实现的功能接口——但为了简单起见,我将其设为普通功能)。
- Test - class 具有执行两个脚本的主要方法。
浏览器
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class Browser extends Application {
private static WebEngine webEngine;
private static JSObject window;
@Override
public void start(Stage primaryStage) throws Exception {
WebView browser = new WebView();
webEngine = browser.getEngine();
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<State>() {
@Override
public void changed(ObservableValue<? extends State> observable, State oldState, State newState) {
if (newState == State.SUCCEEDED) {
window = (JSObject) webEngine.executeScript("window;");
// The following lines inject jQuery (for pages that don't use already)
webEngine.executeScript("var script = document.createElement(\"script\");");
webEngine.executeScript("script.src = \"http://code.jquery.com/jquery-1.12.0.min.js\";");
webEngine.executeScript("document.getElementsByTagName(\"body\")[0].appendChild(script);");
}
}
});
primaryStage.setScene(new Scene(browser));
primaryStage.show();
//Platform.setImplicitExit(false);
}
public static JSObject getWindow() {
return window;
}
public static Object executeScript(String script) throws InterruptedException, ExecutionException {
FutureTask<Object> task = new FutureTask<>(new Callable<Object>() {
@Override
public Object call() throws Exception {
return webEngine.executeScript(script);
}
});
Platform.runLater(task);
return task.get();
}
public static void load(String url) {
Platform.runLater(new Runnable() {
@Override
public void run() {
webEngine.load(url);
}
});
}
public static void start() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Application.launch(Browser.class);
} catch (IllegalStateException e) {}
}
}).start();
}
}
JQueryFunction
import java.util.concurrent.ExecutionException;
public class JQueryFunction {
public String function1(int index, String text) {
return (index+1) + ": " + text;
}
public String function2(int index, String text) throws InterruptedException, ExecutionException {
return (String) Browser.executeScript("$($(\".BlueArrows:eq(2) li\").find(\"a:first\")[index]).text();");
}
}
测试
public class Test {
public static void main(String[] args) throws Exception {
Browser.start();
Thread.sleep(1000); // Only used here to simplify the code
Browser.load("https://docs.oracle.com/javase/tutorial/");
Thread.sleep(3500); // Only used here to simplify the code
Browser.getWindow().setMember("javaApp", new JQueryFunction());
Browser.executeScript("$(\".BlueArrows:eq(0) li\").find(\"a:first\").text(function(index, text) { return javaApp.function1(index, text); });");
Browser.executeScript("$(\".BlueArrows:eq(0) li\").find(\"a:first\").text(function(index, text) { return javaApp.function2(index, text); });");
}
}
当我 运行 测试时,第一个 executeScript
运行s 如预期的那样改变了一些元素的文本(前置索引号)。
但是 second executeScript
永远 stuck 并粘住 GUI,实际上整个 JavaFX 应用程序。
我明白为什么会这样了...
executeScript
方法调用 WebEngine(通过 Platform.runLater
)执行 jQuery 遍历元素并调用 Java 函数(每次都有不同的参数)。
第一次执行(调用 function1)从 function1 获取返回的字符串并将其应用于元素的文本。 (完全符合预期)!
第二次执行(调用function2)执行了调用Java函数的jQuery函数,但是Java函数需要多执行一些Java脚本(或jQuery),但WebEngine在第一次执行完成之前不会执行。
但是第一次执行不会完成,因为它取决于第二次执行的结果。
WebEngine 的编程方式是他将仅在一个线程中(在 FX 线程内)一个任务接一个任务(串行)执行。
有什么办法可以解决吗?
为什么 WebEngine 仅限于在 JavaFX 应用程序下工作?
为什么它必须只与一个线程一起工作?
避免您遇到的 "deadlock" 的一种可能方法是当您已经 运行 在 GUI 线程(JavaFX 应用程序线程)中时不调用 Platform.runLater
。类似于:
public static Object executeScript(String script) throws InterruptedException, ExecutionException {
if(Platform.isFxApplicationThread()) {
return webEngine.executeScript(script);
}
FutureTask<Object> task = new FutureTask<>(new Callable<Object>() {
@Override
public Object call() throws Exception {
return webEngine.executeScript(script);
}
});
Platform.runLater(task);
return task.get();
}
我想使用(JavaFX 的 WebEngine)在网页上执行一些 Java脚本(和 jQuery)并处理结果(使用 Java代码)。
我对自己执行某些脚本的回调函数有问题。
为了尽可能简单地说明我的问题,我做了一个最小化的代码来显示不需要的结果(它只是一个更大项目的一部分)。
所以,我创建了三个 classes:
- Browser - 一个 class 包装了 WebEngine 并用作加载网页和执行脚本的浏览器。
- JQueryFunction - class 列出了几个与 jQuery 回调函数一起使用的函数,可以通过多种方式实现。 (基本上它们应该是用户按照自己的方式实现的功能接口——但为了简单起见,我将其设为普通功能)。
- Test - class 具有执行两个脚本的主要方法。
浏览器
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class Browser extends Application {
private static WebEngine webEngine;
private static JSObject window;
@Override
public void start(Stage primaryStage) throws Exception {
WebView browser = new WebView();
webEngine = browser.getEngine();
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<State>() {
@Override
public void changed(ObservableValue<? extends State> observable, State oldState, State newState) {
if (newState == State.SUCCEEDED) {
window = (JSObject) webEngine.executeScript("window;");
// The following lines inject jQuery (for pages that don't use already)
webEngine.executeScript("var script = document.createElement(\"script\");");
webEngine.executeScript("script.src = \"http://code.jquery.com/jquery-1.12.0.min.js\";");
webEngine.executeScript("document.getElementsByTagName(\"body\")[0].appendChild(script);");
}
}
});
primaryStage.setScene(new Scene(browser));
primaryStage.show();
//Platform.setImplicitExit(false);
}
public static JSObject getWindow() {
return window;
}
public static Object executeScript(String script) throws InterruptedException, ExecutionException {
FutureTask<Object> task = new FutureTask<>(new Callable<Object>() {
@Override
public Object call() throws Exception {
return webEngine.executeScript(script);
}
});
Platform.runLater(task);
return task.get();
}
public static void load(String url) {
Platform.runLater(new Runnable() {
@Override
public void run() {
webEngine.load(url);
}
});
}
public static void start() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Application.launch(Browser.class);
} catch (IllegalStateException e) {}
}
}).start();
}
}
JQueryFunction
import java.util.concurrent.ExecutionException;
public class JQueryFunction {
public String function1(int index, String text) {
return (index+1) + ": " + text;
}
public String function2(int index, String text) throws InterruptedException, ExecutionException {
return (String) Browser.executeScript("$($(\".BlueArrows:eq(2) li\").find(\"a:first\")[index]).text();");
}
}
测试
public class Test {
public static void main(String[] args) throws Exception {
Browser.start();
Thread.sleep(1000); // Only used here to simplify the code
Browser.load("https://docs.oracle.com/javase/tutorial/");
Thread.sleep(3500); // Only used here to simplify the code
Browser.getWindow().setMember("javaApp", new JQueryFunction());
Browser.executeScript("$(\".BlueArrows:eq(0) li\").find(\"a:first\").text(function(index, text) { return javaApp.function1(index, text); });");
Browser.executeScript("$(\".BlueArrows:eq(0) li\").find(\"a:first\").text(function(index, text) { return javaApp.function2(index, text); });");
}
}
当我 运行 测试时,第一个 executeScript
运行s 如预期的那样改变了一些元素的文本(前置索引号)。
但是 second executeScript
永远 stuck 并粘住 GUI,实际上整个 JavaFX 应用程序。
我明白为什么会这样了...
executeScript
方法调用 WebEngine(通过 Platform.runLater
)执行 jQuery 遍历元素并调用 Java 函数(每次都有不同的参数)。
第一次执行(调用 function1)从 function1 获取返回的字符串并将其应用于元素的文本。 (完全符合预期)!
第二次执行(调用function2)执行了调用Java函数的jQuery函数,但是Java函数需要多执行一些Java脚本(或jQuery),但WebEngine在第一次执行完成之前不会执行。
但是第一次执行不会完成,因为它取决于第二次执行的结果。
WebEngine 的编程方式是他将仅在一个线程中(在 FX 线程内)一个任务接一个任务(串行)执行。
有什么办法可以解决吗?
为什么 WebEngine 仅限于在 JavaFX 应用程序下工作?
为什么它必须只与一个线程一起工作?
避免您遇到的 "deadlock" 的一种可能方法是当您已经 运行 在 GUI 线程(JavaFX 应用程序线程)中时不调用 Platform.runLater
。类似于:
public static Object executeScript(String script) throws InterruptedException, ExecutionException {
if(Platform.isFxApplicationThread()) {
return webEngine.executeScript(script);
}
FutureTask<Object> task = new FutureTask<>(new Callable<Object>() {
@Override
public Object call() throws Exception {
return webEngine.executeScript(script);
}
});
Platform.runLater(task);
return task.get();
}