目录

设计

这份文档概述了库的设计,同时提供了通用规则,以帮助你理解如何与其进行交互。

对象 

所有库对象可分为以下类别:

  • 服务对象;
  • 不可变数据对象。

服务对象允许在数据对象只保存数据时执行一些操作。 服务对象可以使用数据对象。

像 IEngine, IBrowser, IProfile IBrowserSettings, IFrame, IDocument, IJsObject 实例都是 服务对象。 而 EngineOptions, Size, Rectangle 则是 不可变数据对象。

实例化 

要创建不可变数据对象或服务对象,请使用其构造函数、构建器或其静态方法之一。 以下是使用构建器的示例:

C#
VB
EngineOptions options = new EngineOptions.Builder 
{
    RenderingMode = RenderingMode.HardwareAccelerated,
    Language = Language.EnglishUs
}.Build();
IEngine engine = EngineFactory.Create(options);
Dim options As EngineOptions = New EngineOptions.Builder With 
{
    .RenderingMode = RenderingMode.HardwareAccelerated,
    .Language = Language.EnglishUs
}.Build()
Dim engine As IEngine = EngineFactory.Create(options)

销毁 

每个必须手动处理的服务对象都实现了 IDisposable 接口。 要处理服务对象并释放所有已分配的内存和资源,请调用 IDisposable.Dispose()方法。 例如:

C#
VB
engine.Dispose();
engine.Dispose()

诸如 IFrame 这样的一些服务对象可以自动释放,比如当网页被卸载时。 这些对象实现了 IAutoDisposable 接口。

如果您使用已处理的对象,则会发生ObjectDisposedException 。

关系 

服务对象的生命周期可以依赖于另一个对象的生命周期。 当该服务对象被处理时,依赖于该服务对象的所有服务对象将被自动释放。 例如:

  • 当您处理 IEngine时,它的所有 IBrowser 实例都会自动释放;
  • 当您处理 IBrowser时,它的所有 IFrame 实例都会自动处理。

方法 

返回 Task<T> 实例的方法是异步执行的。 如果该方法返回某个值,它会同步执行,阻塞当前线程执行,直到收到返回值。

处理程序 

每个允许注册处理程序的对象都具有 IHandler<in T> 或 IHandler<in T, out TResult> 接口类型的属性。 要注册和注销处理程序,请使用属性设置器和获取器。

IHandler<in T, out TResult> 接口有默认实现:

  • Handler<T, TResult> 类允许包装 lambda 和方法组;
  • AsyncHandler<T, TResult> 类允许包装异步 lambda 和方法组或返回 Task<TResult> 的 lambda 和方法组。

异步 

下面的示例演示了如何注册一个返回Task 以异步提供响应的异步处理程序:

C#
VB
browser.ShowContextMenuHandler =
    new AsyncHandler<ShowContextMenuParameters, ShowContextMenuResponse
    >(ShowContextMenu);

// 在相同类中声明的异步方法。
private async Task<ShowContextMenuResponse> ShowContextMenu(ShowContextMenuParameters p)
{
    // ...
}
browser.ShowContextMenuHandler = 
    New AsyncHandler(Of ShowContextMenuParameters, ShowContextMenuResponse)(
        AddressOf ShowContextMenu)

' 在相同类中声明的异步方法。
Private Async Function ShowContextMenu(p As ShowContextMenuParameters) As Task(Of ShowContextMenuResponse)
    ' ...
End Function

任务结果可以从不同的线程异步提供。

通过返回的 Task 参数提供响应,否则 IEngine 将等待响应直到终止。

同步 

下面的示例演示了如何注册和注销通过返回值返回响应的常规 Handler :

C#
VB
browser.CreatePopupHandler =
    new Handler<CreatePopupParameters, CreatePopupResponse>(p =>
    {
        return CreatePopupResponse.Create();
    });
browser.CreatePopupHandler = 
    New Handler(Of CreatePopupParameters, CreatePopupResponse)(Function(p)
        Return CreatePopupResponse.Create()
    End Function)

处理程序如何运行 

实现处理程序时,请注意:

  • Chromium 会等待处理程序的响应,然后才继续处理程序所响应的请求、导航或对话框,因此运行缓慢的处理程序会延迟它所响应的操作。
  • 处理程序在线程池线程上调用,而不是在 UI 线程上调用。多个处理程序可能同时运行,例如在并行网络请求时,因此处理程序代码必须是线程安全的。
  • IBrowser.Keyboard、IBrowser.Mouse 和 IBrowser.Touch 的键盘、鼠标和触摸处理程序是例外。在离屏渲染模式下以及在 macOS 上,它们在传递输入的线程上运行,该线程通常是 UI 线程。请确保它们快速返回,否则 UI 会冻结。
  • 如果处理程序抛出异常,DotNetBrowser 会将其写入日志,而日志默认处于禁用状态。请在处理程序内部捕获异常并返回明确的响应。
  • 有些处理程序在返回之前会阻塞浏览器或引擎。它们的 API 参考会说明不允许在处理程序内部调用浏览器或引擎的方法。例如,INavigation.StartNavigationHandler 不允许调用浏览器方法,INavigation.ShowHttpErrorPageHandler 不允许调用引擎方法。请仅根据处理程序的参数决定响应。

事件 

DotNetBrowser 通过常规的 .NET 事件报告通知,例如 IEngine.Disposed 或 INavigation.FrameLoadFinished。

事件处理程序在内部事件线程上运行,而不是在 UI 线程上运行。DotNetBrowser 使用多个事件线程,例如每个浏览器的导航事件各使用一个线程。每个线程逐个传递其事件,因此运行缓慢的事件处理程序会延迟同一线程上排在它之后的事件。来自不同线程的事件可能同时运行,即使是同一对象的事件也是如此:例如,IBrowser.TitleChanged 和 IBrowser.FrameCreated 使用不同的线程。请对多个事件处理程序共享的数据进行同步访问。

大多数对象的 Disposed 事件会在调用 Dispose() 的线程上同步触发。IEngine.Disposed 则不同:它在 Chromium 主进程退出时触发,包括崩溃之后,因此它可能在任意线程上触发。

线程安全 

该库具备线程安全性:可在不同线程中安全使用 DotNetBrowser 对象。

从处理程序和事件更新 UI 

除了上面所述的输入处理程序之外,处理程序和事件都在 UI 线程以外的线程上运行。Disposed 事件也可能在调用 Dispose() 的线程上运行,而该线程可能是 UI 线程。UI 框架只允许在 UI 线程上更改控件。否则,WPF 和 Avalonia UI 会抛出 InvalidOperationException,WinUI 3 会抛出 COMException。Windows Forms 只有在调试器下才会可靠地抛出 InvalidOperationException;没有调试器时,调用可能以不可预测的方式失败。请使用非阻塞调用将 UI 更新发送到 UI 线程:

  • WPF 中的 Dispatcher.BeginInvoke();
  • Windows Forms 中的 Control.BeginInvoke();
  • WinUI 3 中的 DispatcherQueue.TryEnqueue();
  • Avalonia UI 中的 Dispatcher.UIThread.Post()。

有关示例,请参阅跨线程操作无效。

不要在处理程序中阻塞等待 UI 线程,例如使用 Dispatcher.Invoke() 或 Control.Invoke()。如果此时 UI 线程正在等待一个必须在处理程序返回后才能完成的 DotNetBrowser 调用,两个线程就会互相等待。UI 会冻结,直到该调用超时并抛出 TimeoutException。在事件处理程序中,阻塞调用还会延迟排在它之后的事件,直到 UI 线程空闲。