Webkit是一個多進程構架,內核WebCore和JS引擎JavaScriptCore都處在WebProcess進程中,而用戶界面相關的處理則處在UIProcess進程中。(詳見Webkit客戶端進程解析)
Webkit提供了大量的API供客戶程序調用,但是這些API都是在客戶進程中調用的,我們無法訪問到內核部分的數據結構並處理,如DOM樹、Render樹、加載的Web資源等等。為了解決這一問題,Webkit提供了一個運行在內核進程的InjectedBundle來提供對內核數據的操作。
InjectedBundle類似於一個插件,單獨編譯成一個動態庫,在內核進程運行到特定情況時會調用InjectedBundle中注冊的對應函數來實現自定義操作。每個WebProcess只能加載一個InjectedBundle,用戶可以在創建WebProcess的時候指定使用哪個InjectedBundle。
接下來我們就動手制作一個自己的InjectedBundle然后用Webkit加載它。
1. 准備工作
我采用的編譯環境是VC2005
(1)首先需要下載並編譯Webkit(詳見Windows平台編譯Webkit)
(2)然后創建一個空項目,修改項目屬性
a. 配置類型:動態庫(.dll)
b. 添加附加包含目錄:Webkit生成文件路徑\inlude 和 Webkit生成文件路徑\include\include(一定要加兩個,第二個是windows平台缺少的第三方庫頭文件)
c. 添加附加庫目錄:Webkit生成文件路徑\lib
好,項目配置完畢!接下來實現Webkit所需的接口
2. 編寫InjectedBundle
先上代碼
#include <WebKit2/WKBundleInitialize.h>
#pragma comment(lib, "WebKit.lib")
extern "C" __declspec(dllexport)
void WKBundleInitialize(WKBundleRef bundle, WKTypeRef initializationUserData)
{
// 初始化代碼
}
InjectedBundle只需要實現這一個函數即可(是不是很簡單),其中參數bundle是Webkit給你的InjectedBundle分配的標志(identifier),可以用它來調用一些InjectedBundle的API,所以存下來為妙;參數initializationUserData是用戶利用Webkit加載該InjectedBundle時傳入的一些數據(我們可以用它來傳啟動參數)。
接下來我們要做的就是在該初始化函數中注冊我們需要的回調函數
在Webkit中以Client結尾的結構體都是一個回調函數組,比如WKBundlePageLoaderClient就是一組處理頁面加載的回調函數,每個Client都有一個版本號和clientInfo,clientInfo是用來在回調函數中傳遞用戶參數。利用對應的WKBundleSetXXXClient就能夠注冊某一個回調函數組。
我們需要注冊的第一組回調函數是WKBundleClient,內容如下
struct WKBundleClient {
int version; // 版本號
const void * clientInfo; // 用戶參數
WKBundleDidCreatePageCallback didCreatePage; // page創建完畢
WKBundleWillDestroyPageCallback willDestroyPage; // page將要銷毀
WKBundleDidInitializePageGroupCallback didInitializePageGroup; // page組初始化完畢
WKBundleDidReceiveMessageCallback didReceiveMessage; // 收到用戶消息
};
前兩個已經說過了,這里主要講didCreatePage和didReceiveMessage
(1)didCreatePage
該函數是在創建一個Page對象之后被調用的,原型是
typedef void (*WKBundleDidCreatePageCallback)(WKBundleRef bundle, WKBundlePageRef page, const void* clientInfo);
在回調里我們能獲得所創建Page的引用page,利用這個引用可以調用一些和page相關的API。其中比較重要的是WKBundlePageSetXXXClient,利用這一組函數可以設置該頁面的一些回調,比如之前說過的WKBundlePageLoaderClient,包含了didReceiveTitleForFrame(當收到Frame的標題后調用),didFinishLoadForFrame(當一個Frame對象加載完畢后被調用)等回調函數。利用這些回調函數就能在不同階段實現我們想要的功能了。
(2)didReceiveMessage
該函數是在收到客戶進程的消息后調用的。Webkit設計了一個消息隊列機制使得兩個進程之間能夠通信,客戶進程通過調用WKContextSetInjectedBundleClient給InjectedBundle發消息,InjectedBundle通過WKBundlePostMessage向客戶進程發消息。我們利用這個方法就能在兩個進程間交換數據。例如把InjectedBundle處理的結果傳給客戶進程,或者客戶進程向InjectedBundle發指令。
消息的格式是messageName + messageBody
messageName是WKStringRef,即一個字符串
messageBody是任意的WKType,如:WKDictionaryRef, WKDataRef, WKArrayRef等等。
在InjectedBundle寫完之后編譯生成一個.dll文件就可以拿來使用了。
下一篇中將介紹怎樣在Webkit中加載InjectedBundle,待續...