2008年8月5日星期二

浅谈Gecko关键部分之十二Embedding

随着Gecko内核的广泛使用,人们会期望Gecko内核能作为一个相对独立的拥有能访问Web、解析Html、渲染页面等浏览器基本功能的动态库甚至作为一个ActiveX或GtkWidget以供其他程序将其嵌入,就像大家熟知的MyIE作为一个外壳程序将IE内核以ActiveX的方式嵌入到其中,这样外壳程序相对简单,往往基于应用层,而内核则专注于html、css、dom等浏览器核心部分的处理,一旦能将浏览器内核以相对固定相对简单的接口形式提供给外部程序使用,则会大大拓展其应用包括动态脚本语言如python、perl、ruby都可能将其应用其中,其他的外壳程序也会应运而生。其实IE内核及Safari内核Webkit都提供了相对简单固定的接口供外部调用,这样才有了MyIE及Safari。那么Gecko内核是否也有类似的接口,外部程序又是如何嵌入的,这些与IE内核及Webkit等有什么差别呢?

一、认识Embedding接口
Gecko内核作为相对独立的部分嵌入到外部程序中,在本质上外部程序与Gecko内核的关系其实很类似Plugin与Gecko内核的关系,外部程序与Gecko内核需要分别实现一些接口,各负其责,以供对方调用,这样两者相互结合实现所要的功能。不过Gecko内核官方目前提供给外部调用的接口封装得不是很简单,使用起来也不是非常的方便,但通过对GTKMozEmbed、winEmbed等的了解,Gecko内核部分往往通过nsWebBrowser来实现public nsIWebBrowser、nsIWebNavigation、
nsIWebBrowserSetup、nsIDocShellTreeItem、nsIBaseWindow、nsIWebBrowserPersist、nsIWebBrowserFocus、 nsIWebProgressListener等接口,以代表一个浏览器页面浏览实例;

而外壳程序部分需要实现nsIWebBrowserChrome、nsIEmbeddingSiteWindow、nsIWebProgressListener、nsISHistoryListener、nsIContextMenuListener等接口以代表一个浏览器页面浏览外壳,供Gecko内核调用。

其中nsWebBrowser初始化后通过setcontainerWindow来设置其nsIWebBrowserChrome containerWindow为其对应的外壳实现,然后通过InitWindow、Create等方法可真正创建一个浏览器页面浏览实例,而外壳实现通过设置其nsIWebBrowser webBrowser为前面初始化的nsWebBrowser实例,这样两者可互为引用,同时外壳部分可向nsWebBrowser添加一些如WebProgress、ContextMenu等listener,以供Gecko内核nsWebBrowser需要时调用通知。

另外外壳部分需要实现接口nsIWindowCreator,并通过调用window watch service的方法setWindowCreator来告诉window watch service由外壳部分来createChromeWindow,以保证内核在由页面脚本通过window.open来打开新窗口时统一由外壳部分来创建新开的窗口。

上面提到的是Gecko内核为了实现Embedding,需要由内核部分与外壳部分分别实现的基本接口及相关调用关系,其中Gecko内核还提供有诸如NS_InitEmbedding、NS_TermEmbedding等XPCOM方面的接口以供外壳部分充分利用Gecko内核提供的XPCOM强大组件管理等功能。

为了更方便外壳程序的调用,Gecko也提供了诸如GtkMozEmbed widget及MozApp、MozView、 MozViewListener等,其实质仍属于上面提到的外壳部分,只不过更加的方便用户使用罢了。

二、外部程序如何嵌入Gecko内核
为了能真正了解如何将目前的Gecko内核嵌入到外部程序中,最好还是先参考Roll your own browser - An embedding HowTo按照其提供的编译方式,了解TestGtkEmbed、mfcEmbed等实现。通过对这些实例的了解才能对嵌入Gecko内核有一定的了解。

外部程序嵌入Gecko内核的主要步骤如下:
1、通过NS_InitEmbedding初始化内核;
2、通过 do_CreateInstance(NS_WEBBROWSER_CONTRACTID, &rv)创建mWebBrowser;
3、创建实现nsIWebBrowserChrome、nsIEmbeddingSiteWindow接口实例mpBrowserImpl;
4、创建绑定窗口
mpBrowserImpl->Init(mpBrowserFrameGlue, mWebBrowser);

mWebBrowser->SetContainerWindow(NS_STATIC_CAST(nsIWebBrowserChrome*, mpBrowserImpl));

nsCOMPtrsetup(do_QueryInterface(mWebBrowser));
if (setup)
setup->SetProperty(nsIWebBrowserSetup::SETUP_IS_CHROME_WRAPPER,PR_TRUE);

rv = mBaseWindow->InitWindow(nsNativeWidget(m_hWnd), nsnull,0, 0, rcLocation.right - rcLocation.left, rcLocation.bottom - rcLocation.top);
rv = mBaseWindow->Create();

5、添加监听
void mWebBrowser->AddWebBrowserListener(weakling, NS_GET_IID(nsIWebProgressListener));

6、设定WindowCreator、preference及promptservice等;

7、装载页面mWebNav=nsCOMPtrsetup(do_QueryInterface(mWebBrowser));
if(mWebNav)
mWebNav->loadURI();

8、通过NS_TermEmbedding卸载内核,退出程序;

三、
与IE内核、Webkit等嵌入方面的差别
同样作为浏览器内核,其实有很多文章就Gecko内核、IE内核、Webkit等多方面进行过比较如Comparison of layout engines。但就嵌入易用方面来讲,它们之间差别还是蛮大的,个人认为IE内核充分利用windows com技术,作为一个独立的ActiveX,其拥有很好接口封装,对windows用户来讲,大大提高了其易用性复用性;

Webkit内核本身一开始就专注于浏览器内核部分如KHTML、KJS,其提供给windows、gtk、mac等方面的接口也相对完毕;

Gecko内核往往不仅仅专注于浏览器内核如HTML、Javascript等方面,同时它还提供了XPCOM、XUL,从大的方面看,嵌入Gecko内核访问通用的html类的Web网站,只利用了其中一部分功能,其XUL、XPCOM等功能几乎可以不用,这样一来也是一种浪费,所以目前Embedding方面的接口,既要考虑到XPCOM方面,又要考虑到用户嵌入内核的易用性,显然没有IE内核及Webkit内核,简单明了,要想嵌入Gecko内核,需要对其进行深入的了解,这样反而降低了嵌入的意义。

顺便提一下,Avant/Orca Browser创始人宣称花了3年多的时间才将Gecko内核研究透彻,弄出个基于Gecko内核的浏览器Orca Browser,但其利用方式,不是基于Embedding的,而是基于Xulrunner的,这样既迎合了Gecko本身,同时能充分利用其强大功能,可谓一举两得,但是要想用好它,可要花上3、5年功夫不可。

四、总结
其实目前通过Embedding的方式使用Gecko内核的还不是很多,使用起来也比较困难,需要较深入的了解XPCOM等内核知识,比较好的实例也就只有上面提到的TestGtkEmbed、mfcEmbed等,可喜的是Mozilla最近似乎想重新加强其对Embedding方面的支持。详见Embedding/NewApiChris Blizzard's blog

另外Second life提供了一组接口以更加方便嵌入Gecko内核。详见uBrowser and LLMozLib

四、参考资源
Mozilla project Embedding Mozilla
Mozilla developer center-Embedding Mozilla

Mozilla Embedding FAQ
Gecko Embedding Basics
Mozilla embedding APIs overview