导航
本指南描述了导航事件,并展示了如何加载 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, int timeoutInSeconds)
方法:
navigation.loadUrlAndWait("https://www.google.com", Duration.ofSeconds(45));
navigation.loadUrlAndWait("https://www.google.com", Duration.ofSeconds(45))
此方法会阻止当前线程执行,直到资源的主框架完全加载,或直到给定的 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)
由于 Chromium 的限制,URL 字符串的长度不得超过 2MB。 Chromium 将忽略加载超过此限制的 URL 字符串的尝试。
自定义方案
另一种从字符串加载 HTML 的方法是基于 InterceptUrlRequestCallback
。 这个想法是注册回调并拦截特定的 URL 请求以将所需的 HTML 作为 HTTP 响应返回。 例如:
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 请求将被拦截,<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()}")
}
您可以通过删除记录项来修改后退/前进列表:
// Returns the number of entries in the back/forward list.
int entryCount = navigation.entryCount();
// Remove navigation entries at index.
for (int i = entryCount - 2; i >= 0; i--) {
boolean 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")
}
筛选 URL
你可以决定是否应该忽略对某个特定URL的导航请求。
以下代码演示了如何忽略对所有以 https://www.google
开头的 URL 的导航请求:
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 Response.ignore();
}
return Response.start();
});
navigation.set(StartNavigationCallback::class.java, StartNavigationCallback { params ->
// Ignore navigation requests to the URLs that start
// with "https://www.google"
if (params.url().startsWith("https://www.google")) {
Response.ignore()
} else {
Response.start()
}
})
过滤资源
使用 BeforeUrlRequestCallback
回调,您可以确定是否应加载 HTML、图像、JavaScript 或 CSS 文件、网站图标等资源。 默认情况下加载所有资源。 要修改默认行为,请注册您自己的回调实现,您可以在其中决定应取消或加载哪些资源。
以下示例演示了如何抑制所有图像:
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();
// Indicates whether the navigation will be performed
// in the scope of the same document.
boolean isSameDocument = event.isSameDocument();
});
navigation.on(NavigationStarted::class.java) { event ->
val url = event.url()
// Indicates whether the navigation will be performed
// in the scope of the same document.
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 -> {
// The navigation redirect URL.
String url = event.destinationUrl();
});
navigation.on(NavigationRedirected::class.java) { event ->
// The navigation redirect 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
中的内容加载结束时获得通知,请使用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
中的内容加载因某种原因失败时获得通知,请使用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
中的文档加载完毕时获得通知,请使用FrameDocumentLoadFinished
事件。 例如:
navigation.on(FrameDocumentLoadFinished.class, event -> {
Frame frame = event.frame();
});
navigation.on(FrameDocumentLoadFinished::class.java) { event ->
val frame = event.frame()
}
此时,延迟脚本被执行,标记为“document_end”的内容脚本被注入到框架中。