导航

本指南介绍导航相关事件,并展示如何加载 URL 和文件、过滤导航请求、使用导航历史等。

加载 URL 

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

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

下面的示例展示了如何使用 Navigation.loadUrl(String) 方法导航到 https://html5test.jiku.co

Java
Kotlin
var navigation = browser.navigation();
navigation.loadUrl("https://html5test.jiku.co");
val navigation = browser.navigation
navigation.loadUrl("https://html5test.jiku.co")

上述代码会向给定资源发起导航请求后立即返回。它不会等待资源完全加载完毕。

如果你需要阻塞当前线程,直到资源完全加载完毕,可以使用 Navigation.loadUrlAndWait() 方法:

Java
Kotlin
// 使用默认 45 秒超时时间等待。
navigation.loadUrlAndWait("https://html5test.jiku.co");
// 使用自定义超时时间。
navigation.loadUrlAndWait("https://html5test.jiku.co", Duration.ofSeconds(30));
// 使用默认 45 秒超时时间等待。
navigation.loadUrlAndWait("https://html5test.jiku.co")
// 使用自定义超时时间。
navigation.loadUrlAndWait("https://html5test.jiku.co", Duration.ofSeconds(30))

该方法会阻塞当前线程,直到资源的主 Frame 完全加载完成,或者达到指定的超时时间。如果在超时时间内资源仍未加载完成,将抛出 TimeoutException 异常。

你可以使用 Navigation.defaultTimeout() 方法获取默认超时时间的当前值:

Java
Kotlin
Duration timeout = Navigation.defaultTimeout();
val timeout: Duration = Navigation.defaultTimeout()

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

使用 POST 加载 URL 

要加载网页并发送 POST 数据,请使用 Navigation.loadUrl(LoadUrlParams) 方法。下面的代码演示了如何向 URL 发送纯文本 POST 数据:

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)

除了纯文本数据外,你还可以发送 FormDataMultipartFormData 或任意的 ByteData

MultipartFormData
FormData
ByteData
import tech.fuzio.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();
// 从字符串创建。
var message = ByteData.of("from string");

// 从字节数组创建。
var avatar = ByteData.of(imageBytes);

// 指定内容类型。
var pdf = ByteData.of(
        pdfBytes,
        ContentType.newBuilder("application/pdf").build()
);

Fuzio 会自动注入 Content-TypeContent-Length 头。在 multipart 情况下,还会自动包含表单边界以及每个部分的 Content-DispositionContent-Type

默认的内容类型如下:

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

如果你需要覆盖默认的 Content-TypeContent-Length 请求头,可以在 LoadUrlParams 中通过额外请求头添加您自己的值。

加载文件 

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

例如:

Java
Kotlin
navigation.loadUrl(new File("index.html").getAbsolutePath());
val indexPage = File("index.html")
navigation.loadUrl(indexPage.absolutePath)

加载 HTML 

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

有两种可行的方式:

这些方法有以下区别:

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

Data URL 

该方式的思路是:将所需 HTML 转换为 base64 字符串,使用转换后的字符串生成 Data URI,然后像下面这样加载该 URL:

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)

你也可以使用 Navigation.loadHtml(String html) 方法,它会自动根据给定的 HTML 生成 Data URI:

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

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

自定义方案 

另一种从字符串加载 HTML 的方法基于 InterceptUrlRequestCallback。其思路是注册该回调,并拦截某个特定的 URL 请求,将所需 HTML 作为 HTTP 响应返回。例如:

Java
Kotlin
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");
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")

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

重新加载 

重新加载当前已加载网页有多种方式:

使用 HTTP 缓存重新加载:

Java
Kotlin
navigation.reload();
navigation.reload()

忽略 HTTP 缓存重新加载:

Java
Kotlin
navigation.reloadIgnoringCache();
navigation.reloadIgnoringCache()

使用 HTTP 缓存重新加载并检查是否重新提交:

Java
Kotlin
navigation.reloadAndCheckForRepost();
navigation.reloadAndCheckForRepost()

忽略 HTTP 缓存重新加载并检查是否重新提交:

Java
Kotlin
navigation.reloadIgnoringCacheAndCheckForRepost();
navigation.reloadIgnoringCacheAndCheckForRepost()

停止 

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

Java
Kotlin
navigation.stop();
navigation.stop()

后退与前进 

Fuzio 支持操作导航的后退-前进历史记录列表。

当你创建 Browser 实例时,默认情况下它会导航到 about:blank 网页,因此导航后退-前进列表中始终有一个条目。

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

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

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

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

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

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

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

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()}")
}

你可以通过删除条目来修改后退-前进列表:

Java
Kotlin
// 返回后退/前进列表中的条目数。
var entryCount = navigation.entryCount();
// 删除索引处的导航条目。
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);
}
// 返回后退/前进列表中的条目数。
val entryCount = navigation.entryCount()
// 删除索引处的导航条目。
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 的导航请求:

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

自定义错误页面 

当 Chromium 遇到 HTTP 或网络错误时,它会显示默认的错误页面。在 Fuzio 中,你可以用自己的页面替换该页面。

Chromium 中的错误页面

要替换当 Chromium 收到来自服务器的空响应的 HTTP 错误时出现的错误页面,请使用 ShowHttpErrorPageCallback

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

    if (httpStatus == NOT_FOUND) {
        // 显示自定义 HTML 页面。
        return ShowHttpErrorPageCallback.Response.show(
                "<p>Oops! Not found!</p>");
    } else {
        // 显示默认的 Chromium 错误页面。
        return ShowHttpErrorPageCallback.Response.showDefault();
    }

});
navigation.register(ShowHttpErrorPageCallback { params ->
    val url = params.url()
    val httpStatus = params.httpStatus()
    if (httpStatus === NOT_FOUND) {
        // 显示自定义 HTML 页面。
        ShowHttpErrorPageCallback.Response.show(
            "<p>Oops! Not found!</p>"
        )
    } else {
        // 显示默认的 Chromium 错误页面。
        ShowHttpErrorPageCallback.Response.showDefault()
    }
})

要替换当 Chromium 遇到网络错误时出现的错误页面,请使用 ShowNetErrorPageCallback

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

    if (error == CERT_DATE_INVALID) {
        // 显示自定义 HTML 页面。
        return ShowNetErrorPageCallback.Response.show(
                "<p>Invalid certificate!</p>");
    } else {
        // 显示默认的 Chromium 错误页面。
        return ShowNetErrorPageCallback.Response.showDefault();
    }
});
navigation.register(ShowNetErrorPageCallback { params ->
    val url = params.url()
    val error = params.error()
    if (error == CERT_DATE_INVALID) {
        // 显示自定义 HTML 页面。
        ShowNetErrorPageCallback.Response.show(
            "<p>Invalid certificate!</p>"
        )
    } else {
        // 显示默认的 Chromium 错误页面。
        ShowNetErrorPageCallback.Response.showDefault()
    }
})

过滤资源 

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

以下示例演示了如何阻止所有图像:

Java
Kotlin
var 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.register(BeforeUrlRequestCallback { params ->
    if (params.urlRequest().resourceType() === IMAGE) {
        BeforeUrlRequestCallback.Response.cancel()
    } else {
        BeforeUrlRequestCallback.Response.proceed()
    }
})

导航事件 

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

加载开始 

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

Java
Kotlin
navigation.on(LoadStarted.class, event -> {});
navigation.subscribe<LoadStarted> { event -> }

该事件对应于标签页加载指示器开始转动的时刻。

加载完成 

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

Java
Kotlin
navigation.on(LoadFinished.class, event -> {});
navigation.subscribe<LoadStarted> { event -> }

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

导航开始 

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

Java
Kotlin
navigation.on(NavigationStarted.class, event -> {
    var url = event.url();
    // 指示导航是否将在同一文档范围内执行。
    var isSameDocument = event.isSameDocument();
});
navigation.subscribe<NavigationStarted> { event ->
    val url = event.url()
    // 指示导航是否将在同一文档范围内执行。
    val isSameDocument = event.isSameDocument
}

导航停止 

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

Java
Kotlin
navigation.on(NavigationStopped.class, event -> {});
navigation.subscribe<NavigationStopped> { event -> }

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

导航重定向 

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

Java
Kotlin
navigation.on(NavigationRedirected.class, event -> {
    // 导航重定向的 URL。
    var url = event.destinationUrl();
});
navigation.subscribe<NavigationRedirected> { event ->
    // 导航重定向的 URL。
    val url = event.destinationUrl()
}

导航完成 

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

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()
    }
}

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

如果该事件是由于导航已提交而触发的,那么文档加载过程此时仍可能尚未完成。

该事件也会在 same-document(在同一文档范围内)导航时触发,例如片段导航或 window.history.pushState()/window.history.replaceState(),这类导航不会导致文档本身发生变化。请使用 NavigationFinished.isSameDocument() 检查它是否为同一文档内导航。

Frame 加载完成 

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

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()
}

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

Frame 加载失败 

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

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 文档加载完成 

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

Java
Kotlin
navigation.on(FrameDocumentLoadFinished.class, event -> {
    var frame = event.frame();
});
navigation.subscribe<FrameDocumentLoadFinished> { event ->
    val frame = event.frame()
}

此时,延迟脚本已执行完成,并且标记为 “document_end” 的内容脚本已注入到 Frame 中。

加载 PDF 文件 

将 PDF 文件加载到浏览器中时,请使用 PdfDocumentLoaded 事件来检测文档何时加载完成:

Java
Kotlin
browser.on(PdfDocumentLoaded.class, event -> {
    var url = event.url();
    var frame = event.frame();

   // 此事件是开始 PDF 打印的好时机。
    frame.print();
});
browser.subscribe<PdfDocumentLoaded> { event ->
    val url = event.url()
    val frame = event.frame()

    // 此事件是开始 PDF 打印的好时机。
    frame.print()
}

文档可能因意外错误而无法加载,网络故障、密码错误、PDF 文件损坏等。在这种情况下,Fuzio 将发出 PdfDocumentLoadFailed 事件:

Java
Kotlin
browser.on(PdfDocumentLoadFailed.class, event -> {
    var url = event.url();
    var frame = event.frame();
});
browser.subscribe<PdfDocumentLoadFailed> { event ->
    val url = event.url()
    val frame = event.frame()
}

请注意,不应使用 Navigation.loadUrlAndWait() 方法和 FrameLoadFinished 事件来等待 PDF 文档加载。

微信咨询

即库客服

微信公众号二维码

技术客服

微信公众号二维码