监听 DOM 内容变化
本教程展示了如何构建一个小型 Java 应用程序,该程序监听加载到 JxBrowser 中的 HTML 页面发生的变化。
前提条件
为完成本教程,您将需要:
- Git
- Java 8 或更高版本
- 有效的 JxBrowser 许可证。可以是评估版或商业版。有关许可证的更多信息,请参阅许可指南。
创建项目
本教程示例应用程序的代码与其他示例一起,存储在一个基于 Gradle 的 GitHub 仓库中。
如果您想构建一个基于 Maven 的项目,请参考 Maven 配置指南。如果您希望从头开始构建一个基于 Gradle 的项目,请参考 Gradle 配置指南。
获取代码
要获取代码,请执行以下命令:
$ git clone https://github.com/TeamDev-IP/JxBrowser-Examples
$ cd JxBrowser-Examples/tutorials/content-changes
现在我们位于所有示例的根目录下。本教程的代码位于 tutorials/content-changes
目录下。
添加许可证
要运行本教程,您需要设置许可证密钥。
页面
我们将加载一个简单的 HTML 页面,在该页面上将显示一个每秒自动递增的计数器。
下面是计数器的代码:
<div>
<span class="counter" id="counter"></span>
</div>
该计数器使用 jQuery 进行更新:
<script crossorigin="anonymous" src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
var counter = 1;
setInterval(function() { $(".counter").text(counter++); }, 1000);
});
</script>
以下是页面的完整代码,该代码作为名为 index.html
的资源文件包含在项目中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<div>
<span class="counter" id="counter"></span>
</div>
<script crossorigin="anonymous" src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
var counter = 1;
setInterval(function() { $(".counter").text(counter++); }, 1000);
});
</script>
</body>
</html>
JavaScript 代码
由于我们希望从 HTML 代码中通知我们应用程序的 Java 代码,因此需要编写 JavaScript 代码来处理 DOM 修改并将其传递给我们的 Java 代码。我们本可以直接在 HTML 代码中完成此操作,但我们想展示如何从 Java 动态添加此类代码。
首先,我们获取要跟踪的元素:
const element = document.getElementById('counter');
然后,我们创建一个 MutationObserver
实例,并为其提供一个回调函数,用于将数据传递给 Java 代码。
const observer = new MutationObserver(
function(mutations) {
window.java.onDomChanged(element.innerHTML);
});
这里最重要的部分是以下调用:
window.java.onDomChanged(element.innerHTML);
我们在这里引用存储在 window
对象中的一个对象,该对象的属性名为 java
。java
是包含要调用的 Java 对象的属性的名称。我们使用 java
这个词来强调我们正在调用一个 Java 对象。这个属性可以是任何符合您应用程序意义的 JavaScript 标识符。
我们调用的对象方法是 onDomChanged()
。稍后,当我们创建用于监听内容更改的 Java 类时,将添加此方法。我们传递计数器元素的 innerHTML
属性。 因此,该方法将接受一个 String
参数。
现在,让我们告诉 observer
跟踪 DOM 的变化:
const config = {childList: true};
observer.observe(element, config);
以下是我们放置在资源文件中作为 observer.js
文件的完整 JavaScript 代码:
const element = document.getElementById('counter');
const observer = new MutationObserver(
function(mutations) {
window.java.onDomChanged(element.innerHTML);
});
const config = {childList: true};
observer.observe(element, config);
Java 代码
加载资源的实用工具
在前面的部分中,我们审查了存储为资源的 HTML 和 JavaScript 代码。现在我们需要加载它们的代码。我们将使用 Guava 中的 Resources
来实现最简单的方法。
以下是我们稍后将使用的实用工具方法 load()
的代码:
private static String load(String resourceFile) {
URL url = ContentListening.class.getResource(resourceFile);
try (Scanner scanner = new Scanner(url.openStream(),
Charsets.UTF_8.toString())) {
scanner.useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
} catch (IOException e) {
throw new IllegalStateException("无法加载资源 " +
resourceFile, e);
}
}
创建 Browser 和 BrowserView
为了简化示例,我们将所有代码放在 main()
方法下。实际的应用程序会有更结构化的代码。
首先,我们需要创建一个 Engine
和 Browser
:
Engine engine = Engine.newInstance(
EngineOptions.newBuilder(HARDWARE_ACCELERATED).build());
Browser browser = engine.newBrowser();
然后,在 Swing 的事件分发线程(EDT)中,我们创建一个 BrowserView
和 JFrame
。然后将新创建的 BrowserView
添加到 Frame 中。
SwingUtilities.invokeLater(() -> {
BrowserView view = BrowserView.newInstance(browser);
JFrame frame = new JFrame("内容监听");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(view, BorderLayout.CENTER);
frame.setSize(700, 500);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
现在我们需要创建一个对象来监听 DOM 变化。
用于监听 DOM 变化的 Java 对象
您可能还记得,我们之前审查的 JavaScript 代码需要一个带有名为 onDomChanged()
的方法的对象,该方法接受一个 String
参数。
以下是该类的定义:
public static class JavaObject {
@SuppressWarnings("unused") // 由回调处理代码调用。
@JsAccessible
public void onDomChanged(String innerHtml) {
System.out.println("DOM 节点已发生变化:" + innerHtml);
}
}
现在我们需要让 JavaScript 代码与我们的 Java 对象进行通信。让我们开始吧。
连接 JavaScript 和 Java
为了让 JavaScript 代码与我们的 Java 对象进行通信,我们将实现一个 InjectJsCallback
将实例传递给 Browser.set()
。
browser.set(InjectJsCallback.class, params -> {
Frame frame = params.frame();
String window = "window";
JsObject jsObject = frame.executeJavaScript(window);
if (jsObject == null) {
throw new IllegalStateException(
format("未找到 '%s' JS 对象", window));
}
jsObject.putProperty("java", new JavaObject());
return Response.proceed();
});
在这段代码中,我们:
- 获取了 JavaScript
window
对象的一个实例。 - 创建了一个监听内容变化的 Java 对象,并将其设置为名为
java
的属性添加到window
对象中。前面在 JavaScript 代码中,我们使用MutationObserver
来将数据传递给与此属性相关联的对象。
接下来,我们应该注册一个 FrameLoadFinished
事件监听器,该监听器将加载包含 MutationObserver
置的 JavaScript 代码,并让 Browser
执行该代码,在 DOM 模型准备就绪时完成 JavaScript 和 Java 之间的连接。
browser.navigation().on(FrameLoadFinished.class, event -> {
String javaScript = load("observer.js");
event.frame().executeJavaScript(javaScript);
});
剩下的步骤是将页面加载到 Browser 中:
String html = load("index.html");
String base64Html = Base64.getEncoder().encodeToString(html.getBytes(UTF_8));
String dataUrl = "data:text/html;base64," + base64Html;
browser.navigation().loadUrl(dataUrl);
完整的 Java 代码
以下是完整的 Java 代码:
import static com.teamdev.jxbrowser.engine.RenderingMode.HARDWARE_ACCELERATED;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Charsets;
import com.teamdev.jxbrowser.browser.Browser;
import com.teamdev.jxbrowser.browser.callback.InjectJsCallback;
import com.teamdev.jxbrowser.browser.callback.InjectJsCallback.Response;
import com.teamdev.jxbrowser.engine.Engine;
import com.teamdev.jxbrowser.engine.EngineOptions;
import com.teamdev.jxbrowser.frame.Frame;
import com.teamdev.jxbrowser.js.JsAccessible;
import com.teamdev.jxbrowser.js.JsObject;
import com.teamdev.jxbrowser.navigation.event.FrameLoadFinished;
import com.teamdev.jxbrowser.view.swing.BrowserView;
import java.awt.BorderLayout;
import java.io.IOException;
import java.net.URL;
import java.util.Base64;
import java.util.Scanner;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
/**
* 这个示例演示了如何从一个 Java 对象监听 DOM 的变化。
*/
public final class ContentListening {
public static void main(String[] args) {
Engine engine = Engine.newInstance(
EngineOptions.newBuilder(HARDWARE_ACCELERATED).build());
Browser browser = engine.newBrowser();
SwingUtilities.invokeLater(() -> {
BrowserView view = BrowserView.newInstance(browser);
JFrame frame = new JFrame("内容监听");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(view, BorderLayout.CENTER);
frame.setSize(700, 500);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
browser.set(InjectJsCallback.class, params -> {
Frame frame = params.frame();
String window = "window";
JsObject jsObject = frame.executeJavaScript(window);
if (jsObject == null) {
throw new IllegalStateException(
format("未找到 '%s' JS 对象", window));
}
jsObject.putProperty("java", new JavaObject());
return Response.proceed();
});
browser.navigation().on(FrameLoadFinished.class, event -> {
String javaScript = load("observer.js");
event.frame().executeJavaScript(javaScript);
});
String html = load("index.html");
String base64Html = Base64.getEncoder().encodeToString(html.getBytes(UTF_8));
String dataUrl = "data:text/html;base64," + base64Html;
browser.navigation().loadUrl(dataUrl);
}
/**
* 将资源内容加载为字符串。
*/
private static String load(String resourceFile) {
URL url = ContentListening.class.getResource(resourceFile);
try (Scanner scanner = new Scanner(url.openStream(),
Charsets.UTF_8.toString())) {
scanner.useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
} catch (IOException e) {
throw new IllegalStateException("无法加载资源 " +
resourceFile, e);
}
}
/**
* 观察 DOM 变化的对象。
*
* <p>从 JavaScript 代码调用的类和方法必须是公开的。
*/
public static class JavaObject {
@SuppressWarnings("unused") // 由回调处理代码调用。
@JsAccessible
public void onDomChanged(String innerHtml) {
System.out.println("DOM 节点已发生变化:" + innerHtml);
}
}
}
如果您运行该程序,您应该会看到带有计数器的 Browser 窗口,并且当 Browser 窗口中的计数器变化时,控制台会输出相应的信息。
总结
在本教程中,我们创建了一个小型 Java 应用程序,用于监听已加载网页中 DOM 的变化。
该应用程序由以下部分组成:
- 带有将要变化的 DOM 元素的 HTML 页面,并且我们知道该元素的 ID。
- JavaScript 代码,使用
MutationObserver
来通知与window
对象关联的 Java 对象。 - 监听 DOM 事件的 Java 对象。
- Java 代码,向
Browser
实例添加InjectJsCallback
,加载网页,并使Browser
实例执行 JavaScript 代码,使MutationObserver
将变化传递给监听的 Java 对象。