CefSharp中文帮助文档「建议收藏」

2022-11-04 21:53:50 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

CefSharp是围绕Chromium嵌入式框架( Chromium Embedded Framework,CEF)的.Net包装器。CEF是一个基于Google Chromium项目的开源项目。与Chromium项目本身(主要专注于Google Chrome应用程序开发)不同,CEF专注于促进第三方应用程序中的嵌入式浏览器用例CEF基于多进程Chromium Content API,因此,当前仅存在Chromium的部分功能。例如,对扩展的支持是有限的,仅实现了一部分Extension API

CefSharp提供三种不同的类型:WinFormsWPFOffScreen。在WPFOffScreen版本使用的OffScreen Rendering(OSR)渲染模式。在OSR模式中,每个帧被渲染到缓冲器,然后或者在屏幕上绘制作为的情况下WPF或可作为BitmapOffScreen所有版本都使用CefSharpCefSharp.Core库,因此API在这三种风格中,大部分使用的库都完全相同。这减少了代码重复并降低了添加新功能的维护负担,唯一的缺点是该WPF版本并不像它可能的那样友好(您可以将其ChromiumWebBrowser归类,并在应用程序中实现所需的任何缺少的部分)。您也可以托管WinFormsWPF使用中的版本号WindowsFormsHost,可能需要绕过该WPF版本的某些限制(CEF尚未在OSR模式中实现完全的触摸屏支持,在上存在一个开放问题CEF Issue Tracker,如果您需要这样做,请参与其中)。

发行说明

有关每个版本的发行说明,请访问https://github.com/cefsharp/CefSharp/releases,如果您有问题或对更改有所好奇,请抽出时间阅读它们。如果遇到问题,请查看“已知问题”部分,通常会有一些说明包含有关发行版的有用信息。

软件需求

CefSharp使用Visual C (VC )与本机C API交互,因此它只能在Windows上运行。(没有Windows APP Store版本)。CefSharp在每个第二Chromium版本上发布版本,例如47、49、51。每个CefSharp版本都有其自己的分支,有关每个分支的详细信息和要求,请参见https://github.com/cefsharp/CefSharp#release-branches。Google最近去除了对较早的操作系统的支持,例如Windows XP,Vista及其服务器版本。如果您要求您的应用程序在这些操作系统上运行,请查看发行版以获取更多详细信息https://github.com/cefsharp/CefSharp/releases

CefSharp要求

  • 微软.Net 4.5.2或更高
  • Microsoft Visual C 可再发行组件包(x86或者x64取决于您的应用程序)。要确定Visual C 您需要哪个版本,请参见https://github.com/cefsharp/CefSharp#release-branches

笔记:

  • 您可以将其VC Redist Dll's与您的应用程序打包,有关详细信息,请参见https://github.com/cefsharp/CefSharp/wiki/Frequently-asked-questions#Inclusion_vcredist
  • 可在此处获得用于Visual Studio 2012和Visual Studio 2013的Microsoft .NET Framework 4.5.2开发人员包:https : //www.microsoft.com/en-gb/download/details.aspx?id=42637

任何CPU支持

较新的版本现在支持定位AnyCPU,有关如何实现此功能的详细信息,请参见https://github.com/cefsharp/CefSharp/issues/1714。可以使用相同的技术将libcef.dll等等移动到磁盘上的其他文件夹或公共位置。

需要知道/限制

  • 要指定CachePath用于Cookie的持久性,如保存密码等,需要默认指定In-Memory Cache(类似于Incogneto)。有关使用以下内容初始化CEF的示例,请参见下面的“初始化和关闭”部分CachePath
  • 您可以使用Network.clearBrowserCache清除磁盘缓存,请参阅#3158以获得有关执行DevTools命令的详细信息。
  • 在中app.manifest为您的应用添加,以获取HiDPI支持,应用兼容性(在上运行Windows 10)和中的工具提示WinForms。这些示例包含示例app.manifest文件。这非常重要(CEF Forum • [SOLVED] Check failed: fallback_available)
  • 类似于日志中的错误Check failed: fallback_available == base::win::GetVersion() > base::win::VERSION_WIN8 (1 vs. 0)是您的应用程序需要app.manifest使用相关compatibility条目的标志。
  • CEFInitialized/Shutdown每个进程只能有一次,请参阅以下部分以获取完整详细信息,这是对基础Chromium框架的限制。
  • Minimal同时为版本WPFWinForms版本添加了设计师支持57.0.0,有关详细信息,请参见#1989(WPF)和#1946(WinForms)。设计人员的支持需要您定位x86(理论上AnyCPU也应该有效,但尚未经过测试)。Visual Studio是,x86因此您无法使用该x64版本。对于较旧的版本,不提供设计人员支持(设计人员将引发异常)。
  • 仅在默认的AppDomain中运行,有一些变通办法,例如https://github.com/flole/CefSharp.AppDomain和 https://github.com/stever/AppHostCefSharp
  • 由于资源有限,一次仅支持一个版本,请参阅https://github.com/cefsharp/CefSharp#release-branches以查看哪个版本为最新版本。如果您使用的是旧版本,则遇到问题,则必须升级到当前支持的版本。
  • 仅在Windows没有App Store版本上运行。
  • 支持.Net Core,但需要其他步骤,请参见https://github.com/cefsharp/CefSharp.MinimalExample#net-core-support
  • Sandboxing尚未实现,因为直接在上添加支持在技术上是不可行的CefSharp,有关详细信息,请参见#697。
  • WinForms屏幕键盘上的屏幕可能会受益于disable-usb-keyboard-detect命令行参数 https://github.com/cefsharp/CefSharp/issues/1691#issuecomment-323603277
  • WPFHigh DPI建议具有监视器的用户.Net 4.6在其目标计算机上安装,因为其中存在一个错误,该错误.Net Framework可能会导致MILERR_WIN32ERROR Exception参见#2035的详细信息
  • CEF当前不支持PNaCl加载所需的内容,Google Earth请参见CEF Forum • Google Earth Web

例子

CefSharp源代码包含的许多不同的特征的实施例。还有一个MinimalExample项目使用最新的Nuget软件包提供非常简单的Browser实现。这MinimalExample是入门的最佳位置,下载此项目并使其运行以作为基础参考,以确保一切都在您的系统上正常工作。

https://github.com/cefsharp/CefSharp.MinimalExample

记录中

默认情况下CEF,在应用程序的执行文件夹(例如)中维护其自己的日志文件(’Debug.log’)bin。要禁用日志记录更改settings.LogSeverity,并更改文件名/路径,请使用settings.LogFile

调试问题时,首先要检查的地方是此日志文件,因为它包含低级Chromium消息。如果您看到错误或警告,请搜索CEF Forum • Index page和https://bitbucket.org/chromiumembedded/cef/issues?status=new&status=open

工艺流程

CEF使用多进程运行。处理窗口创建,绘画和网络访问的主进程称为browser进程。通常,此过程与主机应用程序相同,并且大多数应用程序逻辑将在浏览器进程中运行闪烁呈现和JavaScript执行在单独的render过程中进行。一些应用程序逻辑(例如JavaScript绑定)也将在渲染过程中运行。默认进程模型将为每个唯一的来源(方案 域)生成一个新的渲染过程。将根据需要生成其他进程,例如处理插件(如Flash)的“插件”进程和处理加速合成的“ gpu”进程。

默认情况CefSharp下,该render进程的默认实现称为CefSharp.BrowserSubProcess.exe。如上所述,将多次产生此过程以表示单独的过程。从版本开始,51.0.0可以提供自己的自定义BrowserSubProcess,因为可执行文件现在是基础VC 实现的非常简单的包装。

chromiumembedded / cef / issues / #2498 – Add support for site-per-process — Bitbucket包含有关当前默认流程模型的详细信息。

线程数

CEF使用多个线程进行不同级别的处理。例如browser,该过程包含以下通常引用的线程:

  • UI线程:是浏览器过程中的主线程。默认情况下CefSharp使用,setting.MultiThreadedMessageLoop = true因此该CEF UI线程不同于您的主应用程序线程
  • IO线程:在浏览器进程中用于处理IPC和网络消息
  • FILE线程:在浏览器进程中用于与文件系统进行交互
  • RENDERER线程:是渲染器过程中的主线程

初始化和关闭

Initialize每个进程(应用程序)只能调用一次。可以运行您的应用程序的多个实例,您需要CachePath为每个实例提供唯一的实例,请参阅CefSettings下文。

有关如何在运行时更改设置,隔离浏览器实例,为不同实例设置不同的缓存路径的详细信息,请参见请求上下文(浏览器隔离)。

重要的是要注意,有必要初始化基础CEF库。这可以通过显式和隐式两种方式之一来实现。创建新实例时ChromiumWebBrowser,它将检查CEF是否已初始化,如果尚未初始化,请使用默认值为您初始化。对于那些希望指定一些自定义设置的用户,您可以CEF如下所示显式初始化自己:

代码语言:javascript复制
public static void Init()
{
    var settings = new CefSettings();

    // Increase the log severity so CEF outputs detailed information, useful for debugging
    settings.LogSeverity = LogSeverity.Verbose;
    // By default CEF uses an in memory cache, to save cached data e.g. to persist cookies you need to specify a cache path
    // NOTE: The executing user must have sufficient privileges to write to this folder.
    settings.CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\Cache");

    Cef.Initialize(settings);
}

对于Cef.ShutdownChromiumWebBrowser(ChromiumWebBrowser is the WinForms web browser control,ChrominuWebBrowser是Winform的浏览器控件将钩住相关Application Exit事件的WinForms和WPF实例,并且默认情况下调用Cef.Shutdown()。设置CefSharpSettings.ShutdownOnExit=false; 禁用此行为。在ChromiumWebBrowser创建事件的第一个实例之前,需要设置此值,因为事件处理程序已挂接到ChromiumWebBrowser该类的静态构造函数中。

重要的是要注意CEF Initialize/Shutdown 必须在主应用程序线程(通常是UI线程)上调用。如果您在不同的线程上调用它们,则您的应用程序将挂起。

一个使用Initialize/Shutdown手动调用/的示例,WinForms可以将该示例应用于WPF使用该CefSharp.OffScreen包的控制台应用程序(该OffScreen示例位于https://github.com/cefsharp/CefSharp.MinimalExample是一个很好的起点,其中也有一个示例)主项目存储库,它要高级一些)。

代码语言:javascript复制
public class Program
{
        [STAThread]
        public static void Main()
        {
            //For Windows 7 and above, best to include relevant app.manifest entries as well
            Cef.EnableHighDPISupport();
	    
            //We're going to manually call Cef.Shutdown below, this maybe required in some complex scenarios
            CefSharpSettings.ShutdownOnExit = false;

            //Perform dependency check to make sure all relevant resources are in our output directory.
            Cef.Initialize(new CefSettings(), performDependencyCheck: true, browserProcessHandler: null);

            var browser = new BrowserForm();
            Application.Run(browser);
	    
            //Shutdown before your application exists or it will hang.
            Cef.Shutdown();
        }
}

综上所述

  • Cef.Initialize并且Cef.Shutdown每个进程(应用程序)只能调用一次Initialize,因此仅使用完后CefSharp后才调用Shutdown
  • Cef.Initialize并且Cef.Shutdown必须在同一线程上调用。
  • Cef.Initialize如果您创建新ChromiumWebBrowser实例并且尚未调用,则会为您隐式调用Cef.Initialize
  • 对于WinForms和WPF实例,默认情况下ChromiumWebBrowser相关的Application Exit事件被钩住,然后默认调用Cef.Shutdown()方法处理。设置CefSharpSettings.ShutdownOnExit = false;用于禁用此行为。在ChromiumWebBrowser创建事件的第一个实例之前,需要设置此值,因为事件处理程序已挂接到ChromiumWebBrowser该类的静态构造函数中。
  • 在其中CefSharp.OffScreen,必须Cef.Shutdown()在应用程序存在之前显式调用它,否则它将挂起。

CefSettings和BrowserSettings

CefSettings结构允许配置应用程序范围的CEF设置。一些通常配置的成员包括:

  • BrowserSubprocessPath 此路径是子流程启动的独立可执行文件的路径。通常,无需更改此设置。
  • MultiThreadedMessageLoop 在CefSharp中默认值为True,尽管可以将其集成CEF到您的应用程序现有的消息循环中,请参阅下面的MultiThreadedMessageLoop部分。
  • CommandLineArgsDisabled 设置为true可禁用使用标准CEF和Chromium命令行参数配置浏览器进程功能的功能。有关更多信息,请参见“命令行参数”部分。
  • RootCachePath 所有CefSettings.CachePathRequestContextSettings.CachePath值必须具有相同的根目录。如果此值为空且CefSettings.CachePath非空,则默认为该CefSettings.CachePath值。如果此值为非空值,那么它必须是绝对路径。非空RootCachePath可以与空CefSettings.CachePath结合使用,在您希望浏览器连接到以“隐身模式”创建的Global RequestContext(默认)的实例以及使用基于磁盘的缓存使用自定义RequestContext创建的实例的情况下, 。有关更多详细信息,请参见下面的RequestContext部分。
  • CachePath 全局浏览器缓存的数据将存储在磁盘上的位置。此值是非空的,那么它必须是绝对路径,该路径必须等于CefSettings.RootCachePath或子目录(如果RootCachePath为空,则默认为该值)。如果该值为空,则将在“隐身模式”下创建浏览器,在该模式下,将使用内存中的缓存进行存储,并且不会将任何数据持久化到磁盘上。如果指定了缓存路径,则诸如localStorage之类的HTML5数据库将仅在会话之间持久存在。可以RequestContext通过该RequestContextSettings.CachePath值覆盖单个实例。有关更多详细信息,请参见下面的RequestContext部分。
  • Locale 将传递给Blink的语言环境字符串。如果为空,则将使用默认语言环境“ en-US”。也可以使用“ lang”命令行开关进行配置。更改此项以同时设置上下文菜单语言。
  • LogFile 用于调试日志的目录和文件名。如果为空,将使用默认名称“ debug.log”,并将文件写入应用程序目录。也可以使用“ log-file”命令行开关进行配置。
  • LogSeverity 日志严重性。仅记录此严重级别或更高级别的消息。也可以使用“ log-severity”命令行开关进行配置,其值为“ verbose”,“ info”,“ warning”,“ error”,“ error-report”或“ disable”。
  • ResourcesDirPath 资源目录的标准路径。如果此值为空,则cef.pak和/或devtools_resources.pak文件必须位于模块目录中。也可以使用“ resources-dir-path”命令行开关进行配置。
  • LocalesDirPath 语言环境目录的标准路径。如果此值为空,则语言环境目录必须位于模块目录中。在Mac OS X上,始终从应用程序包Resources目录中加载打包文件的情况下,将忽略此值。也可以使用“ locales-dir-path”命令行开关进行配置。
  • RemoteDebuggingPort 设置为1024到65535之间的值以在指定的端口上启用远程调试。例如,如果指定8080,则远程调试URL将为http:// localhost:8080。可以从任何CEF或Chrome浏览器窗口中远程调试CEF。也可以使用“ remote-debugging-port”命令行开关进行配置。

有许多设置和命令行参数可能会影响CEF的行为方式。这里有些例子:

代码语言:javascript复制
public static void Init()
{
    // Specify Global Settings and Command Line Arguments
    var settings = new CefSettings();

    // By default CEF uses an in memory cache, to save cached data e.g. to persist cookies you need to specify a cache path
    // NOTE: The executing user must have sufficient privileges to write to this folder.
    settings.CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\Cache");;

    // There are many command line arguments that can either be turned on or off

    // Enable WebRTC                            
    settings.CefCommandLineArgs.Add("enable-media-stream");
    
    //Disable GPU Acceleration
    settings.CefCommandLineArgs.Add("disable-gpu");

    // Don't use a proxy server, always make direct connections. Overrides any other proxy server flags that are passed.
    // Slightly improves Cef initialize time as it won't attempt to resolve a proxy
    settings.CefCommandLineArgs.Add("no-proxy-server"); 

    Cef.Initialize(settings);
}

些设置可以应用于特定ChromiumWebBrowser实例。如果您正在使用WPF,则可以BrowserSettings在中指定XAML

代码语言:javascript复制
var browser = new ChromiumWebBrowser(url)
{
    BrowserSettings =
    {
        DefaultEncoding = "UTF-8",
        WebGl = CefState.Disabled
    }
};
代码语言:javascript复制
<!--xmlns:cefSharpCore="clr-namespace:CefSharp;assembly=CefSharp.Core"-->
<!--xmlns:cefSharpWpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"-->

<cefSharpWpf:ChromiumWebBrowser>
    <cefSharpWpf:ChromiumWebBrowser.BrowserSettings>
        <cefSharpCore:BrowserSettings DefaultEncoding="UTF-8"/>
    </cefSharpWpf:ChromiumWebBrowser.BrowserSettings>
</cefSharpWpf:ChromiumWebBrowser>

IBrowser,IFrame和IBrowserHost

IBrowserIFrame对象用于将命令发送到浏览器,并在回调方法中返回状态信息。这两个对象都是包装类,是对浏览器的包装。每个IBrowser对象都有一个代表顶层框架的main IFrame对象,以及零个或多个sub IFrame对象。

例如,加载两个HTML<iframe>的浏览器将具有三个IFrame对象(顶级框架和两个<iframe>)。

要将URL加载到浏览器主机中:

代码语言:javascript复制
browser.MainFrame.LoadUrl(someurl);

CefSharp提供了许多扩展方法,使执行常见任务更加容易。请参阅参考资料WebBrowserExtensions,以获取这些方法的来源,并更好地了解如何执行常见任务。

IBrowserHost 代表更底层的浏览器方法。

处理程序

CefSharp为了方便起见,提供了一些事件,如下所示(有关所有常见事件以及有关其用法的详细信息,请参见IWebBrowser API文档):

  • ConsoleMessage
  • StatusMessage
  • FrameLoadStart
  • FrameLoadEnd
  • LoadError
  • LoadingStateChanged

这些是简单的事件,仅提供一小部分提供的基础处理程序CEF。这些事件仅在主浏览器中被调用,对于弹出窗口处理,您可以使用IDisplayHandler和来访问通知ILoadHandler

为了确定页面何时完成加载,我建议在FrameLoadEnd上使用LoadingStateChanged。重要的是要记住,完成的加载不同于完成的渲染。当前尚无确定网页何时完成渲染的方法(Flash,动态内容,动画等功能,甚至像移动鼠标或滚动之类的简单任务也将导致渲染新帧)。

IDialogHandlerIDisplayHandlerIDownloadHandlerIContextMenuHandlerILifeSpanHandlerILoadHandlerIRequestHandler是一些更常见的处理程序(参见其余部分的源极/ API DOC)。这些仅以方便的.NET方式包装基础CEF处理程序。例如CEF的CefDownloadHandlerIDownloadHandlerCefSharp实施这些处理程序将使您能够访问作为CEF框架的基础事件和回调。可以使用回调以异步方式执行许多处理程序的成员。所有处理程序都遵循一致的模式:返回a的处理程序bool询问您是否要自己处理。如果否,则返回false默认操作。true如果您自己处理,请返回。

它们是您实现并分配给ChromiumWebBrowser实例的基本接口。例如

代码语言:javascript复制
browser.DownloadHandler = new DownloadHandler();

理想情况下,您应在ChromiumWebBrowser实例化实例后立即设置处理程序。有关更多详细示例,请参见源代码中的示例项目。当前没有可用的默认实现,因此您必须实现每种方法。(如果您希望提供默认实现,请提交请求请求)。

有关处理程序的一些一般说明

  • IDownloadHandler 需要实现,以允许下载文件,进度通知,暂停,取消等
  • IRequestHandler用于处理导航,重定向,资源加载通知等
  • IDialogHandler用于文件对话框通知
  • IDisplayHandler用于地址更改,状态消息,控制台消息,全屏模式更改通知(以及更多)
  • ILoadHandler用于加载状态,其中一些映射到事件,将其用于弹出窗口
  • ILifeSpanHandler用于处理弹出窗口和关闭事件
  • IKeyboardHandler用于键盘事件
  • IJsDialogHandler用于javascript消息框/弹出窗口
  • IDragHandler用于拖动开始
  • IContextMenuHandler用于自定义上下文菜单
  • IFocusHandler用于与焦点有关的通知
  • IResourceRequestHandlerFactory是CefSharp独有的接口,无需执行IRequestHandler和IResourceRequestHandler就可以拦截资源请求。
  • IRenderProcessMessageHandler用于CefSharp呈现过程中发送的自定义消息
  • IFindHandler用于查找通知

可以使用来修改响应ResponseFilter。请参阅以下部分。

请求处理Request Handling

CEF支持两种方法来处理应用程序内部的网络请求。

  1. Scheme Handler方法允许用于靶向特定原点(方案 结构域)的请求的处理程序的注册。
  2. Request Interception 方法允许在处理应用程序的自由裁量权的任意请求。

使用HTTP(S)方案而不是自定义方案,可以避免一系列潜在的问题。

如果您选择使用自定义方案(比其他任何事情http://https://等),你必须用CEF注册它,这样它会像预期的那样。如果您希望自定义方案的行为类似于HTTP(支持POST请求并强制实施HTTP访问控制(CORS)限制),则应将其注册为“标准”方案。如果您打算对其他方案执行跨域请求或将POST请求发送XMLHttpRequest到方案处理程序,则应使用HTTP方案而不是自定义方案,以避免潜在的问题。IsSecureIsCorsEnabled参数最近添加。

处理程序可以使用这两个内置的方案(http://https://,等)和自定义方案。使用内置方案时,请为您的应用程序选择一个唯一的域名(如myappinternal)。实现ISchemeHandlerFactory和IResourceHandler类来处理请求并提供响应数据。有关IResourceHandler的默认实现,请参阅ResourceHandler,它具有许多有用的静态帮助器方法。

Scheme Handler

处理程序可与内置方案(HTTP,HTTPS等)和自定义方案一起使用。使用内置方案时,请为您的应用程序选择一个唯一的域名(如myappinternal)。实现ISchemeHandlerFactory和IResourceHandler类以处理请求并提供响应数据。有关IResourceHandler的默认实现,请参阅ResourceHandler,它具有许多有用的静态帮助器方法。

计划处理程序通过CefSettings.RegisterScheme函数进行注册。例如,您可以为“ localfolder:// cefsharp /”请求注册一个处理程序(下面还有另一个示例,并且在项目源代码中有一些有效的示例):

代码语言:javascript复制
settings.RegisterScheme(new CefCustomScheme
{
	SchemeName = "localfolder",
	DomainName = "cefsharp",
	SchemeHandlerFactory = new FolderSchemeHandlerFactory(rootFolder: @"........CefSharp.ExampleResources",
							hostName: "cefsharp", //Optional param no hostname/domain checking if null
							defaultPage: "home.html") //Optional param will default to index.html
});

该FolderSchemeHandlerFactory是使用一个方案处理从磁盘读取文件的简单默认实现。您可以使用自定义方案(换句话说,您可以以形式提供URL customscheme://folder/yourfile)或标准方案(https://https://)。

实现自己的工厂的示例可能如下所示:

代码语言:javascript复制
public class CefSharpSchemeHandlerFactory : ISchemeHandlerFactory
{
	public const string SchemeName = "custom";

	private static readonly IDictionary<string, string> ResourceDictionary;

	static CefSharpSchemeHandlerFactory()
	{
		ResourceDictionary = new Dictionary<string, string>
		{
			{ "/home.html", Resources.home_html },
			{ "/bootstrap/bootstrap.min.css", Resources.bootstrap_min_css },
			{ "/bootstrap/bootstrap.min.js", Resources.bootstrap_min_js },
			{ "/BindingTest.html", Resources.BindingTest },
			{ "/ExceptionTest.html", Resources.ExceptionTest },
			{ "/PopupTest.html", Resources.PopupTest },
			{ "/SchemeTest.html", Resources.SchemeTest }
		};
	}

	public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
	{
		//Notes:
		// - The 'host' portion is entirely ignored by this scheme handler.
		// - If you register a ISchemeHandlerFactory for http/https schemes you should also specify a domain name
		// - Avoid doing lots of processing in this method as it will affect performance.
		// - Uses the Default ResourceHandler implementation

		var uri = new Uri(request.Url);
		var fileName = uri.AbsolutePath;

		string resource;
		if (ResourceDictionary.TryGetValue(fileName, out resource) && !string.IsNullOrEmpty(resource))
		{
			var fileExtension = Path.GetExtension(fileName);
			return ResourceHandler.FromString(resource, , mimeType: Cef.GetMimeType(fileExtension));
		}

		return null;
	}
}

提供的ResourceHandler是IResourceHandler的默认实现,并且包含许多用于创建类的静态帮助器方法。有关更多详细信息,请参见下面的“资源处理程序”部分。

使用静态方法的一些示例是:

代码语言:javascript复制
ResourceHandler.FromStream(stream, mimeType);
ResourceHandler.FromString(htmlString, includePreamble:true, mimeType:Cef.GetMimeType(fileExtension));
ResourceHandler.FromFilePath("CefSharp.Core.xml", mimeType);

最后,您必须使用以下代码注册此方案处理程序:

代码语言:javascript复制
public static void Init()
{
	// Pseudo code; you probably need more in your CefSettings also.
	var settings = new CefSettings();

	settings.RegisterScheme(new CefCustomScheme
	{
		SchemeName = "custom",
		SchemeHandlerFactory = new CefSharpSchemeHandlerFactory()
	});

	Cef.Initialize(settings);
}

计划注册必须在调用之前进行,这一点很重要Cef.Initialize()

请求拦截Request Interception

IResourceRequestHandler.GetResourceRequestHandler支持拦截任意请求。它使用与方案处理程序方法相同的IResourceHandler类。提供的ResourceHandler是IResourceHandler的默认实现,并且包含许多用于创建类的静态帮助器方法。有关更多详细信息,请参见下面的“资源处理程序”部分。

代码语言:javascript复制
public class CustomResourceRequestHandler : CefSharp.Handler.ResourceRequestHandler
{
	protected override IResourceHandler GetResourceHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request)
	{
		//ResourceHandler has many static methods for dealing with Streams, 
		// byte[], files on disk, strings
		// Alternatively ou can inheir from IResourceHandler and implement
		// a custom behaviour that suites your requirements.
		return ResourceHandler.FromString("Welcome to CefSharp!", mimeType: Cef.GetMimeType("html"));
	}
}

public class CustomRequestHandler : CefSharp.Handler.RequestHandler
{
	protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
	{
		//Only intercept specific Url's
		if (request.Url == "http://cefsharp.test/" || request.Url == "https://cefsharp.test/")
		{
			return new CustomResourceRequestHandler();
		}

		//Default behaviour, url will be loaded normally.
		return null;
	}
}

browser.RequestHandler = new CustomRequestHandler();

IWebBrowser.RegisterResourceHandler和IWebBrowser.UnRegisterResourceHandler扩展方法提供提供一种简单的方法IResourceHandler对于给定的Url

例如,您可以请求一个虚构的URL并提供一个响应,就像该网站是真实的一样。

资源处理程序ResourceHandler

ISchemeHandlerFactory和IResourceRequestHandler.GetResourceHandler使用IResourceHandler接口来表示响应(流 头 状态码,等等)。IResourceHandler的默认实现只是ResourceHandler。

为了方便起见,ResourceHandler包含许多静态方法

  • ResourceHandler.FromString
  • ResourceHandler.FromByteArray
  • ResourceHandler.ForErrorMessage
  • ResourceHandler.FromStream
  • ResourceHandler.FromFilePath

使用静态方法的一些示例是:

代码语言:javascript复制
ResourceHandler.FromStream(stream, mimeType);
ResourceHandler.FromString(htmlString, includePreamble:true, mimeType:Cef.GetMimeType(fileExtension));
ResourceHandler.FromFilePath("CefSharp.Core.xml", mimeType);

该源代码包含ISchemeHandlerFactory的详细示例。

实施实例ResourceHandler.ProcessRequestAsync。如果需要完全控制,则可以实现IResourceHandler,但是在大多数情况下,这不是必需的。

代码语言:javascript复制
//A simple example of a ResourceHandler that downloads a file from the internet.
public class ExampleResourceHandler : ResourceHandler
{
	public override CefReturnValue ProcessRequestAsync(IRequest request, ICallback callback)
	{
		Task.Run(() =>
		{
			using (callback)
			{
				var httpWebRequest = (HttpWebRequest)WebRequest.Create("http://samples.mplayerhq.hu/SWF/zeldaADPCM5bit.swf");

				var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();

				// Get the stream associated with the response.
				var receiveStream = httpWebResponse.GetResponseStream();
				var mime = httpWebResponse.ContentType;

				var stream = new MemoryStream();
				receiveStream.CopyTo(stream);
				httpWebResponse.Close();

				//Reset the stream position to 0 so the stream can be copied into the underlying unmanaged buffer
				stream.Position = 0;

				//Populate the response values
				ResponseLength = stream.Length;
				MimeType = mime;
				StatusCode = (int)HttpStatusCode.OK;
				Stream = stream;

				callback.Continue();
			}
		});

		return CefReturnValue.ContinueAsync;
	}
}

响应过滤Response Filtering

IResourceRequestHandler.GetResourceResponseFilter()支持过滤响应请求而接收的数据。您可以检索原始响应数据,也可以将数据追加到响应中,例如在文件末尾注入一些自定义CSS。您可以根据需要重写响应。可用于接收任何请求的响应,即AJAX(XHRHttpRequest)/ POST / GET。

将响应作为UTF8字符串获取的基本示例是:

代码语言:javascript复制
public class CustomResourceRequestHandler : CefSharp.Handler.ResourceRequestHandler
{
	private readonly System.IO.MemoryStream memoryStream = new System.IO.MemoryStream();

	protected override IResponseFilter GetResourceResponseFilter(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response)
	{
		return new CefSharp.ResponseFilter.StreamResponseFilter(memoryStream);
	}

	protected override void OnResourceLoadComplete(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength)
	{
		//You can now get the data from the stream
		var bytes = memoryStream.ToArray();

		if (response.Charset == "utf-8")
		{
			var str = System.Text.Encoding.UTF8.GetString(bytes);
		}
		else
		{
			//Deal with different encoding here
		}
	}
}

public class CustomRequestHandler : CefSharp.Handler.RequestHandler
{
	protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
	{
		//Only intercept specific Url's
		if (request.Url == "http://cefsharp.github.io/" || request.Url == "https://cefsharp.github.io/")
		{
			return new CustomResourceRequestHandler();
		}

		//Default behaviour, url will be loaded normally.
		return null;
	}
}

browser.RequestHandler = new CustomRequestHandler();

当前,StreamResponseFilter是框架中提供的唯一过滤器。

将dataIn复制到dataOut的简单响应过滤器。数据以块的形式进行流传输,通常大小为64kb。

代码语言:javascript复制
/// <summary>
/// PassThruResponseFilter - copies all data from DataIn to DataOut.
/// Upstream documentation link
/// https://magpcss.org/ceforum/apidocs3/projects/(default)/CefResponseFilter.html#Filter(void*,size_t,size_t&,void*,size_t,size_t&)
/// </summary>
public class PassThruResponseFilter : IResponseFilter
{
	bool IResponseFilter.InitFilter()
	{
		return true;
	}

	FilterStatus IResponseFilter.Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten)
	{
		if (dataIn == null)
		{
			dataInRead = 0;
			dataOutWritten = 0;

			return FilterStatus.Done;
		}

		//Calculate how much data we can read, in some instances dataIn.Length is
		//greater than dataOut.Length
		dataInRead = Math.Min(dataIn.Length, dataOut.Length);
		dataOutWritten = dataInRead;

		var readBytes = new byte[dataInRead];
		dataIn.Read(readBytes, 0, readBytes.Length);
		dataOut.Write(readBytes, 0, readBytes.Length);

		//If we read less than the total amount avaliable then we need
		//return FilterStatus.NeedMoreData so we can then write the rest
		if (dataInRead < dataIn.Length)
		{
			return FilterStatus.NeedMoreData;
		}

		return FilterStatus.Done;
	}

	public void Dispose()
	{

	}
}

有关IResourceFilter的CefSharp.Example其他示例实现,请参见源代码中的项目。该功能实现起来非常复杂。提出任何问题之前,请确保您已阅读并调试了现有示例。

从磁盘/数据库/嵌入式资源/流中加载HTML / CSS / JavaScript / etc

CefSharp.WebBrowserExtensions类中提供了一些扩展方法,以方便使用。

代码语言:javascript复制
//Load a data encoded Uri
//NOTE There are limits to the size of a Data Uri, use the overload that takes a Url if you need to load large files
LoadHtml(this IWebBrowser browser, string html, bool base64Encode = false);

//Register a ResourceHandler with the `ResourceRequestHandlerFactory` and calls browser.Load
LoadHtml(this IWebBrowser browser, string html, string url)`;

//Register a resource handler with the `ResourceRequestHandlerFactory`
RegisterResourceHandler(this IWebBrowser browser, string url, Stream stream, string mimeType = 
ResourceHandler.DefaultMimeType);

//Unregister a resource handler with the `ResourceRequestHandlerFactory`
UnRegisterResourceHandler(this IWebBrowser browser, string url);

//In `WinForms` you can pass a `HtmlString` directly into the constructor and have it load as a Data Uri
new ChromiumWebBrowser((CefSharp.Web.HtmlString)"<html><body style='background:red;'>Data Uri Test</body></html>");

有关data:包含URI本身中的请求正文的已编码URI的更多信息,请参见Data URLs – HTTP | MDN

自己生成Data URI将类似于:

代码语言:javascript复制
const string html = "<html><head><title>Test</title></head><body><h1>Html Encoded in URL!</h1></body></html>";
var base64EncodedHtml = Convert.ToBase64String(Encoding.UTF8.GetBytes(html));
browser.Load("data:text/html;base64,"   base64EncodedHtml);

文件URI(file:///)

我强烈建议不要file:///从本地磁盘加载时使用。应用了不同的安全限制,并且存在许多限制。我建议使用Scheme处理程序或实现自己的处理程序IResourceRequestHandlerFactory。(data:特别是对于OffScreen项目而言,加载编码的URI也非常方便)。

如果您选择忽略此建议,则必须解决file:///自己遇到的任何问题。ceforum是最好的资源。

代理解析

有两个用于配置代理服务器的选项。

  1. CEF使用与Google Chrome相同的命令行标志。
    • 参见chromiumembedded / cef / wiki / GeneralUsage — Bitbucket
    • 如果您使用命令行args指定代理,则您将无法在运行时使用对其进行更改SetPreference
    • 所有ChromiumWebBrowser实例将共享同一个代理
  2. 可以使用IRequestContext.SetPreference在运行时设置/更改代理设置。
    • IRequestContext.SetPreference必须在CEF UI线程上调用。使用Cef.UIThreadTaskFactory在上生成任务CEF UI ThreadIRequestContextHandler方法已经在CEF UI线程上调用,因此您可以SetPreference直接调用。
    • 可以逐个指定代理设置,Request Context从而使您可以ChromiumWebBrowser使用不同的代理来拥有不同的实例。
    • 阅读下面的“请求上下文”部分,以获取更多详细信息和基本代码示例。

如果代理要求身份验证,则将使用值为的IRequestHandler.GetAuthCredentials()回调执行以检索用户名和密码。isProxytrue

在c# – CefSharp 3 set proxy at Runtime – Stack Overflow上可以找到使用Preferencesin设置代理的一些其他示例。CefSharp

请求上下文(浏览器隔离)

隔离浏览器实例的方法,包括提供自定义缓存路径,不同的代理设置,不同的Cookie管理器以及许多其他功能RequestContext。在较新的版本中,PPAPI插件的加载是在该RequestContext级别上进行的。在CEF条款的底层类是CefRequestContext

以下是一些关键点:

  • 默认情况下,将使用全局请求上下文(所有浏览器共享的设置)
  • 您可以在运行时使用以下命令更改某些(不是全部)设置 Preferences
  • 如果要使用以下命令更改值,请不要使用命令行参数 SetPreference
  • SetPreference必须在上调用CEF UI Thread。使用Cef.UIThreadTaskFactory在CEF UI Thread
  • WinFormsRequestContext在创建浏览器实例后立即设置
  • OffScreen:传递RequestContext给构造函数
  • WPF:调用后在您的Control/Window构造函数中设置InitializeComponent()
  • 插件加载通知通过IRequestContextHandler接口处理
  • 将RequestContextSettings.CachePath设置为持久化cookie,数据,localStorage等
  • RequestContextSettings.CachePath 必须等于CefSettings.RootCachePath的子代,请参见https://github.com/cefsharp/CefSharp/issues/3111#issuecomment-629713608
代码语言:javascript复制
//WinForms Examples - WPF and OffScreen are similar, see notes above.

//Default implementation of RequestContext
//Default settings will be used, this means an in-memory cache (no data persisted)
browser = new ChromiumWebBrowser();
browser.RequestContext = new RequestContext();

//CustomRequestContextHanler needs to implement `IRequestContextHandler`
//Default settings will be used, this means an in-memory cache (no data persisted)
browser = new ChromiumWebBrowser();
browser.RequestContext = new RequestContext(new CustomRequestContextHandler());

//Custom settings and CustomRequestContextHandler
//Use the specified cache path (if empty, in memory cache will be used). To share the global
//browser cache and related configuration set this value to match the CefSettings.CachePath
//value.
var requestContextSettings = new RequestContextSettings { CachePath = cachePath };
browser = new ChromiumWebBrowser();
browser.RequestContext = new RequestContext(requestContextSettings, new CustomRequestContextHandler());

有关更多详细示例,请参见项目源。

代码语言:javascript复制
//When you are already on the CEF UI Thread you can use the following
string errorMessage;
//You can set most preferences using a `.` notation rather than having to create a complex set of dictionaries.
//The default is true, you can change to false to disable
context.SetPreference("webkit.webprefs.plugins_enabled", true, out errorMessage);
//Change the minimum font size to 24pt
context.SetPreference("webkit.webprefs.minimum_font_size", 24, out errorMessage);

//To execute on the CEF UI Thread you can use 
Cef.UIThreadTaskFactory.StartNew(delegate
{
    string errorMessage;
    //Use this to check that settings preferences are working in your code

    //the browser variable is an instance of ChromiumWebBrowser
    var success = browser.RequestContext.SetPreference("webkit.webprefs.minimum_font_size", 24, out errorMessage);
}); 

在OnRequestContextInitialized中设置首选项(建议使用此方法来设置代理,因为它将在浏览器尝试加载任何网页之前被调用)

代码语言:javascript复制
public class RequestContextHandler : IRequestContextHandler
{
	IResourceRequestHandler IRequestContextHandler.GetResourceRequestHandler(IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
	{
            //Return null for the default behaviour
            return null;
	}

	bool IRequestContextHandler.OnBeforePluginLoad(string mimeType, string url, bool isMainFrame, string topOriginUrl, WebPluginInfo pluginInfo, ref PluginPolicy pluginPolicy)
	{
	    //pluginPolicy = PluginPolicy.Disable;
	    //return true;

    	    return false;
	}

	void IRequestContextHandler.OnRequestContextInitialized(IRequestContext requestContext)
	{
		//You can set preferences here on your newly initialized request context.
		//Note, there is called on the CEF UI Thread, so you can directly call SetPreference

		//Use this to check that settings preferences are working in your code
		//You should see the minimum font size is now 24pt
		string errorMessage;
		var success = requestContext.SetPreference("webkit.webprefs.minimum_font_size", 24, out errorMessage);

		//You can set the proxy with code similar to the code below
		//https://stackoverflow.com/questions/36095566/cefsharp-3-set-proxy-at-runtime has some additional examples
		//var v = new Dictionary<string, object>
		//{
		//    ["mode"] = "fixed_servers",
		//    ["server"] = "scheme://host:port"
		//};
		//string errorMessage;
		//bool success = requestContext.SetPreference("proxy", v, out errorMessage);
	}
}

打印

CEF API仅公开了有限的打印支持。当前不支持在Kiosk模式下打印(打印到没有对话框的默认设置)。建议的解决方法是先打印,PDF然后使用3rd party应用程序来打印PDF

如果您需要更好的打印支持,则应在上进行讨论ceforum。在CEF问题追踪器上已经有公开的讨论和未解决的问题。

  • CEF Forum • PrintHandler for PDF reader and context menu
  • chromiumembedded / cef / issues / #1283 – Adding Print Options to Cef3 — Bitbucket
  • CEF Forum • Enable Kiosk-Printing Mode in CEF

高DPI显示/支持

WinForms/WPF需要使使用DPI的桌面应用程序能够在高DPI显示器DPI Scale设置大于的显示器)上正确运行DPI100%

注意如果鼠标光标在浏览器中的位置不正确,或者浏览器显示带有渲染/调整大小的黑框/边框,则需要制作您的应用程序DPI Aware。应用程序的其他部分也可能会显得模糊或尺寸不正确。

有许多选项可用于配置流程的DPI意识:

  1. 通过应用程序清单设置(通常是首选)
  2. 通过app.config(仅限WinForms,目标是.Net 4.7及更高版本)
  3. 通过API调用以编程方式

Windows 10 1703具有其他改进,有关更多详细信息,请参见High-DPI Scaling Improvements for Desktop Applications in the Windows 10 Creators Update (1703) – Windows Developer Blog。

WinForms高DPI

从.NET Framework 4.7开始,Windows Forms包括针对常见的高DPI和动态DPI方案的增强功能。在.NET Framework的早期版本中,您使用清单添加了高级DPI支持。不再建议使用此方法,因为它会覆盖app.config文件中定义的设置。请确保阅读Windows窗体中的High DPI支持以获取Microsoft的更多详细信息。

应用清单

重要事项 如果您要定位.Net 4.7或以上定位,Microsoft建议DPI Awareness通过app.config而不是进行配置app.manifest。请确保阅读Windows窗体中的High DPI支持以获取Microsoft的更多详细信息。

使用应用程序清单设置默认感知。以下示例是Win 10 1703及更高版本上的PerMonitor DPI Aware和旧版本上的PerMonitor DPI感知。确保阅读了https://docs.microsoft.com/zh-cn/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows#dpi-awareness-mode,其中讨论了不同的DPI Awareness选项。如果您的项目还没有app.manifest使用Visual Studio New Item模板,则可以使用模板来添加模板,而不是手动添加模板以确保添加文件中的相关<ApplicationManifest/>条目csproj/vbproj(这是一种特殊类型)。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
        <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
        <dpiAware>true/PM</dpiAware>
    </windowsSettings>
</application>
</assembly>

以编程方式

在代码中设置高DPI,可以使用Cef.EnableHighDPISupport();。辅助方法。这将调用Chromium base :: win :: EnableHighDPISupport(); 功能。然后,您将拥有与Chromium用途完全相同的设置。

Cef.EnableHighDPISupport(); 必须在应用程序执行的最早期就调用,最好在应用程序入口点(Program.Main)中调用。

该CefSharp.MinimalExample.WinForms项目包含一个工作的例子。

WPF高DPI

应用清单

添加相关条目,请参阅app.manifest中针对Microsoft的建议打开Windows级每个监视器的DPI感知。

有关工作示例,请参见https://github.com/cefsharp/CefSharp/blob/cefsharp/84/CefSharp.Wpf.Example/app.manifest了解工作示例。如果您的项目还没有app.manifest使用Visual Studio New Item模板,则可以使用模板来添加模板,而不是手动添加模板以确保添加文件中的相关<ApplicationManifest/>条目csproj/vbproj(这是一种特殊类型)。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
        <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
        <dpiAware>true/PM</dpiAware>
    </windowsSettings>
</application>
</assembly>

以编程方式

WPF默认情况下,应用程序具有自动生成的Program.Main入口点,这使得以编程方式设置更加困难DPI。有关如何创建的信息,请参见c# – No Main() in WPF? – Stack Overflow,Program.Main然后可以调用Cef.EnableHighDPISupport();。。这 必须在你的应用程序执行很早就被调用,最好在您的自定义Program.Main第一个电话。

屏幕外高DPI

添加相关app.manifest条目或调用Cef.EnableHighDPISupport()(请参阅上面的示例)。阅读WinForms以上部分,选择适合您需求的选项。

高DPI附加信息

Chromium默认情况下,将在单独的子流程中执行所有渲染。特别是GPU Compositor需要有一个DPI Awareness与您的主应用程序匹配的需求。目前,所使用的默认CefSharp.BrowserSubprocess.exe值为Per Monitor DPI Aware。作为一种解决方法,请使用disable-gpu-compositing命令行arg,并将DPI Awareness使用您的主应用程序进程的,而不是由所DPI Awareness指定的GPU Process(用于GPU Compositing)。禁用GPU Compositing可能会对性能产生影响,当https://github.com/cefsharp/CefSharp/issues/2927完成后,将有可能以编程方式设置DPI Awareness使用的CefSharp.BrowserSubprocess.exe

代码语言:javascript复制
var settings = new CefSettings();
settings.CefCommandLineArgs.Add("disable-gpu-compositing");
Cef.Initialize(settings);

另外,您可以尝试使用force-device-scale-factor 命令行标志。

代码语言:javascript复制
var settings = new CefSettings();
settings.CefCommandLineArgs.Add("force-device-scale-factor", "1");
Cef.Initialize(settings);
  • https://msdn.microsoft.com/zh-cn/library/windows/desktop/dn469266(v=vs.85).aspx 这是一篇很长的MSDN文章,但是如果您的应用程序需要在较高的环境下运行,则有必要阅读DPI显示。
  • Improving the high-DPI experience in GDI based Desktop Apps – Windows Developer Blog
  • https://docs.microsoft.com/zh-cn/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process#setting-default-awareness-with-the-application-manifest

多线程消息循环

CefSharp默认使用setting.MultiThreadedMessageLoop = true。这使您的应用程序能够非常快速地启动并运行,需要注意一些重要的事情,但这可能并不适合所有人。

  • 对消息泵使用其他线程。
  • CEF UI线程与应用程序的UI线程不同,这可能导致消息处理中的某些断开连接。
    • 一个示例是打开菜单,然后在浏览器控件中单击并使菜单保持打开状态。
  • 低级Win32消息不会在CEF和之间传播WinForms

可以将CEF集成到应用程序的现有消息循环中。将CEF集成到现有消息循环中的一种非常简单的实现涉及在UI线程上使用每秒调用30/60次的计时器。

代码语言:javascript复制
var settings = new CefSettings();
settings.MultiThreadedMessageLoop = false; //This defaults to true

Cef.Initialize(settings);

- For WPF use [DispatcherTimer](https://docs.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatchertimer?view=netframework-4.8)
- For WinForms use [Timer](https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.timer?view=netframework-4.8)

//Set the timer Interval to 30 times per second, can be increased to 60 if required
//For WPF
timer.Interval = TimeSpan.FromMilliseconds(1000 / 30);
//For WinForms
timer.Interval = 1000 / 30;
timer.Tick  = UiThreadTimerTick;
timer.Start();

//Before closing your app
//Calling Cef.DoMessageLoopWork() after Cef.Shutdown has been called will result in
//an access violation, make sure you stop you timer first.
timer.Tick -= UiThreadTimerTick;
timer.Stop();

private void UiThreadTimerTick(object sender, EventArgs e)
{
    //Must be called on the UI Thread.
    Cef.DoMessageLoopWork();
}

更高级的选项包括设置CefSettings.ExternalMessagePump = true;。和实现 IBrowserProcessHandler.OnScheduleMessagePumpWork。这样可以CEF在需要执行工作时发出通知,在某些情况下,这可能会使您的应用程序响应速度更快。有关其他详细信息,请参见https://github.com/cefsharp/CefSharp/issues/1748。项目源代码中包含更多高级示例。

您可以在使用时挂接消息循环MultiThreadedMessageLoop,尽管这很复杂。项目源代码包含一个示例,网址为https://github.com/cefsharp/CefSharp/blob/v53.0.0/CefSharp.WinForms.Example/BrowserTabUserControl.cs#L224 您可以使用此方法获取Win32鼠标消息。

弹出窗口

一个常见的请求是控制弹出窗口的创建。实施ILifeSpanHandler.OnBeforePopup以控制如何创建弹出窗口。要完全取消弹出窗口的创建return true;

代码语言:javascript复制
bool ILifeSpanHandler.OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser)
{
    //Set newBrowser to null unless you're attempting to host the popup in a new instance of ChromiumWebBrowser
    newBrowser = null;

    return true; //Return true to cancel the popup creation
}

您可以取消弹出窗口的创建,然后以新的形式打开URL ChromiumWebBrowser使用此方法您选择实例中。重要的是要注意,使用此方法将不存在父子关系。因此,一般不建议这样做。

实验选项1:允许您使用中的newBrowser参数托管弹出窗口OnBeforePopup。有一些已知问题(在GitHub项目上搜索)。如果您使用此方法遇到问题,那么您将必须承担责任并通过CEF项目解决该问题。同样重要的是要注意LoadingStateChangedetc不会被弹出窗口调用。如果使用此方法,请实现相关的处理程序。

实验选项2:IWindowInfo.SetAsChild用于指定父句柄。要在WPF中使用此功能,您将需要使用WinForms主机。使用此方法,您将需要处理move和resize事件。大致如下所示:

  • 抓住IBrowserHost从新创建的IBrowser实例表示弹出然后订阅窗口移动的通知和呼叫NotifyMoveOrResizeStarted
  • SetWindowPos大小更改时在浏览器上调用HWND(隐藏时设置为0,0以停止渲染)

尽管它们是实验性的,但是在项目源中有一些示例,并且不能保证它们在起作用。备选案文2的例子不完整,尽管有报告表明它运作良好,尽管此人从未提供过有效的例子。

JavaScript整合

1.如何从.NET调用JavaScript方法?

代码语言:javascript复制
//There are a number of extension methods that simplify execution, they all work on the main frame
//They all exists in the CefSharp.WebBrowserExtensions class, make sure you add "using CefSharp;"
browser.ExecuteScriptAsync("document.body.style.background = 'red';");

// When executing multiple statements, group them together in an IIFE
// https://developer.mozilla.org/en-US/docs/Glossary/IIFE
// For Google.com pre-populate the search text box and click the search button
browser.ExecuteJavaScriptAsync("(function(){ document.getElementsByName('q')[0].value = 'CefSharp Was Here!'; document.getElementsByName('btnK')[0].click(); })();");

如果您的网页包含多个框架,则可以在子框架上执行脚本

代码语言:javascript复制
browser.GetBrowser().GetFrame("SubFrame").ExecuteJavaScriptAsync("document.body.style.background = 'red';");

我什么时候可以开始执行JavaScript?

JavaScript只能在V8Context中执行。在IRenderProcessMessageHandler.OnContextCreatedIRenderProcessMessageHandler.OnContextReleased提供时,可以执行JavaScript的一个边界。OnContextCreated/OnContextReleased将每帧调用一次,用于frame.IsMain检查主帧。

尝试开始访问DOM是很诱人的OnFrameLoadStart,而V8Contextwill已创建,您将能够执行DOM尚未完成加载的脚本。如果您需要DOM尽早访问,请订阅DOMContentLoadedJavaScript下面是一些执行示例。

代码语言:javascript复制
browser.RenderProcessMessageHandler = new RenderProcessMessageHandler();

public class RenderProcessMessageHandler : IRenderProcessMessageHandler
{
  // Wait for the underlying JavaScript Context to be created. This is only called for the main frame.
  // If the page has no JavaScript, no context will be created.
  void IRenderProcessMessageHandler.OnContextCreated(IWebBrowser browserControl, IBrowser browser, IFrame frame)
  {
    const string script = "document.addEventListener('DOMContentLoaded', function(){ alert('DomLoaded'); });";

    frame.ExecuteJavaScriptAsync(script);
  }
}

//Wait for the page to finish loading (all resources will have been loaded, rendering is likely still happening)
browser.LoadingStateChanged  = (sender, args) =>
{
  //Wait for the Page to finish loading
  if (args.IsLoading == false)
  {
    browser.ExecuteJavaScriptAsync("alert('All Resources Have Loaded');");
  }
}

//Wait for the MainFrame to finish loading
browser.FrameLoadEnd  = (sender, args) =>
{
  //Wait for the MainFrame to finish loading
  if(args.Frame.IsMain)
  {
    args.Frame.ExecuteJavaScriptAsync("alert('MainFrame finished loading');");
  }
};

有关执行的一些注意事项JavaScript

  • 脚本在框架级别执行,并且每个页面至少有一个框架(MainFrame)。
  • IWebBrowser.ExecuteScriptAsync扩展方法是为了向下兼容性,可以使用它作为快捷方式来执行js在主框架上。
  • 如果框架不包含JavaScript,则不会V8Context创建任何框架。
  • 对于没有上下文的框架,一旦框架加载完成,就可以使用创建V8Context IFrame.ExecuteJavaScriptAsync
  • DOM不会已完成加载时OnFrameLoadStart被触发
  • IRenderProcessMessageHandler.OnContextCreated/OnContextReleased 仅针对主机。

2.如何调用返回结果的JavaScript方法?

如果您需要调用(评估)返回值的JavaScript,请使用以下方法之一:

代码语言:javascript复制
//An extension method that evaluates JavaScript against the main frame.
Task<JavascriptResponse> response = await browser.EvaluateScriptAsync(script);
//Evaluate javascript directly against a frame
Task<JavascriptResponse> response = await frame.EvaluateScriptAsync(script);

//An extension method that evaluates Javascript Promise against the main frame.
//Uses Promise.resolve to return the script execution into a promise regardless of the return type
//This method differs from EvaluateScriptAsync in that your script **must return** a value
//Examples below
Task<JavascriptResponse> response = await browser.EvaluateScriptAsPromiseAsync(script);

JavaScript代码是异步执行的,因此返回Task,其中包含错误消息,结果和success(bool)标志。这是评估时需要了解的基本知识JavaScript

  • 确保您阅读了 何时可以开始执行JavaScript?。
  • 脚本在框架级别执行,并且每个页面至少有一个框架(MainFrame)。
  • 脚本在渲染过程中执行,并通过进行传输IPC返回出于性能原因所需的数据
  • 支持原始数据类型:int,double,date,bool和string。
  • 在某种程度上支持对象,并且将以形式返回对象。支持IDictionary<string, object>使用dynamic关键字来简化访问属性值的过程。
  • 您不能直接返回DOM Element(或任何具有循环引用的元素),需要创建一个仅包含您需要返回的信息的新对象。
  • 支持包含上面列出的原语和对象的数组,它们将以形式返回IList<object>
  • Array Like像HTMLCollection这样的对象不能直接使用Array.from返回并返回数组
  • 可以返回的对象图的复杂度受到限制(当前不支持带有循环引用的图),在这种情况下,您可能需要使用JavaScriptJSON.stringify()方法将JavaScript对象转换为JSON字符串,然后将该字符串返回您的.NET代码。然后,您可以使用类似JSON.NET的方式将该字符串解码为.NET对象。有关更多详细信息,请参见MDN JSON.stringify。有关与结合使用的一些指导,请参见javascript – How to JSON.stringify a dom element? – Stack Overflow。JSON.stringifyHTMLElement
代码语言:javascript复制
//Start with something simple, the following will return the value 2 as type int
//Don't use the `return` keyword
Task<JavascriptResponse> response = await frame.EvaluateScriptAsync("1   1");
//A javascript IFFE will be evaluated and it's result returned.
//https://developer.mozilla.org/en-US/docs/Glossary/IIFE
//If you want to execute multiple lines of javascript then an IIFE is recommended to
//avoid any variable scoping issues
var script = @"(function() { let val = 1   1; return val; })();";
Task<JavascriptResponse> response = await frame.EvaluateScriptAsync(script);
//If your script uses a Promise then you must use the EvaluateScriptAsPromiseAsync method, it differs slightly
//in that you must return the value.
//The following will return a Promise that after one second resolves with a simple objec
var script = "return new Promise(function(resolve, reject) { setTimeout(resolve.bind(null, { a: 'CefSharp', b: 42, }), 1000); });"
Task<JavascriptResponse> javascriptResponse = await browser.EvaluateScriptAsPromiseAsync(script);
//You can access the object using the dynamic keyword for convenience.
dynamic result = javascriptResponse.Result;
var a = result.a;
var b = result.b;
//EvaluateScriptAsPromiseAsync calls Promise.resolve internally so even if your code doesn't
//return a Promise it will still execute successfully.
var script = @"return (function() { return 1   1; })();";
Task<JavascriptResponse> response = await frame.EvaluateScriptAsPromiseAsync(script);
// An example that gets the Document Height
var task = frame.EvaluateScriptAsync("(function() { var body = document.body, html = document.documentElement; return  Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); })();");
//Continue execution on the UI Thread
task.ContinueWith(t =>
{
if (!t.IsFaulted)
{
var response = t.Result;
EvaluateJavaScriptResult = response.Success ? (response.Result ?? "null") : response.Message;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
//HTMLElement/HTMLCollection Examples
//As stated above, you cannot return a HTMLElement/HTMLCollection directly.
//It's best to return only the data you require, here are some examples of using Array.from to convert a HTMLCollection  into an array of objects
//which can be returned to your .Net application.
//Get all the span elements and create an array that contains their innerText
var script = @"Array.from(document.getElementsByTagName('span')).map(x => ( x.innerText));";
Task<JavascriptResponse> response = await frame.EvaluateScriptAsync(script);
//Get all the a tags and create an array that contains a list of objects 
//Second param is the mapping function
var script = @"Array.from(document.getElementsByTagName('a'), x => ({ innerText : x.innerText, href : x.href }));";
Task<JavascriptResponse> response = await frame.EvaluateScriptAsync(script);
//List of Links, click represents a function pointer which can be used to execute the link click)
//In .Net the https://cefsharp.github.io/api/86.0.x/html/T_CefSharp_IJavascriptCallback.htm is used
//to represent the function.
var script = @"Array.from(document.getElementsByTagName('a')).map(x => ({ innerText: x.innerText, click: x.click}));";
Task<JavascriptResponse> response = await frame.EvaluateScriptAsync(script);
//Execute the following against google.com to get the `I'm Feeling Lucky` button then click the button in .Net
//NOTE: This is a simple example, you could return an aggregate object consisting of data from multiple html elements.
const string script = @"(function()
{
let element = document.getElementsByName('btnI')[0];
let obj = {};
obj.id = element.id;
obj.nodeValue = element.nodeValue;
obj.localName = element.localName;
obj.tagName = element.tagName;
obj.innerText = element.innerText;
obj.click = element.click;
obj.attributes = Array.from(element.attributes).map(x => ({name: x.name, value: x.value}));
return obj;
})();";
var javascriptResponse = await browser.EvaluateScriptAsync(script);
dynamic result = javascriptResponse.Result;
var clickJavascriptCallback = (IJavascriptCallback)result.click;
await clickJavascriptCallback.ExecuteAsync();
//Dispose of the click callback when done
clickJavascriptCallback.Dispose();

3.如何将.NET类公开给JavaScript?

JavaScript的绑定(JSB)允许之间的通信JavaScript.Net。当前有两个不同的实现,Async版本和较旧的Sync版本。该Sync版本不再被积极开发,它依赖于WCF该版本不可用.Net Core或即将发布.Net 5.0

异步JavaScript绑定(JSB)

概要

  • 用于Native Chromium IPC在浏览器进程和渲染进程之间来回传递消息,因此非常快。
  • 仅基于消息methods是受支持的,Native Chromium IPC并且只能以某种async方式使用(Property不能以异步方式完成获取/设置)
  • Methods可以返回简单对象,structs并且classes受支持,仅将的副本Properties传输到JavaScript。就像webservice/ajax打电话一样,您会得到一个响应对象。
  • JavaScript callbacks通过IJavascriptCallback支持
  • 所有方法调用都是非阻塞的,并返回可以是的标准JavaScript Promiseawaited
  • 默认情况下CamelCase,方法名称会转换为(第一个字母转换为小写,MyFunction变为myFunction)。这可以通过在注册对象之前设置browser.JavascriptObjectRepository.NameConverter属性来配置,将其设置为null以禁用名称转换,详细示例如下。
  • JavaScript Binding API详细介绍了可用的不同方法。
  • Exceptions.Net被抓住,Promise意志将会被抓住rejected
  • 请参阅高级异步JavaScript绑定(JSB)Wiki,请确保您先阅读此内容

如果您不熟悉这里提供的所有Chromium内容,那么async programming可以参考一些非常有用的文章

  • Promise – JavaScript | MDN
  • https://developers.google.com/web/fundamentals/primers/async-functions
  • https://developers.google.com/web/fundamentals/primers/promises

绑定Async对象JavaScript

该CefSharp.BindObjectAsync方法被称为在Javascript结合的对象。CefSharp.BindObjectAsync返回一个Promise,当绑定的对象可用时,该Promise将被解决。在全局上下文(window对象的属性)中创建对象。如果调用时CefSharp.BindObjectAsync没有任何参数,则所有已注册的对象都将被绑定。名称绑定是更具描述性的选项。

简单的工作流程如下所示:

  • 第1步:创建一个您希望公开使用javascript的类(不要使用您的Form/WindowControl
  • 步骤2向您的课程注册一个实例JavaScriptObjectRepository
  • 步骤3使用您要注册的对象的名称来调用CefSharp.BindObjectAsync,例如CefSharp.BindObjectAsync("boundAsync");(对象只有在Promise解析后才可用。

仅支持方法。如果需要设置属性,则创建Get/Set方法。

步骤1建立课程

一个简单的类如下所示:

代码语言:javascript复制
public class BoundObject
{
public int Add(int a, int b)
{
return a   b;
}
}

步骤2向您的课程注册一个实例JavaScriptObjectRepository

该过程的第二部分是向中注册对象JavascriptObjectRepository(可通过browser.JavascriptObjectRepository属性访问)。您有两个选项用于在中注册对象.Net,第一个选项是预先注册的,通常在创建ChromiumWebBrowser实例后立即完成。第二个选项更加灵活,并允许Resolved在需要时放置对象。

第一种选择:

代码语言:javascript复制
//For async object registration (equivalent to the old RegisterAsyncJsObject)
browser.JavascriptObjectRepository.Register("boundAsync", new BoundObject(), true, BindingOptions.DefaultBinder);

第二种选择(首选)

代码语言:javascript复制
browser.JavascriptObjectRepository.ResolveObject  = (sender, e) =>
{
var repo = e.ObjectRepository;
if (e.ObjectName == "boundAsync")
{
BindingOptions bindingOptions = null; //Binding options is an optional param, defaults to null
bindingOptions = BindingOptions.DefaultBinder //Use the default binder to serialize values into complex objects
bindingOptions = new BindingOptions { Binder = new MyCustomBinder() }); //Specify a custom binder
repo.NameConverter = null; //No CamelCase of Javascript Names
//For backwards compatability reasons the default NameConverter doesn't change the case of the objects returned from methods calls.
//https://github.com/cefsharp/CefSharp/issues/2442
//Use the new name converter to bound object method names and property names of returned objects converted to camelCase
repo.NameConverter = new CamelCaseJavascriptNameConverter();
repo.Register("boundAsync", new BoundObject(), isAsync: true, options: bindingOptions);
}
};

要在.Net绑定对象时得到通知,JavaScript您可以订阅ObjectBoundInJavascript事件或ObjectsBoundInJavascript事件(这两个事件显然非常相似)。

代码语言:javascript复制
browser.JavascriptObjectRepository.ObjectBoundInJavascript  = (sender, e) =>
{
var name = e.ObjectName;
Debug.WriteLine($"Object {e.ObjectName} was bound successfully.");
};   

步骤3呼叫CefSharp.BindObjectAsync

代码语言:javascript复制
<script type="text/javascript">
(async function()
{
await CefSharp.BindObjectAsync("boundAsync");
//The default is to camel case method names (the first letter of the method name is changed to lowercase)
boundAsync.add(16, 2).then(function (actualResult)
{
const expectedResult = 18;
assert.equal(expectedResult, actualResult, "Add 16   2 resulted in "   expectedResult);
});
})();
</script>

进行CefSharp.BindObjectAsync调用时,将JavascriptObjectRepository查询以查看是否已注册具有给定名称的对象,如果未找到匹配的对象,ResolveObject则引发该事件。对于不带任何参数的CefSharp.BindObjectAsync调用,则如果已注册对象,则将它们全部绑定,如果未注册对象,则将ResolveObjectObjectName设置为All

本节仅介绍基础知识,还有许多高级选项,请查看高级异步Javascript绑定。

如果您想查看一个可行的示例,请查看CefSharp MinimalExample Javascript Binding Demo分支,特别是commit


同步JavaScript绑定(JSB)

这是一个遗产功能-任何正在创建新应用程序的人都在使用Async JavaScript Binding(JSB)实现,因为它正在积极开发中。该Sync版本仅会收到针对回归的错误修复。

  • 使用WCF通信服务(微软还没有为支持WCF.Net Core/.Net 5.0,有没有长远的未来WCF)。
  • 同时支持方法和属性
  • 呼叫以某种sync方式执行且正在阻塞,长时间运行的呼叫会阻塞Render Process并导致您的应用显示缓慢或无响应。
  • 支持半复杂的对象结构
  • 有时,该WCF服务无法完全关闭,并减慢了应用程序的关闭速度

绑定对象 JavaScript

绑定是由JavaScript启动的,当绑定的对象可用时,该CefSharp.BindObjectAsync方法将返回Promise解析的结果。在全局上下文(window对象的属性)中创建对象。如果调用时CefSharp.BindObjectAsync没有任何参数,则所有已注册的对象都将被绑定。名称绑定是更具描述性的选项。

简单的工作流程如下所示:

  • 第1步创建一个您希望向JavaScript公开的类(不要使用您的Form/WindowControl
  • 步骤2CefSharp.BindObjectAsync使用您要注册的对象的名称进行调用,例如CefSharp.BindObjectAsync("myObject");(对象只有在Promise解析后才能使用。
  • 步骤3JavaScriptObjectRepository

步骤1

代码语言:javascript复制
public class BoundObject
{
public string MyProperty { get; set; }
public void MyMethod()
{
// Do something really cool here.
}
public void TestCallback(IJavascriptCallback javascriptCallback)
{
const int taskDelay = 1500;
Task.Run(async () =>
{
await Task.Delay(taskDelay);
using (javascriptCallback)
{
//NOTE: Classes are not supported, simple structs are
var response = new CallbackResponseStruct("This callback from C# was delayed "   taskDelay   "ms");
await javascriptCallback.ExecuteAsync(response);
}
});
}
}

步骤2调用CefSharp.BindObjectAsync,下面Binding的对象示例如下所示:

注意这是一个两部分的过程,有关详细信息,请参见下面的示例

代码语言:javascript复制
<script type="text/javascript">
(async function()
{
await CefSharp.BindObjectAsync("boundAsync");
boundAsync.div(16, 2).then(function (actualResult)
{
const expectedResult = 8
assert.equal(expectedResult, actualResult, "Divide 16 / 2 resulted in "   expectedResult);
});
boundAsync.error().catch(function (e)
{
var msg = "Error: "   e   "("   Date()   ")";
});
})();
(async () =>
{
await CefSharp.BindObjectAsync("boundAsync");
boundAsync.hello('CefSharp').then(function (res)
{
assert.equal(res, "Hello CefSharp")
});
})();
CefSharp.BindObjectAsync("boundAsync2").then(function(result)
{
boundAsync2.hello('CefSharp').then(function (res)
{
assert.equal(res, "Hello CefSharp")
// NOTE the ability to delete a bound object
assert.equal(true, CefSharp.DeleteBoundObject("boundAsync2"), "Object was unbound");
assert.ok(window.boundAsync2 === undefined, "boundAsync2 is now undefined");
});
});
</script>

第三步

该过程的第二部分是将对象注册为JavascriptObjectRepository(可通过browser.JavascriptObjectRepository属性访问)。您有两个选项用于在中注册对象.Net,第一个选项是预先注册的,通常在创建ChromiumWebBrowser实例后立即完成。第二个选项更加灵活,并允许Resolved在需要时放置对象。

进行CefSharp.BindObjectAsync调用时,JavascriptObjectRepositoryis查询查询是否已指定给定名称的对象,如果找不到匹配的对象,ResolveObject则引发该事件。对于CefSharp.BindObjectAsync不带任何参数的调用,则如果已经注册了对象,则将它们全部绑定,如果没有注册任何对象,则将ResolveObjectObjectName设置为All

代码语言:javascript复制
//When a 
browser.JavascriptObjectRepository.ResolveObject  = (sender, e) =>
{
var repo = e.ObjectRepository;
if (e.ObjectName == "boundAsync2")
{
BindingOptions bindingOptions = null; //Binding options is an optional param, defaults to null
bindingOptions = BindingOptions.DefaultBinder //Use the default binder to serialize values into complex objects,
bindingOptions = new BindingOptions { Binder = new MyCustomBinder() }); //No camelcase of names and specify a custom binder
//For backwards compatability reasons the default NameConverter doesn't change the case of the objects returned from methods calls.
//https://github.com/cefsharp/CefSharp/issues/2442
//Use the new name converter to bound object method names and property names of returned objects converted to camelCase
repo.NameConverter = new CamelCaseJavascriptNameConverter();
repo.Register("bound", new BoundObject(), isAsync: false, options: bindingOptions);
}
};

在实际的JS代码中,您将使用这样的对象(默认为CamelCase Javascript Names,可通过JavascriptObjectRepository.NameConverter进行控制,请参见上面的示例)。

代码语言:javascript复制
bound.myProperty; // use this syntax to access the property
bound.myMethod(); // use this to call the method.
bound.testCallback(callback); //Pass a function in to use as a callback

请注意:

  • 不要注册表格/窗口/控件。如果需要,创建一个类并代理调用。
  • 默认情况下,方法和属性都更改为camelCase(即首字母小写)以使其在JavaScript代码中自然使用。禁用将browser.JavascriptObjectRepository.NameConverter设置为null
  • 属性支持复杂对象(如果适用),因此您现在可以执行bound.subObject.myFunction()bound.subObject.myProperty = 1
  • 现在可以通过IBinder接口实现对函数的复杂对象支持,您可以实现自己的或使用DefaultBinder例如repo.Register("bound", new BoundObject(), BindingOptions.DefaultBinder);

RegisterAsyncJsObject

此方法已删除。请参阅异步JavaScript绑定(JSB)。

RegisterJsObject

这已被删除。请参阅同步JavaScript绑定(JSB)。

Adobe Flash Player(Pepper Flash)

注意:现在不建议使用Flash,并且Chromium将删除支持,有关更多详细信息,请参阅Flash Roadmap。从版本开始,81默认情况下现在已禁用它,请参见https://github.com/cefsharp/CefSharp/issues/3048#issuecomment-592263009

CefSharp可以从Adobe下载可以自动发现并加载的Pepper Flash的系统范围安装。从下拉列表中选择FP for Opera and Chromium-PPAPI版本。要测试Flash是否正常运行,只需加载Adobe – Flash Player。

注意首次打开Flash时,将短暂显示控制台窗口,显示NOT SANDBOXED。有一个问题在Chromium问题跟踪,但不幸的是Google已经将其标记为WontFix。一些聪明的人一起激活成功教程了一些解决方法。它们很复杂,我从未尝试过。请访问https://github.com/cefsharp/CefSharp/issues/1259中的链接以获取详细信息。

屏幕外渲染(OSR)

WPF和OffScreen版本使用OffScreen Rendering(OSR)渲染模式。在OSR模式每帧被渲染到缓冲器中,然后在屏幕上或者绘制为在WPF的情况下,或提供作为BitmapOffScreen

WPF

对于WPF控件,用户输入(鼠标单击/移动和按键)将通过IBrowserHost界面上的方法转发到基础浏览器。可以访问每个Bitmap渲染的对象。

应特别注意ChromiumWebBrowser在内托管ViewBox。这远非理想,因为渲染了每一帧,然后进行后处理来调整图像的大小/缩放。这会严重影响性能,并且通常会降低质量(通常很模糊)。您可以使用调整调整大小的质量RenderOptions.SetBitmapScalingMode。最好避免使用ViewBox。您可以通过调整来缩放浏览器中包含的内容ZoomLevel,这是迄今为止性能最高的选项。

屏幕外

对于CefSharp.OffScreen包装,将每个帧渲染到Bitmap并暴露以供使用。如果希望通过键盘或鼠标与浏览器进行交互,则可以使用IBrowser主机界面上的方法。模拟按键和鼠标单击/移动可能非常复杂。您可以使用WPF控件作为开始示例,因为它使用相同的方法(添加调试以查看所需的事件顺序)。按键和鼠标的点击/移动通常由多个部件,up/down与许多其它可能的组合。

用户代理

您可以通过设置CefSettingsBase.UserAgent Property来指定自定义UserAgent

UserAgent可以在运行时使用DevTools协议来改变看到c# – CefSharp Change UserAgent in real time – Stack Overflow的一个例子。

您可以在中修改User-AgentHTTP标头IResourceRequestHandler.OnBeforeResourceLoad,这对于每个请求都需要完成。它不做的是UserAgent将浏览器报告更改为JavaScript。

代码语言:javascript复制
public class CustomResourceRequestHandler : CefSharp.Handler.ResourceRequestHandler
{
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
{
//Set the header by name, override the existing value
request.SetHeaderByName("user-agent", "MyBrowser CefSharp Browser", true);
return CefReturnValue.Continue;
}
}
public class CustomRequestHandler : CefSharp.Handler.RequestHandler
{
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
{
//Where possible only intercept specific Url's
//Load https://www.whatismybrowser.com/detect/what-is-my-user-agent in the browser and you'll
//see our custom user agent
if (request.Url == "https://www.whatismybrowser.com/detect/what-is-my-user-agent")
{
return new CustomResourceRequestHandler();
}
//Default behaviour, url will be loaded normally.
return null;
}
}
browser.RequestHandler = new CustomRequestHandler();

开发工具

您可以从CefSharp中打开DevTools。并非所有功能都起作用。任何缺少的东西都需要在CEF中实施。

代码语言:javascript复制
browser.ShowDevTools();

您可以将Chrome连接到正在运行的实例。这可能会为您提供更多选项(不幸的是,并非Chrome中存在所有选项)。

代码语言:javascript复制
var settings = new CefSettings();
settings.RemoteDebuggingPort = 8088;
Cef.Initialize(settings);

http://localhost:8088在Chrome中打开。

屏幕截图

底层的CEF Web浏览器不是特别适合于截屏。以下是一些注意事项和警告:

屏幕外/ WPF

无论OffscreenWPF使用的屏幕外着色(OSR),其中每一帧被渲染为位图。它仍然是一个网络浏览器,并不是特别适合这种情况。这里有一些注意事项:

  • 降低帧频以使其更容易捕获帧可能值得考虑
  • 页面加载完成后,您需要等待一段时间,以允许浏览器呈现
  • 当前尚无确定网页何时完成渲染的方法(Flash,动态内容,动画等功能,甚至像移动鼠标或滚动之类的简单任务也将导致渲染新帧)。
  • 一种确定何时大致完成渲染的激活成功教程方法是让计时器在每次渲染帧时重置,如果没有其他帧渲染,则计时器将归档(不理想)

WinForms

这是在Windows下拍摄屏幕快照的一些示例

  • 使用BitBlt方法捕获当前视图
  • 使用滚动捕获整页

Win32内存不足

使用32bit版本时,请确保您的应用程序支持大地址(处理大于2gb的地址)

根据http://magpcss.org/ceforum/viewtopic.php?f=6&t=15120#p34802中的建议,现在看来有必要在32位应用程序运行时在应用程序可执行文件上设置“大地址感知”链接器设置。遇到高内存负载。

https://msdn.microsoft.com/zh-CN/library/wz223b1z.aspx

CefSharp附带的默认x86 SubProcess可以识别大型地址,您也应该使应用程序识别。

将大地址感知链接器设置应用于可执行文件后,如果仍然遇到完全相同的问题,请在http://magpcss.org/ceforum/viewtopic.php?f=6&t=15120上讨论您的问题。

使用PostData加载URL

有两种加载URL的方法Post Data,第一种是修改现有的Request。在下面的示例中,Request如果我们访问http://httpbin.org/post,我们会将发布数据添加到

代码语言:javascript复制
public class CustomRequestHandler : CefSharp.Handler.RequestHandler
{
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
{
//Where possible only intercept specific Url's
//Load http://httpbin.org/post in the browser and you'll
//see the post data
if (request.Url == "http://httpbin.org/post")
{
return new CustomResourceRequestHandler();
}
//Default behaviour, url will be loaded normally.
return null;
}
}
public class CustomResourceRequestHandler : CefSharp.Handler.ResourceRequestHandler
{
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
{
//Modify the request to add post data
//Make sure to read https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
var postData = new PostData();
postData.AddData("test=123&data=456");
request.Method = "POST";
request.PostData = postData;
//Set the Content-Type header to whatever suites your requirement
request.SetHeaderByName("Content-Type", "application/x-www-form-urlencoded", true);
//Set additional Request headers as required.
return CefReturnValue.Continue;
}
}
//Load http://httpbin.org/post in the browser to see the post data
browser = new ChromiumWebBrowser("http://httpbin.org/post");
browser.RequestHandler = new CustomRequestHandler();

第二种方法是使用IFrame.LoadRequest,仅当您首次成功执行导航后才能使用此方法。例如,您必须先导航到google.com,然后才能调用IFrame.LoadRequest。加载about:blank是不够的,因为它是特例,并且不会产生渲染过程。

代码语言:javascript复制
public void LoadCustomRequestExample()
{
var frame = browser.GetMainFrame();
//Create a new request knowing we'd like to use PostData
var request = frame.CreateRequest(initializePostData:true);
request.Method = "POST";
request.Url = "http://httpbin.org/post";
//Set AllowStoredCredentials so cookies are sent with Request
request.Flags = UrlRequestFlags.AllowStoredCredentials;
request.PostData.AddData("test=123&data=456");
frame.LoadRequest(request);
}

该browser.LoadUrlWithPostData扩展方法可用于简单的情况下,它会调用LoadRequest并针对具有相同的限制进行了成功的导航应用。

拼写检查

默认情况下CefSettings.Locale将指示使用哪个字典,默认为en-US。可以在运行中配置拼写检查的许多方面,enable/disable在运行中进行更改dictionary,甚至启用多个词典。使用RequestContext.SetPreference(有关RequestContext如何设置首选项的详细信息,请参阅本文档的部分)。

只能使用spellcheck.dictionaries首选项(重要的是使用复数版本) 动态地更改拼写检查https://bitbucket.org/chromiumembedded/cef/issues/2222/spell-checking-language-cannot-be-changed#comment-38338016

这是一些有用的链接

http://magpcss.org/ceforum/viewtopic.php?f=6&t=14911&p=33882&hilit=spellcheck#p33882 https://cs.chromium.org/chromium/src/components/spellcheck/browser/pref_names.cc?type = cs&q =%22spellcheck.dictionary%22&l = 11 https://cs.chromium.org/chromium/src/components/spellcheck/browser/pref_names.cc?type=cs&q="spellcheck.dictionary"&l=15

并非所有语言都支持拼写检查,请参阅CEF Forum • CEF spellcheck.dictionaries for Chinese Language

Web组装

在较新的版本中默认为启用,请参见WebAssembly – Chrome Platform Status

对于较旧的版本,您需要手动启用,WebAssembly请参见chromiumembedded / cef / issues / #2101 – Add WebAssembly support — Bitbucket

settings.javascript_flags 转换为 settings.JavascriptFlags = "--expose-wasm";

异常处理

捕获非托管异常非常困难,并且CEF可能处于损坏状态,需要您的应用程序终止并重新启动。由于这是一个一般的编程主题,因此不在本文的CefSharp特别讨论范围之内,因此有一些资源可帮助您开始自己进行研究。

http://stackoverflow.com/questions/233255/how-does-setunhandledexceptionfilter-work-in-net-winforms-applications https://msdn.microsoft.com/zh-CN/library/windows/desktop/ms680634(v = vs.85).aspx https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Application.cs,8243b844777a16c3 https://referencesource.microsoft.com/#System。 Windows.Forms / winforms / Managed / System / WinForms / Application.cs,3192

在混合的本地/ CLR环境中捕获未处理的异常 Capturing unhandled exceptions in a mixed native/CLR environment – Ivan Krivyakov

依赖检查

CefSharp 有一个非常简单的类,用于检查是否存在所有相关的非托管资源。

代码语言:javascript复制
//Perform dependency check to make sure all relevant resources are in our output directory.
//https://cefsharp.github.io/api/86.0.x/html/M_CefSharp_Cef_Initialize_1.htm
Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);
//Manually check
//https://cefsharp.github.io/api/86.0.x/html/T_CefSharp_DependencyChecker.htm
DependencyChecker.AssertAllDependenciesPresent(cefSettings.Locale, cefSettings.LocalesDirPath, cefSettings.ResourcesDirPath, cefSettings.PackLoadingDisabled, cefSettings.BrowserSubprocessPath);

这不是100%万无一失的,如果您遇到问题并且所有资源都存在,请禁用依赖性检查。在某些情况下,它不起作用。

https://github.com/cefsharp/CefSharp/wiki/Output-files-description-table-(Redistribution)

多媒体(音频/视频)

CEF并且随后CefSharp仅支持免费提供的音频和视频编解码器。要查看CefSharp您所使用的版本支持,请在ChromiumWebBrowser实例中打开HTML5test – How well does your browser support HTML5?。

  • MP3专利已过期,因此受65.0.0病房支持。
  • H264/AAC被归类为Proprietary Codecs并且不受支持,它们要求您获得许可。喜欢Netflix/Twitter/Instagram使用的网站H264,其视频因此无法播放。请参阅Information about the H.264 patent license — Free Software Foundation — Working together for free software,以获取Free Software Foundation有关该主题的一些评论。

CEF对的支持编译H264/AAC超出了该项目的范围。以下内容仅供参考,请不要寻求支持CEF

  • CEF Forum • [SOLVED] Build CEF with proprietary codecs support
  • https://github.com/cefsharp/CefSharp/issues/1934#issuecomment-279305821
  • https://github.com/mitchcapper/CefSharpDockerfiles

屏幕(虚拟)键盘

WinForms版本已经内置在屏幕键盘的支持,它已经报道,有时它并不总是正确弹出,使用disable-usb-keyboard-detect命令行参数 https://github.com/cefsharp/CefSharp/issues/1691#issuecomment-323603277报道解决这个问题。

WPF屏幕版本(虚拟)开始,它没有内置的支持,从版本开始,73VirtualKeyboardRequested事件现在会在您的应用程序应显示虚拟键盘时提供通知。不幸的是,Windows 7, 8.1 and 10由于没有.Net API显示虚拟键盘的功能,因此很难提供支持的默认实现。Windows 10 Only在https://github.com/cefsharp/CefSharp/commit/0b57e526158e57e522d46671404c557256529416中添加了一个示例如果您需要支持,Windows 8 and 10则https://github.com/maximcus/WPFTabTip可能会有用。对于Windows 7 Incorporating the Windows 7 onscreen keyboard into a WPF app – Stack Overflow有一些建议。

CefSharp中RequestHandler的处理流程

OnBeforeResourceLoad()->GetResourceResponseFilter()->OnResourceResponse()->OnResourceLoadComplete()

OnBeforeResourceLoad()

Called before a resource request is loaded. For async processing return CefSharp.CefReturnValue.ContinueAsync and execute CefSharp.IRequestCallback.Continue(System.Boolean) or CefSharp.IRequestCallback.Cancel

在加载资源请求之前调用。异步处理返回CefSharp.CefReturnValue.ContinueAsync并且执行CefSharp.IRequestCallback.Continue(System.Boolean)或者CefSharp.IRequestCallback.Cancel。所以,这一步可以修改请求。

GetResourceResponseFilter()

Called on the CEF IO thread to optionally filter resource response content. 在CEF IO线程上调用以选择性地筛选资源响应内容。

OnResourceResponse()

在收到资源响应时在CEF IO线程上调用。若要允许资源正常加载,则返回false。 重定向或重试资源修改请求(url、headers或post body)并返回true。 无法在此回调中修改响应对象。

OnResourceLoadComplete()

资源加载完成后调用,可以获取或修改响应数据 当资源加载完成时在CEF IO线程上调用

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/191660.html原文链接:https://javaforall.cn

0 人点赞