导航

本指南描述了导航事件,并展示了如何加载 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()
    }
})

导航事件

加载网页是一个复杂的过程,在此过程中会触发不同的导航事件。 下图显示了在加载网页时导航事件可能被触发的顺序: 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();
    // 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”的内容脚本被注入到框架中。

Go Top