IBrowser,IFrame 和 IBrowserHost 對象.
從 .NET 中調用帶有返回結果的 Javascript 方法..
創建一個新的 WPF 項目,目標框架爲 .NET Framework 4.5.2,本項目將使用 CefSharp 3 庫,該庫僅支持 x86 和 x64 應用程序,這意味着項目必須指定編譯目標,不支持 Any Cpu。創建項目後,首先打開配置管理器,Solution -> Configuration Manager:
由默認 Debug 配置創建新的 x86 和 x64 的編譯配置:
完成配置創建後,引入 CefSharp.Wpf Nuget 包:
轉存失敗重新上傳取消
現在,保存所有更改並關閉 Visual Studio,因爲 CefSharp 需要完全重啓以加載其依賴項。重啓打開 Visual Studio 項目,編譯。
CEF 運行時包含多個進程
- browser 進程: 主進程,負責創建窗口,渲染和網絡訪問,該進程大部分情況下等同於「宿主應用程序」所在的進程,且執行大部分邏輯。
- render 進程: 渲染和與 Javascript 交互(如 JS 對象綁定)由另一個 render 進程處理,進程模型將會爲每一個單獨的 Origin([scheme] + [domain]) 開闢一個 render 進程。
- plugin 進程: 負責處理諸如 Flash 等插件
- gpu 進程: 處理渲染加速等
不同級別的進程可管理多個線程,browser 進程包含了以下線程:
- UI 線程: browser 進程的主線程,默認情況下 CefSharp 使用 setting.MultiThreadedMessageLoop = true 設置,這使得 CEF 的 UI 線程與宿主應用程序的 UI 線程使用不同的線程。
- IO 線程: 在 browser 進程中負責處理 IPC 和網絡消息
- File 線程: 在 browser 進程中負責與「文件系統」交互
- Renderer 線程: render 進程的主線程
Initialize 和 Shutdown
Initialize 方法僅允許每個應用程序調用一次,該方法用於初始化底層 CEF 庫,可以顯式或隱式的調用該方法:
- 隱式調用: 首次創建 ChromiumWebBrowser 實例時,將檢查 CEF 是否已被初始化,如果沒有,則使用默認配置調用初始化方法
- 顯式調用: 當希望指定自定義配置時,可顯式調用 CEF.Initialize(CefSettings settings) 方法
ChromiumWebBrowser 的 WPF 和 Winform 的實現都在對應的 Exit 事件中默認調用了 Shutdown 方法。要禁用這一行爲,可在任一 ChromiumWebBrowser 實例創建之前設置 CefSharpSettings.ShutdownOnExit = false。
Initialize 和 Shutdown 都必須在應用程序的主線程中調用,如果在另外的線程中調用它們,應用程序將會掛起。
在 CefSharp.OffScreen 應用中,在應用程序退出前必須顯式調用 Cef.Shutdown() 方法。
CefSettings 覆蓋了應用程序級別的配置,一些常見的配置包括:
- BrowserSubprocessPath: 啓用子進程的路徑,通常不建議更改
- MultiThreadedMessageLoop: 默認爲 true,也可以設置爲 false 並將 Cef 集成至現有應用程序的消息泵中,參考 https://github.com/cefsharp/CefSharp/issues/1748
- CommandLineArgsDisabled: 設置爲 true 以禁用使用標準的 Chromium 命令行參數控制瀏覽器行爲
- CachePath: 緩存數據在本機上存放的路徑,若爲空,則使用臨時緩存和內存緩存。僅當該設置不爲空時,HTML5 數據庫(如 localStorage)纔會被持久化
- Locale: 傳給 Blink 的本地化信息,en-US 爲默認值,可由命令行參數 lang 控制
- LogFile: Debug 日誌使用的持久化目錄與文件名。./debug.log 爲默認值。可由命令行參數 log-file 控制
- LogSeverity: 類似於 LogLevel,僅當等於或高於該級別的日誌將會記錄,可由 log-severity 命令行參數控制,可接收 verbose、info、warning、error、error-report 和 disabled 值。
- ResourceDirPath: 資源路徑,可由 resources-dir-path 命令行參數控制
- LocalesDirPath: 本地化信息路徑,可由 locales-dir-path 命令行參數控制
- RemoteDebuggingPort: 可在 1024 - 65535 之間取值,用以啓用遠程調試,可由另一個 CEF 或谷歌瀏覽器訪問,可由 remote-debugging-port 命令行參數控制。
BrowserSettings 覆蓋 ChromiumWebBrowser 實例級別的配置,具體參考 BrowserSettings 類型
IBrowser,IFrame 和 IBrowserHost 對象
IBrowser 和 IFrame 對象用於向瀏覽器發送命令及從回掉函數中接收狀態信息,每一個 IBrowser 對象都至少包含一個 IFrame 對象代表頂層窗口。即,如果一個瀏覽器窗口加載了兩個 <iframe> 元素將會包含 3 個 IFrame 對象(一個頂層 IFrame 和兩個 <iframe>)。
在一個 IBrowser 對象中加載 url:
1 |
browser.MainFrame.LoadUrl(url); |
IBrowserHost 代表更底層的瀏覽器方法,例如:
- Print()
- ShowDevTools()
- CloseDevTools()
- StartDownload(string url)
CefSharp 提供了一系列 Handler 對象,這些對象以 .NET 實現封裝了瀏覽器的常見行爲和事件。例如,當需要知道一個頁面是否加載完成時,可偵聽 LoadingStateChanged 事件。多數 Handler 的方法都提供了異步實現,所有 Handler 都遵循一個實現模式: 返回 bool 的方法意味着詢問你是否需要自行處理,返回 false 表示使用默認實現,返回 true 表示由開發人員提供自定義實現。IWebBrowser 定義了以下常見的 IXXXHandler:
- IDownloadHandler: 下載文件,進度通知,暫停,取消等
- IRequestHandler: 處理導航、重定向、資源加載通知等
- IDialogHandler: 文件對話框通知
- IDisplayHandler: 地址欄變更,狀態信息,控制檯信息,全屏模式更改通知等
- ILoadHandler: 加載狀態信息、事件,彈出框通知信息
- ILifeSpanHandler: 彈出框彈出及關閉事件
- IKeyboardHandler: 鍵盤事件
- IJsDialogHandler: javascript 消息框/彈出框
- IDragHandler: 拖拽事件
- IContextMenuHandler: 定製右鍵菜單
- IFocusHandler: 焦點相關通知
- IResourceHandlerFactory: 攔截資源請求相關
- IGeolocationHandler: 地理位置請求相關
- IRenderProcessMessageHandler: 從 render 進程發送的自定義 CefSharp 消息相關
- IFindHandler: 與查找通知相關
通常,使用定製化的 Handler 需要在創建 ChromiumWebBrowser 之後立即對相應的 Handler 賦值:
1 |
browser.DownloadHandler = new DownloadHandler(); |
各個 Handler 的具體用法參見 Handlers
CEF 遵循使用同樣的命令行命令來設置代理,如果代理有認證要求,可通過 IRequestHandler.GetAuthCredentials() 方法提供。
請求上下文(RequestContext)用於隔離 IBrowser 實例,包括可提供單獨的緩存路徑、單獨的代理配置、單獨的 Cookie 管理器及其他相關設置。RequestContext 有以下關鍵點:
- 默認情況下,提供一個全局 RequestContext,由多個 IBrowser 實例共享設置
- 可在運行時通過 Preferences 更改某些設置,這種情況下不要使用命令行工具
- Winform 在創建 IBrowser 實例之後立即設置 RequestContext;OffScreen 則須將 RequestContext 作爲構造函數參數傳入;WPF 則在 InitialComponent() 方法之後設置
- Plugin 加載通知通過 IRequestContextHandler 接口處理
- 設置 RequestContextSettings.CachePath 以持久化 Cookie,localStorage 等數據
如果應用程序經常顯示爲黑屏,則很有可能需要開啓「高 DPI」支持。要啓用「高 DPI」支持,需要在對應應用程序的 app.manifest 文件中加入相應的節點以通知 Windows 該程序支持「高 DPI」,
- WinForm: 加入 app.manifest 並在第一時間調用 Cef.EnableHighDPISupport()
- WPF: 加入 app.manifest
- OffScreen: 加入 app.manifest
app.manifest 類似於以下內容:
1 |
<asmv3:application> |
Javascript 集成
Javascript 方法僅能在一個 V8Context 中執行,IRenderProcessMessageHandler.OnContextCreated 和 IRenderProcessMessageHandler.OnContextReleased 爲 Javascript 代碼的執行劃定了一個清晰的邊界。通常:
- Javascript 在 Frame 級別執行,每個頁面至少包含一個 Frame
- IWebBrowser.ExecuteScriptAsync 方法保留下來用於向後兼容,該方法可作爲快速執行 Javascript 方法的入口
- OnFrameLoadStart 事件觸發時 DOM 還沒有加載完成
- IRenderProcessMessageHandler.OnContextCreated 和 IRenderProcessMessageHandler.OnContextReleased 僅在主 Frame 上觸發。
1 |
browser.RenderProcessMessageHandler = new RenderProcessMessageHandler(); |
從 .NET 中調用帶有返回結果的 Javascript 方法
如果希望調用一個帶有返回結果的 Javascript 方法,使用 Task<JavascriptResponse> EvaluateScriptAsync(string script, TimeSpan? timeout),Javascript 異步執行並最終返回一個 JavascriptResponse 類型的實例,包含錯誤消息,執行結果和是否成功的標誌。
1 |
// Get Document Height |
返回結果的類型僅支持基元類型,如 int,bool,string 等,目前還沒有一個合適的方法將 Javascript 類型映射爲一個 .NET 類型,但可藉助 JSON.toStringify() 返回 JSON 字符串的形式來組織複雜類型,之後在 .NET 中利用 JSON.NET 將該字符串反序列化爲相應的類型。
Javascript 綁定(JSB)實現了 Javascript 和 .NET 之間的通信,目前該方法提供了異步和同步版本的實現
異步 Javascript Binding API
- 利用 Native Chromium IPC 在 browser 進程和 render 進程之間傳遞數據,非常快速
- 僅支持 .NET 方法,屬性的 Get/Set 不能以異步模型完成綁定
- 方法可返回基元類型,結構和複雜類型,這些類型僅屬性被傳遞自 Javascript,可將其看作 DTO
- 通過 IJavascriptCallback 接口支持 Javascript 回調函數
- 異步模型返回一個標準的 Javascript Promise
CefSharp 提供了 Javascript 對象 CefSharp 以支持 JSB,CefSharp.BindObjectAsync() 方法返回一個 Promise 對象,並在綁定對象可用時解析該對象,綁定的對象在全局上下文(window 對象的屬性)創建。
CefSharp.BindObjectAsync
CefSharp.BindObjectAsync(objectName, settings) 綁定對象:
1 |
async function bindBridgeObject(){ |
settings 由以下兩個屬性:
- NotifyIfAdlreadyBound: 若爲 true 則觸發 .NET IJavascriptObjectRepository.ObjectBoundInJavascript 事件,默認值 true
- IgnoreCache: 若爲 true,則忽略本地緩存
返回一個 JavaScript Promise 對象。
CefSharp.DeleteBoundObject
CefSharp.DeleteBoundObject(objectName),刪除匹配名稱匹配的綁定對象:
1 |
CefSharp.DeleteBoundObject("boundAsync"); |
返回 true/false
CefSharp.RemoveObjectFromCache
CefSharp.RemoveObjectFromCache(objectName),從緩存中移除匹配名稱的綁定對象:
1 |
CefSharp.RemoveObjectFromCache("boundAsync"); |
CefSharp.IsObjectCached
CefSharp.IsObjectCached(objectName) 檢查指定名稱的對象是否被緩存:
1 |
CefSharp.IsObjectCached("boundAsync") === true; |
返回 true/false
集成示例
- 在 .NET 中創建一個將要暴露給 Javascript 的類型:
1 |
public class BoundObject |
- 將上述類型的實例註冊至 IBrowser.JavascriptObjectRepository:
1 |
// set ConcurrentTaskExecution to true if you want do concurrent jobs at one time. |
- 在 Javascript 中調用 CefSharp.BindObjectAsync() 方法解析綁定對象:
1 |
await CefSharp.BindObjectAsync("boundAsync"); |
在 .NET 中接收綁定成功事件:
1 |
browser.JavascriptObjectRepository.ObjectBoundInJavascript += (sender, e) => |