Introdução
Instalação
Guias
- Engine
- Profile
- Browser
- BrowserView
- Navegação
- Conteúdo
- DOM
- JavaScript
- Pop-ups
- Diálogos
- Downloads
- Rede
- Cache
- Cookies
- Proxy
- Autenticação
- Plugins
- Impressão
- Senhas
- Perfis de dados do Usuário
- Cartões de Crédito
- Mídia
- Área de transferência
- Zoom
- Corretor Ortográfico
- Implantação
- Chromium
Resolução de Problemas
JavaScript
Este guia descreve como acessar o JavaScript numa página Web carregada, executar código JavaScript, injetar objetos Java para chamar Java a partir do JavaScript, etc.
Executando JavaScript
O JxBrowser permite acessar e executar código JavaScript numa página Web carregada.
Para acessar o JavaScript, certifique-se de que a página Web está completamente carregada e que o JavaScript está habilitado.
Para executar código JavaScript, use o método Frame.executeJavaScript(String)
. Este método bloqueia a execução da thread atual e aguarda até que o código fornecido seja executado. O método retorna um java.lang.Object
que representa o resultado da execução. O método devolve null
se o resultado da execução for null
ou undefined
.
O exemplo a seguir executa o código JavaScript que retorna um título do document
:
String title = frame.executeJavaScript("document.title");
val title = frame.executeJavaScript<String>("document.title")
Você pode executar qualquer código JavaScript:
double number = frame.executeJavaScript("123");
boolean bool = frame.executeJavaScript("true");
String string = frame.executeJavaScript("'Hello'");
JsFunction alert = frame.executeJavaScript("window.alert");
JsObject window = frame.executeJavaScript("window");
Element body = frame.executeJavaScript("document.body");
JsPromise promise = frame.executeJavaScript("Promise.resolve('Success')");
JsArray array = frame.executeJavaScript("['Apple', 'Banana']");
JsArrayBuffer arrayBuffer = frame.executeJavaScript("new ArrayBuffer(8)");
JsSet set = frame.executeJavaScript("new Set([1, 2, 3, 4])");
JsMap map = frame.executeJavaScript("new Map([['John', '32'], ['Mary', '26']])");
val number = frame.executeJavaScript<Double>("123")
val bool = frame.executeJavaScript<Boolean>("true")
val string = frame.executeJavaScript<String>("'Hello'")
val alert = frame.executeJavaScript<JsFunction>("window.alert")
val window = frame.executeJavaScript<JsObject>("window")
val body = frame.executeJavaScript<Element>("document.body")
val promise = frame.executeJavaScript<JsPromise>("Promise.resolve('Success')")
val array = frame.executeJavaScript<JsArray>("['Apple', 'Banana']")
val arrayBuffer = frame.executeJavaScript<JsArrayBuffer>("new ArrayBuffer(8)")
val set = frame.executeJavaScript<JsSet>("new Set([1, 2, 3, 4])")
val map = frame.executeJavaScript<JsMap>("new Map([['John', '32'], ['Mary', '26']])")
Se você não quiser bloquear a execução da thread atual, então você pode usar o método Frame.executeJavaScript(String javaScript, Consumer<?> callback)
. Este método executa o código JavaScript fornecido de forma assíncrona e fornece o resultado da execução através do callback
fornecido:
frame.executeJavaScript("document.body", (Consumer<Element>) body -> {
String html = body.innerHtml();
});
frame.executeJavaScript("document.body", Consumer<Element> { body ->
val html = body.innerHtml()
})
Conversão de Tipo
O JavaScript e o Java trabalham com tipos primitivos diferentes. O JxBrowser implementa uma conversão automática de tipos JavaScript para tipos Java e vice-versa.
JavaScript para Java
As seguintes regras são utilizadas para converter JavaScript em tipos Java:
- Os números JavaScript são convertidos para
java.lang.Double
- JavaScript
string
parajava.lang.String
- JavaScript
boolean
parajava.lang.Boolean
- JavaScript
null
ouundefined
paranull
- JavaScript
Promise
paraJsPromise
- Os objetos JavaScript são agrupados como
JsObject
- As funções JavaScript são agrupadas como
JsFunction
- Os objetos JavaScript DOM Node são envolvidos como
JsObject
eEventTarget
- JavaScript
ArrayBuffer
é envolvido comoJsArrayBuffer
- JavaScript
Array
é envolvido comoJsArray
- JavaScript
Set
é envolvido comoJsSet
- JavaScript
Map
é envolvido comoJsMap
No exemplo acima, sabemos que document.title
é uma string, então definimos o valor de retorno como java.lang.String
.
Java para JavaScript
As seguintes regras são utilizadas para converter tipos Java em JavaScript:
java.lang.Double
é convertido para JavaScriptNumber
java.lang.String
para JavaScriptstring
java.lang.Boolean
para JavaScriptboolean
- Java
null
para JavaScriptnull
JsObject
para um objeto JavaScript apropriadoJsPromise
para JavaScriptPromise
EventTarget
para um objeto JavaScript DOM Node apropriado- O
java.lang.Object
será envolvido num objeto proxy JavaScript java.util.List<?>
para JavaScriptArray
ou objeto proxyJsArray
para JavaScriptArray
java.util.Set<?>
para JavaScriptSet
ou objeto proxyJsSet
para JavaScriptSet
java.util.Map<?,?>
para JavaScriptMap
ou objeto proxyJsMap
para JavaScriptMap
byte[]
para JavaScriptArrayBuffer
JsArrayBuffer
para JavaScriptArrayBuffer
Se passar um objeto Java não primitivo para o JavaScript, este será convertido num objeto proxy. As chamadas de métodos e propriedades para este objeto serão delegadas no objeto Java. Por razões de segurança, o JavaScript pode acessar apenas os métodos e campos do objeto Java injetado que são explicitamente marcados como acessíveis, seja usando a anotação @JsAccessible
ou através da classe JsAccessibleTypes
.
As coleções Java que não são criadas acessíveis ao JavaScript utilizando a anotação @JsAccessible
ou através da classe JsAccessibleTypes
são convertidas em coleções JavaScript. O conteúdo da coleção convertida é uma cópia profunda da coleção Java. As modificações da coleção convertida em JavaScript não afetam a coleção em Java.
As colecções Java que são feitas acessíveis ao JavaScript utilizando a anotação @JsAccessible
ou através da classe JsAccessibleTypes
são agrupadas num objeto proxy JavaScript. Estes objetos proxy podem ser utilizados para modificar a coleção em Java.
Wrappers DOM
Pelas regras da conversão automática de tipos, os objetos DOM do JavaScript são agrupados como JsObject
e EventTarget
. Ela te permite trabalhar com os objetos DOM do JavaScript através da API DOM do JxBrowser.
No exemplo a seguir, retornamos o document
que representa o objeto DOM do JavaScript. Neste caso, o valor de retorno pode ser definido como JsObject
ou Document
:
Document document = frame.executeJavaScript("document");
val document = frame.executeJavaScript<Document>("document")
JsObject document = frame.executeJavaScript("document");
val document = frame.executeJavaScript<JsObject>("document")
Trabalhando com JsObject
Para trabalhar com objetos JavaScript a partir de código Java, utilize a classe JsObject
. Ela permite trabalhar com as propriedades do objeto e chamar as suas funções.
Propriedades
Para obter os nomes das propriedades de um objeto JavaScript, incluindo as propriedades dos objetos protótipos, utilize o método propertyNames()
:
List<String> propertyNames = jsObject.propertyNames();
val propertyNames = jsObject.propertyNames()
Para verificar se o objeto JavaScript possui uma propriedade especificada, utilize o método hasProperty(String)
:
boolean has = jsObject.hasProperty("<property-name>");
val has = jsObject.hasProperty("<property-name>")
Para obter o valor da propriedade de um objeto JavaScript pelo seu nome, utilize property(String)
. Por exemplo:
JsObject document = frame.executeJavaScript("document");
document.property("title").ifPresent(title -> {});
val document = frame.executeJavaScript<JsObject>("document")!!
document.property<String>("title").ifPresent { title -> }
O valor de retorno representa java.lang.Object
que pode ser definido para o tipo necessário. Veja Conversão de Tipos.
É possível remover uma propriedade utilizando a seguinte abordagem:
boolean success = jsObject.removeProperty("<property-name>");
val success = jsObject.removeProperty("<property-name>")
Funções
Para chamar uma função com o nome e os argumentos necessários, utilize a função call(String methodName, Object... args)
method. O exemplo a seguir demonstra como chamar a função document.getElementById()
do JavaScript:
JsObject element = document.call("getElementById", "elementId");
val element: JsObject = document.call("getElementById", "elementId")
que é equivalente ao seguinte código em JavaScript:
var element = document.getElementById("demo");
O método lança JsException
se ocorrer um erro durante a execução da função.
Encerramento
Os objetos V8 que possuem uma contraparte JsObject
não estão sujeitos à coleta de lixo do V8. Por padrão, mantemos estes objetos
na memória até a página ser descarregada.
Para otimizar a utilização da memória, você pode ativar a recolha de lixo por objeto:
jsObject.close();
jsObject.close()
O fechamento de JsObject
marca o objeto V8 correspondente como coletável, mas não libera o objeto imediatamente.
Após a chamada do método close()
, as tentativas de utilização do JsObject
darão origem à ObjectClosedException
.
JsFunctionCallback
A outra maneira de chamar Java a partir de JavaScript é usando JsFunctionCallback
.
A ponte JavaScript-Java te permite associar JsFunctionCallback
a uma propriedade JavaScript que
será tratada como uma função que pode ser invocada no código JavaScript.
Por exemplo, você pode registrar uma função JavaScript associada à instância JsFunctionCallback
utilizando o seguinte código:
JsObject window = frame.executeJavaScript("window");
if (window != null) {
window.putProperty("sayHello", (JsFunctionCallback) args ->
"Hello, " + args[0]);
}
val window = frame.executeJavaScript<JsObject>("window")
window?.putProperty("sayHello", JsFunctionCallback { args -> "Hello, ${args[0]}" })
Agora, em JavaScript, você pode invocar esta função da seguinte forma:
window.sayHello('John');
JsFunction
Desde 7.7, você pode trabalhar com as funções JavaScript diretamente a partir do código Java e passar a referência a uma função do JavaScript para o Java. Por exemplo:
JsObject window = frame.executeJavaScript("window");
if (window != null) {
JsFunction alert = frame.executeJavaScript("window.alert");
if (alert != null) {
alert.invoke(window, "Hello world!");
}
}
val window = frame.executeJavaScript<JsObject>("window")
if (window != null) {
val alert = frame.executeJavaScript<JsFunction>("window.alert")
alert?.invoke<Any>(window, "Hello world!")
}
JsPromise
Desde 7.17 você pode trabalhar com JavaScript Promises diretamente a partir do código Java. Por exemplo:
JsPromise promise = frame.executeJavaScript(
"new Promise(function(resolve, reject) {\n"
+ " setTimeout(function() {\n"
+ " resolve('Hello Java!');\n"
+ " }, 2000);"
+ "})");
promise.then(results -> {
System.out.println(results[0]);
return promise;
}).then(results -> {
System.out.println(results[0]);
return promise;
}).catchError(errors -> {
System.out.println(errors[0]);
return promise;
});
val promise = frame.executeJavaScript<JsPromise>(
"""new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('Hello Java!');
}, 2000);
})
"""
)!!
promise.then { results ->
println(results[0])
promise
}.then { results ->
println(results[0])
promise
}.catchError { errors ->
println(errors[0])
promise
}
Chamando Java a partir de JavaScript
Quando você passa um java.lang.Object
como um valor de propriedade, ou um argumento ao chamar a função JavaScript,
o objeto Java será automaticamente envolvido em um objeto JavaScript.
Ela te permite injetar objetos Java no JavaScript e invocar os seus métodos e campos públicos a partir do JavaScript.
Por razões de segurança, apenas os métodos e campos públicos não estáticos anotados com @JsAccessible
ou declarados na classe anotada com @JsAccessible
podem ser acessados a partir do JavaScript. Os métodos e campos protegidos, privados ou privados de pacote anotados, ou os métodos e campos declarados numa classe com esses modificadores, permanecem inacessíveis a partir do JavaScript.
Para injetar um objeto Java no JavaScript, defina a classe do objeto Java e marque o método público que deve ser acessível a partir do JavaScript com @JsAccessible
:
public final class JavaObject {
@JsAccessible
public String sayHelloTo(String firstName) {
return "Hello " + firstName + "!";
}
}
class JavaObject {
@JsAccessible
fun sayHelloTo(firstName: String) = "Hello $firstName!"
}
Injeta uma instância do objeto Java no JavaScript antes de o JavaScript ser executado na página Web carregada:
browser.set(InjectJsCallback.class, params -> {
JsObject window = params.frame().executeJavaScript("window");
window.putProperty("java", new JavaObject());
return InjectJsCallback.Response.proceed();
});
browser.set(InjectJsCallback::class.java, InjectJsCallback { params ->
val window = params.frame().executeJavaScript<JsObject>("window")
window?.putProperty("java", JavaObject())
InjectJsCallback.Response.proceed()
})
Agora você pode fazer referência ao objeto e chamar o seu método a partir do JavaScript:
window.java.sayHelloTo("John");
Regras de anotação
A anotação @JsAccessible
permite expor métodos e campos de um objeto Java injetado ao JavaScript.
Você pode tornar acessíveis apenas tipos, métodos e campos públicos. A lista completa dos casos suportados é a seguinte:
- Uma classe de nível superior ou uma interface
- Uma classe estática aninhada ou uma interface
- Um método não estático de uma classe ou de uma interface
- Um campo não estático de uma classe
A anotação não pode ser aplicada a tipos, métodos e campos não-públicos. Os métodos e campos públicos de um tipo não-público são considerados não-públicos. Quando se anota um tipo, todos os seus métodos e campos públicos tornam-se acessíveis ao JavaScript. Quando se anota um método ou um campo de um tipo não-anotado, apenas o membro anotado se torna acessível ao JavaScript.
Um método acessível continua a sê-lo quando é substituído numa subclasse. Isto significa que você pode tornar uma interface acessível e passar qualquer uma das suas implementações para o JavaScript: todos os métodos declarados na interface serão acessíveis a partir do JavaScript. Outros métodos e campos declarados na classe de implementação permanecerão inacessíveis a menos que você os marque explicitamente ou a todo o tipo com esta anotação.
Outra forma de tornar um tipo acessível a partir do JavaScript é utilizando JsAccessibleTypes
. Isto é particularmente útil quando se quer tornar acessível um dos tipos principais do Java (por exemplo, java.util.List
), ou um tipo de uma biblioteca de terceiros que não pode ser tornado acessível usando esta anotação.
Exemplos:
Os métodos e campos anotados de uma classe pública de nível superior são acessíveis:
public final class TopClass {
@JsAccessible
public Object accessibleField;
@JsAccessible
public void accessibleMethod() {}
}
class TopClass {
@JsAccessible
var accessibleField: Any? = null
@JsAccessible
fun accessibleMethod() {}
}
Os métodos e campos anotados de uma classe pública estática aninhada são acessíveis:
public final class TopClass {
public static class NestedClass {
@JsAccessible
public Object accessibleField;
@JsAccessible
public void accessibleMethod() {}
}
}
class TopClass {
class NestedClass {
@JsAccessible
var accessibleField: Any? = null
@JsAccessible
fun accessibleMethod() {}
}
}
Os métodos e campos não-anotados de uma classe anotada são acessíveis:
@JsAccessible
public final class TopClass {
public Object accessibleField;
public void accessibleMethod() {}
}
@JsAccessible
class TopClass {
var accessibleField: Any? = null
fun accessibleMethod() {}
}
Os métodos e campos de uma classe anotada de base são acessíveis aos herdeiros:
public final class TopClass {
@JsAccessible
public static class BaseNestedClass {
public Object accessibleFieldFromInheritor;
public void accessibleMethodFromInheritor() {}
}
public static class NestedClass extends BaseNestedClass {
public Object inaccessibleField;
public void inaccessibleMethod() {}
}
}
class TopClass {
@JsAccessible
open class BaseNestedClass {
var accessibleFieldFromInheritor: Any? = null
fun accessibleMethodFromInheritor() {}
}
class NestedClass : BaseNestedClass() {
var inaccessibleField: Any? = null
fun inaccessibleMethod() {}
}
}
Os métodos e campos herdados não são acessíveis se eles ou a classe em que são declarados não estiverem anotados:
public final class TopClass {
public static class BaseNestedClass {
public Object inaccessibleField;
public void inaccessibleMethod() {}
}
@JsAccessible
public static class NestedClass extends BaseNestedClass {
public Object accessibleField;
public void accessibleMethod() {}
}
}
class TopClass {
open class BaseNestedClass {
var inaccessibleField: Any? = null
fun inaccessibleMethod() {}
}
@JsAccessible
class NestedClass : BaseNestedClass() {
var accessibleField: Any? = null
fun accessibleMethod() {}
}
}
Os métodos sobrescritos da classe são acessíveis:
public final class TopClass {
public static class BaseNestedClass {
@JsAccessible
public void method() {}
}
public static class NestedClass extends BaseNestedClass {
@Override
public void method() {} // acessível
}
}
class TopClass {
open class BaseNestedClass {
@JsAccessible
open fun method() {
}
}
class NestedClass : BaseNestedClass() {
override fun method() {} // acessível
}
}
Os métodos implementados da interface são acessíveis:
public static class TopClass {
public interface NestedInterface {
@JsAccessible
void method();
}
public static class AccessibleImplementor implements NestedInterface {
@Override
public void method() { } // accessible
}
}
class TopClass {
interface NestedInterface {
@JsAccessible
fun method()
}
class AccessibleImplementor : NestedInterface {
override fun method() {} // acessível
}
}
Se a assinatura de um método Java acessível tiver um parâmetro numérico primitivo, o número transferido do JavaScript será verificado quanto à possibilidade de conversão para o tipo de parâmetro Java. Se a conversão puder ser efetuada sem perda de dados e não forem encontrados outros métodos sobrecarregados adequados, o método será invocado.
Se existir mais do que um método que possa aceitar os parâmetros passados, o JavaScript lança uma exceção para indicar que a chamada de método solicitada é ambígua e não pode ser executada.
Se não forem encontrados métodos ou campos que correspondam ao nome solicitado, o JavaScript lança uma exceção indicando que o membro solicitado não existe.
Se tanto o método como o campo com o mesmo nome solicitado pelo JavaScript existirem, o JavaScript lança uma exceção para indicar que o membro solicitado é ambíguo e não pode ser acessado.
Conversão Automática de Tipos
A ponte JavaScript-Java fornece a funcionalidade de conversão automática de tipos quando chama um método público do objeto Java injetado a partir do JavaScript.
A biblioteca converte automaticamente o Number
JavaScript fornecido para o tipo Java necessário se
for possível. Se detectarmos que o número dado não pode ser convertido para, por exemplo, um Java byte
sem perda de dados, então a biblioteca lança uma exceção e notifica o JavaScript que não existe
nenhum método Java apropriado. Se o valor fornecido puder ser convertido sem perda de dados, a biblioteca
o converte e invoca o método Java adequado.
Por exemplo, se você injetar o seguinte objeto Java no JavaScript:
public final class JavaObject {
@JsAccessible
public int method(int intValue) {
return intValue;
}
}
class JavaObject {
@JsAccessible
fun method(intValue: Int) = intValue
}
Depois pode chamá-lo a partir do JavaScript e passar o valor Number
do JavaScript que pode ser convertido para
e Integer
sem perda de dados:
window.javaObject.method(123);
Mas, se você passar um valor Double
que não pode ser convertido para um Integer
sem perda de dados, você
receberá um erro:
window.javaObject.method(3.14); // <- erro
Chamada a Partir de Bibliotecas
Os objetos Java injetados são um tipo especial de objetos. Eles se comportam de forma diferente dos objetos JavaScript normais e não se destinam a ser passados diretamente para as bibliotecas JavaScript.
Antes de as utilizar em bibliotecas JavaScript, recomendamos que as envolva no Proxy. Neste exemplo, criamos um objeto proxy que implementa o acesso de leitura aos membros acessíveis do JS:
const proxy = new Proxy({__java: myJavaObject}, {
get(target, prop, receiver) {
for (let javaMemberName in target.__java) {
if (prop === javaMemberName) {
return target.__java[prop]
}
}
return Reflect.get(...arguments);
},
...
});
Mensagens de Console
O JxBrowser permite receber todas as mensagens de saída enviadas para o console através da função JavaScript console.log()
. Você pode escutar as mensagens com os seguintes níveis:
DEBUG
LOG
WARNING
ERROR
Para receber uma notificação quando o Console receber uma mensagem, utilize o evento ConsoleMessageReceived
. Por exemplo:
browser.on(ConsoleMessageReceived.class, event -> {
ConsoleMessage consoleMessage = event.consoleMessage();
ConsoleMessageLevel level = consoleMessage.level();
String message = consoleMessage.message();
});
browser.on(ConsoleMessageReceived::class.java) { event ->
val consoleMessage = event.consoleMessage()
val level = consoleMessage.level()
val message = consoleMessage.message()
}