List icon Conteúdo

Ouvir as alterações de conteúdo do DOM

Este tutorial mostra como criar um pequeno aplicativo Java que escuta alterações ocorridas em uma página HTML carregada no JxBrowser.

Pré-requisitos

Para realizar este tutorial, você vai precisar de:

  • Git.
  • Java 17 ou superior.
  • Uma licença válida do JxBrowser. Pode ser de avaliação ou comercial. Para mais informações sobre o licenciamento, consulte o guia Licenciamento.

Configurando um projeto

O código do aplicativo de exemplo para este tutorial está disponível junto com outros exemplos de um repositório do GitHub como um projeto baseado no Gradle.

Se você deseja construir um projeto baseado em Maven, consulte o guia Configuração Maven. Se quiser construir um projeto baseado no Gradle a partir do zero, consulte o guia Configuração Gradle.

Obtendo o código

Para obter o código, execute os seguintes comandos:

$ git clone https://github.com/TeamDev-IP/JxBrowser-Examples
$ cd JxBrowser-Examples/tutorials/content-changes

Agora, estamos no diretório raiz de todos os exemplos. O código deste tutorial está sob o diretório tutorials/content-changes.

Adicionar a licença

Para executar este tutorial, é necessário configurar uma chave de licença.

A página

Vamos carregar uma página HTML simples na qual mostraremos um contador que se incrementa automaticamente a cada segundo.

Aqui está o código do contador:

<div>
  <span class="counter" id="counter"></span>
</div>

O contador é atualizado utilizando 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>

Aqui está o código completo da página, que está incluído no projeto como o arquivo de recurso chamado 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>

O código JavaScript

Como queremos notificar o código Java da nossa aplicação a partir do nosso código HTML, precisamos do código JavaScript que trata das modificações do DOM e as transmite ao nosso código Java. Podíamos tê-lo feito diretamente no código HTML, mas queremos mostrar como adicionar esse código dinamicamente a partir de Java.

Primeiro, obtemos o elemento a seguir:

const element = document.getElementById('counter');

Em seguida, criamos uma instância MutationObserver com um retorno de chamada para passar os dados para o código Java.

const observer = new MutationObserver(
    function(mutations) {
        window.java.onDomChanged(element.innerHTML);
    });

A parte mais importante aqui é esta chamada:

window.java.onDomChanged(element.innerHTML);

Aqui fazemos referência a um objeto armazenado no objeto window, uma vez que a propriedade denominada java é o nome da propriedade que contém o objeto Java a ser chamado. Utilizamos a palavra java para realçar o fato de estarmos chamando um objeto Java. Pode ser qualquer identificador JavaScript que corresponda ao sentido da sua aplicação.

O método do objeto que estamos chamando é onDomChanged(). Mais tarde, adicionaremos este método quando criarmos uma classe Java para ouvir as alterações de conteúdo. Passamos a propriedade innerHTML do elemento contador. Assim, o método aceitará um parâmetro String.

Agora, vamos dizer ao observer para acompanhar as mudanças no DOM:

const config = {childList: true};
observer.observe(element, config);

Aqui está o código JavaScript completo que colocamos nos recursos como arquivo observer.js:

const element = document.getElementById('counter');
const observer = new MutationObserver(
    function(mutations) {
        window.java.onDomChanged(element.innerHTML);
    });
const config = {childList: true};
observer.observe(element, config);

O código Java

Utilitário para carregar recursos

Nas seções anteriores, analisamos o código HTML e JavaScript armazenado como recursos. Agora, precisamos do código que os carrega. Vamos utilizar a abordagem mais simples utilizando o utilitário Resources da classe do Guava.

Aqui está o código do método utilitário load() que usaremos mais tarde:

private static String load(String resourceFile) {
    var url = ContentListening.class.getResource(resourceFile);
    try (var 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);
    }
}

Criação do Browser e do BrowserView

Para simplificar o exemplo, vamos colocar todo o código no método main(). Uma aplicação real teria um código mais estruturado.

Antes de mais nada, precisamos criar um Engine e um Browser:

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

Em seguida, no Swing EDT, criamos um BrowserView e um JFrame. Em seguida, adicionamos o recém-criado BrowserView ao frame.

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

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

Agora, precisamos criar um objeto que ouça as alterações do DOM.

Objeto Java para ouvir alterações no DOM

Como você deve se lembrar, o código JavaScript que revisamos anteriormente precisava de um objeto com o método chamado onDomChanged() que aceita o argumento String.

Aqui está a classe:

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

Agora, precisamos criar código JavaScript para conversar com o nosso objeto Java. Vamos fazer isso.

Conectando JavaScript e Java

Para que o código JavaScript converse com nosso objeto Java, implementaremos um InjectJsCallback passando a instância para Browser.set().

browser.set(InjectJsCallback.class, params -> {
    var frame = params.frame();
    var 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();
});

Neste código, nós:

1. Obtemos uma instância do objeto JavaScript `window`.
2. Criamos um objeto Java que escuta as alterações de conteúdo e o define como uma propriedade chamada `java`
na `window`. Anteriormente, no código JavaScript, fizemos `MutationObserver` para passar dados
o objeto associado a esta propriedade.

Em seguida, devemos registrar o evento listener FrameLoadFinished que carregará o código JavaScript, o com o arranjo MutationObserver, e fará com que o Browser execute o código, finalizando a ligação entre JavaScript e Java quando o modelo DOM estiver pronto.

browser.navigation().on(FrameLoadFinished.class, event -> {
    var javaScript = load("observer.js");
    event.frame().executeJavaScript(javaScript);
});

O passo seguinte é carregar a página no browser:

var html = load("index.html");
var base64Html = Base64.getEncoder().encodeToString(html.getBytes(UTF_8));
var dataUrl = "data:text/html;base64," + base64Html;
browser.navigation().loadUrl(dataUrl);

Código Java completo

É isso. Aqui está o código Java completo:


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;

/**
 * Este exemplo demonstra como ouvir alterações DOM a partir de um objeto Java.
 */
public final class ContentListening {

    public static void main(String[] args) {
        var engine = Engine.newInstance(
                EngineOptions.newBuilder(HARDWARE_ACCELERATED).build());
        var browser = engine.newBrowser();
        SwingUtilities.invokeLater(() -> {
            var view = BrowserView.newInstance(browser);

            var 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 -> {
            var frame = params.frame();
            var 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 -> {
            var javaScript = load("observer.js");
            event.frame().executeJavaScript(javaScript);
        });

        var html = load("index.html");
        var base64Html = Base64.getEncoder().encodeToString(html.getBytes(UTF_8));
        var dataUrl = "data:text/html;base64," + base64Html;
        browser.navigation().loadUrl(dataUrl);
    }

    /**
     * Carrega o conteúdo de um recurso como uma cadeia de caracteres.
     */
    private static String load(String resourceFile) {
        var url = ContentListening.class.getResource(resourceFile);
        try (var 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);
        }
    }

    /**
     * O objeto que observa as alterações do DOM.
     *
     * <p>A classe e os métodos que são invocados a partir do código JavaScript devem ser públicos.
     */
    public static class JavaObject {

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

Se você executar este programa, deverá ver a janela do browser com o contador e a saída da console seguindo alteração na janela do browser.

Resumo

Neste tutorial, criamos um pequeno aplicativo Java que escuta as alterações do DOM numa página carregada da Web.

A aplicação é composta pelas seguintes partes:

  • A página HTML com um elemento DOM que vai ser alterado e para o qual sabemos o ID.
  • O código JavaScript, que utiliza MutationObserver para notificar um objeto Java associado com o objeto window.
  • O objeto Java que ouve os eventos DOM.
  • O código Java que adiciona um InjectJsCallback a uma instância Browser, carrega a página web e faz com que a instância Browser execute o código JavaScript que faz com que o MutationObserver passe as alterações para o objeto Java que está a ouvir.