List icon 目录

导航

本指南描述了导航事件,并展示了如何加载 URL 和文件、过滤导航请求、处理导航历史等。

加载 URL

要导航到由 URL 标识的资源,您可以使用以下方法之一:

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

以下示例展示了如何使用 Navigation.loadUrl(String) 方法导航到 https://www.google.com

Navigation navigation = browser.navigation();
navigation.loadUrl("https://www.google.com");
val navigation = browser.navigation()
navigation.loadUrl("https://www.google.com")

上面的代码请求导航到给定的资源并立即退出。它不会等到资源完全加载。

如果您需要阻塞当前线程执行,直到资源完全加载,请使用 Navigation.loadUrlAndWait(String url, Duration timeout) 方法:

navigation.loadUrlAndWait("https://www.google.com", Duration.ofSeconds(45));
navigation.loadUrlAndWait("https://www.google.com", Duration.ofSeconds(45))

此方法会阻塞当前线程执行,直到资源的主 Frame 完全加载,或者达到给定的 45 秒超时时间。

如果导航失败,将会抛出 NavigationException 异常。

如果资源在超时时间内仍未加载完成,则会抛出 TimeoutException 异常。

使用 POST 加载 URL

要加载网页并发送 POST 数据,请使用 Navigation.loadUrl(LoadUrlParams) 方法。以下代码展示了如何构造 POST 数据并将其发送到 URL。

TextData data = TextData.of("post data");
LoadUrlParams params = LoadUrlParams.newBuilder(url)
        .uploadData(data)
        .addExtraHeader(HttpHeader.of("Content-Type", "text/plain"))
        .build();
navigation.loadUrl(params);
val data = TextData.of("post data")
val params = LoadUrlParams.newBuilder(url)
        .uploadData(data)
        .addExtraHeader(HttpHeader.of("Content-Type", "text/plain"))
        .build()
navigation.loadUrl(params)

其他类型的 POST 数据也可用: MultipartFormData, FormData, ByteData.

加载文档

您可以使用相同的方法从本地文件系统加载 HTML 文件。您只需要提供 HTML 文件的绝对路径,而非 URL。

例如:

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

加载 HTML

本节将介绍如何在 Frame 中加载 HTML。

有两种可能的方法:

这两种方法有以下区别:

功能 Data URL 自定义方案
支持 JavaScript-Java 桥接
支持 InjectJsCallback
支持 InjectCssCallback
从 HTTP 加载 <iframe>
从文件系统加载 <iframe>
从 HTTP 加载图像
从文件系统加载图像
产生网络事件
产生导航事件
显示 PDF 和打印预览
<iframe> 中显示 PDF 和打印预览

Data URL

这种方法的思想是将所需的 HTML 转换为一个 base64 字符串,使用转换后的字符串生成 Data URI,然后加载此 URL,如下所示:

String html = "<html><body>Hello</body></html>";
String base64Html = Base64.getEncoder().encodeToString(html.getBytes(UTF_8));
String 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)

您也可以使用 Frame.loadHtml(String html) 方法,它会自动从给定的 HTML 生成 data URI。

String html = "<html><body>Hello</body></html>";
browser.mainFrame().ifPresent(frame -> frame.loadHtml(html));
val html = "<html><body>Hello</body></html>"
browser.mainFrame().ifPresent { frame -> frame.loadHtml(html) }

由于 Chromium 的限制,URL 字符串的长度不得超过 2MB。尝试加载超过此限制的 URL 字符串将被 Chromium 忽略。

自定义方案

另一种从字符串加载 HTML 的方法是基于 InterceptUrlRequestCallback。这个方法的思路是注册回调函数并拦截特定的 URL 请求,以 HTTP 响应的形式返回所需的 HTML。例如:

InterceptUrlRequestCallback interceptCallback = params -> {
    if (params.urlRequest().url().endsWith("?hello")) {
        byte[] bytes = "<html><body>Hello</body></html>".getBytes();
        UrlRequestJob job = params.newUrlRequestJob(
                UrlRequestJob.Options
                .newBuilder(HttpStatus.OK)
                .addHttpHeader(HttpHeader.of("Content-Type", "text/html"))
                .build());
        job.write(bytes);
        job.complete();
        return Response.intercept(job);
    }
    return Response.proceed();
};

EngineOptions options = EngineOptions.newBuilder(renderingMode)
        .addScheme(HTTP, interceptCallback)
        .build();
Engine engine = Engine.newInstance(options);
Browser browser = engine.newBrowser();
browser.navigation().loadUrl("http://load.html/?hello");
val interceptCallback = InterceptUrlRequestCallback { params ->
    if (params.urlRequest().url().endsWith("?hello")) {
        val bytes = "<html><body>Hello</body></html>".toByteArray()
        val job = params.newUrlRequestJob(
            UrlRequestJob.Options
                .newBuilder(HttpStatus.OK)
                .addHttpHeader(HttpHeader.of("Content-Type", "text/html"))
                .build()
        )
        job.write(bytes)
        job.complete()
        Response.intercept(job)
    } else {
        Response.proceed()
    }
}

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

?hello 结尾的 URL 请求将被拦截,并在 Browser 中加载 <html><body>Hello</body></html> HTML。

重新加载

有几个选项可以重新加载当前加载的网页:

使用 HTTP 缓存重新加载:

navigation.reload();
navigation.reload()

忽略 HTTP 缓存重新加载:

navigation.reloadIgnoringCache();
navigation.reloadIgnoringCache()

使用 HTTP 缓存重新加载并检查重新发布:

navigation.reloadAndCheckForRepost();
navigation.reloadAndCheckForRepost()

忽略 HTTP 缓存重新加载并检查重新发布:

navigation.reloadIgnoringCacheAndCheckForRepost();
navigation.reloadIgnoringCacheAndCheckForRepost()

停止

使用 Navigation.stop() 方法取消任何挂起的导航或下载操作,并停止任何动态页面元素,如背景声音和动画。例如:

navigation.stop();
navigation.stop()

后退和前进

JxBrowser 允许操作导航前进-后退历史列表。

当您创建一个 Browser 实例时,它默认导航到 about:blank 网页,因此导航前进-后退列表中总是有一个条目。

要加载前进-后退列表中的前一个位置,请使用以下方法:

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

要加载前进-后退列表中的下一个位置,请使用:

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

要导航到前进-后退列表中特定索引处的条目,请使用:

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

您可以遍历前进-后退列表并获取每个导航条目的详细信息:

for (int index = 0; index < navigation.entryCount(); index++) {
    NavigationEntry 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()}")
}

您可以通过删除记录项来修改后退/前进列表:

// 返回前进/后退列表中的条目数量。
int entryCount = navigation.entryCount();
// 移除指定索引处的导航条目。
for (int i = entryCount - 2; i >= 0; i--) {
    boolean success = navigation.removeEntryAtIndex(i);
    System.out.println("索引为 " + i + " 处的导航条目成功移除了吗? " + success);
}
// 返回前进/后退列表中的条目数量。
val entryCount = navigation.entryCount()
// 移除指定索引处的导航条目。
for (i in entryCount - 2 downTo 0) {
    val success = navigation.removeEntryAtIndex(i)
    println("索引为 $i 的导航条目成功移除了吗? $success")
}

筛选 URL

你可以决定是否应该忽略对特定 URL 的导航请求。

以下代码演示了如何忽略对所有以 https://www.google 开头的 URL 的导航请求:

navigation.set(StartNavigationCallback.class, params -> {
    // 忽略以 "https://www.google" 开头的 URL 的导航请求
    if (params.url().startsWith("https://www.google")) {
        return Response.ignore();
    } 
    return Response.start();
});
navigation.set(StartNavigationCallback::class.java, StartNavigationCallback { params ->
    // 忽略以 "https://www.google" 开头的 URL 的导航请求
    if (params.url().startsWith("https://www.google")) {
        Response.ignore()
    } else {
        Response.start()
    }
})

过滤资源

使用 BeforeUrlRequestCallback 回调,您可以确定是否应加载如 HTML、图像、JavaScript 或 CSS 文件、favicon(网站图标)等资源。默认情况下,所有资源都会被加载。要修改默认行为,请注册您自己的回调实现,在该实现中决定应取消或加载哪些资源。

以下示例演示了如何禁止加载所有图片:

Network network = engine.network();
network.set(BeforeUrlRequestCallback.class, params -> {
    if (params.urlRequest().resourceType() == IMAGE) {
        return BeforeUrlRequestCallback.Response.cancel();
    }
    return BeforeUrlRequestCallback.Response.proceed();
});
val network = engine.network()
network.set(BeforeUrlRequestCallback::class.java, BeforeUrlRequestCallback { params ->
    if (params.urlRequest().resourceType() === IMAGE) {
        BeforeUrlRequestCallback.Response.cancel()
    } else {
        BeforeUrlRequestCallback.Response.proceed()
    }
})

导航事件

加载网页是一个复杂的过程,在此过程中会触发不同的导航事件。下图显示了加载网页时可能触发导航事件的顺序: Navigation Events Flow

加载开始

要在内容加载开始时获得通知,请使用 LoadStarted 事件。例如:

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

此事件对应于选项卡的微调器开始旋转的时刻。

加载结束

要在内容加载完成时获得通知,请使用 LoadFinished 事件。例如:

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

此事件对应于标签页的加载指示器停止旋转的时刻。

导航开始

要在导航开始时获得通知,请使用 NavigationStarted 事件。例如:

navigation.on(NavigationStarted.class, event -> {
    String url = event.url();
    // 指示导航是否将在同一文档的范围内执行。
    boolean isSameDocument = event.isSameDocument();
});
navigation.on(NavigationStarted::class.java) { event ->
    val url = event.url()
    // 指示导航是否将在同一文档的范围内执行。
    val isSameDocument = event.isSameDocument()
}

导航停止

要在导航停止时获得通知,请使用 NavigationStopped 事件。例如:

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

当通过 Navigation.stop() 方法停止导航时会触发此事件。

导航重定向

要在导航被重定向到新的 URL 时获得通知,请使用 NavigationRedirected 事件。例如:

navigation.on(NavigationRedirected.class, event -> {
    // 导航重定向的 URL。
    String url = event.destinationUrl();
});
navigation.on(NavigationRedirected::class.java) { event -> 
    // 导航重定向的 URL。
    val url = event.destinationUrl()
}

导航结束

要在导航完成时收到通知,请使用 NavigationFinished 事件。例如:

navigation.on(NavigationFinished.class, event -> {
    String url = event.url();
    Frame frame = event.frame();
    boolean hasCommitted = event.hasCommitted();
    boolean isSameDocument = event.isSameDocument();
    boolean isErrorPage = event.isErrorPage();
    if (isErrorPage) {
        NetError error = event.error();
    }
});
navigation.on(NavigationFinished::class.java) { 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()
    }
}

此事件在导航提交、中止或被新导航替换时触发。要知道导航是否已提交,请使用 NavigationFinished.hasCommitted();要知道导航是否导致错误页面,请使用 NavigationFinished.isErrorPage()

如果事件是因为导航已提交而被调用,则文档加载仍将继续进行。

该事件由同一文档(在同一文档的范围内)导航触发,例如片段导航或 window.history.pushState()/window.history.replaceState(),这些操作不会导致文档内容的变化。请使用 NavigationFinished.isSameDocument() 来检查它是否是同一个文档的导航。

Frame 加载完成

要在 Frame 中的内容加载完成时收到通知,请使用 FrameLoadFinished 事件。例如:

navigation.on(FrameLoadFinished.class, event -> {
    String url = event.url();
    Frame frame = event.frame();
});
navigation.on(FrameLoadFinished::class.java) { event -> 
    val url = event.url()
    val frame = event.frame()
}

此事件对应于 Frame 中的内容被完全加载的时刻。

Frame 加载失败

要在 Frame 中的内容由于某种原因加载失败时收到通知,请使用 FrameLoadFailed 事件。例如:

navigation.on(FrameLoadFailed.class, event -> {
    String url = event.url();
    NetError error = event.error();
});
navigation.on(FrameLoadFailed::class.java) { event -> 
    val url = event.url()
    val error = event.error()
}

Frame 文档加载完成

要在 Frame 中的文档加载完成时收到通知,请使用 FrameDocumentLoadFinished 事件。例如:

navigation.on(FrameDocumentLoadFinished.class, event -> {
    Frame frame = event.frame();
});
navigation.on(FrameDocumentLoadFinished::class.java) { event -> 
    val frame = event.frame()
}

此时,延迟脚本(deferred scripts)已经执行,并且标记为 “document_end” 的内容脚本 已经被注入到 Frame 中。

Go Top