在 .NET Framework 中集成 CefSharp

快速開始.

進程 Process 

線程 Threads 

Initialize 和 Shutdown.

Initialize.

Shutdown.

配置

CefSettings 

BrowserSettings 

IBrowser,IFrame 和 IBrowserHost 對象.

Handlers 

配置代理.

請求上下文.. 

高 DPI 顯示器支持..

Javascript 集成..

從 .NET 中調用 Javascript 方法.. 

從 .NET 中調用帶有返回結果的 Javascript 方法.. 

將一個 .NET 類型暴露給 Javascript 

 

快速開始

創建一個新的 WPF 項目,目標框架爲 .NET Framework 4.5.2,本項目將使用 CefSharp 3 庫,該庫僅支持 x86 和 x64 應用程序,這意味着項目必須指定編譯目標,不支持 Any Cpu。創建項目後,首先打開配置管理器,Solution -> Configuration Manager:
http://blog.frosthe.net/dotnet-cefsharp/ConfigManager1.png
由默認 
Debug 配置創建新的 x86 和 x64 的編譯配置:
http://blog.frosthe.net/dotnet-cefsharp/ConfigManager2.png
完成配置創建後,引入 
CefSharp.Wpf Nuget 包:
NuGet_CefSharp.WPF.pnguploading.4e448015.gif轉存失敗重新上傳取消http://blog.frosthe.net/dotnet-cefsharp/NuGet_CefSharp.WPF.png
現在,
保存所有更改並關閉 Visual Studio,因爲 CefSharp 需要完全重啓以加載其依賴項。重啓打開 Visual Studio 項目,編譯。

進程 Process

CEF 運行時包含多個進程

  • browser 進程: 主進程,負責創建窗口,渲染和網絡訪問,該進程大部分情況下等同於「宿主應用程序」所在的進程,且執行大部分邏輯。
  • render 進程: 渲染和與 Javascript 交互(如 JS 對象綁定)由另一個 render 進程處理,進程模型將會爲每一個單獨的 Origin([scheme] + [domain]) 開闢一個 render 進程。
  • plugin 進程: 負責處理諸如 Flash 等插件
  • gpu 進程: 處理渲染加速等

線程 Threads

不同級別的進程可管理多個線程,browser 進程包含了以下線程:

  • UI 線程: browser 進程的主線程,默認情況下 CefSharp 使用 setting.MultiThreadedMessageLoop = true 設置,這使得 CEF 的 UI 線程與宿主應用程序的 UI 線程使用不同的線程。
  • IO 線程: 在 browser 進程中負責處理 IPC 和網絡消息
  • File 線程: 在 browser 進程中負責與「文件系統」交互
  • Renderer 線程: render 進程的主線程

Initialize 和 Shutdown

Initialize

Initialize 方法僅允許每個應用程序調用一次,該方法用於初始化底層 CEF 庫,可以顯式或隱式的調用該方法:

  • 隱式調用: 首次創建 ChromiumWebBrowser 實例時,將檢查 CEF 是否已被初始化,如果沒有,則使用默認配置調用初始化方法
  • 顯式調用: 當希望指定自定義配置時,可顯式調用 CEF.Initialize(CefSettings settings) 方法

Shutdown

ChromiumWebBrowser 的 WPF 和 Winform 的實現都在對應的 Exit 事件中默認調用了 Shutdown 方法。要禁用這一行爲,可在任一 ChromiumWebBrowser 實例創建之前設置 CefSharpSettings.ShutdownOnExit = false

Initialize 和 Shutdown 都必須在應用程序的主線程中調用,如果在另外的線程中調用它們,應用程序將會掛起。

在 CefSharp.OffScreen 應用中,在應用程序退出前必須顯式調用 Cef.Shutdown() 方法。


配置

CefSettings

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

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)

Handlers

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」支持。要啓用「高 DPI」支持,需要在對應應用程序的 app.manifest 文件中加入相應的節點以通知 Windows 該程序支持「高 DPI」,

  • WinForm: 加入 app.manifest 並在第一時間調用 Cef.EnableHighDPISupport()
  • WPF: 加入 app.manifest
  • OffScreen: 加入 app.manifest

app.manifest 類似於以下內容:

1
2
3
4
5

<asmv3:application>
  <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
    <dpiAware>true/PM</dpiAware>
  </asmv3:windowsSettings>
</asmv3:application>


Javascript 集成

從 .NET 中調用 Javascript 方法

Javascript 方法僅能在一個 V8Context 中執行,IRenderProcessMessageHandler.OnContextCreated 和 IRenderProcessMessageHandler.OnContextReleased 爲 Javascript 代碼的執行劃定了一個清晰的邊界。通常:

  • Javascript 在 Frame 級別執行,每個頁面至少包含一個 Frame
  • IWebBrowser.ExecuteScriptAsync 方法保留下來用於向後兼容,該方法可作爲快速執行 Javascript 方法的入口
  • OnFrameLoadStart 事件觸發時 DOM 還沒有加載完成
  • IRenderProcessMessageHandler.OnContextCreated 和 IRenderProcessMessageHandler.OnContextReleased 僅在主 Frame 上觸發。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

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');");
  }
};

從 .NET 中調用帶有返回結果的 Javascript 方法

如果希望調用一個帶有返回結果的 Javascript 方法,使用 Task<JavascriptResponse> EvaluateScriptAsync(string script, TimeSpan? timeout),Javascript 異步執行並最終返回一個 JavascriptResponse 類型的實例,包含錯誤消息,執行結果和是否成功的標誌。

1
2
3
4
5
6
7
8
9
10
11

// Get 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 ); })();", null);

task.ContinueWith(t =>
{
    if (!t.IsFaulted)
    {
        var response = t.Result;
        EvaluateJavaScriptResult = response.Success ? (response.Result ?? "null") : response.Message;
    }
}, TaskScheduler.FromCurrentSynchronizationContext());

返回結果的類型僅支持基元類型,如 intboolstring 等,目前還沒有一個合適的方法將 Javascript 類型映射爲一個 .NET 類型,但可藉助 JSON.toStringify() 返回 JSON 字符串的形式來組織複雜類型,之後在 .NET 中利用 JSON.NET 將該字符串反序列化爲相應的類型。

將一個 .NET 類型暴露給 Javascript

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
2
3

async function bindBridgeObject(){
    await CefSharp.BindObjectAsync(objectName, settings);
}

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

集成示例

  1. 在 .NET 中創建一個將要暴露給 Javascript 的類型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class BoundObject
{
    public class AsyncBoundObject
    {
        public void Error()
        {
            throw new Exception("This is an exception coming from C#");
        }
       
        public int Div(int divident, int divisor)
        {
            return divident / divisor;
        }
    }
}

  1. 將上述類型的實例註冊至 IBrowser.JavascriptObjectRepository:

1
2
3
4
5
6
7
8
9
10
11
12
13

// set ConcurrentTaskExecution to true if you want do concurrent jobs at one time.
CefSharpSettings.ConcurrentTaskExecution = true;

browser.JavascriptObjectRepository.ResolveObject += (sender, e) =>
{
    var repo = e.ObjectRepository;
    if (e.ObjectName == "bridge")
    {
        var boundObject = new ServiceBridge();
        var bindingOptions = new BindingOptions();
        repo.Register("bridge", boundObject, isAsync: true, options: bindingOptions);
    }
};

  1. 在 Javascript 中調用 CefSharp.BindObjectAsync() 方法解析綁定對象:

1
2
3
4
5
6

   await CefSharp.BindObjectAsync("boundAsync");

boundAsync.div(16, 2).then(function (actualResult)
{
       alert("Calculated value from calling .NET Object: " + actualResult);
});

在 .NET 中接收綁定成功事件:

1
2
3
4
5
6

browser.JavascriptObjectRepository.ObjectBoundInJavascript += (sender, e) =>
{
        var name = e.ObjectName;

        Debug.WriteLine($"Object {e.ObjectName} was bound successfully.");
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章