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,待续...