Contents

Navigation

This guide describes the navigation events, and shows how to load URLs and files, filter navigation requests, work with navigation history, etc.

Loading URL 

To navigate to a resource identified by a URL you can use one of the following methods:

  • Navigation.loadUrl(String url)
  • Navigation.loadUrl(LoadUrlParams params)

The following example shows how to navigate to https://www.google.com using the Navigation.loadUrl(String) method:

Java
Kotlin

var navigation = browser.navigation();
navigation.loadUrl("https://html5test.teamdev.com");

val navigation = browser.navigation
navigation.loadUrl("https://html5test.teamdev.com")

The code above requests navigation to the given resource and exits immediately. It does not wait until the resource is loaded completely.

If you need to block the current thread execution until the resource is loaded completely, use the Navigation.loadUrlAndWait() method:

Java
Kotlin

// Wait with the default timeout of 45 seconds.
navigation.loadUrlAndWait("https://html5test.teamdev.com");
// Wait with the custom timeout.
navigation.loadUrlAndWait("https://html5test.teamdev.com", Duration.ofSeconds(30));

// Wait with the default timeout of 45 seconds.
navigation.loadUrlAndWait("https://html5test.teamdev.com")
// Wait with the custom timeout.
navigation.loadUrlAndWait("https://html5test.teamdev.com", Duration.ofSeconds(30))

This method blocks the current thread execution until the main frame of the resource is loaded completely or until the given timeout is reached. If the resource has not been loaded within a timeout, the TimeoutException exception will be thrown.

You can get the current value of the default timeout using the Navigation.defaultTimeout() method:

Java
Kotlin

Duration timeout = Navigation.defaultTimeout();

val timeout: Duration = Navigation.defaultTimeout()

If navigation fails, the NavigationException exception will be thrown.

Loading URL with POST 

To load a web page and send POST data, use the Navigation.loadUrl(LoadUrlParams) method. The following code demonstrates how to send the plain text POST data to a URL:

Java
Kotlin

var data = TextData.of("post data");
var params = LoadUrlParams.newBuilder(url)
        .uploadData(data)
        .addExtraHeader(HttpHeader.of("header name", "header value"))
        .build();
navigation.loadUrl(params);

val params = LoadUrlParams(
    url = url,
    data = TextData("post data"),
    extraHeaders = listOf(
        HttpHeader("header name", "header value")
    )
)
navigation.loadUrl(params)

Besides the plain text data, you can send FormData, MultipartFormData or arbitrary ByteData:

MultipartFormData
FormData
ByteData

import com.teamdev.jxbrowser.net.File;
...
var avatarFile = File.newBuilder()
        .bytesValue(avatarBytes)
        .contentType("image/png")
        .build();

var data = MultipartFormData.newBuilder()
        .addPair(Pair.of("user", "john"))
        .addPair(Pair.of("avatar", avatarFile))
        .build();

var data = FormData.newBuilder()
        .addPair(Pair.of("street", "Forthlin Rd"))
        .addPair(Pair.of("house", "20"))
        .build();

// From the string.
var message = ByteData.of("from string");

// From the byte array.
var avatar = ByteData.of(imageBytes);

// With the specific content type.
var pdf = ByteData.of(
        pdfBytes,
        ContentType.newBuilder("application/pdf").build()
);

JxBrowser will automatically inject the Content-Type and Content-Length headers. In the multipart case, it will also include the form boundary plus each part’s Content-Disposition and Content-Type.

The default content types are:

  • text/plain for TextData
  • application/octet-stream for ByteData
  • application/x-www-form-urlencoded for FormData
  • multipart/form-data for MultipartFormData

If you need to override the default Content-Type or Content-Length headers, add your own values as extra headers in LoadUrlParams.

Loading local content 

You can load local content in several ways, and each way has specific capabilities and limitations. The following sections explains how to choose the approach that best fits your use case.

Loading files from disk 

The most direct way to load an HTML file is to use a file:// URL:

Java
Kotlin

navigation.loadUrl(new File("index.html").getAbsolutePath());

val indexPage = File("index.html")
navigation.loadUrl(indexPage.absolutePath)

Be aware, that loading pages using file:// comes with important limitations. When you load content this way, Chromium applies stricter security rules than it does for regular HTTP or HTTPS pages.

Pages loaded using the file:// scheme are subject to the following limitations:

  • A page cannot reference local resources outside its own directory.
  • A page cannot dynamically load resources via JavaScript.
  • Chromium blocks these pages from making HTTP requests to remote servers.
  • JavaScript APIs that require a secure context may not function under the file:// scheme.

Use this approach with caution, as the restrictions in Chromium may change between releases without prior notice.

Loading HTML 

You can load arbitrary HTML strings into JxBrowser by converting them to a data URI:

The idea of this approach is to convert the required HTML to a base64 string, generate the Data URI with the converted string, and load this URL as shown below:

Java
Kotlin

var html = "<html><body>Hello</body></html>";
var base64Html = Base64.getEncoder().encodeToString(html.getBytes(UTF_8));
var dataUrl = "data:text/html;charset=utf-8;base64," + base64Html;
browser.navigation().loadUrl(dataUrl);

val html = "<html><body>Hello</body></html>"
val base64Html = Base64.getEncoder().encodeToString(html.toByteArray(UTF_8))
val dataUrl = "data:text/html;charset=utf-8;base64,$base64Html"
browser.navigation.loadUrl(dataUrl)

You can also use the Navigation.loadHtml(String html) method, which automatically generates the data URI from the given HTML:

Java
Kotlin

var html = "<html><body>Hello</body></html>";
browser.navigation().loadHtml(html);

val html = "<html><body>Hello</body></html>"
browser.navigation.loadHtml(html)

Pages loaded through data URIs are subject to the following limitations:

  • The URL string must not exceed 2 MB due to the Chromium limit. Any attempt to load a longer URL is ignored by Chromium.
  • Such pages cannot send requests to HTTPS servers or reference HTTPS resources, because Chromium treats data URLs as an insecure context.
  • Such pages are not permitted to use local storage.
  • Anchor navigation is not supported.

Loading by intercepting requests 

Another way to load local content is to intercept network requests and substitute the response with the content you want to deliver.

This approach requires manual implementation, but it provides fine-grained control over the process:

  • You can load any type of data: HTML, images, JSON, and more.
  • You can set arbitrary HTTP response headers, including Content-Type and Cookie.
  • You can load large amounts of data asynchronously.

In this approach, you register an InterceptUrlRequestCallback that intercepts requests made under https:// or another chosen scheme and lets you construct the response at runtime:

Java
Kotlin

// This callback intercepts requests with the `?hello` parameter and returns
// `<html><body>Hello</body></html>` as the response.
InterceptUrlRequestCallback interceptCallback = params -> {
    if (params.urlRequest().url().endsWith("?hello")) {
        var bytes = "<html><body>Hello</body></html>".getBytes();
        var job = params.newUrlRequestJob(
                UrlRequestJob.Options
                        .newBuilder(HttpStatus.OK)
                        .addHttpHeader(HttpHeader.of("Content-Type", "text/html"))
                        .build()
        );
        job.write(bytes);
        job.complete();
        return InterceptUrlRequestCallback.Response.intercept(job);
    }
    return InterceptUrlRequestCallback.Response.proceed();
};

var options = EngineOptions.newBuilder(renderingMode)
        .addScheme(HTTP, interceptCallback)
        .build();
var engine = Engine.newInstance(options);
var browser = engine.newBrowser();
browser.navigation().loadUrl("http://load.html/?hello");

// This callback intercepts requests with the `?hello` parameter and returns
// `<html><body>Hello</body></html>` as the response.
val interceptCallback = InterceptUrlRequestCallback { params ->
    if (params.urlRequest().url().endsWith("?hello")) {
        val bytes = "<html><body>Hello</body></html>".toByteArray()
        val options = UrlRequestJobOptions(
            status = HttpStatus.OK,
            headers = listOf(HttpHeader("Content-Type", "text/html"))
        )
        val job = params.newUrlRequestJob(options).apply {
            write(bytes)
            complete()
        }
        InterceptUrlRequestCallback.Response.intercept(job)
    } else {
        InterceptUrlRequestCallback.Response.proceed()
    }
}

val engine = Engine(renderingMode) {
    schemes.add(HTTP, interceptCallback)
}
val browser = engine.newBrowser()
browser.navigation.loadUrl("http://load.html/?hello")

In addition to standard HTTP and HTTPS schemes, you can intercept requests to custom protocols:

Java
Kotlin

// This will intercept all requests to my-app://
var options = EngineOptions.newBuilder(renderingMode)
        .addScheme(Scheme.of("my-app"), interceptCallback)
        .build();

// This will intercept all requests to my-app://
val engine = Engine(renderingMode) {
    schemes.add(Scheme("my-app"), interceptCallback)
}

Chromium treats custom protocols as a secure context, allowing full use of JavaScript APIs and network requests just as on a standard HTTPS page.

For more details, see the tutorial on loading local resources with request interceptors.

Reloading 

There are several options to reload the currently loaded web page:

Reload using HTTP cache:

Java
Kotlin

navigation.reload();

navigation.reload()

Reload ignoring HTTP cache:

Java
Kotlin

navigation.reloadIgnoringCache();

navigation.reloadIgnoringCache()

Reload using HTTP cache and check for repost:

Java
Kotlin

navigation.reloadAndCheckForRepost();

navigation.reloadAndCheckForRepost()

Reload ignoring HTTP cache and check for repost:

Java
Kotlin

navigation.reloadIgnoringCacheAndCheckForRepost();

navigation.reloadIgnoringCacheAndCheckForRepost()

Stopping 

Use the Navigation.stop() method to cancel any pending navigation or download operation, and stop any dynamic page elements, such as background sounds and animations. For example:

Java
Kotlin

navigation.stop();

navigation.stop()

Back & forward 

JxBrowser allows working with the navigation back-forward history list.

When you create a Browser instance it navigates to the about:blank web page by default, so there is always one entry in the navigation back-forward list.

To load the previous location in the back-forward list use the following approach:

Java
Kotlin

if (navigation.canGoBack()) {
    navigation.goBack();
}

if (navigation.canGoBack) {
    navigation.goBack()
}

To load the next location in the back-forward list use:

Java
Kotlin

if (navigation.canGoForward()) {
    navigation.goForward();
}

if (navigation.canGoForward) {
    navigation.goForward()
}

To navigate to the entry at a specific index in the back-forward list use:

Java
Kotlin

if (index >= 0 && index < navigation.entryCount()) {
    navigation.goToIndex(index);
}

if (index >= 0 && index < navigation.entryCount()) {
    navigation.goToIndex(index)
}

You can go through the back-forward list and get the details about every navigation entry:

Java
Kotlin

for (int index = 0; index < navigation.entryCount(); index++) {
    var navigationEntry = navigation.entryAtIndex(index);
    System.out.println("URL: " + navigationEntry.url());
    System.out.println("Title: " + navigationEntry.title());
}

for (index in 0 until navigation.entryCount()) {
    val navigationEntry = navigation.entryAtIndex(index)
    println("URL: ${navigationEntry.url()}")
    println("Title: ${navigationEntry.title()}")
}

You can modify the back-forward list by removing the entries:

Java
Kotlin

// Returns the number of entries in the back/forward list.
var entryCount = navigation.entryCount();
// Remove navigation entries at index.
for (int i = entryCount - 2; i >= 0; i--) {
    var success = navigation.removeEntryAtIndex(i);
    System.out.println("Was the navigation entry at the index " + i +
            " successfully removed? " + success);
}

// Returns the number of entries in the back/forward list.
val entryCount = navigation.entryCount()
// Remove navigation entries at index.
for (i in entryCount - 2 downTo 0) {
    val success = navigation.removeEntryAtIndex(i)
    println("Was the navigation entry at the index $i successfully removed? $success")
}

Filtering URLs 

You can decide whether navigation request to a specific URL should be ignored or not.

The following code demonstrates how to ignore navigation requests to all URLs that start with https://www.google:

Java
Kotlin

navigation.set(StartNavigationCallback.class, params -> {
    // Ignore navigation requests to the URLs that start
    // with "https://www.google".
    if (params.url().startsWith("https://www.google")) {
        return StartNavigationCallback.Response.ignore();
    }
    return StartNavigationCallback.Response.start();
});

navigation.register(StartNavigationCallback { params ->
    // Ignore navigation requests to the URLs that start
    // with "https://www.google".
    if (params.url().startsWith("https://www.google")) {
        StartNavigationCallback.Response.ignore()
    } else {
        StartNavigationCallback.Response.start()
    }
})

Customizing error pages 

When a Chromium encounters an HTTP or a network error, it shows the default error page. In JxBrowser, you can replace that page with your own.

An error page in Chromium

To replace an error page that occurs when Chromium receives an HTTP error with an empty response from the server, use ShowHttpErrorPageCallback:

Java
Kotlin

navigation.set(ShowHttpErrorPageCallback.class, params -> {
    var url = params.url();
    var httpStatus = params.httpStatus();

    if (httpStatus == NOT_FOUND) {
        // Show a custom HTML page.
        return ShowHttpErrorPageCallback.Response.show(
                "<p>Oops! Not found!</p>");
    } else {
        // Show the default Chromium error page.
        return ShowHttpErrorPageCallback.Response.showDefault();
    }

});

navigation.register(ShowHttpErrorPageCallback { params ->
    val url = params.url()
    val httpStatus = params.httpStatus()
    if (httpStatus === NOT_FOUND) {
        // Show a custom HTML page.
        ShowHttpErrorPageCallback.Response.show(
            "<p>Oops! Not found!</p>"
        )
    } else {
        // Show the default Chromium error page.
        ShowHttpErrorPageCallback.Response.showDefault()
    }
})

To replace an error page that occurs when Chromium encounters a network error, use ShowNetErrorPageCallback:

Java
Kotlin

navigation.set(ShowNetErrorPageCallback.class, params -> {
    var url = params.url();
    var error = params.error();

    if (error == CERT_DATE_INVALID) {
        // Show a custom HTML page.
        return ShowNetErrorPageCallback.Response.show(
                "<p>Invalid certificate!</p>");
    } else {
        // Show the default Chromium error page.
        return ShowNetErrorPageCallback.Response.showDefault();
    }
});

navigation.register(ShowNetErrorPageCallback { params ->
    val url = params.url()
    val error = params.error()
    if (error == CERT_DATE_INVALID) {
        // Show a custom HTML page.
        ShowNetErrorPageCallback.Response.show(
            "<p>Invalid certificate!</p>"
        )
    } else {
        // Show the default Chromium error page.
        ShowNetErrorPageCallback.Response.showDefault()
    }
})

Canceling and redirecting URL requests 

Use the BeforeUrlRequestCallback callback to control whether a URL request should be canceled, redirected, or handled normally. This callback is useful for selectively blocking the loading of resources such as HTML, images, CSS, etc.

By default, all requests are processed normally. To modify this behavior, register your own callback implementation and decide how to proceed with each individual request.

The following example demonstrates how to:

  • block the loading of images;
  • redirect requests to .htm files to .html files;
  • handle the remaining requests as usual.
Java
Kotlin

var network = engine.network();
network.set(BeforeUrlRequestCallback.class, params -> {
    var urlRequest = params.urlRequest();
    if (urlRequest.resourceType() == IMAGE) {
        return BeforeUrlRequestCallback.Response.cancel();
    }
    if (urlRequest.url().endsWith(".htm")) {
        var newUrl = urlRequest.url() + "l";
        return BeforeUrlRequestCallback.Response.redirect(newUrl);
    }
    return BeforeUrlRequestCallback.Response.proceed();
});

val network = engine.network
network.register(BeforeUrlRequestCallback { params ->
    val urlRequest = params.urlRequest()
    if (urlRequest.resourceType() === IMAGE) {
        BeforeUrlRequestCallback.Response.cancel()
    } else if (urlRequest.url().endsWith(".htm")) {
        val newUrl = urlRequest.url() + "l";
        BeforeUrlRequestCallback.Response.redirect(newUrl)
    } else {
        BeforeUrlRequestCallback.Response.proceed()
    }
})

Loading a web page is a complex process during which different navigation events are fired. The following diagram shows the order in which the navigation events might be fired when loading a web page: Navigation Events Flow

Load started 

To get notifications when content loading has started please use the LoadStarted event. For example:

Java
Kotlin

navigation.on(LoadStarted.class, event -> {});

navigation.subscribe<LoadStarted> { event -> }

This event corresponds to the moment when the spinner of the tab starts spinning.

Load finished 

To get notifications when content loading has finished please use the LoadFinished event. For example:

Java
Kotlin

navigation.on(LoadFinished.class, event -> {});

navigation.subscribe<LoadStarted> { event -> }

This event corresponds to the moment when the spinner of the tab stops spinning.

To get notifications when navigation has started please use the NavigationStarted event. For example:

Java
Kotlin

navigation.on(NavigationStarted.class, event -> {
    var url = event.url();
    // Indicates whether the navigation will be performed
    // in the scope of the same document.
    var isSameDocument = event.isSameDocument();
});

navigation.subscribe<NavigationStarted> { event ->
    val url = event.url()
    // Indicates whether the navigation will be performed
    // in the scope of the same document.
    val isSameDocument = event.isSameDocument
}

To get notifications when navigation has stopped please use the NavigationStopped event. For example:

Java
Kotlin

navigation.on(NavigationStopped.class, event -> {});

navigation.subscribe<NavigationStopped> { event -> }

This event is fired when navigation is stopped via the Navigation.stop() method.

To get notifications when navigation has been redirected to a new URL please use the NavigationRedirected event. For example:

Java
Kotlin

navigation.on(NavigationRedirected.class, event -> {
    // The navigation redirect URL.
    var url = event.destinationUrl();
});

navigation.subscribe<NavigationRedirected> { event ->
    // The navigation redirect URL.
    val url = event.destinationUrl()
}

To get notifications when navigation has finished please use the NavigationFinished event. For example:

Java
Kotlin

navigation.on(NavigationFinished.class, event -> {
    var url = event.url();
    var frame = event.frame();
    var hasCommitted = event.hasCommitted();
    var isSameDocument = event.isSameDocument();
    var isErrorPage = event.isErrorPage();
    if (isErrorPage) {
        var error = event.error();
    }
});

navigation.subscribe<NavigationFinished> { event ->
    val url = event.url()
    val frame = event.frame()
    val hasCommitted = event.hasCommitted()
    val isSameDocument = event.isSameDocument
    val isErrorPage = event.isErrorPage
    if (isErrorPage) {
        val error = event.error()
    }
}

This event is fired when navigation is committed, aborted, or replaced by a new one. To know if the navigation has committed, use NavigationFinished.hasCommitted(); use NavigationFinished.isErrorPage() to know if the navigation resulted in an error page.

If the event is called because the navigation committed, the document load will still be ongoing.

The event is fired by same-document (in the scope of the same document) navigations, such as fragment navigations or window.history.pushState()/window.history.replaceState(), which will not result in a document change. Please use NavigationFinished.isSameDocument() to check if it is a same-document navigation.

Frame load finished 

To get notifications when content loading in the Frame has finished please use the FrameLoadFinished event. For example:

Java
Kotlin

navigation.on(FrameLoadFinished.class, event -> {
    var url = event.url();
    var frame = event.frame();
});

navigation.subscribe<FrameLoadFinished> { event ->
    val url = event.url()
    val frame = event.frame()
}

This event corresponds to the moment when the content in the Frame has been loaded completely.

Frame load failed 

To get notifications when content loading in the Frame has failed for some reason, use the FrameLoadFailed event. For example:

Java
Kotlin

navigation.on(FrameLoadFailed.class, event -> {
    var url = event.url();
    var error = event.error();
});

navigation.subscribe<FrameLoadFailed> { event ->
    val url = event.url()
    val error = event.error()
}

Frame document load finished 

To get notifications when the document loading in the Frame has finished please use the FrameDocumentLoadFinished event. For example:

Java
Kotlin

navigation.on(FrameDocumentLoadFinished.class, event -> {
    var frame = event.frame();
});

navigation.subscribe<FrameDocumentLoadFinished> { event ->
    val frame = event.frame()
}

At this point, deferred scripts were executed, and the content scripts marked “document_end” get injected into the frame.