目录

导航

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

加载 URL 

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

  • INavigation.LoadUrl(string url)
  • INavigation.LoadUrl(LoadUrlParameters parameters)

下面的示例展示了如何使用 INavigation.LoadUrl(string) 方法导航到 https://www.google.com:

C#
VB
INavigation navigation = browser.Navigation;
navigation.LoadUrl("https://www.google.com");
Dim navigation As INavigation = browser.Navigation
navigation.LoadUrl("https://www.google.com")

同步加载 URL 

LoadUrl 方法向给定资源发送一个导航请求,并返回一个 Task<NavigationResult>。无论页面已加载、加载失败还是被停止,该任务都会在导航结束时完成。请检查导航结果以了解是哪一种情况。

如果您需要阻塞当前线程执行直到导航结束,请使用 Task.Wait() 方法:

C#
VB
navigation.LoadUrl("https://www.google.com").Wait();
navigation.LoadUrl("https://www.google.com").Wait()

该方法会阻塞当前线程执行,直到导航结束或达到默认的 100 秒超时时间为止。如果导航未在此期间内结束,任务会因 TimeoutException 而失败。Wait() 会将其包装在 AggregateException 中抛出,而 await 会直接抛出它。

导航结果 

导航结束时,返回的 Task<NavigationResult> 会完成,即使导航失败也是如此。NavigationResult.LoadResult 属性说明导航是如何结束的:

  • LoadResult.Completed 表示主框架为此导航加载的文档触发了 JavaScript load 事件,或者同一文档内的导航(例如跳转到锚点)已完成。
  • LoadResult.Failed 表示页面未加载:发生了网络错误,或者 Chromium 显示了它的错误页面而不是所请求的页面。
  • LoadResult.Stopped 表示加载被中止,例如被 Stop() 或新的导航中止,因此页面可能只加载了一部分,或者完全没有加载。

以下代码加载页面并检查结果:

C#
VB
NavigationResult result = await browser.Navigation.LoadUrl(url);
if (result.LoadResult != LoadResult.Completed)
{
    NetError? error = result.Error;
    // 页面未加载,或者加载已被停止。
    return;
}
Dim result As NavigationResult = Await browser.Navigation.LoadUrl(url)
If result.LoadResult <> LoadResult.Completed Then
    Dim [error] As NetError? = result.Error
    ' 页面未加载,或者加载已被停止。
    Return
End If

要了解导航失败的原因,请检查 NavigationResult.Error。对于 LoadResult.Failed,它通常包含网络错误,例如 NetError.NameNotResolved;例如当 GoBack() 没有可导航的条目时,它为 null。对于 LoadResult.Stopped,它为 NetError.Aborted 或 null。

LoadResult.Completed 并不表示页面已经可供您的代码使用。在 load 事件发生时,HTML、样式、图片以及页面当时包含的 iframe 已经加载,但脚本稍后加载或渲染的内容可能仍然缺失。如果页面随后自行导航,例如通过设置 location.href 或使用 <meta http-equiv="refresh">,新文档会替换任务所报告的文档。请参阅等待页面内容。

HTTP 错误不是网络错误。当服务器以 404 或 500 等状态码响应并发送自己的 HTML 页面时,导航会以 LoadResult.Completed 完成,并且 NavigationFinished 事件报告的 IsErrorPage 为 false。当服务器以这样的状态码发送空响应体时,Chromium 会改为显示它自己的错误页面,结果为 LoadResult.Failed;请参阅自定义错误页面。NavigationResult 不包含 HTTP 状态码。要获取状态码,请使用主框架 NavigationFinished 事件的 ResponseCode 属性。

Reload()、GoBack()、GoForward() 和 GoTo() 方法以及 IFrame.LoadUrl() 也返回 Task<NavigationResult>。在调用之后,框架报告的以下情况中最先发生的一种会使其任务完成:加载完成或失败、错误页面、导航中止,或同一文档内的导航。因此,页面在同一时刻开始的导航也可能使该任务完成。

使用 POST 加载 URL 

要通过 URL 加载网页并发送 POST 数据,请使用 INavigation.LoadUrl(LoadUrlParameters) 方法。 下面的代码演示了如何使用额外的标头加载 URL 和发送不同格式的 POST 数据。

你可以以 key=value 的格式向网络服务器发送网路表单数据:

C#
VB
navigation.LoadUrl(new LoadUrlParameters("https://postman-echo.com/post")
{
    UploadData = new FormData(new Dictionary<string, string>()
    {
        {"key", "value"}
    })
});
navigation.LoadUrl(New LoadUrlParameters("https://postman-echo.com/post") With {
    .UploadData = New FormData(New Dictionary(Of String, String)() From {
        {"key", "value"}
    })
})

您需要为每种请求类型明确指定 “Content-Type” 标头。

如果您想发送一些纯文本,请添加额外的标头来指示网络服务器它是纯文本:

C#
VB
navigation.LoadUrl(new LoadUrlParameters("https://postman-echo.com/post")
{
    UploadData = new TextData("Some text..."),
    HttpHeaders = new[]
    {
        new HttpHeader("Content-Type", "text/plain")
    }
});
navigation.LoadUrl(New LoadUrlParameters("https://postman-echo.com/post") With {
    .UploadData = New TextData("Some text..."),
    .HttpHeaders = 
    { 
        New HttpHeader("Content-Type", "text/plain") 
    }
})

您可以发送任何类型的数据。 为此,请指定其 Content-Type。 请参阅下面发送 JSON 数据的代码示例:

C#
VB
navigation.LoadUrl(new LoadUrlParameters("https://postman-echo.com/post")
{
    PostData = "{\"title\":\"Hello\"}",
    HttpHeaders = new[]
    {
        new HttpHeader("Content-Type", "application/json")
    }
});
navigation.LoadUrl(New LoadUrlParameters("https://postman-echo.com/post") With {
    .PostData = "{""title"":""Hello""}",
    .HttpHeaders = 
    { 
        New HttpHeader("Content-Type", "application/json") 
    }
})

加载文件 

您可以使用相同的方法从本地文件系统加载 HTML 文件,方法是提供 HTML 文件的绝对路径而非 URL。 请参阅以下代码示例:

C#
VB
navigation.LoadUrl(Path.GetFullPath("index.html"));
navigation.LoadUrl(Path.GetFullPath("index.html"))

加载 HTML 

要将 HTML 加载到浏览器中,您可以创建一个 data: 编码的 URI,然后使用常规的 LoadUrl 调用加载它。 下面是一个例子:

C#
VB
var html = "<html><head></head><body><h1>Html Encoded in URL!</h1></body></html>";
var base64EncodedHtml = Convert.ToBase64String(Encoding.UTF8.GetBytes(html));
browser.Navigation.LoadUrl("data:text/html;base64," + base64EncodedHtml).Wait();
Dim html = "<html><head></head><body><h1>Html Encoded in URL!</h1></body></html>"
Dim base64EncodedHtml = Convert.ToBase64String(Encoding.UTF8.GetBytes(html))
browser.Navigation.LoadUrl("data:text/html;base64," & base64EncodedHtml).Wait()

Chromium 将数据编码 URI 的最大长度限制为 2MB。

但是,使用此方法时无法设置基本 URL。

另一种可能的方法是使用处理程序注册一个 Scheme 并拦截相应的请求以提供 HTML。 请参阅相应的文章.

加载 PDF 文件 

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

C#
VB
browser.PdfDocumentLoaded += (sender, args) =>
{
    var url = args.Url;
    var frame = args.Frame;

    // This event is a good place to start PDF printing.
    frame.Print();
};
AddHandler browser.PdfDocumentLoaded,
    Sub(sender, args)
        Dim url = args.Url
        Dim frame = args.Frame

        ' This event is a good place to start PDF printing.
        frame.Print()
    End Sub

文档可能由于意外错误未能加载,例如网络故障, 密码错误, PDF 文件损坏等。在这种情况下,DotNetBrowser 会触发 PdfDocumentLoadFailed 事件:

C#
VB
browser.PdfDocumentLoadFailed += (sender, args) =>
{
    var url = args.Url;
    var frame = args.Frame;
};
AddHandler browser.PdfDocumentLoadFailed,
    Sub(sender, args)
        Dim url = args.Url
        Dim frame = args.Frame
    End Sub

请注意,不应使用 Navigation.LoadUrl() 任务的返回结果或 FrameLoadFinished 事件来等待 PDF 文档加载完成。

重载 

有几种重新加载当前加载的网页的选项:

  • 使用 HTTP 缓存重新加载:
C#
VB
navigation.Reload();
navigation.Reload()
  • 忽略 HTTP 缓存重新加载:
C#
VB
navigation.ReloadIgnoringCache();
navigation.ReloadIgnoringCache()
  • 使用 HTTP 缓存重新加载并检查是否重新发布:
C#
VB
navigation.ReloadAndCheckForRepost();
navigation.ReloadAndCheckForRepost()
  • 忽略 HTTP 缓存重新加载并检查是否重新发布:
C#
VB
navigation.ReloadIgnoringCacheAndCheckForRepost();
navigation.ReloadIgnoringCacheAndCheckForRepost()

停止 

使用 INavigation.Stop() 方法取消任何挂起的导航或下载操作,并停止任何动态页面元素,例如背景声音和动画。 请参阅以下代码示例:

C#
VB
navigation.Stop();
navigation.Stop()

后退 & 前进 

DotNetBrowser 允许使用导航后退-前进历史列表。

当您创建一个 IBrowser 实例时,它默认导航到 about:blank 网页。 在导航后退-前进列表中始终有一个条目。

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

C#
VB
if (navigation.CanGoBack()) {
    navigation.GoBack();
}
If navigation.CanGoBack() Then
    navigation.GoBack()
End If

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

C#
VB
if (navigation.CanGoForward()) {
    navigation.GoForward();
}
If navigation.CanGoForward() Then
    navigation.GoForward()
End If

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

C#
VB
if (index >=0 && index < navigation.EntryCount) {
    navigation.GoTo(index);
}
If index >=0 AndAlso index < navigation.EntryCount Then
    navigation.GoTo(index)
End If

要翻阅后退-前进列表并获得每个导航条目的详细信息,请使用以下方法。

C#
VB
for (int index = 0; index < navigation.EntryCount; index++) {
    INavigationEntry navigationEntry = navigation.EntryAt(index);
    Console.WriteLine("URL: " + navigationEntry.Url);
    Console.WriteLine("Title: " + navigationEntry.Title);
}
For index As Integer = 0 To navigation.EntryCount - 1
    Dim navigationEntry As INavigationEntry = navigation.EntryAt(index)
    Console.WriteLine("URL: " & navigationEntry.Url)
    Console.WriteLine("Title: " & navigationEntry.Title)
Next

要通过删除条目来修改后退-前进列表,请使用以下方法。

C#
VB
// 返回后退/前进列表中的条目数。
int entryCount = navigation.EntryCount;
// 删除索引处的导航条目。
for (int i = entryCount - 2; i >= 0; i--) {
    bool success = navigation.RemoveEntryAt(i);
    Console.WriteLine("Navigation entry at index " + i +
            " has been removed successfully? " + success);
}
' 返回后退/前进列表中的条目数。
Dim entryCount As Integer = navigation.EntryCount
' 删除索引处的导航条目。
For i As Integer = entryCount - 2 To 0 Step -1
    Dim success As Boolean = navigation.RemoveEntryAt(i)
    Console.WriteLine("Navigation entry at index " & i & 
                      " has been removed successfully? " & success)
Next

过度滚动历史导航 

在硬件加速渲染模式下,DotNetBrowser 允许通过在带有触摸屏的设备上向左/向右滑动来后退/前进导航。 默认情况下,过度滚动导航是禁用的。 要启用它,请使用 IBrowserSettings.OverscrollHistoryNavigationEnabled 属性:

C#
VB
browser.Settings.OverscrollHistoryNavigationEnabled = true;
browser.Settings.OverscrollHistoryNavigationEnabled = True

过滤 URL 

您可以决定是否忽略特定 URL 的导航请求。

请参阅下面的代码示例,其中展示了如何忽略所有以 https://www.google 开头的 URL 的导航请求:

C#
VB
navigation.StartNavigationHandler = 
    new Handler<StartNavigationParameters, StartNavigationResponse>((p) =>
    {
        // 忽略以 "https://www.google" 开头的 URL 
        // 导航请求。
        if (p.Url.StartsWith("https://www.google"))
        {
            return StartNavigationResponse.Ignore();
        }
        return StartNavigationResponse.Start();
    });
navigation.StartNavigationHandler = 
    New Handler(Of StartNavigationParameters, StartNavigationResponse)(Function(p)
        ' 忽略以 "https://www.google" 开头的 URL 
        ' 导航请求。
        If p.Url.StartsWith("https://www.google") Then
            Return StartNavigationResponse.Ignore()
        End If
        Return StartNavigationResponse.Start()
    End Function)

过滤资源 

SendUrlRequestHandler “处理程序可让您确定 HTML、图像、JavaScript 或 CSS 文件、favicon 等资源是否已加载。 默认情况下,加载所有资源。 要修改默认行为,请在您决定取消或加载哪些资源的地方注册您自己的处理程序实现。

请参阅下面的代码示例,了解如何抑制所有图像:

C#
VB
engine.Profiles.Default.Network.SendUrlRequestHandler =
    new Handler<SendUrlRequestParameters, SendUrlRequestResponse>(p =>
    {
        if (p.UrlRequest.ResourceType == ResourceType.Image)
        {
            return SendUrlRequestResponse.Cancel();
        }
        return SendUrlRequestResponse.Continue();
    });
engine.Profiles.Default.Network.SendUrlRequestHandler = 
    New Handler(Of SendUrlRequestParameters, SendUrlRequestResponse)(Function(p)
        If p.UrlRequest.ResourceType = ResourceType.Image Then
            Return SendUrlRequestResponse.Cancel()
        End If
        Return SendUrlRequestResponse.Continue()
    End Function)

导航事件 

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

这些事件都不表示页面已经可供您的代码使用。它们报告的是加载的不同阶段,其中一些针对每个框架触发,有些对一个页面会触发多次。请按以下方式使用它们:

要等待您需要的内容出现在页面上,请参阅等待页面内容。

导航事件在后台线程上触发,每个事件线程一次触发一个事件。不要阻塞它们的处理程序:运行缓慢的处理程序会延迟其后的事件。请参阅事件。

导航事件处理程序的运行顺序可能与它们所报告的加载阶段的顺序不同。例如,某个文档的 FrameLoadFinished 处理程序可能会在其 NavigationFinished 或 FrameDocumentLoadFinished 处理程序之前运行。LoadUrl() 返回的任务也可能在这些处理程序运行之前完成。请不要依赖它们的顺序。

加载开始 

要在内容加载开始时获得通知,请使用 LoadStarted 事件。 请参阅以下代码示例:

C#
VB
navigation.LoadStarted += (s, e) => {};
AddHandler navigation.LoadStarted, Sub(s, e)
End Sub

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

加载完成 

要在浏览器停止加载内容时获得通知,请使用 LoadFinished 事件。例如:

C#
VB
navigation.LoadFinished += (s, e) => {};
AddHandler navigation.LoadFinished, Sub(s, e)
End Sub

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

该事件报告的是整个浏览器的加载状态,而不是某一个页面的加载状态。每次浏览器开始并停止加载时,它都会再次触发:例如,当 iframe 加载时、当页面自行导航时,或当同一文档内的导航完成时。因此,一个页面可能会多次触发该事件。该事件不会说明加载的是哪个 URL 或哪个框架。请使用它来更新 UI,而不是用来判断页面是否已加载。

加载进度 

LoadProgressChanged 事件允许获取有关加载进度的通知:

C#
VB
navigation.LoadProgressChanged += (s, e) =>
{
    // 表示网页加载进度的值。
    double progress = e.Progress;
};
AddHandler navigation.LoadProgressChanged, Sub(s, e)
    ' 表示网页加载进度的值。
    Dim progress As Double = e.Progress
End Sub

导航开始 

要在导航开始时获得通知,请使用 NavigationStarted 事件。 请参阅以下代码示例:

C#
VB
navigation.NavigationStarted += (s, e) => 
{
    string url = e.Url;
    // 指示导航是否
    // 将在同一文档范围内执行。
    bool isSameDocument = e.IsSameDocument;
};
AddHandler navigation.NavigationStarted, Sub(s, e)
    Dim url As String = e.Url
    ' 只是导航是否
    ' 将在同一文档范围内执行。
    Dim isSameDocument As Boolean = e.IsSameDocument
End Sub

导航停止 

要在导航停止时获得通知,请使用 NavigationStopped 事件。 请参阅以下代码示例:

C#
VB
navigation.NavigationStopped += (s, e) => {};
AddHandler navigation.NavigationStopped, Sub(s, e)
End Sub

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

导航重定向 

要在导航重定向到新 URL 时获得通知,请使用 NavigationRedirected 事件。 请参阅以下代码示例:

C#
VB
navigation.NavigationRedirected += (s, e) => {
    // 导航重定向 URL。
    string url = e.Url;
};
AddHandler navigation.NavigationRedirected, Sub(s, e)
    ' 导航重定向 URL。
    Dim url As String = e.Url
End Sub

导航完成 

要在导航完成时获得通知,请使用 NavigationFinished 事件。 请参阅以下代码示例:

C#
VB
navigation.NavigationFinished += (s, e) => {
    string url = e.Url;
    IFrame frame = e.Frame;
    bool hasCommitted = e.HasCommitted;
    bool isSameDocument = e.IsSameDocument;
    bool isErrorPage = e.IsErrorPage;
    if (isErrorPage) {
        NetError error = e.ErrorCode;
    }
    // HTTP 状态码;如果没有带标头的响应,则为 0。
    int responseCode = e.ResponseCode;
    bool wasServerRedirect = e.WasServerRedirect;
};
AddHandler navigation.NavigationFinished, Sub(s, e)
    Dim url As String = e.Url
    Dim frame As IFrame = e.Frame
    Dim hasCommitted As Boolean = e.HasCommitted
    Dim isSameDocument As Boolean = e.IsSameDocument
    Dim isErrorPage As Boolean = e.IsErrorPage
    If isErrorPage Then
        Dim [error] As NetError = e.ErrorCode
    End If
    ' HTTP 状态码;如果没有带标头的响应,则为 0。
    Dim responseCode As Integer = e.ResponseCode
    Dim wasServerRedirect As Boolean = e.WasServerRedirect
End Sub

每个框架中的每个导航在被提交、中止或被新导航替换时,都会触发此事件。要知道导航是否已提交,请使用 NavigationFinishedEventArgs.HasCommitted。 要了解导航是否导致错误页面,请使用 NavigationFinishedEventArgs.IsErrorPage。

当导航提交新文档时,该文档开始加载。处理程序运行时,文档可能已经加载完成,也可能仍在加载,因此不要在此事件中操作 DOM,也不要运行依赖页面内容的脚本。如果导航尚未提交,Frame 属性为 null。

该事件由 same-document(在同一文档范围内)的导航触发,比如片段导航或 window.history.pushState()/window.history.replaceState(),这些导航不会导致文档的更改。 使用 NavigationFinishedEventArgs.IsSameDocument 属性来检查是否是同一文档导航。

框架加载完成 

要在 IFrame 中的内容加载完成时获得通知,请使用 FrameLoadFinished 事件。 请参阅以下代码示例:

C#
VB
navigation.FrameLoadFinished += (s, e) =>
{
    string url = e.ValidatedUrl;
    IFrame frame = e.Frame;
};
AddHandler navigation.FrameLoadFinished, Sub(s, e)
    Dim url As String = e.ValidatedUrl
    Dim frame As IFrame = e.Frame
End Sub

此事件对应于框架触发 JavaScript load 事件的时刻:文档及其图片、样式和 iframe 已经加载。请注意:

  • 该事件针对每个框架触发。包含三个 iframe 的页面会触发四次,通常先为 iframe 触发。请检查 Frame.IsMain 以找到主框架的事件。
  • 主框架会为每个新文档再次触发该事件,例如当页面通过 JavaScript 或 <meta http-equiv="refresh"> 自行导航时,或在提交表单之后。URL 可能保持不变。
  • 页面之后添加的 iframe 会在主框架的事件之后触发它们自己的事件。
  • 同一文档内的导航不会触发此事件。
  • 脚本在 load 事件之后加载或渲染的内容可能仍然缺失。请参阅等待页面内容。

框架加载失败 

要在 Frame 中的内容加载因某种原因失败时获得通知,请使用 FrameLoadFailed 事件。 请参阅以下代码示例:

C#
VB
navigation.FrameLoadFailed += (s, e) =>
{
    string url = e.ValidatedUrl;
    NetError error = e.ErrorCode;
};
AddHandler navigation.FrameLoadFailed, Sub(s, e)
    Dim url As String = e.ValidatedUrl
    Dim [error] As NetError = e.ErrorCode
End Sub

框架文件加载完成 

要在 Frame 中的文档加载完成时获得通知,请使用FrameDocumentLoadFinished 事件。 请参阅以下代码示例:

C#
VB
navigation.FrameDocumentLoadFinished += (s, e) => 
{
    IFrame frame = e.Frame;
};
AddHandler navigation.FrameDocumentLoadFinished, Sub(s, e)
    Dim frame As IFrame = e.Frame
End Sub

此时,延迟脚本被执行,标记为 document_end 的内容脚本被注入到框架中。该事件对应于 JavaScript DOMContentLoaded 事件,页面会在 load 事件之前到达该事件,因此图片和 iframe 可能仍在加载。对于同一框架,它的处理程序仍可能在 FrameLoadFinished 处理程序之后运行。

跟踪导航状态 

以下代码检查主框架是否已提交所请求的页面,且没有 Chromium 错误页面或 HTTP 错误状态。此时文档可能仍在加载:

C#
VB
navigation.NavigationFinished += (s, e) =>
{
    // 跳过 iframe、同一文档内的导航以及没有框架的导航。
    if (e.Frame == null || !e.Frame.IsMain || e.IsSameDocument)
    {
        return;
    }

    bool succeeded = e.HasCommitted && !e.IsErrorPage && e.ResponseCode < 400;
};
AddHandler navigation.NavigationFinished, Sub(s, e)
    ' 跳过 iframe、同一文档内的导航以及没有框架的导航。
    If e.Frame Is Nothing OrElse Not e.Frame.IsMain OrElse e.IsSameDocument Then
        Return
    End If

    Dim succeeded As Boolean =
        e.HasCommitted AndAlso Not e.IsErrorPage AndAlso e.ResponseCode < 400
End Sub

当您组合多个导航事件来跟踪状态时,请记住以下规则:

  • 未提交的导航没有加载所请求的页面。例如,当导航被中止或被新的导航替换时,就会发生这种情况。请检查 HasCommitted,而不只是 IsErrorPage。
  • 如果 NavigationFinished 报告了 IsErrorPage,或者主框架触发了 FrameLoadFailed,请将该导航视为失败,即使 FrameLoadFinished 也在其之前或之后触发。Chromium 会在其错误页面加载完成时触发 FrameLoadFinished。
  • 同一框架中可能同时运行多个导航,而事件不会说明它们属于哪个导航。上一个导航的迟到事件可能会在下一个导航开始之后到达。要获取由您的代码启动的导航的结果,请使用 LoadUrl() 返回的任务。
  • 当导航没有产生带标头的响应时,ResponseCode 为 0:例如,对于 about:blank、同一文档内的导航以及网络故障。因此,单独的 0 既不表示成功,也不表示失败。

等待页面内容 

没有任何导航事件会告诉您页面已经可供您的代码使用。许多页面会在 load 事件之后通过脚本加载或渲染其内容,有些页面会在加载后立即自行导航。对于这样的页面,请等待您需要的内容出现在页面上,而不是等待某个事件。

一种可行的方法是注入 JavaScript 代码,在特定元素出现时通知您的应用程序。例如,以下代码会等待 ID 为 login-form 的元素出现在主框架中。该 JavaScript 代码每 100 毫秒检查一次该元素,最多检查 30 秒,并返回一个在找到该元素时被履行的 promise;请参阅使用 JavaScript Promise:

C#
VB
IJsPromise promise = await browser.MainFrame.ExecuteJavaScript<IJsPromise>(
    @"new Promise(resolve => {
          const deadline = Date.now() + 30000;
          const check = () => {
              if (document.getElementById('login-form')) {
                  resolve(true);
              } else if (Date.now() < deadline) {
                  setTimeout(check, 100);
              }
          };
          check();
      })");

var found = new TaskCompletionSource<bool>();
promise.Then(result => { found.TrySetResult(true); });

TimeSpan timeout = TimeSpan.FromSeconds(30);
if (await Task.WhenAny(found.Task, Task.Delay(timeout)) != found.Task)
{
    // 元素未及时出现,或者页面已导航离开。
}
Dim promise As IJsPromise =
    Await browser.MainFrame.ExecuteJavaScript(Of IJsPromise)(
        "new Promise(resolve => {" &
        "    const deadline = Date.now() + 30000;" &
        "    const check = () => {" &
        "        if (document.getElementById('login-form')) {" &
        "            resolve(true);" &
        "        } else if (Date.now() < deadline) {" &
        "            setTimeout(check, 100);" &
        "        }" &
        "    };" &
        "    check();" &
        "})")

Dim found As New TaskCompletionSource(Of Boolean)()
promise.Then(Sub(result) found.TrySetResult(True))

Dim timeout As TimeSpan = TimeSpan.FromSeconds(30)
If Await Task.WhenAny(found.Task, Task.Delay(timeout)) IsNot found.Task Then
    ' 元素未及时出现,或者页面已导航离开。
End If

请始终使用超时。如果页面在代码等待期间导航到新文档,该 promise 将永远不会被履行。如果页面可能这样做,请在开始检查之前订阅 FrameLoadFinished,并在主框架每次加载新文档时重新开始检查,所有检查共用一个总超时。如果文档在调用 Then() 之前发生变化,Then() 会抛出 ObjectDisposedException;请像处理超时一样处理它。

单击或提交表单之后 

当单击按钮或提交表单会加载新文档时,没有可以等待的任务。一种可行的方法是等待主框架的 FrameLoadFinished 事件。请在触发导航之前订阅该事件,以免错过它。例如:

C#
VB
IElement button = browser.MainFrame.Document.DocumentElement
                         .GetElementById("submit");

var loaded = new TaskCompletionSource<string>();
EventHandler<FrameLoadFinishedEventArgs> onLoaded = (s, e) =>
{
    if (e.Frame.IsMain)
    {
        loaded.TrySetResult(e.ValidatedUrl);
    }
};

browser.Navigation.FrameLoadFinished += onLoaded;
try
{
    button.Click();

    TimeSpan timeout = TimeSpan.FromSeconds(30);
    if (await Task.WhenAny(loaded.Task, Task.Delay(timeout)) != loaded.Task)
    {
        // 新页面未及时加载。
    }
}
finally
{
    browser.Navigation.FrameLoadFinished -= onLoaded;
}
Dim button As IElement =
    browser.MainFrame.Document.DocumentElement.GetElementById("submit")

Dim loaded As New TaskCompletionSource(Of String)()
Dim onLoaded As EventHandler(Of FrameLoadFinishedEventArgs) =
    Sub(s, e)
        If e.Frame.IsMain Then
            loaded.TrySetResult(e.ValidatedUrl)
        End If
    End Sub

AddHandler browser.Navigation.FrameLoadFinished, onLoaded
Try
    button.Click()

    Dim timeout As TimeSpan = TimeSpan.FromSeconds(30)
    Dim first As Task = Await Task.WhenAny(loaded.Task, Task.Delay(timeout))
    If first IsNot loaded.Task Then
        ' 新页面未及时加载。
    End If
Finally
    RemoveHandler browser.Navigation.FrameLoadFinished, onLoaded
End Try

该事件只告诉您主框架加载了一个新文档。它也可能是重定向的中间页面或 Chromium 错误页面,因此在使用页面之前,请检查导航状态或您期望的内容。如果新页面通过脚本加载其内容或自行导航,请在此之后等待内容,如上文所示。

如果单击操作只是更新当前文档(例如在单页应用程序中),则不会加载新文档,也不会触发 FrameLoadFinished。在这种情况下,请直接等待您期望的内容,如上一个示例所示。

常见错误 

  • 将 LoadFinished 视为页面已加载的标志,或者统计 LoadFinished 事件的次数。该事件报告的是整个浏览器的加载状态,对一个页面可能触发多次。
  • 处理 FrameLoadFinished 时不检查 Frame.IsMain。第一个事件通常来自 iframe。
  • 在 NavigationFinished 中操作 DOM。处理程序运行时,文档可能仍在加载。
  • 依赖导航事件的顺序。它们的处理程序的运行顺序可能与它们所报告的加载阶段的顺序不同。
  • 在页面已经继续导航之后,仍使用事件中的框架或 DOM。处理程序运行时,框架可能已显示另一个文档或已被处理,上一个文档的 DOM 和 JavaScript 对象也已被处理,因此使用它们会抛出 ObjectDisposedException。请在处理程序中检查 Frame.IsMain 和 URL,并在需要时重新获取 Document。
  • 在导航事件处理程序中阻塞,例如使用 Task.Wait()、Task.Result 或长时间的 DOM 扫描。这会延迟其后的所有导航事件,而在处理程序中等待另一个导航可能会以 TimeoutException 结束。
  • 期望事件数量与 Chrome DevTools 中显示的一样多。DevTools 只显示一次主框架的 load 事件,而 DotNetBrowser 会报告每个框架以及加载状态的每次变化。

自定义错误页面 

要覆盖标准 Chromium 错误页面,请使用 ShowHttpErrorPageHandler 和 ShowNetErrorPageHandler 处理程序,分别处理 HTTP 错误(如 404 Not Found)或网络错误(如 ERR_CONNECTION_REFUSED)。 请参阅以下示例:

HTTP 错误页面

C#
VB
browser.Navigation.ShowHttpErrorPageHandler =
    new Handler<ShowHttpErrorPageParameters, ShowHttpErrorPageResponse>(arg =>
    {
        string url = arg.Url;
        HttpStatusCode httpStatusCode = arg.HttpStatus;
        return ShowHttpErrorPageResponse.Show("HTTP Error web page:" + httpStatusCode);
    });
browser.Navigation.ShowHttpErrorPageHandler = 
    New Handler(Of ShowHttpErrorPageParameters, ShowHttpErrorPageResponse)(Function(arg)
        Dim url As String = arg.Url
        Dim httpStatusCode As HttpStatusCode = arg.HttpStatus
        Return ShowHttpErrorPageResponse.Show("HTTP Error web page:" & httpStatusCode)
    End Function)

网络错误页面

C#
VB
browser.Navigation.ShowNetErrorPageHandler = 
    new Handler<ShowNetErrorPageParameters, ShowNetErrorPageResponse>(arg =>
    {
        return ShowNetErrorPageResponse.Show("Network error web page");
    });
browser.Navigation.ShowNetErrorPageHandler = 
    New Handler(Of ShowNetErrorPageParameters, ShowNetErrorPageResponse)(Function(arg)
        Return ShowNetErrorPageResponse.Show("Network error web page")
    End Function)

服务器提供的自定义错误页面不会被这些处理程序拦截,并且无法使用此功能进行覆盖。 上述处理程序仅在覆盖 Chromium 默认错误页面时调用。