导航
本指南描述了导航事件,并展示了如何加载 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()
}
})
导航事件
加载网页是一个复杂的过程,在此过程中会触发不同的导航事件。下图显示了加载网页时可能触发导航事件的顺序:
加载开始
要在内容加载开始时获得通知,请使用 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 中。