The 7.0 version of JxBrowser introduces a major improvement to both internals and the public API of the library. This guide shows how to change your application code written with JxBrowser version 6.x to work with version 7.0.

Overview

Why Migrate?

We recommend you to update your code to the latest version because all the new features, Chromium upgrades, support of the new operating systems and JDKs, bug fixes, security patches, performance, and the memory usage enhancements are applied on top of the latest version.

How Long Does It Take?

From our experience, upgrade to the new major version takes from a couple of hours to a few days depending on the number of the features you use in your application. As usual, we strongly recommend to test your software after the upgrade in all the environments it supports.

Getting Help

Please contact us if you need any assistance with migration.

Key Changes

API & Architecture

In JxBrowser 7 the architecture of the library has been improved. Now JxBrowser allows you to create two absolutely independent Browser instances and control their life cycle. Along with this internal architecture change the public API has been improved and extended with the new features as well.

Please see the Mapping section to find out how the main functionality of 6.x maps to 7.0.

System Requirements

Starting with this version JxBrowser no longer supports Oracle JREs 1.6 and 1.7, Apple’s and IBM’s JREs, macOS 10.9, and OSGi. Current System Requirements are available here.

JARs

The structure of the library has changed. Now the library consists of the following JARs:

  • jxbrowser-7.0.jar — the core classes and interfaces;
  • jxbrowser-swing-7.0.jar — the classes and interfaces for embedding into a Swing app;
  • jxbrowser-javafx-7.0.jar — the classes and interfaces for embedding into a JavaFX app;
  • jxbrowser-win32-7.0.jar — the Chromium 32-bit binaries for Windows;
  • jxbrowser-win64-7.0.jar — the Chromium 64-bit binaries for Windows;
  • jxbrowser-linux64-7.0.jar — the Chromium 64-bit binaries for Linux;
  • jxbrowser-mac-7.0.jar — the Chromium binaries for macOS.

Basic Concepts

The way to create and dispose the objects has changed in the new API.

To create a service object use the <ServiceObject>.newInstance() static method. For example:

Browser browser = Browser.newInstance(engine.getId());

To create an immutable data object use the Object.newBuilder() static method. For example:

EngineOptions engineOptions = EngineOptions.newBuilder()
        .setLanguage(Language.ENGLISH_US)
        .build();

Every object that should be disposed manually implements the com.teamdev.jxbrowser.Closable interface, e.g. com.teamdev.jxbrowser.Browser. To dispose the object call the com.teamdev.jxbrowser.Closable.close() method. For example:

browser.close();

Please note that some service objects depend on the other service objects. When you dispose a service object, all the service objects depending on it will be disposed automatically, so you do not need to close them manually. Please see the Architecture guide for more details about the key objects and their interaction principles.

Some objects can be identified by a unique ID. To find an object instance with the required ID use the Object.with() static method. For example:

Browser.with(browserId).ifPresent(browser -> {});

If a method can return null, its return value is wrapped into the java.util.Optional. For example:

browser.getMainFrame().ifPresent(frame -> {});

Events

The way to register an event listener has changed.

v6.x

We use a classic Observer pattern that consists of the addXxxListener(XxxListener listener) and removeXxxListener(XxxListener listener) methods that allow you to register and unregister an event listener. For example:

TitleListener titleListener = new TitleListener() {
    @Override
    public void onTitleChange(TitleEvent event) {}
};
browser.addTitleListener(titleListener);

The event listener should be unregistered using an appropriate method when you do not want to receive the event notifications:

browser.removeTitleListener(titleListener);

v7.0

A service object that allows registering event listeners implements the com.teamdev.jxbrowser.event.Observable interface. To register and unregister an event listener use the Observable.on() and Observable.remove() methods. For example:

EventListener<TitleChanged> titleListener = event -> {};
browser.on(TitleChanged.class, titleListener);

The event listener should be unregistered using an appropriate method when you do not want to receive the event notifications:

browser.remove(titleListener);

Callbacks

The logic of registering event handlers (callbacks) has changed as well.

v6.x

To register an event handler use the setXxxHandler(XxxHandler handler) method to set and remove an event handler. For example:

browser.setDialogHandler(new DialogHandler() {
    ...
    @Override
    public CloseStatus onConfirmation(DialogParams params) {
        return CloseStatus.OK;
    }
    ...
});
browser.setDialogHandler(null);

v7.0

Event handlers are now named Callbacks. Each object that allows registering callbacks implements the com.teamdev.jxbrowser.callback.Advisable interface. To register and unregister a callback the Advisable.set() and Advisable.remove() methods should be used. For example:

browser.set(ConfirmCallback.class, (params, callback) -> callback.ok());
browser.remove(ConfirmCallback.class);

Please do not forget to return the result through the given callback parameter, otherwise the engine will wait blocked until termination.

Thread Safety

The library is not thread-safe, so please do not work with the library from different threads at the same time.

Dropped Functionality

In the new version the following functionality has been temporarily dropped. We are going to provide alternatives in the next updates:

  • Taking screenshots of the loaded web page.
  • Suppressing mouse and keyboard events.
  • Displaying the BeforeUnload dialog when closing Browser.
  • IME and Accessibility on macOS in the hardware accelerated (heavyweight) rendering mode.
  • WebStorage API.

Mapping

In this section we will show how the main functionality of 6.x maps to 7.0.

Engine

Creating Engine

6.x

The Engine is not a part of the public API. It is created internally when the first Browser instance is created in the application. Only one Engine can be created and used in the application.

7.0

The Engine is a part of the public API now. You can create and use multiple Engine instances in the application. To create an Engine instance with the required options use the following code:

Engine engine = Engine.newInstance(EngineOptions.newBuilder()
        // The language used on the default error pages and GUI.
        .setLanguage(Language.ENGLISH_US)
        // The absolute path to the directory where the data such as cache,
        // cookies, history, GPU cache, local storage, visited links, web data,
        // spell checking dictionary files, etc. is stored.
        .setUserDataDir("/Users/Me/JxBrowser/UserData")
        // How the content of the web pages will be rendered.
        .setRenderingMode(RenderingMode.HARDWARE_ACCELERATED)
        .build());

The Engine runs a separate native process where the Chromium with the provided options is created and initialized. The native process communicates with the Java process via the Inter-Process Communication (IPC) bridge. If the native process is unexpectedly terminated because of a crash, the Java process will continue running.

You can use the following notification to find out when the Engine is unexpectedly terminated, so that you can re-create the Engine and restore the Browser instances:

engine.on(EngineCrashed.class, event -> {
   // Clear all the allocated resources and re-create the Engine.
});

Closing Engine

6.x

The Engine is closed automatically when the last Browser instance is closed in the application.

7.0

The Engine should be closed manually via the close() method when it is no longer required:

engine.close();

When you close the Engine, all its Browser instances are closed automatically. Any attempt to use an already closed Engine will lead to an exception.

To find out when the Engine is closed use the following notification:

engine.on(EngineClosed.class, event -> {});

Browser

Creating Browser

6.x

Browser browser = new Browser();

7.0

Browser browser = Browser.newInstance(engine.getId());

Creating Browser with Options

6.x

BrowserContextParams browserContextParams =
        new BrowserContextParams("Users/Me/JxBrowser/Data");
browserContextParams.setStorageType(StorageType.MEMORY);
BrowserContext browserContext = new BrowserContext(browserContextParams);
Browser browser = new Browser(BrowserType.HEAVYWEIGHT, browserContext);

7.0

The BrowserContext functionality has been moved to Engine. So, the same code should be replaced with:

Engine engine = Engine.newInstance(EngineOptions.newBuilder()
        .setUserDataDir("/Users/Me/JxBrowser/UserData")
        .setRenderingMode(RenderingMode.HARDWARE_ACCELERATED)
        .setIncognito(true)
        .build());
Browser browser = Browser.newInstance(engine.getId());

Closing Browser

6.x

browser.dispose();

7.0

browser.close();

BrowserView

Creating Swing BrowserView

6.x

import com.teamdev.jxbrowser.chromium.swing.BrowserView;
...
BrowserView view = new BrowserView(browser);

7.0

import com.teamdev.jxbrowser.view.swing.BrowserView;
...
BrowserView view = BrowserView.newInstance(browser.getId());

Creating JavaFX BrowserView

6.x

import com.teamdev.jxbrowser.chromium.javafx.BrowserView;
...
BrowserView view = new BrowserView(browser);

7.0

import com.teamdev.jxbrowser.view.javafx.BrowserView;
...
BrowserView view = BrowserView.newInstance(browser.getId());

Frame

Working with Frame

6.x

A web page may consist of the main frame that may contain multiple sub-frames. To perform an operation with a frame you need to pass the frame’s identifier to the corresponding method. For example:

// Get HTML of the main frame.
browser.getHTML();

// Get HTML of the all frames.
for (Long frameId : browser.getFramesIds()) {
    String html = browser.getHTML(frameId);
}

7.0

The Frame is a part of the public API now. You can work with the frame through the Frame instance. For example:

// Get HTML of the main frame if it exists.
browser.getMainFrame().ifPresent(Frame::getHtml);

// Get HTML of all the frames.
for (Frame frame : browser.getAllFrames()) {
    String html = frame.getHtml();
}

Loading URL

6.x

browser.loadURL("https://www.google.com");

7.0

browser.getNavigation().loadUrl("https://www.google.com");

Loading HTML

6.x

browser.loadHTML("<html><body></body></html>");

7.0

browser.getMainFrame().ifPresent(frame ->
        frame.loadHtml("<html><body></body></html>"));

Start Loading

6.x

browser.addLoadListener(new LoadAdapter() {
    @Override
    public void onStartLoadingFrame(StartLoadingEvent event) {
        String url = event.getValidatedURL();
    }
});

7.0

browser.on(NavigationStarted.class, event -> {
    String url = event.getUrl();
});

Finish Loading

6.x

browser.addLoadListener(new LoadAdapter() {
    @Override
    public void onFinishLoadingFrame(FinishLoadingEvent event) {
        String html = browser.getHTML(event.getFrameId());
    }
});

7.0

browser.on(FrameLoadFinished.class, event ->
        Frame.with(event.getFrameId()).ifPresent(frame -> {
            String html = frame.getHtml();
        }));

Fail Loading

6.x

browser.addLoadListener(new LoadAdapter() {
    @Override
    public void onFailLoadingFrame(FailLoadingEvent event) {
        NetError errorCode = event.getErrorCode();
    }
});

7.0

browser.on(NavigationFinished.class, event -> {
    NetError errorCode = event.getErrorCode();
});

Network

Configuring User-Agent

6.x

BrowserPreferences.setUserAgent("My User-Agent");

7.0

Engine engine = Engine.newInstance(EngineOptions.newBuilder()
        ...
        .setUserAgent("My User-Agent")
        .build());

Configuring Accept-Language

6.x

browserContext.setAcceptLanguage("fr, en-gb;q=0.8, en;q=0.7");

7.0

engine.getNetworkService().setAcceptLanguage("fr, en-gb;q=0.8, en;q=0.7");

Configuring Proxy

6.x

ProxySerivce proxyService = browserContext.getProxyService();
proxyService.setProxyConfig(new CustomProxyConfig("http-proxy-server:80"));

7.0

ProxyService proxyService = engine.getProxyService();
proxyService.setProxyConfig(ProxyConfigs.createCustom("http-proxy-server:80"));

Redirecting URL Request

6.x

browserContext.getNetworkService().setNetworkDelegate(new NetworkDelegate() {
    @Override
    public void onBeforeURLRequest(BeforeURLRequestParams params) {
        params.setURL("https://www.google.com");
    }
});

7.0

engine.getNetworkService().set(BeforeUrlRequestCallback.class, (params, callback) ->
        callback.redirect("https://www.google.com"));

Overriding HTTP Headers

6.x

browserContext.getNetworkService().setNetworkDelegate(new NetworkDelegate() {
    ...
    @Override
    public void onBeforeSendHeaders(BeforeSendHeadersParams params) {
        HttpHeadersEx headers = params.getHeadersEx();
        headers.setHeader("User-Agent", "MyUserAgent");
        headers.setHeader("Content-Type", "text/html");
    }
});

7.0

engine.getNetworkService().set(BeforeSendHeadersCallback.class, (params, callback) -> {
    List<HttpHeader> headers = Arrays.asList(
            HttpHeader.newBuilder()
                    .setName("User-Agent")
                    .setValue("MyUserAgent")
                    .build(),
            HttpHeader.newBuilder()
                    .setName("Content-Type")
                    .setValue("text/html")
                    .build());
    callback.overrideHeaders(headers);
});

DOM

Accessing Document

6.x

DOMDocument document = browser.getDocument();
if (document != null) {
    String baseURI = document.getBaseURI();
}

7.0

browser.getMainFrame().ifPresent(frame ->
        frame.getDocument().ifPresent(document -> {
            String baseUri = document.getBaseUri();
        }));

DOM Events

Working with Events

6.x

element.addEventListener(DOMEventType.OnClick, new DOMEventListener() {
    @Override
    public void handleEvent(DOMEvent event) {
        DOMEventTarget eventTarget = event.getTarget();
        if (eventTarget != null) {
            ...
        }
    }
}, false);

7.0

element.addEventListener(EventTypes.CLICK, event ->
        event.getTarget().ifPresent(eventTarget -> {
            ...
        }), false));

JavaScript

Calling JavaScript from Java

6.x

String string = browser.executeJavaScriptAndReturnValue("'Hello'").asString().getValue();
double number = browser.executeJavaScriptAndReturnValue("123").asNumber().getValue();
boolean bool = browser.executeJavaScriptAndReturnValue("true").asBoolean().getValue();
JSObject window = browser.executeJavaScriptAndReturnValue("window").asObject();

7.0

The JSValue class has been removed. The type conversion is done automatically now:

browser.getMainFrame().ifPresent(frame -> {
    String string = frame.executeJavaScript("'Hello'");
    double number = frame.executeJavaScript("123");
    boolean bool = frame.executeJavaScript("true");
    JsObject window = frame.executeJavaScript("window");
});

Calling Java from JavaScript

6.x

In Java code:

public class JavaObject {
    public void foo(String text) {}
}

JSValue window = browser.executeJavaScriptAndReturnValue("window");
if (window.isObject()) {
    window.asObject().setProperty("java", new JavaObject());
}

In JavaScript code:

window.java.foo("Hello");

7.0

For security reasons only public methods annotated with @JsAccessible can be accessed from JavaScript.

In Java code:

public class JavaObject {
    @JsAccessible
    public void foo(String text) {}
}

JsObject window = frame.executeJavaScript("window");
window.setProperty("java", new JavaObject());

In JavaScript code:

window.java.foo("Hello");

Pop-ups

Suppressing Pop-ups

6.x

browser.setPopupHandler(new PopupHandler() {
    @Override
    public PopupContainer handlePopup(PopupParams params) {
        return null;
    }
});

7.0

browser.set(CanOpenPopupCallback.class, (params, callback) -> callback.cannot());

Opening Pop-ups

6.x

browser.setPopupHandler(new PopupHandler() {
    @Override
    public PopupContainer handlePopup(PopupParams params) {
        return new PopupContainer() {
            @Override
            public void insertBrowser(Browser popupBrowser, Rectangle initialBounds) {}
        };
    }
});

7.0

browser.set(CanOpenPopupCallback.class, (params, callback) -> callback.can());
browser.set(OpenPopupCallback.class, (params, callback) -> {
    Browser popupBrowser = Browser.with(params.getPopupBrowserId())
            .orElseThrow(IllegalStateException::new);
    callback.done();
});

Dialogs

JavaScript Dialogs

6.x

browser.setDialogHandler(new DialogHandler() {
    @Override
    public void onAlert(DialogParams params) {
    }

    @Override
    public CloseStatus onConfirmation(DialogParams params) {
        return CloseStatus.CANCEL;
    }

    @Override
    public CloseStatus onPrompt(PromptDialogParams params) {
        params.setPromptText("Text");
        return CloseStatus.OK;
    }
    ...
});

7.0

browser.set(AlertCallback.class, (params, callback) -> callback.ok());
browser.set(ConfirmCallback.class, (params, callback) -> callback.no());
browser.set(PromptCallback.class, (params, callback) -> callback.ok("Text"));

File Dialogs

6.x

browser.setDialogHandler(new DialogHandler() {
    @Override
    public CloseStatus onFileChooser(FileChooserParams params) {
        FileChooserMode mode = params.getMode();
        if (mode == FileChooserMode.Open) {
            params.setSelectedFiles(new File(params.getDefaultFileName()));
        }
        if (mode == FileChooserMode.OpenMultiple) {
            params.setSelectedFiles(new File("file1.txt"), new File("file2.txt"));
        }
        if (mode == FileChooserMode.Save) {
            params.setSelectedFiles(new File(params.getDefaultFileName()));
        }
        return CloseStatus.OK;
    }
    ...
});

7.0

browser.set(OpenFileCallback.class, (params, callback) ->
        callback.open(new File(params.getFileName())));
browser.set(OpenFilesCallback.class, (params, callback) ->
        callback.open(new File("file1.txt"), new File("file2.txt")));
browser.set(SaveFileCallback.class, (params, callback) ->
        callback.save(new File(params.getFileName())));

Color Dialogs

6.x

browser.setDialogHandler(new DialogHandler() {
    @Override
    public CloseStatus onColorChooser(ColorChooserParams params) {
        params.setColor(params.getColor());
        return CloseStatus.OK;
    }
    ...
});

7.0

browser.set(SelectColorCallback.class, (params, callback) ->
        callback.select(params.getColor()));

SSL Certificate Dialogs

6.x

browser.setDialogHandler(new DialogHandler() {
    @Override
    public CloseStatus onSelectCertificate(CertificatesDialogParams params) {
        X509Certificate x509Certificate = ...;
        PrivateKey privateKey = ...;
        params.setSelectedCertificate(new Certificate(x509Certificate, privateKey));
        return CloseStatus.OK;
    }
    ...
});

7.0

private static final File CLIENT_CERT_FILE = new File("<cert-file>.p12");
private static final String CLIENT_CERT_PASSWORD = "<cert-password>";
...
browser.set(SelectClientCertificateCallback.class, (params, callback) ->
        callback.select(ClientCertificates.create(CLIENT_CERT_FILE,
                CLIENT_CERT_PASSWORD, KeyStoreType.PKCS12)));

Printing

Configuring Printing

6.x

browser.setPrintHandler(new PrintHandler() {
    @Override
    public PrintStatus onPrint(PrintJob printJob) {
        PrintSettings printSettings = printJob.getPrintSettings();
        printSettings.setPrinterName("Microsoft XPS Document Writer");
        printSettings.setLandscape(true);
        printSettings.setPrintBackgrounds(true);
        ...
        return PrintStatus.CONTINUE;
    }
});

7.0

Functionality that allows configuring printing has been removed. Instead it got possible to display a standard Print Preview dialog where you can provide the required settings:

browser.set(PrintCallback.class, (params, callback) -> callback.printPreview());

Suppressing Printing

6.x

browser.setPrintHandler(new PrintHandler() {
    @Override
    public PrintStatus onPrint(PrintJob printJob) {
        return PrintStatus.CANCEL;
    }
});

7.0

browser.set(PrintCallback.class, (params, callback) -> callback.cancel());

Cache

Accessing Cache

6.x

browser.getCacheStorage().clearCache(new Callback() {
    @Override
    public void invoke() {
        // Cache has been cleared.
    }
});

7.0

engine.getCacheService().clearCache(() -> {
    // Cache has been cleared.
});

Cookies

Accessing Cookies

6.x

List<Cookie> cookies = browser.getCookieStorage().getAllCookies();

7.0

Collection<Cookie> cookies = engine.getCookieService().getCookies();

Chromium

Switches

The library does not support all the possible Chromium switches. It allows configuring Chromium with the switches, but we do not guarantee that the passed switches will work correctly or work at all. We recommend to check this functionality before using.

6.x

BrowserPreferences.setChromiumSwitches(
        "--<switch_name>",
        "--<switch_name>=<switch_value>"
);

7.0

Engine engine = Engine.newInstance(EngineOptions.newBuilder()
        .addSwitch("--<switch_name>")
        .addSwitch("--<switch_name>=<switch_value>")
        .build());

API Keys

6.x

BrowserPreferences.setChromiumVariable(
        "GOOGLE_API_KEY", "My API Key");
BrowserPreferences.setChromiumVariable(
        "GOOGLE_DEFAULT_CLIENT_ID", "My Client ID");
BrowserPreferences.setChromiumVariable(
        "GOOGLE_DEFAULT_CLIENT_SECRET", "My Client Secret");

7.0

Engine engine = Engine.newInstance(EngineOptions.newBuilder()
        .setGoogleApiKey("My API Key")
        .setGoogleDefaultClientId("My Client ID")
        .setGoogleDefaultClientSecret("My Client Secret")
        ...
        .build());
Go Top