This guide describes how to access JavaScript on a loaded web page, execute JavaScript code, inject Java objects to call Java from JavaScript, etc.

Executing JavaScript

JxBrowser allows accessing and executing JavaScript code on a loaded web page.

To access JavaScript make sure that web page is loaded completely and JavaScript is enabled.

To execute JavaScript code use the Frame.executeJavaScript(String) method. This method blocks the current thread execution and waits until the given code is executed. The method returns a java.lang.Object that represents the result of the execution. The method returns null if the result of the execution is null or undefined.

The following example executes the JavaScript code that returns a title of the document:

String title = frame.executeJavaScript("document.title");

You can execute any JavaScript code:

double number = frame.executeJavaScript("123");
boolean bool = frame.executeJavaScript("true");
String string = frame.executeJavaScript("'Hello'");
JsObject window = frame.executeJavaScript("window");
Element body = frame.executeJavaScript("document.body");

If you would like do not block the current thread execution, then you can use the Frame.executeJavaScript(String javaScript, Consumer<?> callback) method. This method executes the given JavaScript code asynchronously and provides the result of the execution through the given callback:

frame.executeJavaScript("document.body", (Consumer<Element>) body -> {
    String html = body.innerHtml();
});

Type Conversion

JavaScript and Java work with different primitive types. JxBrowser implements an automatic type conversion from JavaScript to Java types and vice versa.

JavaScript to Java

The following rules are used to convert JavaScript into Java types:

  • JavaScript numbers are converted to java.lang.Double
  • JavaScript string to java.lang.String
  • JavaScript boolean to java.lang.Boolean
  • JavaScript null and undefined to null
  • JavaScript objects get wrapped as JsObject
  • JavaScript DOM Node objects get wrapped as both JsObject and EventTarget.

In the example above we know that the document.title is a string, so we set the return value to java.lang.String.

Java to JavaScript

The following rules are used to convert Java into JavaScript types:

  • java.lang.Double is converted to JavaScript Number
  • java.lang.String to JavaScript string
  • java.lang.Boolean to JavaScript boolean
  • Java null to JavaScript null
  • JsObject to an appropriate JavaScript object
  • EventTarget to an appropriate JavaScript DOM Node object
  • java.lang.Object will be wrapped into a JavaScript object.

DOM Wrappers

By the rules of the automatic type conversion JavaScript DOM objects get wrapped as both JsObject and EventTarget. It allows you to work with the JavaScript DOM objects through JxBrowser DOM API.

In the following example we return the document that represents the JavaScript DOM object. In this case the return value can be set to JsObject or Document:

Document document = frame.executeJavaScript("document");
JsObject document = frame.executeJavaScript("document");

Working with JsObject

To work with JavaScript objects from Java code please use the JsObject class. It allows working with the object properties and calling its functions.

Properties

To get property names of a JavaScript object, including properties from the prototype objects, please use the propertyNames() method:

List<String> propertyNames = jsObject.propertyNames();

To check whether JavaScript object has a specified property, please use the hasProperty(String) method:

boolean has = jsObject.hasProperty("<property-name>");

To get value of the JavaScript object property by its name please use property(String). For example:

JsObject document = frame.executeJavaScript("document");
document.property("title").ifPresent(title -> {});

The return value represents java.lang.Object that can be set to the required type. See the Type Conversion.

You can remove a property using the following approach:

boolean success = jsObject.removeProperty("<property-name>");

Functions

To call a function with the required name and arguments use the call(String methodName, Object... args) method. The following example demonstrates how to call the document.getElementById() JavaScript function:

JsObject element = document.call("getElementById", "elementId");

which is equivalent to the following code in JavaScript:

var element = document.getElementById("demo");

The method throws JsException if an error occurs during the function execution.

Calling Java from JavaScript

When you pass a java.lang.Object as a property value or an argument when calling JavaScript function, the Java object will be automatically converted/wrapped into a JavaScript object.

It allows injecting Java objects into JavaScript and invoking its public methods from the JavaScript code.

@JsAccessible

For security reasons only the public methods annotated with @JsAccessible or declared in the class annotated with the @JsAccessible can be accessed from JavaScript. Java object’s fields are not accessible. If JavaScript calls the method that does not exist in the injected Java object or the method is not public and has not been annotated, a JavaScript exception with an appropriate error message will be thrown. For example, if the method does not exist, the error message will look like “Uncaught No such method”.

The following example demonstrates how to pass a Java object as a property value and call its public method from JavaScript:

public class JavaObject {
    @JsAccessible
    public String sayHelloTo(String firstName) {
        return "Hello " + firstName + "!";
    }
}
...
JsObject window = frame.executeJavaScript("window");
window.putProperty("java", new JavaObject());

Then you can refer to the object and call its method from the JavaScript code:

console.log(window.java.sayHelloTo("John"));

The Console output should look like:

Hello John!

Annotating Rules

Since annotating classes and methods with the @JsAccessible has quite a lot of different cases related to inheritance, all of the main cases are explained below.

Annotated methods of a public top level class are accessible:

public final class TopClass {
    @JsAccessible
    public void accessibleMethod() {}
}

Annotated methods of a public static nested class are accessible:

public final class TopClass {
    public static class NestedClass {
        @JsAccessible
        public void accessibleMethod() {}
    }
}

Unannotated methods of an annotated class are accessible:

@JsAccessible
public final class TopClass {
    public void accessibleMethod() {}
}

Methods of a base annotated class are accessible from inheritors:

public final class TopClass {
  
    @JsAccessible
    public static class BaseNestedClass {
        public void accessibleMethodFromInheritor() {}
    }
    
    public static class NestedClass extends BaseNestedClass {
        public void inaccessibleMethod() {}
    }
}

Inherited methods are not accessible if they or the class they are declared in are not annotated:

public final class TopClass {
  
    public static class BaseNestedClass {
        public void inaccessibleMethod() {}
    }
    
    @JsAccessible
    public static class NestedClass extends BaseNestedClass {
        public void accessibleMethod() {}
    }
}

Overridden accessible methods become inaccessible. To make them accessible, annotate them or their declaring class with @JsAccessible.

public final class TopClass {
  
    public static class BaseNestedClass {
        @JsAccessible
        public void method() {}
    }
    
    public static final class NestedClass extends BaseNestedClass {
        @Override
        public void method() {} // <- inaccessible
    }
    
    public static final class AnotherNestedClass extends BaseNestedClass {
        @Override
        @JsAccessible
        public void method() {} // <- accessible
    }
}

Type Conversion

The JavaScript-Java bridge provides automatic types conversion functionality when calling a public method of the injected Java object from JavaScript.

The library automatically convert the given JavaScript Number to the required Java type if it is possible. If we detect that the given number cannot be converted to, for example, a Java byte without data loss, then the library throws an exception and notifies JavaScript that there is no appropriate Java method. If the given value can be converted without data loss, then the library converts it and invokes the appropriate Java method.

For example, if you inject the following Java object into JavaScript:

public final class JavaObject {

    @JsAccessible
    public int method(int intValue) {
        return intValue;
    }
}

Then you can call it from JavaScript and pass the JavaScript Number value that can be converted to an Integer without data loss:

window.javaObject.method(123);

But, if you pass Double value that cannot be converted to an Integer without data loss, you will get an error:

window.javaObject.method(3.14); // <- error

JsFunctionCallback

The other way to call Java from JavaScript is using JsFunctionCallback.

JavaScript-Java bridge allows you to associate JsFunctionCallback with a JavaScript property that will be treated as a function that can be invoked in JavaScript code.

For example, you can register a JavaScript function associated with the JsFunctionCallback instance using the following code:

JsObject window = frame.executeJavaScript("window");
if (window != null) {
    window.putProperty("sayHello", (JsFunctionCallback) args ->
            "Hello, " + args[0]);
}

Now, in JavaScript you can invoke this function in the following way:

window.sayHello('John');

Console Messages

JxBrowser allows receiving all output messages sent to the Console via the console.log() JavaScript function. You can listen to the messages with the following levels:

  • DEBUG
  • LOG
  • WARNING
  • ERROR

To get a notification when the Console gets a message please use the ConsoleMessageReceived event. For example:

browser.on(ConsoleMessageReceived.class, event -> {
    ConsoleMessage consoleMessage = event.consoleMessage();
    ConsoleMessageLevel level = consoleMessage.level();
    String message = consoleMessage.message();
});
Go Top