监听DOM内容变化

本教程展示了如何构建一个小型 Java 应用程序来监听加载到 JxBrowser 中的 HTML 页面中发生的变化。

前提条件

为完成本教程,您将需要:

  • Git
  • Java 8 or higher
  • 有效的 JxBrowser 许可证。 它可以是评估版或商业版。 有关许可的更多信息,请参阅许可指南。

创建项目

本教程的示例应用程序的代码与其他示例一起,可从GitHub仓库作为基于Gradle的项目获得。

如果您想构建一个基于 Maven 的项目,请参考 Maven Config 指南。 如果您想从头开始构建一个基于 Gradle 的项目,请参考 Gradle Config 指南。

获取代码

要获取代码,请执行以下命令:

$ 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 对象这一事实。 它可以是任何符合您应用程序意义的 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("Unable to load resource " +
                resourceFile, e);
    }
}

创建浏览器和浏览器视图

为了使例子更简单化,我们将把所有代码放在 main() 方法下。 一个真正的应用程序应该有更结构化的代码。

首先,我们需要创建一个 EngineBrowser

Engine engine = Engine.newInstance(
        EngineOptions.newBuilder(HARDWARE_ACCELERATED).build());
Browser browser = engine.newBrowser();

然后在 Swing EDT 中,我们创建一个 BrowserViewJFrame。 然后我们将新创建的BrowserView 添加到框架中。

SwingUtilities.invokeLater(() -> {
    BrowserView view = BrowserView.newInstance(browser);

    JFrame frame = new JFrame("Content Listening");
    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") // Invoked by the callback processing code.
    @JsAccessible
    public void onDomChanged(String innerHtml) {
        System.out.println("DOM node changed: " + 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 object not found", window));
    }
    jsObject.putProperty("java", new JavaObject());
    return Response.proceed();
});

在这段代码中,我们:

  1. 获取一个 JavaScript window 对象的实例。
  2. 创建一个监听内容变化的Java对象,并将其设置为 window 中一个名为java 的属性。在前面的JavaScript代码中,我们使 MutationObserver 将数据传递给与此属性相关的对象。

然后我们应该注册 FrameLoadFinished 事件监听器,该监听器将加载JavaScript代码,即有 MutationObserver 安排的代码,并使 Browser 执行该代码,在DOM模型准备好时完成JavaScript和Java之间的连接。

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);

完整的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;

/**
 * This example demonstrates how to listen to DOM changes from a Java object.
 */
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("Content Listening");
            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 object not found", 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);
    }

    /**
     * Loads a resource content as a string.
     */
    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("Unable to load resource " +
                    resourceFile, e);
        }
    }

    /**
     * The object observing DOM changes.
     *
     * <p>The class and methods that are invoked from JavaScript code must be public.
     */
    public static class JavaObject {

        @SuppressWarnings("unused") // invoked by callback processing code.
        @JsAccessible
        public void onDomChanged(String innerHtml) {
            System.out.println("DOM node changed: " + innerHtml);
        }
    }
}

如果您运行该程序,您应该会看到带有计数器的浏览器窗口以及浏览器窗口更改后的控制台输出。

结论

在本教程中,我们创建了一个小型的Java应用程序,用于监听已加载网页中DOM的变化。

该应用程序由以下部分组成:

  • 带有将要变化的 DOM 元素的 HTML 页面,我们知道它的 ID。
  • 使用 MutationObserver 通知与 window 对象关联的 Java 对象的 JavaScript 代码。 with the window object.
  • 监听DOM事件的Java对象。
  • InjectJsCallback 添加到 Browser 实例、加载网页并使 Browser 实例执行使 MutationObserver 将更改传递给监听 Java 对象的 JavaScript 代码的 Java 代码。
Go Top