显示标签为“Mozilla相关”的博文。显示所有博文
显示标签为“Mozilla相关”的博文。显示所有博文

2009年3月25日星期三

浏览器大战正当时

最近关于浏览器的新闻真可谓满天飞,这不IE8终于面世啦;Chrome1.0正式版也早已推出,Chrome社区正忙乎2.0及linux版本呢;Safari可也没闲着,才不久也来了个Safari4.0beta版,引得大众一番轰动;Firefox可也不断的忙乎着升级,从3.0一步一步升到3.0.7哪,以目前的势头可没暂停终止的迹象。。。

再说啦国内上网本可炒得不一乐乎,从供应商到硬件平台,甚至到用户的使用等等,可惜真正走到用户手中,或许还得要有些日子,其中一直让人好奇的是上网本究竟使用哪款浏览器内核,对用户而言仅仅上上网浏览浏览新闻、逛逛论坛、看看视频、参加参加网上社区就够了吗?有这样便宜的硬件平台及好用的浏览器吗?

如此林林总总让人目不暇接,于是在沉寂了一段时间之后,想借此机会总结总结自己的一些使用体会及对浏览器今后发展的一些看法。

一、各款浏览器大比拼
  • IE8
从IE8正式版本推出后的第一时间,就下载试用过它,经过一段时间的对比测试,第一时间使用的感受如下:
在浏览器大混战中IE8正奋力追赶,几乎没有独创或值得大说特说的新特性,几乎就是在模仿或抄袭其他浏览器受人欢迎的特性如多页签、多进程、web开发人员工具等;

IE8虽然在很多方面有所改进但仍然是IE系列浏览器,关键方面没有质的飞跃,好的方面有持续支持已有的框架、风格、设置、扩展等,令人诟病以久的W3C标准支持及推动仍举步艰难,这不ACID3测试还是保持在30分左右的水平呢?

经过一些测试发现其加载页面速度及占用资源方面,与Chrome相比仍然有一定的差距,总体而言Chrome要想超越IE系列浏览器关键要在扩展方式及应用方面向IE、Firefox学习,当然还要有用户推广方面更大的努力;

IE8虽然看起来步伐不是很快,但在其拥有庞大的用户基础之上进行稳步推进是非常值得肯定的,特别是它保持了对原有扩展的支持的基础之上,实现了多进程及安全方面的加强。从目前看来要想IE浏览器使用WebKit内核简直就是痴人说梦,忽悠忽悠而已,因为微软仍然对Web采取封闭而非开放合作的态度。。。
  • Chrome
Chrome浏览器自去年九月份推出以来就属当红炸鸡啦,赚足了眼球,当然Google及Chrome社区其实正不余遗力地加强Chrome浏览器的功能及满足用户的潜在需求及对广大网民的承诺,正与其一位开发人员所讲由于目前的浏览器太差,从而有想要开发浏览器的驱动力;

到目前为止其稳定性相比测试版本有了较大的提升,加载速度方面也算佼佼者啦,唯一的缺憾在于其扩展的支持,从其实现架构及WebKit内核来讲要达到类似IE ActiveX或Firefox Extension那样的方便也许难度不小;

令人值得称道的是其linux gtk版本或许不久就能面试啦,对linux用户来讲是一大福音;

  • Safari
Safari4.0beta版的发布有点出乎人们的意料,Apple在其网站Safari全新特色上对其特性进行过一番"吹嘘",通过仔细阅读其内容,发现Apple也似乎正努力的打造一款极具个性的Safari浏览器,其中从Chrome中学了不少东东如页签的位置、TopSite等,当然其中令人有趣的页签拖动还是独特的,还有类似的界面及操作风格延续了Apple的优良传统;

虽然其JavascriptCore内核没变,但起了个商业上的名称nitro,一时真让人莫名其妙,也许只要Apple自己知道改名的真正原因;

虽然其ACID3测试的得分在90分以上(Chrome80-90,Firefox70多一点),但其在Windows上的资源使用情况就没有宣称的那么好啦,特别是拥有Adobe flash的页面上;其架构上仍然没有使用多进程,与Firefox一样,但其消耗内存及CPU可比Firefox高的多,并且还不稳定,也许是Beta版的原因吧,看样其要想在Windows上分一杯羹难度不少;

那么Apple为什么还要"折腾"Safari哪?我想或许其算盘在Mac及iPhone上呢?Apple也许还担心哪天Chrome杀到Mac平台呢?要是现在不紧跟浏览器的发展,到那时Chrome壮大啦,自己又没有守住自己的领地哪滋味可就真不好受,再说Chrome使用的WebKit内核可是由Apple主导开发的呢?改个Javascript引擎的名字为Nitro,或许相比JavascriptCore、SFX起来,更好听,更像V8那样容易让人记住;

WebKit的发展来源于Apple,当然也离不开Apple,其对W3C标准的支持及扩展付出了极大的努力,这一点让人钦佩,特别是其对ACID3测试所作的努力及Javascript引擎实现方式的突破;

在这场浏览器大战中,没有Apple,没有WebKit,也就没有了大战的平台,虽然目前结果未知,但WebKit内核本身非常值得关注,也许WebKit内核的发展是大战成败的关键,有了Google、Apple、Adobe等的参与与使用,WebKit内核的市场份额经过大战之后肯定会得到大量的提升,其发展同样令人瞩目;

  • Firefox
Mozilla自从推出Firefox1.5,就逐步的从上一次失败的阴影中走出来,到了2008年推出Firefox3.0开始使用Gecko1.9,才让人真正的感觉到凤凰涅磐的滋味,那些家伙,狂热的技术追逐者,令世人刮目相看,其架构、其扩展性、其开放性、其开拓性,成为推动Web技术发展与应用的根本;

正可谓Firefox是这场浏览器大战的导火索,由一群对技术孜孜不倦的家伙发起对Web发展及应用的号角;这不其倡导的多页签、xul、xbl、npapi、cavas等,在Firefox上得到充分的应用与展现,有些方面IE、WebKit还在苦苦的追逐呢;网上有人评论Firefox离开了Google财务的支持,也许即将面临死亡,听起来有些骇然,从商业角度讲或许他是对的,但Mozilla社区对Web技术的开拓创新等却不会那么轻易的死掉,只要他们坚持这条道同时考虑考虑用户感受,先锋不会再次成为先烈,饿死的或许只会是那不思进取,抱残守缺的家伙。。。。

二、浏览器发展方向
浏览器的发展推动Web的发展与应用,同时Web的发展与应用促使浏览器更加的强壮,浏览器的发展离不开Web的发展,目前看来其发展方向应该在于:
  • 更加的标准化
也许IE被逼着卷入这场浏览器大战,不如说被逼着卷入标准化大战,Firefox、Chrome、Safari、Opera都自觉不自觉的坚持w3c标准,并且还不断的推动标准的发展,从目前的态势看Microsoft不得不应战,虽然极不情愿,也许大势所趋吧,识时务者为俊杰;

Firefox、WebKit正不遗余力的推动html5 video等标签、增加工作线程、跨域名XmlHttpRequest等,或许未来的标准就此产生;

  • 更加的开放
上面提及的几个浏览器大部分搭乘开源的顺风,实施开源,公开透明,行自由开放之风,这样Web的发展及应用或许更加的开放;

  • 更加的复杂
由于Web应用更加的复杂及多样性,对浏览器的要求及本地系统、硬件的要求越来越高,所以多进程的出现,从另一个侧面体现其复杂性及可靠性要求的提高。试想一个程序,它可使用来自世界各地形式各异的Web数据,同时尽可能的按照同样的方式展现给不同的用户,虽然有公共遵守的W3C标准,但要求其高效、可靠的完成,其实现本身的复杂性就可想而知了,如果Web数据进行更进一步的多样化,那其复杂度自然增加;

  • 小型设备化应用
由iPhone开启手机浏览互联网的新风,如火如荼的andriod、QtWebKit、Fennec、MiniOpera、UcWeb等正大力的拓展浏览器的嵌入式应用,上网本正是在这样的背景上进入人们的视眼,上网本还没火起来,曾经的维纳斯电视上网计划或许在不久的将来会真正的走进人们的客厅;这样看起来上面提到的浏览器大战似乎才刚刚开局呢。。。。


三、如何把握浏览器开发及商业应用
目前浏览器的开发主要体现在IE内核、Firefox Gecko内核、WebKit内核,从历史的角度看IE内核的扩展更加的容易,面对的PC用户庞大,但同样仅受限于windows平台使用;

针对Gecko内核,最近几年的extension快速扩展及应用普及加速了Firefox的发展与应用,并且得到一些开发者及网民的好评,同时其可应用于Windows、Mac、Linux平台,其扩展性令人赞叹;

而WebKit内核,发端应用于Mac平台Safari,名声大振于iPhone,Chrome的出现将其推到风口浪尖,其特性鲜明,虽然其移植性较好,可移植到不同平台,包括嵌入式应用,但其扩展性就没那么方便啦,有些平台对NPAPI插件的全方位支持也显得那么的难得,也许需要给他们一点时间吧,目前看来要想对其进行一定程度的开发难道还是不少。。。

从以前的历史经验中我们都知道浏览器这样一个软件,受到广大用户的青睐,可没有哪家大公司直接从中获得过与其实际地位相符的利润,包括免费捆绑销售的IE及Netscape等等,从这点看来与Office相比太令人惭愧啦,可见开发浏览器可不那么好赚钱,商业模式不是那么直接,那么写写扩展是否能获取一定回报哪,看看以前IE ActiveX的流行,或许能让我们打开一片联想的天地。

从更大的角度看,直接从浏览器本身来收取费用如Opera及以前的Netscape,是比较难的,关键得从应用服务来收费,如Firefox从内嵌Google搜索栏来获取收费,Adobe从嵌入的Flash插件来获取丰厚利润回报,但是一个好的扩展应用则需要对浏览器本身及其应用的充分理解。

也许上网本及各式各样的嵌入设备的出现再加上捆绑免费的浏览器,正是浏览器获取应用及回报的另一途径。

在浏览器扩展的同时及时提供优秀的Web应用或许是获取商业利润的又一方式,这或许是Google、IBM等大力推广云计算的良苦用心啦。

面对如此林林总总的方式,作为一个开源浏览器的爱好者,你找到了利用浏览器或其技术实现利润收入的方式了吗?也许大家还有更好的方式,不免大家一起分享分享。。。

2008年8月20日星期三

浅谈Gecko关键部分之十四Build System

Mozilla作为一个开源组织,为我们提供了完整的源代码,以便我们进行编译、调试等,为了能真正的运用Gecko内核,我们需要自己动手从源代码中进行编译,进而进行调试,从而真正了解、运用Gecko内核,以致修改、改进Gecko内核,或者自己编写出符合Gecko内核编写规则的应用等。下面提供一些参考资源,以便大家完成编译、调试等。


参考资源

2008年8月17日星期日

浅谈Gecko关键部分之十三Xulrunner

Gecko内核作为一个浏览器内核,为了能充分利用其已有的功能,Mozilla提供了一个可作为运行环境平台的Xulrunner,来扩展Gecko内核的应用。那么究竟什么是Xulrunner、如何实现一个Xulrunner应用程序、它和Firefox、ThunderBird等有什么区别、目前有哪些应用基于Xulrunner等等,通过下面的学习研究,希望能逐步的解答这些疑问。

一、什么是Xulrunner?
XULRunner is a Mozilla runtime package that can be used to bootstrap XUL+XPCOM applications that are as rich as Firefox and Thunderbird. It will provide mechanisms for installing, upgrading, and uninstalling these applications. XULRunner will also provide libxul, a solution which allows the embedding of Mozilla technologies in other projects and products.

其实质上是一个运行环境,类似于java虚拟机、Adobe AIR、Yahoo! Widget等,只不过它目前支持xul、xpcom、js、html等,而其他的运行环境大都有其对应的支持格式及实现方式等。有了Xulrunner,为开发Rich Internet application提供另一种实现方式。

Xulrunner基于Gecko内核,充分利用了其功能及实现方式,而Gecko内核的功能、性能及实现方式等在Firefox中得到了充分的体现与运用。这样Xulrunner应用程序基本上完全可以应用Firefox所拥有的功能。

二、如何实现一个Xulrunner应用程序
1、下载、安装Xulrunner;

2、创建应用目录结构,设置应用application.ini及chrome manifest;
application.ini内容大致如下:
[App]
Vendor=***
Name=Bamboo
Version=1.0
BuildID=2008052906
Copyright=Copyright (c) 1998 - 2008 mozilla.org
ID={ec6039f7-c60a-784f-9b0e-23b3a9e67323}
[Gecko]
MinVersion=1.9
MaxVersion=1.9
[XRE]
EnableProfileMigrator=1
EnableExtensionManager=1
[Crash Reporter]
Enabled=1
ServerURL=https://crash-reports.mozilla.com/submit

其中ID表示xulrunner应用的唯一ID,在编写extension时须指定该extension能够应用的目标程序,在其install.rdf的targetApplication项的ID中指定已知的应用ID(也即xulrunner应用在其application.ini中指定的ID)。如Firefox的应用ID为{ec8030f7-c20a-464f-9b0e-13a3a9e97384}。

3、创建xul、js文件;

4、设置应用选项;
在pref.js中指定Xulrunner应用程序启动的主窗口,如
pref("toolkit.defaultChromeURI", "chrome://myapp/content/main.xul");

5、运行Xulrunner应用程序;
.\xulrunner\xulrunner.exe application.ini

具体参考
Getting Started with XULRunner


三、Xulrunner和Firefox、ThunderBird等有什么区别?
Xulrunner作为一个运行环境,以供他人开发基于Xulrunner、基于Gecko内核的应用程序,从Gecko1.9以后,从严格的意义来讲,Firefox、ThunderBird等是基于Xulrunner而开发的,也是一个Xulrunner应用,只不过它得到Mozilla官方的支持,它是浏览器。

Mozilla在开发Firefox、Xulrunner、Gecko内核的过程中,不断的相互扩充,相互优化,相互促进,往往一个Firefox主要版本都对应一个Xulrunner版本,Xulrunner、Firefox对某一特性的支持程度有时有一些略微不一致,往往是由于对应版本未及时更新而造成的。

自Firefox3.0以来,通过firefox.exe -app application.ini的调用方式同样可以运行一个Xulrunner应用,只不过此时该Xulrunner应用的Gecko内核是当前Firefox对应Gecko内核。

到目前为止不同组织开发了许多基于Xulrunner的应用,如比较著名的有Songbird、Joost、Miro等等。

具体可参考
XULRunner Hall of Fame

四、开发Xulrunner应用程序与Embedding Gecko有什么不同?
以基于Xulrunner的方式开发应用,可以充分利用Gecko内核的所有功能(包括xul、xpcom、js等等),入手比较简单,对开发者而言,只须专注实现自身所关心的部分,但要想真正开发出好的应用,需要对Gecko内核的方方面面拥有较深入的理解,难度较大,虽然其符合许多W3C标准,但Gecko内核的各个方面都自成一个体系(特别是xpcom),与其他的库及开发平台的接口相对较少。同时Mozilla目前也推荐开发人员以运行平台的方式来利用Gecko内核。

通过Embedding Gecko的方式来利用Gecko内核,其实现方式和可利用的外部库等都比较灵活,不一定只限于与Gecko相关的实现方式或外部动态库,其往往将Gecko内核作为一个相对独立的外部库使用,但由于目前Gecko内核在Embedding方面的接口有限,不能让外部程序充分利用Gecko内核的功能,从而限制了外部程序对其利用,同时如只利用Gecko内核中浏览页面的部分功能,而需要嵌入整个Gecko内核,有点得不偿失。

究竟是使用Xulrunner的方式还是使用Embedding Gecko的方式来利用Gecko内核?
这就存在一个取舍的问题,如只须嵌入浏览页面的功能,嵌入IE内核或Webkit内核,肯定方便很多,嵌入Gecko内核,稍现复杂,但嵌入的功能都相对有限;想更进一步了解嵌入Gecko内核的内容,可参考浅谈Gecko关键部分之十二Embedding一节。

如使用Xulrunner的方式来利用Gecko内核,则须完全拥抱Xulrunner平台,可利用的功能丰富强大,需要学习研究的难度曲线较大,但是Mozilla毕竟是一个开源组织,其是否能提供一个高质量的可商用的运行环境,存在一定风险,有可能没有Microsoft、Adobe等商业公司提供的平台那样得到更广泛的支持。虽然如此,但其开放程度及对Web技术趋势的创新是独一无二的,再说目前其提供的文档越来越规范,应用越来越多。

五、总结
总的说来,如想真正深入的了解Web技术或浏览器,使用Xulrunner是个非常好的选择,但代价可能较高,如一旦真正掌握,则至少Web技术实力会很强;

如只是利用Web技术或浏览器,完全可抛开Gecko内核,利用IE内核或Webkit内核,来运用Web技术,但对Web技术的真正把握可能不是很深入;

作为一个开源爱好者,想更深入了解Web技术或浏览器,作手学习研究使用Firefox、Xulrunner等Gecko内核相关应用程序,是个不错的选择,因为它提供了很多非常不错的Web技术及工具如ajax、firebug、xul、venkman等;经过一定时间的运用,一定会收获颇多!

六、参考资源
Mozilla Developer center -XULRunner
Wiki XULRunner
What XULRunner Provides
XULRunner FAQ

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

2008年8月2日星期六

浅谈Gecko关键部分之十一Extension

Extension扩展或称Add-on外挂这些日子随着Firefox3的发布,逐步走入人们的视野,并且有些Extension确实赋有强大的功能,其中不乏有google、yahoo!等这样的大公司打造,甚至有人由此提出Firefox3掀起新的浏览器开发应用模式。那究竟什么是Extension?如何编写Extension?编写Extension需要哪些知识?它与plugin、xpcom等开发有什么不一样?为了解答心中疑问,下面分别就这些问题予以学习研究探讨。

一、什么是Extension?
Extensions add new functionality to Mozilla applications such as Firefox and Thunderbird. They can add anything from a toolbar button to a completely new feature. They allow the application to be customized to fit the personal needs of each user if they need additional features, while keeping the applications small to download.

它从小方面看,其实就是为Firefox等添加一些小的功能,如在菜单栏、地址栏、状态栏等添加一些小的按钮以扩展功能,有些类似于为IE等浏览器的工具栏、菜单栏等添加新的选项。

但从本质上看由于Gecko内核提供了xul、xpinstall、xpconnect、javascript、xpcom、rdf等相关技术,使得可通过extension的方式几乎可以扩展浏览器方方面面的内容(这一点完全可从Firfox Add-ons繁多种类的extension中窥见其一班),更为特别的是整个浏览器Firefox本身几乎就是通过extension方式来实现的,完全超越了传统的扩展IE等浏览器的方式,同时gecko内核提供了包括extension资源标识、卸载安装等一系列支持,以便快速便捷为浏览器开发extension。

Gecko内核有这种对extension全方位的支持,仅此就完全超越了opera、safari、ie等传统浏览器内核,进而开拓出一种新的RIA(Rich Internet Application,富互联网应用系统)应用方式,虽然没有象microsoft、adobe等公司对xaml、flex那样的商业支持,但Gecko内核在RIA应用开发方面的技术研究、实现方式探索、还有开放方面等,都是首屈一指的。


二、如何编写Extension?
编写一个简单的Extension其实非常的简单,参考Building an extension一步一步的来设定extension资源标识rdf文件、chrome及manifest等包固定目录结构、编写js及xul文件、打包发布即可。

其中较为关键的在于xul文件中对xul标签overlay的使用,简单来的说来就是extension想在firefox浏览器的菜单栏或状态栏等什么位置添加上你的extension内容。具体对xul标签overlay的使用可参考XUL overlays

尽管js文件在xul文件中可以加载,但是extension作为对firefox浏览器的扩展,这些overlay xul文件,一般也是在浏览器主窗口文件browser.xul加载时一块加载的。这样如果你的extension有一些初始化的需求,只须在js文件中添加window.addeventlistener('load', initfunc,false)即可。

三、编写Extension需要哪些知识?
编写一个简单的extension需要的知识似乎不要太多,但是编写出一个切实可用的extension,必须对以下方面下番功夫不可。

首先 对Firefox本身的实现需要有一定的认识,如对browser.xul、browser.js的了解等,了解extension的运作机制如什么时候启动extension、如何响应用户事件等。

其次 对xul、html、js、xpconnect需要有基本的认识,了解extension的基本构成元素,进而了解Gecko内核为extension提供的可用资源,同时重点关注自身extension所涉及的相关内容如xpcom组件、plugin、css、dom等等。

最后 充分利用已有工具如venkman、dom inspect、firebug、xpcomviewer等以提供开发效率。


四、extension与plugin、xpcom等的区别
extension作为一种对浏览器的扩展机制,而不是对Gecko内核的扩展机制,而plugin、xpcom往往扩展Gecko内核的基础内容,可看成是对Gecko内核的扩展。其中plugin的扩展往往与页面上某一块区域内容显示相关,而xpcom往往扩展Gecko内核基本组件等。

为了扩展浏览器应用(浏览器其实只是Gecko内核应用的一个典型代表),extension则完全可以使用Gecko内核提供的各种机制及技术,同时其本身也可扩展Gecko内核如提供独立的plugin、xpcom组件来扩展浏览器应用。这样说来extension开发,也可包括plugin、xpcom等等方面的开发,就看你所扩展的应用需求啦。

五、总结
Firfox Add-ons中提供了种类繁多的extensions给大家使用,其中不免有令人耳目一新的东西,令人感慨万千,进而发出互联网应用竟然可以这样的感慨。有了对extension的认识,我们就可以着手阅读他人所写的extension,更深入的了解Gecko内核,编写自己的extension,挖掘属于自己的互联网应用。

总之对Gecko内核了解得越深,编写起extension来就越得心应手,其实写这一系列浅谈Gecko的文章也就是为了了解Gecko内核,能编写extension,进而开拓属于自己的Gecko内核应用、互联网应用。

六、目前所关注的Firefox extensions
Dom Inspector
Firebug
Venkman
Xpcomviewer
IE Tab
Split Browser

Weave //有空可用用Mozilla主推的在线共享书签等
PicLens //一个超酷的3D在线图片、视频管理
HttpFox//一个很好的http分析
Gladder//一个蛮好用的代理
Extension Developer
Delicious

DownloadHelper//可从多个站点下载视频和图片
Feed Sidebar //显示实时书签
。。。。。。。。。。

七、参考资源
Mozilla developer center -Extensions
Building An extension
Extension Frequently Asked Questions
Setting up extension development environment
Code snippets

Mozilla developer center-XUL Overlays
XUL Tutorial:Overlays

WIKI Firefox extensions(Add-on)
WIKI List of Firefox extensions

2008年7月29日星期二

浅谈Gecko关键部分之十XUL

Gecko内核作为一个功能强大的浏览器内核,其提出XUL(XML User Interface Language),并且由该语言来实现整个浏览器Firefox界面部分,XUL的实现从另个角度扩充了浏览器内核渲染引擎的内涵,因为它不仅仅可渲染HTML,同时可以象渲染HTML一样渲染xul,这样将XUL与HTML同等看待的浏览器内核实在独无仅有,从这个角度讲Gecko内核的渲染机制比IE、Webkit、Opera等浏览器内核要强大好多倍,因为它们目前本身仅仅支持HTML的渲染。虽然windows推出的XAML、adobe推出的Flex/Laszlo、yahoo widgets等都具有类似功能,但毕竟还没有与浏览器完全整合在一起。这样看来XUL作为Gecko内核的一部分具有根本而独特的作用,那就让我们抽时间来好好了解了解这个XUL。

一、什么是XUL?
XUL (XML User Interface Language) is Mozilla's XML-based language that lets you build feature-rich cross platform applications that can run connected or disconnected from the Internet. These applications are easily customized with alternative text, graphics and layout so they can be readily branded or localized for various markets.

二、XUL的实现机制
为了充分的达到利用XML+JS来实现图形界面程序,XUL作为一门类似HTML的语言,其中定义了一组标签、事件处理机制、渲染方式等,这些定义没有得到W3C等国际标准组织确认,仅由Mozilla组织定义并实现,解析处理一个xul文件的过程与处理html文件的主要过程是类似的,只不过在content、layout等方面有较大的不同,同时xul文件中可以嵌入html标签,将xul标签与html标签完全融为一体来处理,其主要处理过程完全类似浅谈Gecko关键部分之四The life of an html http Request中所描述的。

关于XUL具体定义的标签、CSS、事件处理等等,内容非常庞大,可具体参考下面提供的参考资源。

三、参考资源
WIKI XUL
Mozilla developer center-xul
XUL Reference
The Joy Of Xul
Xul Overlay
XUL Tutorial
XUL PLANET

WIKI XAML
WIKI Adobe Flex
WIKI Yahoo! Widget

2008年7月28日星期一

浅谈Gecko关键部分之九Plugin

Plugin作为一种对浏览器内核扩充的方式,为丰富浏览器应用提供了更加广阔的空间,如大家熟知的flash、java applet、window media player、vlc、adobe pdf等插件已经在人们的不知不觉中走到人们的生活当中,那么究竟什么是plugin?为firefox写的plugin能在其他浏览器中使用吗?如何为浏览器写plugin?浏览器实现plugin的机制及其基本原理如何?等等这些问题一直困扰在心头,现就这几方面来了解了解Gecko内核中的plugin实现等。

一、什么是plugin?
大家以前应该接触过一些关于plugin插件的概念,如eclipse就提供了很好的插件机制,以扩展ide的功能,apache也提供了丰富的插件机制,以丰富http server的处理请求。他们都有一个共同的特点就是以独立动态库的形式存在,动态库提供的输出接口应该满足主程序所规定的一些要求,以向主程序描述自身的特性及主要功能;而主程序也提供一组公共函数接口以供动态库调用,这样协同作业以完成任务。

在Gecko内核中plugin通过扩展html标签embed、object的MIMEtype类型的方式来拓展显示在一个页面中指定区域的内容。如大家熟知的application/x-shockwave-flash、application/x-java-applet、application/x-vlc-plugin等等,每一个plugin须说明自身所能支持的mime类型。一旦页面中遇到指定标签embed或object的mime类型,gecko内核就会搜索它目前所能支持的mime类型及对应的plugin,然后调用plugin中输出的接口由plugin来完成指定区域内的内容显示。

在Gecko内核中,就扩展的方向及方式的不同,plugin与xpcom拥有本质上的差别,plugin以扩展页面embed/object标签 mime类型的方式存在,往往与页面布局渲染相关,而xpcom是以扩展不同的组件种类,以实现不同内部功能的方式存在,它往往有更大的扩展空间包如字符串管理、线程管理、p2p传输管理等。他们的应用场景有本质上的差别,一般说来plugin相对内核而言更加独立如可以有独立的线程管理、内存管理、渲染方式等,而xpcom往往是内核中的一个组成部分。

Gecko内核plugin机制的实现接口Netscape Plugin Application Programming Interface
简称为NPAPI,它提供了相当完备的跨平台的接口框架,其中包括对Stream、Security、Scripting等方面的支持,目前包括Firefox、Opera、Safari、部分IE等浏览器都支持符合NPAPI接口标准的插件来扩展浏览器的功能。

二、如何编写plugin?
为了能编写plugin,首先需要了解其主要API,下面四个函数接口往往需要由plugin实现并export出去,可由外部即浏览器内核调用。
NPError WINAPI NP_Initialize(NPNetscapeFuncs* pFuncs);
当该plugin动态库第一次加载后浏览器内核会调用该方法,其中参数为当前内核提供给plugin调用的函数接口,plugin往往会将这些接口保存下来,以待以后使用;

NPError WINAPI NP_GetEntryPoints(NPPluginFuncs* pFuncs);
浏览器内核初始plugin动态库后,会调用该方法以获取该plugin实现的接口函数,内核会根据具体情况的不同来调用该plugin提供的不同接口函数;

NPError WINAPI NP_Shutdown();
当浏览器内核需要释放该plugin动态库时会调用该方法以告诉plugin其将会被释放;

char* NP_GetMIMEDescription(void);
浏览器内核通过该方法可获得该plugin支持的MIME类型,以便在解析页面embed/object标签时匹配其Type是否由哪个plugins来支持;

typedef struct _NPPluginFuncs {
uint16 size;
uint16 version;
NPP_NewUPP newp;
NPP_DestroyUPP destroy;
NPP_SetWindowUPP setwindow;
NPP_NewStreamUPP newstream;
NPP_DestroyStreamUPP destroystream;
NPP_StreamAsFileUPP asfile;
NPP_WriteReadyUPP writeready;
NPP_WriteUPP write;
NPP_PrintUPP print;
NPP_HandleEventUPP event;
NPP_URLNotifyUPP urlnotify;
JRIGlobalRef javaClass;
NPP_GetValueUPP getvalue;
NPP_SetValueUPP setvalue;
} NPPluginFuncs;

typedef struct _NPNetscapeFuncs {
uint16 size;
uint16 version;
NPN_GetURLUPP geturl;
NPN_PostURLUPP posturl;
NPN_RequestReadUPP requestread;
NPN_NewStreamUPP newstream;
NPN_WriteUPP write;
NPN_DestroyStreamUPP destroystream;
NPN_StatusUPP status;
NPN_UserAgentUPP uagent;
NPN_MemAllocUPP memalloc;
NPN_MemFreeUPP memfree;
NPN_MemFlushUPP memflush;
NPN_ReloadPluginsUPP reloadplugins;
NPN_GetJavaEnvUPP getJavaEnv;
NPN_GetJavaPeerUPP getJavaPeer;
NPN_GetURLNotifyUPP geturlnotify;
NPN_PostURLNotifyUPP posturlnotify;
NPN_GetValueUPP getvalue;
NPN_SetValueUPP setvalue;
NPN_InvalidateRectUPP invalidaterect;
NPN_InvalidateRegionUPP invalidateregion;
NPN_ForceRedrawUPP forceredraw;
} NPNetscapeFuncs;
NPPluginFuncs、 NPNetscapeFuncs结构中的函数接口分别由plugin和浏览器内核提供实现,而让对方调用。每个接口的具体含义及什么时候调用可参考下面提到的相关资源。

三、Gecko内核的plugin实现机制及基本原理
Gecko内核作为外部调用plugin的主程序,它不仅提供一组NPNetscapeFuncs函数接口给plugin调用,同时需要管理所有的plugin动态库、针对不同embed/object创建不同的plugininstance以及相关安全、创建窗口句柄、释放资源等。

在Gecko内核中提供了如下几个主要头文件、接口及类来完成相关任务。
\mozilla\modules\plugin\base\public\Npapi.h
\mozilla\modules\plugin\base\public\Npupp.h
\mozilla\modules\plugin\base\public\Npruntime.h
这几个头文件包含实现plugin所需要的函数接口、常量定义、数据结构定义等;

nsIPluginTag.idl
主要代表一个plugin动态库基本信息包括文件名、描述等;
nsIPluginTagInfo.idl/nsIPluginTagInfo2.idl
主要代表一个embed/object标签所描述的内容如MIME类型、参数、宽高等;
nsIPlugin.idl
主要描述一个plugin动态库所对应的initialize、shutdown、getMIMEDescription、getValue及创建plugin实例方法createPluginInstance,相当于plugin动态库加载后该plugin所提供的函数入口等;
nsIPluginInstance.idl
主要描述一个plugin动态库所实现的一个plugin实例所对应的函数接口,如initialize、start、stop、destroy、setWindow、newStream、getValue、handleEvent等;
nsIPluginHost.idl
主要描述如何装载plugin动态库、实例化EmbededPlugin、FullPagePlugin、检查指定MIME类型是否有对应plugin来实现等;

由类nsPluginHostImpl来代表"@mozilla.org/plugin/manager;1"、"@mozilla.org/plugin/host;1"来管理plugin等;它主要实现了接口nsIPluginHost、nsIPluginManager2等,提供了
void loadPlugins();
nsIPlugin getPluginFactory(in string aMimeType);
void instantiateEmbeddedPlugin(in string aMimeType, in nsIURI aURL, in nsIPluginInstanceOwner aOwner);
void setUpPluginInstance(in string aMimeType, in nsIURI aURL, in nsIPluginInstanceOwner aOwner);
void stopPluginInstance(in nsIPluginInstance aInstance);
等主要方法;

由类ns4xPlugini来实现nsIPlugin接口,其中维护了上面提到的函数接口
NPPluginFuncs fCallbacks;
PRLibrary* fLibrary;
NP_PLUGINSHUTDOWN fShutdownEntry;
/**
* The browser-side callbacks that a 4.x-style plugin calls.
*/
static NPNetscapeFuncs CALLBACKS;

在nsPluginHostImpl.cpp中通过static nsActivePluginList *gActivePluginList;来维护一组当前活动的plugin
class nsActivePluginList
{
public:
nsActivePlugin * mFirst;
nsActivePlugin * mLast;
PRInt32 mCount;
................................
};

struct nsActivePlugin
{
nsActivePlugin* mNext;
char* mURL;
nsIPluginInstancePeer* mPeer;
nsRefPtr mPluginTag;
nsIPluginInstance* mInstance;
PRTime mllStopTime;
PRPackedBool mStopped;
PRPackedBool mDefaultPlugin;
PRPackedBool mXPConnected;
//Array holding all opened stream listeners for this entry
nsCOMPtr mStreams;
.........................................
};

由nsPluginHostImpl、ns4xPlugin及nsActivePluginList等类能够维护好plugin动态库的加载、创建一个新plugin实例等,哪究竟是由谁来加载动态库、创建新的plugin实例呢?

作为一个html embed/object标签,其对应的元素为nsHTMLObjectElement,其继承自nsGenericHTMLFormElement、nsObjectLoadingContent、nsIDOMHTMLObjectElement,其中nsObjectLoadingContent提供了下面主要的方法
nsObjectLoadingContent::LoadObject(const nsAString& aURI,
PRBool aNotify, const nsCString& aTypeHint, PRBool aForceLoad)

nsresult nsObjectLoadingContent::TryInstantiate(const nsACString& aMIMEType,
nsIURI* aURI)

而nsHTMLObjectElement往往对应有nsObjectFrame;其中包含有 nsRefPtr mInstanceOwner;

而nsPluginInstanceOwner中含有如下元素:
nsPluginNativeWindow *mPluginWindow;
nsCOMPtr mInstance;
nsObjectFrame *mOwner;
nsCOMPtr mContent;
nsCString mDocumentBase;
char *mTagText;
nsCOMPtr mWidget;
nsCOMPtr mPluginTimer;
nsCOMPtr mPluginHost;

有了对基本的数据结构理解之后这样在parser html内容时,会根据embed/object标签创建nsHTMLObjectElement、nsObjectFrame、nsPluginInstanceOwner等实例,进而在nsHTMLObjectElement的LoadObject方法驱动下逐步的调用TryInstantiate、Instantiate、InstantiateEmbeddedPlugin、TrySetUpPluginInstance、Create4xPlugin等等,从而完成一个plugin实例的生成等。

至于其Scripting、Stream、security方面的支持须进一步参考nsIDOMClassInfo、nsGenericHtmlObjectSH及nsIPluginStreamListener等;

四、总结
通过初步了解plugin的实现机制后,然后自己动手参照Mozilla提供的plugin examples及相关API,完成一个基本的Plugin难度其实并不大,但是如想利用plugin技术实现如3D、Stream Media、叠加等方面的效果,须更进一步的了解3D、Stream Media等方面的内容。其实支持Gecko内核的Plugin相当多,如主流的播放器、flash、java applet、silverlight等等都有浏览器插件,再加上支持Gecko内核的plugin一般也支持Opera、Safari,这样如果我们掌握了NPAPI Plugin技术,那么我们就可以充分运用已有的plugin或自己动手创建一个好用的plugin,这样会大大的扩展浏览器的应用,让我们拭目以待吧。。。。。

五、参考资源
WIKI NPAPI
Mozilla projects -plugins
Mozilla developer center -plugins
Netscape Gecko Plugin API Reference
Mozilla Plugin Support on Microsoft Windows

2008年7月24日星期四

浅谈Gecko关键部分之八NSPR

Gecko内核作为一个跨平台的内核,具有很强的跨平台系统处理能力,其中就缺不了Netscape Portable Runtime (NSPR)的功劳,要想深入的了解Gecko内核,就不得不了解了解NSPR,它提供了基础性系统性功能的封装,在此基础上才有了线程管理、内存管理、I/O管理等等,下面提供一些资料以供参考学习。

一、什么是NSPR?
The Netscape Portable Runtime, or NSPR, is a platform abstraction library that makes all operating systems it supports appear the same to Mozilla. NSPR provides platform independence for non-GUI operating system facilities. These facilities include threads, thread synchronization, normal file and network I/O, interval timing and calendar time, basic memory management (malloc and free) and shared library linking.

相关参考资源
NSPR WiKi
Mozilla NSPR HomePage
NSPR Reference

二、为什么了解NSPR?
除了Gecko内核是基于NSPR的,其他一些应用也是基于NSPR;它具有很强的稳定性、可复用性等,了解NSPR,对了解Gecko内核是不可或缺的,就像想了解Apache Http Sever,就必须对APR有所了解。

了解了NSPR,除了让我们能更好更快更清晰的了解Gecko内核,其实还可以让我们从另一个侧面更好的了解不同操作系统的主要差异如线程的实现、线程同步的实现、I/O管理的不同等等,从而提升我们对操作系统的认识,进一步的把握计算机技术。

相关参考资源
Apache Portable Runtime(APR) WiKi
Simple DirectMedia Layer(SDL) WiKi

2008年7月20日星期日

浅谈Gecko关键部分之七安全及其实现

很长一段时间以来Firefox以安全、小巧、快捷著称,从而逐步从饱受不安全批评的IE中抢夺到部分用户的支持,在用户心中确立了Firefox比IE更安全的想法,其实浏览器的安全一直受到人们的广泛关注,但往往不能做到百分百的完美,似乎总有一些遗憾的地方,正所谓道高一尺魔高一丈。为了更深入的了解浏览器技术,安全正可谓不得不面对的问题,也许它比较的复杂,涉及面比较广,还是让我们从安全问题的出现、Gecko内核的安全策略及其主要实现路径等方面来学习研究,以便达到以一窥十的效果。

一、安全问题的出现
浏览器作为一个通往互联网的入口,其可以连接访问的网站成千上万,并且类型也缤纷多彩,正是这一点突出了浏览器功能强大、实用的特点,进而确立其在互联网应用中不可缺失的地位,但同时也带来了人们的疑虑,我们刚才访问的网站是否下载了病毒、是否破坏了操作系统、提交的数据如用户名密码是否泄漏、访问的历史记录是否遭到外部网站的截取等等。这些问题从浏览器出现的一刻起就受到浏览器开发者的关注,到目前为止,有些浏览器还是受到人们广泛的质疑。

其实困扰人们心中的浏览器安全问题往往可分为两类:
对用户本地私有数据的保护

私有数据往往包含两方面,一方面在于操作系统的文件是否遭到访问、读写等,其实一旦存在这类漏洞,其危害是特别大的,往往会导致整个操作系统的不安全,我们以前通过IE访问不良网站时,经常会遭遇到木马等病毒的骚扰,其实问题的出现正是由于病毒利用了activex组件等方面的漏洞,从而重新配置操作系统,并下载病毒文件等。另一方面在于保护用户保存的Cookie、历史访问记录,提交的表单数据是否遭到非法访问,在同一浏览中同时打开的不同页面是否能相互访问,共享数据等。

其实在浏览器设计之初就充分考虑到这些方面的问题,特别在javascript设计之初,因为浏览器能够执行外部网站的操作往往来源于javascript脚本,一旦能控制住浏览器为脚本提供的接口的安全性,浏览器的安全问题就得到架构上保证,再加上javascript本身是脚本语言,这样为任何一句脚本语言提供执行前的检查提供了可能。正是基于这样的出发点,javascript的实现是基于sanbox技术的,脚本语言的执行是在一个安全的上下文中进行的,它所造成的危害是可控的如从理论上讲,javascript是不可以访问操作系统文件的,是不可以上传不由用户指定的任意文件的。当然其中很多相关的安全细则可参考相关js手册,其实现是由内核来提供保证的。

对用户提交给网站数据的保护
浏览器往往通过http协议来与网站进行数据交互,由于http协议属于应用层协议,交互的数据对网络层来讲是透明的,也是不安全的,作为一个能广泛使用的应用程序,为了满洲需对提供的银行帐户与密码进行加密等需求,浏览器必须提供一些数据加密措施来保证数据交互的安全。基于这一点及广泛标准的支持,Gecko内核采用了PKI开源应用模块(相对来说蛮独立)来保证数据加密的要求如对PKCS #5, PKCS #7, PKCS #11, PKCS #12, S/MIME, TLS, SSL v2 and v3, X.509 v3 certificates等方面的支持,而没有使用目前很多项目都采用的OpenSSL来对SSL的支持,不过SSL作为一个标准,无论采取OpenSSL还是PKI,只不过是实现上的不同而已。

其中人们经常听说的反钓鱼技术,往往与此有点关联。
网络钓鱼 (phishing)是国际通用的新字,取phreak(偷接电话线的人)的前两个字母ph取代fishing(钓鱼)的f,是以社会工程学(也就是骗术)结合电脑科技的新犯罪手法。

网络钓鱼的目的是骗取受害人的网站上的帐号密码(网络银行或线上游戏)、信用卡资料以及个人资料,进行例如线上转帐、偷取游戏虚拟宝物、窥伺别人的email以及盗刷银行卡等违法行为。

钓鱼正以一种新的犯罪形式侵犯,在不断侵犯普通网民的合法利益,具体表现在伪装“网络银行”窃取用户的银行卡卡号和密码,转走用户在银行的存款;伪装成网 络游戏的官方网站窃取用户的网游帐号,转走用户在游戏里面的虚拟货币或者虚拟装备; 伪装成送QQ币的网站让用户输入QQ号和密码进而窃取QQ号……

其中Firefox应该是目前提供反钓鱼技术最好的浏览器,它不仅提供反盗取密码等网站,还提供反不安全、非法及含有黄色内容的网站。
其实现机制往往结合nsIContentPolicy接口及维护后台不安全网站列表来共同实现。

如须具体了解数据加密的内容,可更加深入的了解PKI OpenSource等方面的知识,这次主要学习研究对本地私有数据安全方面的内容。

二、Gecko内核的安全策略
为了保证数据的提交与行为的安全执行,Gecko内核中提供两种安全策略,Same-Origin Policy和Security Zones and Signed Scripts。

其中Same-Origin Policy的主要含义在于一段脚本只能访问具有同一来源的window及document的内容,同一来源包括同一主机、同一端口、同一协议,同时为了对扩展域名的支持,同一来源也指具有同一domain的不同window及document

而Security Zones and Signed Scripts指的是为了给一段脚本更高的访问权限,需要由用户确认后来授权该脚本可以拥有更高的权限。其实现的方式有通过SignTool及安全Jar的方式来授权、提示用户由用户来确认的方式来授权。其中前一种相当复杂,普通用户使用较少,而后一种方式应用较多,并且可以由用户自己来配置一些对象的属性、方法的访问权限或自定义一些域名浏览器访问权限,下面示例一些权限设置。
user_pref("capability.policy.default.Window.open", "noAccess");

user_pref("capability.policy.policynames", "strict");
user_pref("capability.policy.strict.sites", "http://www.evil.org http://www.annoying.com"); user_pref("capability.policy.strict.Window.alert", "noAccess");
user_pref("capability.policy.strict.Window.confirm", "noAccess");
user_pref("capability.policy.strict.Window.prompt", "noAccess");

user_pref("capability.principal.codebase.p2.granted", "UniversalXPConnect");
user_pref("capability.principal.codebase.p2.id", "http://192.168.1.200");
user_pref("capability.principal.codebase.p2.subjectName", "");


一段脚本可拥有的浏览器访问权限包括UniversalBrowserRead、UniversalBrowserWrite UniversalXPConnect、UniversalPreferencesRead、UniversalPreferencesWrite CapabilityPreferencesAccess、UniversalFileRead等,它们可以通过netscape.security.PrivilegeManager.enablePrivilege("UniversalPreferencesRead");等方式来设置权限,如果用户的signed.applets.codebase_principal_support配置选项设为true,在设置权限时会提示用户是否设置该codebase权限。

三、安全主要实现路径
首先由javascript SpiderMonkey提供了一组安全方面的接口,以供脚本安全的检查,至于具体的安全规则则由xpconnect及securitymanager来实现,主要接口有JSPrincials、JS_CheckAccess、
JSObjectOps.checkAccess、JSClass.checkAccess、JS_SetPrincipalsTranscoder等;

然后由nsScriptSecurityManager来管理维护其提供的nsPricipal实例及相关的安全检查,其中在其初始化时完成如下主要操作:

nsresult nsScriptSecurityManager::Init()
{
nsresult rv = InitPrefs();//将用户自定义的pricipal组织起来
NS_ENSURE_SUCCESS(rv, rv);
。。。。。。。。。。。。。。。。
// Create our system principal singleton
nsRefPtr system = new nsSystemPrincipal();
NS_ENSURE_TRUE(system, NS_ERROR_OUT_OF_MEMORY);
rv = system->Init();
NS_ENSURE_SUCCESS(rv, rv);

mSystemPrincipal = system;
。。。。。。。。。。。。。。。
//-- Register security check callback in the JS engine
// Currently this is used to control access to function.caller
nsCOMPtr runtimeService =
do_GetService("@mozilla.org/js/xpc/RuntimeService;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);

rv = runtimeService->GetRuntime(&sRuntime);
NS_ENSURE_SUCCESS(rv, rv);

//CheckObjectAccess成为安全检查的主要入口点
JS_SetCheckObjectAccessCallback(sRuntime, CheckObjectAccess);

sXPConnect->GetXPCWrappedNativeJSClassInfo(&sXPCWrappedNativeJSClass,
&sXPCWrappedNativeGetObjOps1,
&sXPCWrappedNativeGetObjOps2);

return NS_OK;
}

在实现的过程中会提供一些nsPrincipal实例如nsNullPrincipal、nsSystemPrincipal来表示不同的安全规则,同时每一个nsPricipal实例都包含一个JSPricipals接口实例,其内容可由JS_SetPrincipalsTranscoder来根据不同情况来设定,这样可以为不同的JSContext、JSClass、JSObject、JSFunction设置对应的JSPricipals,由它可以获得nsPrincipal实例,以了解真正的安全规则。

其中由SpiderMonkey JS Engineer内部或Xpconnect、nsScriptSecurityManager等外部在需要实施安全规则检查的地方如检查xpcom组件接口参数发起JS_CheckAccess的执行,其中会调用上面提到的回调函数CheckObjectAccess,以触发nsSecurityManager来统一对安全的检查,起到sanbox的作用。具体的nsPricipal及nsSecurityManager的管理可参考\mozilla\caps\src\nsPricipal.cpp及nsScriptSecurityManger.cpp等,至于如何为JS对象设置一些JSPricipals则需要更深入对函数JS_SetPrincipalsTranscoder的调用与实现作一些了解。

四、参考资源
Mozilla Security Review and Best Practices Guide
The Same Origin Policy
Signed Scripts in Mozilla
Configurable Security Policies
JSAPI User Guide
Open Source PKI Projects



2008年7月13日星期日

浅谈Gecko关键部分之六认识javascript实现及应用

作为一个目前应用范围非常广的脚本语言javascript,或许每一个程序员都听说过并使用过,但据部分统计,许多程序员对javascript的认识存在一定误解,认为它非常简单,会写javascript简直算不了什么,并且认为它在Web开发中的地位根本没有如php、ruby等开发语言及所谓的框架那么实用好用,以致没有重视javascript在Web开发中所应有的作用。为寻求Gecko内核究竟是如何实现javascript以及它在内核中的地位究竟如何,乃至进一步探讨一下javascript的发展方向,下面分别就什么是Javascript、它在Gecko中的实现及其扩展、应用等方面作一些学习研究总结,以致从整体上对javascript有更清晰的认识。

一、什么是javascript?
javascript是一门动态、弱类型、基于原型的脚本语言,其核心部分包含对脚本的编译、解析、执行、反编译、垃圾回收及标准类/类型如 bool、string、number的实现等;然后在此基础通过将已经符合DOM标准的文档DOM接口按照javascript接口定义及声明的方式绑定/导出到 javascript运行环境中,以丰富javascript的内涵,其本质相当于有了java虚拟机后,通过import不同类型的类库来扩展 java语言的应用。

目前javascript比较独特的地方在于它自身没有一个完整独立的运行环境,其往往依附于浏览器,由浏览器来提供运行环境,并控制或发起javascript进行编译、解析执行脚本、垃圾回收等,其核心部分相当于一个符合ECMAScript标准的动态库,以供浏览器来调用,这样看来其本质是为了对浏览器主要部分的扩展及更灵活运用的支持,从MVC的角度来看,javascript相当于浏览器中的控制部分,其应用场景往往具有一定的局限性、独特性的;而通用的脚本语言如perl、python、 ruby等都提供了完整独立的语言运行环境及线程管理等,并且提供了良好的扩展机制以丰富语言的内涵。

在Gecko内核中xpconnect扮演为javascript提供运行环境及扩展的角色,其不仅将符合DOM标准的接口扩展到javascript运行环境中去,同时将大部分xpcom接口也扩展到整个javascript运行环境中,这一点在其他浏览器如Safari、IE是没有的,这样就奠定了 javascript在整个Gecko内核中不仅可访问文档的DOM接口,同时可以访问极大部分xpcom接口,进而可以使用javascript编写 xpcom组件,以供其他xpcom组件访问。从架构的角度看Gecko内核对javascript的扩展提供了极大的支持,但由于Gecko内核中有一部分重要组件是由javascript来实现的,从而绑定javascript成为Gecko内核的核心部分,虽然这样给程序的开发带来了极大的方便性,但也带来了调试等方面的不便。

二、javascript在Gecko中的实现及其扩展
为了更好的扩展及模块化,Mozilla对 javascript的实现是以SpiderMonkey+xpconnect的方式来实现的,其中SpiderMonkey主要是按照 ECMAscript的方式来实现javascript语言的核心部分,其提供了一组公共JSAPI以供外部调用,SpiderMonkey是相当独立的一部分,它可以单独编译,可以嵌入到其他程序中去,下一个版本可能会整合Tamarin相关的内容;

而xpconnect作为一个xpcom服务类组件,具有sington特性,从javascript 语言的角度来看,它为javascript提供了运行环境,同时按照SpiderMonkey提供的JSAPI对其进行了扩展;从xpcom角度来看,xpconnect将很大一部分xpcom接口以javascript的接口方式绑定出去,以便其能在javascript的运行环境中以 javascript的方式来引用xpcom组件及接口。其复杂程度是相当大的,因为它既要按照ECMAScript标准的要求考虑到脚本语言方面的控制如动态性、安全方面等,同时它要尽可能将xpcom组件方面的接口绑定出去给javascript脚本使用,并且两者的内存管理、对象生命周期的管理机制是完全不同的,再加上javascript及xpcom本身具有一定的动态特性。因此,xpconnect部分也就成为内存泄漏及效率提升方面的重点关注对象。

SpiderMonkey按照ECMAScript标准提供的主要外部接口有:
Starting up相关JS_NewRuntime、JS_NewContext、JS_NewObject、JS_InitStandardClasses
Defining objects and properties相关JS_DefineObject、JS_DefineProperties
Defining functions and classes相关JS_DefineFunctions、JS_InitClass
Execute/Call function相关JS_EvaluateScript、JS_ExecuteScript、JS_CallFunctionName
Shutting down相关JS_DestroyContext、JS_DestroyRuntime、JS_ShutDown
其具体实现及应用可以参考后面提供的网址。

xpconnect is a technology which enables simple interoperation between XPCOM and JavaScript.
其主要内容有
将用c/c++实现的xpcom组件接口(包含标准的DOM、CSS接口等)绑定出去给javascript运行环境使用。
为了统一方便使用,Gecko内核提出供一组nsDOMClassInfo子类,如nsWindowSH、nsLocationSH、 nsElementSH、nsDocumentSH等,作为Script Helper类,他们实现了接口nsIXPCScriptable及nsIClassInfo。

每当xpconnect需要将原生c/c++xpcom组件对象绑定给javascript使用时,它可通过 InitClassesWithNewWrappedGlobal或WrapNative方法来处理,但都会调用到 XPCConvert::NativeInterface2JSObject方法,这个方法主要处理过程是:
首先获取该组件对象对应的nsDOMClassInfo子类实例,整个Gecko内核拥有很多Script Helper nsDOMClassInfo子类实例(其往往在初始化时存储在一个Hash表中,并建好相关查询索引);

然后利用获取的Script Helper nsDOMClassInfo子类实例的PreCreate方法来创建该组件对应的jsObject,同时会创建XPCWrappedNative类实例,在其中保存好mScriptableInfo信息,其如nsCOMPtr mCallback往往为nsDOMClassInfo子类实例,

如需要会为其创建prototype原型对象以便后面共享,这时需要向javascript运行环境提供一组回调API,其主要函数在 mozilla\js\src\xpconnect\Xpcwrappednativejsops.cpp中如 XPC_WN_Helper_AddProperty、XPC_WN_Helper_DelProperty、 XPC_WN_Helper_NewResolve等,通过观测这些回调函数发现他们往往首先会通过传递过来的jsObject获取 XPCWrappedNative对象,然后获取其ScriptableInfo,根据相关flag来决定是否调用Script Helper 实例中对应的方法,而Script Helper实例往往在初始时就已经知道原生绑定出去的xpcom组件能支持的一些xpcom接口,进而可以对原先不认识的接口、属性、方法、参数,进行 QueryInferface,进行WrapNative处理,将结果返回给js使用。

其中需要注意的是有些组件或类对象没有直接实现nsIClassInfo,哪么nsCOMPtr info =do_QueryInterface(aComObj);是如何获得Script Helper nsIClassInfo 实例呢?

费了九牛二虎之力,终于发现Gecko内核中使用了一些诡异的办法来到达目的的。示例如下:

定义一个Macro
#define NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(_class) \
if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { \
foundInterface = NS_GetDOMClassInfoInstance(eDOMClassInfo_##_class##_id); \
if (!foundInterface) { \
*aInstancePtr = nsnull; \
return NS_ERROR_OUT_OF_MEMORY; \
} \
} else

而extern nsIClassInfo* NS_GetDOMClassInfoInstance(nsDOMClassInfoID aID);就是根据ID到sClassInfoData数组中去匹配即可,这个数组在初始化时就填充完毕,其不同ID值对应不同的 nsIClassInfo 实例如nsWindowSH、nsDocumentSH实例等。

而在\mozilla\dom\src\base\nsGlobalWindow.cpp中通过
// QueryInterface implementation for nsGlobalChromeWindow
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsGlobalChromeWindow)
NS_INTERFACE_MAP_ENTRY(nsIDOMChromeWindow)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(ChromeWindow)
NS_INTERFACE_MAP_END_INHERITING(nsGlobalWindow)
来设定do_QueryInterface时对nsIClassInfo的支持。
另外还有一些类也是通过该方式来对nsIClassInfo的支持。

三、javascript在Gecko中的应用
在Gecko内核及Firefox本身的实现中使用了很多javascript代码,其中不仅仅是对基本javascript语言及DOM 接口的使用,同时充分利用了上面提到到xpcom绑定出去的组件接口,假如对xpcom及其绑定原理不是很清楚,也就对Firefox当中许多js看不懂,往往让人纳闷Gecko内核中的js为什么会多出这么多非ECMAScript所定义的关键字如Components、interfaces、classes等,其具体含义是什么?虽然XULPlant.org对其中有一定的说明,但还是显得抽象,不具体。在有了上面最基本的认识后,如需全面掌握Gecko内核究竟向javascript绑定导出了多少接口、属性及方法等,可具体参考文档\mozilla\dom\src\base\nsDOMClassInfo.cpp的nsDOMClassInfo::Init()方法中一大段关于nsIDOMClassInfo方面的初始化。如
DOM_CLASSINFO_MAP_BEGIN(Window, nsIDOMWindow)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMJSWindow)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindowInternal)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMViewCSS)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMAbstractView)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMStorageWindow)
DOM_CLASSINFO_MAP_END

DOM_CLASSINFO_MAP_BEGIN(Location, nsIDOMLocation)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMLocation)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSLocation)
DOM_CLASSINFO_MAP_END

DOM_CLASSINFO_MAP_BEGIN(XMLHttpRequest, nsIXMLHttpRequest)
DOM_CLASSINFO_MAP_ENTRY(nsIXMLHttpRequest)
DOM_CLASSINFO_MAP_ENTRY(nsIJSXMLHttpRequest)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
DOM_CLASSINFO_MAP_ENTRY(nsIInterfaceRequestor)
DOM_CLASSINFO_MAP_END

这样就对Firefox browser.js中的类似下面的代码就不觉得迷糊啦。
const nsCI = Components.interfaces;
const nsIWebNavigation = nsCI.nsIWebNavigation;
window.XULBrowserWindow = new nsBrowserStatusHandler();
window.QueryInterface(nsCI.nsIInterfaceRequestor)
.getInterface(nsIWebNavigation)
.QueryInterface(nsCI.nsIDocShellTreeItem).treeOwner
.QueryInterface(nsCI.nsIInterfaceRequestor)
.getInterface(nsCI.nsIXULWindow)
.XULBrowserWindow = window.XULBrowserWindow;
window.QueryInterface(nsCI.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();

四、总结
经过上面的学习研究,觉得不管是javascript基本语法及应用方面,还是其具体实现方面,还有xpconnect实现方面等,都可单独写一本书来描述,其内容实在太多,并且比较复杂,以后可多花点心力来继续了解他们。但是为了更好的了解其实现过程,能充分发挥javascript优势,不妨多用用DOM Inspecter及javascript debuger venkman等工具。

五、参考资源
Wiki Script language
WiKi Javascript
Core Javascript1.5 Guide
XPConnect(Scriptable Components)
The Mozilla DOM Hacking Guide
JavaScript-DOM Prototypes in Mozilla
JSAPI User Guide
SpiderMonkey Internals
SpiderMonkey Thread Safety
SpiderMonkey Garbage Collection Tips
How to embed the javascript engine
Tamarin

2008年7月7日星期一

浅谈Gecko关键部分之五认识HTML、DOM、CSS、XML、XHTML、SVG等标准

在了解Gecko内核是如何响应用户输入的Web地址,如何渲染、显示包含文字、图片、动画甚至视频的画面给用户的整个过程之后,假如我们深思一下,Gecko内核为什么要这样解析、渲染这个Web地址对应的内容?为什么不那样解析?它在解析、渲染的时候会受到约束吗?它可以自由发挥吗?答案是肯定,它所受的约束也就是我们熟知的W3C标准,它是尽可能按照标准来办事的,当然标准当中也肯定有未尽之事,所以它也有一定的自由发挥空间。正是有了如此众多的W3C标准,让我们的互联网应用是如此的丰富多彩,IE、Safari、Opera等浏览器都基本按照标准办事,所以我们编写的符合标准的Web页面可以在如此多的浏览器上被解析、渲染显示出来,并且效果基本一致。标准也就是协议,所以我们要想深入的了解Gecko内核,必须深入的了解它所支持的标准。下面初略的介绍一下HTML、DOM、CSS、XML、XHTML、SVG等标准。

一、HTML
HTML, an initialism of HyperText Markup Language, is the predominant markup language for web pages. It provides a means to describe the structure of text-based information in a document — by denoting certain text as links, headings, paragraphs, lists, and so on — and to supplement that text with interactive forms, embedded images, and other objects. HTML is written in the form of tags, surrounded by angle brackets. HTML can also describe, to some degree, the appearance and semantics of a document, and can include embedded scripting language code (such as JavaScript) which can affect the behavior of Web browsers and other HTML processors.

具体可参考
HTML Wiki
HTML 4.01 Specification
HTML 5 Specification

二、
DOM

The Document Object Model (DOM) is a platform- and language-independent standard object model for representing HTML or XML and related formats.

A web browser is not obliged to use DOM in order to render an HTML document. However, the DOM is required by JavaScript scripts that wish to inspect or modify a web page dynamically. In other words, the Document Object Model is the way JavaScript sees its containing HTML page and browser state.

具体可参考
DOM Wiki
DOM2 Html Specification

三、CSS
Cascading Style Sheets (CSS) is a stylesheet language used to describe the presentation of a document written in a markup language. Its most common application is to style web pages written in HTML and XHTML, but the language can be applied to any kind of XML document, including SVG and XUL.

具体可参考
CSS Wiki
CSS Specification

四、XML
The Extensible Markup Language (XML) is a general-purpose specification for creating custom markup languages.[1] It is classified as an extensible language because it allows its users to define their own elements. Its primary purpose is to facilitate the sharing of structured data across different information systems, particularly via the Internet,[2] and it is used both to encode documents and to serialize data. In the latter context, it is comparable with other text-based serialization languages such as JSON and YAML.[3]

具体可参考
XML Wiki
XML Specification

五、XHTML
The Extensible Hypertext Markup Language, or XHTML, is a markup language that has the same depth of expression as HTML, but also conforms to XML syntax.

具体可参考
XHTML Wiki
XHTML Specification

六、SVG
Scalable Vector Graphics (SVG) is an XML specification and file format for describing two-dimensional vector graphics, both static and animated. SVG can be purely declarative or may include scripting. Images can contain hyperlinks using outbound simple XLinks.[2] It is an open standard created by the W3C's SVG Working Group.

具体可参考
SVG Wiki
SVG Tiny1.2 Specification

七、总结
面对如此众多的W3C标准,特别是W3C上发布的标准文档,我们往往会看得云里雾里,毕竟这些标准文档是所谓的专家学者所写,够严谨全面的。为了便于日常运用,我们只要把握两条即可1、懂得其基本用途及主要要素; 2、必要时刻需仔细研读之,定会收获众多,她们就像我们的良师益友,平时没事的时候可常习之。




2008年7月5日星期六

浅谈Gecko关键部分之四The life of an html http Request

从用户的角度来看,一个浏览器其实是相当简单的,用户输入一个Web地址,然后按回车,等不了多久,一个具有丰富内容的画面就会出现在用户面前,其中包含五彩缤纷的排列整齐的文字、图片、动画甚至视频,用户通过点击自己感兴趣的地方,新的内容画面就会呈现在用户面前,这样用户就可以只需简单的点击一下鼠标就可以在互联网上自由遨游啦。现在回想起来,也许正是浏览器当初在设计的时候充分考虑到用户的易用性才导致互联网的蓬勃发展,正像谷歌提供搜索一样简单易用并且非常实用,这样说来,互联网的发展离不开浏览器,是她带领人们走向了互联网,回顾历史让我们感慨万千,那么浏览器主要做了些啥?她为什么能支持如此丰富的内容?她的发展方向在哪里?为了解答心中的疑问,首先让我们初步了解Gecko内核是如何完成从用户输入Web地址,直到内容丰富的画面显示出来的整个过程,这个过程涉及的面比较广,目前我们主要关心关键过程、关键点及相关的关键类的实现,争取能有个整体的而又完整的印象。

一、创建nsWebShellWindow实例,准备处理输入的WebURI地址;
因为接收用户打开一个新URI的情况比较多,如通过脚本中的window.open或直接在地址栏中输入或者在新开的一个窗口打开或者在新建的一个页签打开等等,通过对这一系列的打开一个页面的外围场景进行处理后,内核都会新建一个或利用原有的nsWebShellWindow实例来处理整个打开URI的过程。其包含的主要成员有nsComPtr mWindow,代表一个原生窗口,将要打开的页面可在其中显示内容如文字、图片等;nsComPtr mDocShell,代表一个协调者,它管理整个打开URI的过程,其实现类nsDocShell也就成为整个Gecko内核的核心部分,它不仅接收处理打开URI请求,同时提供接口以反馈相关信息给外部如提示正在打开某某地址等。在nsWebShellWindow的Initialize()方法中通过mWindow = do_CreateInstance(kWindowCID, &rv);来创建mWindow,通过mDocShell = do_CreateInstance("@mozilla.org/webshell;1");来创建设置mDocShell为打开URI做好前期准备。

二、准备装载指定URI的内容;
首先由协调者nsDocShell的LoadURI方法来发起装载的请求,经过一番安全或请求Policy等方面的设定后创建nsCOMPtr uriLoader=do_GetService(NS_URI_LOADER_CONTRACTID, &rv);
nsCOMPtr channel;
rv = NS_NewChannel(getter_AddRefs(channel),
aURI, nsnull, nsnull, static_cast(this), loadFlags);
由uriLoader->OpenURI(aChannel, (mLoadType == LOAD_LINK), this);来统一继续处理所有的URI请求,其中会创建一个临时的nsCOMPtr loader =new nsDocumentOpenInfo(aWindowContext, aFlags, this);同时将loader作为一个nsIStreamListener来接收请求而来的数据,以分析请求而来的文档类型如text/html、xml等以决定后续不同的处理,然后由rv = channel->AsyncOpen(loader, nsnull);来真正向Web服务发出请求,并通过异步的方式来处理接收数据,这里往往会涉及到以前提到的线程处理等,总之至此基本的准备动作应该都已准备好,就等待Web服务端返回数据啦。

三、根据接收的数据类型创建Document及相关ContentViewer;
根据异步数据的获取,获取数据的线程向MainThread发出nsInputStreamReadyEvent事件,由主线程来处理相关事件,其往往会调用到nsInputStreamPump中去,其关键调用如下:
nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream *stream)
{
LOG(("nsInputStreamPump::OnInputStreamReady [this=%x]\n", this));

// this function has been called from a PLEvent, so we can safely call
// any listener or progress sink methods directly from here.

for (;;) {
if (mSuspendCount || mState == STATE_IDLE) {
mWaiting = PR_FALSE;
break;
}

PRUint32 nextState;
switch (mState) {
case STATE_START:
nextState = OnStateStart();
break;
case STATE_TRANSFER:
nextState = OnStateTransfer();
break;
case STATE_STOP:
nextState = OnStateStop();
break;
}
...........

mState = nextState;
}
return NS_OK;
}

当STATE_START时,表示已获取部分
初始数据,其中OnStateStart()会调用作为Channel的streamListener的OnStartRequest方法,此时streamListener为uriLoader临时创建的nsDocumentOpenInfo实例loader,由它来处理,其方法OnStartRequest进而由nsDocumentOpenInfo的DispatchContent方法来尝试决定能否处理该文档类型,一旦决定可以处理该类型,进而由nsDSURIContentListener的DoContent来调用nsDocShell的方法CreateContentViewer来创建Document及Viewer,其中主要进行代码包括:
{
..........................................
// Instantiate the content viewer object
nsCOMPtr viewer;
nsresult rv = NewContentViewerObj(aContentType, request, mLoadGroup,
aContentHandler, getter_AddRefs(viewer));

NS_ENSURE_SUCCESS(Embed(viewer, "", (nsISupports *) nsnull),
NS_ERROR_FAILURE);
............................................
}
nsresult
nsDocShell::NewContentViewerObj(const char *aContentType,
nsIRequest * request, nsILoadGroup * aLoadGroup,
nsIStreamListener ** aContentHandler,
nsIContentViewer ** aViewer)
{
nsCOMPtr aOpenedChannel = do_QueryInterface(request);

nsresult rv;
nsCOMPtr catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
if (NS_FAILED(rv))
return rv;

nsXPIDLCString contractId;
rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", aContentType, getter_Copies(contractId));

// Create an instance of the document-loader-factory
nsCOMPtr docLoaderFactory;
if (NS_SUCCEEDED(rv))
docLoaderFactory = do_GetService(contractId.get());

if (!docLoaderFactory) {
return NS_ERROR_FAILURE;
}

// Now create an instance of the content viewer
// nsLayoutDLF makes the determination if it should be a "view-source" instead of "view"
NS_ENSURE_SUCCESS(docLoaderFactory->CreateInstance("view",
aOpenedChannel,
aLoadGroup, aContentType,
static_cast(this),
nsnull,
aContentHandler,
aViewer),
NS_ERROR_FAILURE);

(*aViewer)->SetContainer(static_cast(this));
return NS_OK;
}

这里主要会根据contentType的不同创建不同的Document对象,它由nsContentDLF::CreateInstance来实现。其主要代码如下

nsCOMPtr doc;
nsCOMPtr docv;
do {
// Create the document
doc = do_CreateInstance(aDocumentCID, &rv);//针对htmldocumnt、xuldocument、imagedocument的不同会对应不同的DocumnetCID
if (NS_FAILED(rv))
break;

// Create the document viewer XXX: could reuse document viewer here!
rv = NS_NewDocumentViewer(getter_AddRefs(docv));
if (NS_FAILED(rv))
break;
docv->SetUAStyleSheet(gUAStyleSheet);

doc->SetContainer(aContainer);

// Initialize the document to begin loading the data. An
// nsIStreamListener connected to the parser is returned in
// aDocListener.
rv = doc->StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer, aDocListener, PR_TRUE);
if (NS_FAILED(rv))
break;

// Bind the document to the Content Viewer
rv = docv->LoadStart(doc);
*aDocViewer = docv;
NS_IF_ADDREF(*aDocViewer);
} while (PR_FALSE);

其中需要注意的是在StartDocumentLoad的时候会创建一个mParser = do_CreateInstance(kCParserCID, &rv);作为DocListener,以供后面解析文档内容用;同时会创建nsCOMPtr sink,以供后面组织文档内容用;创建ContentSink的主要代码逻辑如下:
// create the content sink
nsCOMPtr sink;

if (aSink)
sink = aSink;
else {
if (IsXHTML()) {
nsCOMPtr xmlsink;
rv = NS_NewXMLContentSink(getter_AddRefs(xmlsink), this, uri,
docShell, aChannel);

sink = xmlsink;
} else {
nsCOMPtr htmlsink;

rv = NS_NewHTMLContentSink(getter_AddRefs(htmlsink), this, uri,
docShell, aChannel);

sink = htmlsink;
}
NS_ENSURE_SUCCESS(rv, rv);

NS_ASSERTION(sink,
"null sink with successful result from factory method");
}

mParser->SetContentSink(sink);
// parser the content of the URI
mParser->Parse(uri, nsnull, (void *)this);
}

一旦初步组织好nsDocShell、Document、Viewer、Parser、ContentSink之间的关系后,在nsDocumentOpenInfo调用DispatchContent时将返回的DocListener也即nsParser实例赋值给nsDocumentOpenInfo的m_targetStreamListener,以供后续使用,同时告诉Parser、ContentSink作好解析、组织文档准备;

另外通过nsDocShell的Embed方法,初始化DocumentViewer,其主要实现如下:
nsDocShell::SetupNewViewer(nsIContentViewer * aNewViewer)
{
..........................
mContentViewer = aNewViewer;
nsCOMPtr widget;
NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(widget)), NS_ERROR_FAILURE);

nsCOMPtr deviceContext;
if (widget) {
deviceContext = do_CreateInstance(kDeviceContextCID);
NS_ENSURE_TRUE(deviceContext, NS_ERROR_FAILURE);
deviceContext->Init(widget->GetNativeData(NS_NATIVE_WIDGET));
}

nsRect bounds(x, y, cx, cy);

mContentViewer->Init(widget, deviceContext, bounds);
..........................
}

DocumentViewerImpl::InitInternal(nsIWidget* aParentWidget,
nsISupports *aState,
nsIDeviceContext* aDeviceContext,
const nsRect& aBounds,
PRBool aDoCreation,
PRBool aInPrintPreview,
PRBool aNeedMakeCX /*= PR_TRUE*/)
{
.......................................
if (aParentWidget && !mPresContext) {
// Create presentation context
if (mIsPageMode) {
//Presentation context already created in SetPageMode which is calling this method
}
else
mPresContext =
new nsPresContext(mDocument, nsPresContext::eContext_Galley);
NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY);

nsresult rv = mPresContext->Init(aDeviceContext);
if (NS_FAILED(rv)) {
mPresContext = nsnull;
return rv;
}

if (aDoCreation && mPresContext) {
// The ViewManager and Root View was created above (in
// MakeWindow())...

rv = InitPresentationStuff(!makeCX, !makeCX);
}
................................................
}

DocumentViewerImpl::InitPresentationStuff(PRBool aDoInitialReflow, PRBool aReenableRefresh)
{
..............................................

// Create the style set...
nsStyleSet *styleSet;
nsresult rv = CreateStyleSet(mDocument, &styleSet);
NS_ENSURE_SUCCESS(rv, rv);

// Now make the shell for the document
rv = mDocument->CreateShell(mPresContext, mViewManager, styleSet,
getter_AddRefs(mPresShell));

mPresShell->BeginObservingDocument();//mDocument->AddObserver(mPresShell);
..............................................

}

nsDocument::doCreateShell(nsPresContext* aContext,
nsIViewManager* aViewManager, nsStyleSet* aStyleSet,
nsCompatibility aCompatMode,
nsIPresShell** aInstancePtrResult)
{
*aInstancePtrResult = nsnull;

NS_ENSURE_FALSE(mShellsAreHidden, NS_ERROR_FAILURE);

FillStyleSet(aStyleSet);

nsCOMPtr shell;
nsresult rv = NS_NewPresShell(getter_AddRefs(shell));
if (NS_FAILED(rv)) {
return rv;
}

rv = shell->Init(this, aContext, aViewManager, aStyleSet, aCompatMode);
NS_ENSURE_SUCCESS(rv, rv);

// Note: we don't hold a ref to the shell (it holds a ref to us)
NS_ENSURE_TRUE(mPresShells.AppendElementUnlessExists(shell),
NS_ERROR_OUT_OF_MEMORY);
shell.swap(*aInstancePtrResult);

return NS_OK;
}
这样同时准备好与显示相关的nsDeviceContext、nsPresContext、nsPresShell,其中需要留意的是nsPresShell对象是作为nsDocument
实例对象的一个Observer,同时也是nsViewManager实例对象的一个Observer;至此初始化完nsPresShell,并加入到nsViewManager的管理机制中去,为后面创建新的nsView对象及进行布局安排、显示内容作好准备。

四、解析、组织文档内容,同时作好布局
准备
继续查看nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream *stream)方法中的STATE_TRANSFER时,表示已获取相关文档数据,此时会继续利用uriLoader临时创建的nsDocumentOpenInfo,由它的方法OnDataAvailable来继续处理获得的数据,其实现如下:

NS_IMETHODIMP nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count)
{
// if we have retarged to the end stream listener, then forward the call....
// otherwise, don't do anything
nsresult rv = NS_OK;
if (m_targetStreamListener)
rv = m_targetStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
return rv;
}
此时其
m_targetStreamListener值也即上面创建出来的nsParser实例,
这样正好一环套一环,任务转交给了上面准备好的nsParser;
Gecko内核中的Parser有HtmlParser以解析html文档;xmlParser以解析符合xml格式的文档如xul、svg;CssParser以解析Css文档等。解析器通过Tokenizer来认识文档中的标识,然后结合不同文档类型预定义的Atom及ContentSink来组织想要的文档结构。整个HtmlParser的解析过程还是蛮复杂的,不过无论解析的过程怎样,其结果就是会适时的调用ContentSink中的主要方法,如OpenHead、OpenBody、CloseBody、OpenForm、CloseForm、OpenContainer、CloseContainer、StartLayout、ProcessLINKTag、ProcessSCRIPTEndTag、ProcessSTYLEEndTag等;

其中需要留意的是在构建文档结构的同时往往需要对不同的Content Node 创建对应Frame对象,同时针对不同的Frame,还时可能还需要构建nsView,如nsBoxFrame,在一些条件下需要调用方法CreateViewForFrame来创建一个nsView实例,其主要代码如下:
{
// Create a view
nsIView *view = viewManager->CreateView(aFrame->GetRect(), parentView, visibility);
if (view) {
// Insert the view into the view hierarchy. If the parent view is a
// scrolling view we need to do this differently
nsIScrollableView* scrollingView = parentView->ToScrollableView();
if (scrollingView) {
scrollingView->SetScrolledView(view);
} else {
viewManager->SetViewZIndex(view, autoZIndex, zIndex);
// XXX put view last in document order until we can do better
viewManager->InsertChild(parentView, view, nsnull, PR_TRUE);
}
}

// Remember our view
aFrame->SetView(view);
}

NS_IMETHODIMP_(nsIView *)
nsViewManager::CreateView(const nsRect& aBounds,
const nsIView* aParent,
nsViewVisibility aVisibilityFlag)
{
nsView *v = new nsView(this, aVisibilityFlag);
if (v) {
v->SetPosition(aBounds.x, aBounds.y);
nsRect dim(0, 0, aBounds.width, aBounds.height);
v->SetDimensions(dim, PR_FALSE);
v->SetParent(static_cast(const_cast(aParent)));
}
return v;
}

同时对于一些nsView还需要通过其nsIView::CreateWidget方法来创建原生的窗口,主要代码如下

nsresult nsIView::CreateWidget(const nsIID &aWindowIID,
nsWidgetInitData *aWidgetInitData,
nsNativeWidget aNative,
PRBool aEnableDragDrop,
PRBool aResetVisibility,
nsContentType aContentType,
nsIWidget* aParentWidget)
{
nsView* v = static_cast(this);

v->LoadWidget(aWindowIID))
..................................
mWindow->Create(aNative, trect, ::HandleEvent, dx, nsnull, nsnull, aWidgetInitData);
..............................
}
在mWindow->Create中会接着调用如nsWindow::StandardWindowCreate、BaseCreate等;
其中
参数::HandleEvent会在创建原生窗口时保存在nsWindow的属性成员mEventCallback中;这个函数句柄在原生窗口的处理函数中经过一定处理后往往根据一定上下文而去调用它,它会处理如WM_SIZE、WM_PAINT等主要消息,后面的处理会引用到。

五、显示文档内容
在上面解析、组织文档结构时,当调用OpenBody时会适时调用StartLayout,其主要代码如下
{
nsPresShellIterator iter(mDocument);
nsCOMPtr shell;
while ((shell = iter.GetNextShell())) {
// Make sure we don't call InitialReflow() for a shell that has
// already called it. This can happen when the layout frame for
// an iframe is constructed *between* the Embed() call for the
// docshell in the iframe, and the content sink's call to OpenBody().
// (Bug 153815)

PRBool didInitialReflow = PR_FALSE;
shell->GetDidInitialReflow(&didInitialReflow);
if (didInitialReflow) {
// XXX: The assumption here is that if something already
// called InitialReflow() on this shell, it also did some of
// the setup below, so we do nothing and just move on to the
// next shell in the list.
continue;
}

nsRect r = shell->GetPresContext()->GetVisibleArea();
nsCOMPtr shellGrip = shell;
nsresult rv = shell->InitialReflow(r.width, r.height);

}

由nsPresShell的InitialReflow方法来启动布局,主要作用是根据文档元素的类型、属性的不同,而决定是否需要重新布局该文档元素及其子元素,一旦觉得有必要重新布局则调用nsPresShell的PostReflowEvent方法,它通过向主线程MainThread发送一个ReflowEvent,一旦MainThread接收到该ReflowEvent,其会由对应nsPresShell的DoFlushPendingNotifications方法来处理,其最终根据当前的nsView及原生窗口的不同,在windows平台上它会对原生的窗口句柄进行InvalidateRect或UpdateWindow处理,按照windows图形管理的逻辑,系统会根据相关条件,及时向该原生窗口句柄发送原生的WM_PAINT消息,而对处理原生的窗口消息,一般会由窗口创建时提供的窗口回调函数来处理,经过一番处理判断后会触发调用nsWindow::DispatchWindowEvent,进而会调用上面提到的
mEventCallback所对应的公共的函数,其实现如下:
//
// Main events handler
//
nsEventStatus PR_CALLBACK HandleEvent(nsGUIEvent *aEvent)
{
//printf(" %d %d %d (%d,%d) \n", aEvent->widget, aEvent->widgetSupports,
// aEvent->message, aEvent->point.x, aEvent->point.y);
nsEventStatus result = nsEventStatus_eIgnore;
nsView *view = nsView::GetViewFor(aEvent->widget);

if (view)
{
view->GetViewManager()->DispatchEvent(aEvent, &result);
}

return result;
}
进而由nsViewManager::DispatchEvent统一来处理事件,这时经过一些处理,往往需要重新渲染相关View,而渲染View的主要代码如下:

void nsViewManager::RenderViews(nsView *aView, nsIRenderingContext& aRC,
const nsRegion& aRegion)
{
if (mObserver) {
nsView* displayRoot = GetDisplayRootFor(aView);
nsPoint offsetToRoot = aView->GetOffsetTo(displayRoot);
nsRegion damageRegion(aRegion);
damageRegion.MoveBy(offsetToRoot);

aRC.PushState();
aRC.Translate(-offsetToRoot.x, -offsetToRoot.y);
mObserver->Paint(displayRoot, &aRC, damageRegion);
aRC.PopState();
}
}
而上面提到的nsPresShell实例往往作为nsViewManager的一个Observer,这样工作就这样转交给nsPresShell来处理。而nsPresShell的Paint方法的主要内容如下:

PresShell::Paint(nsIView* aView,
nsIRenderingContext* aRenderingContext,
const nsRegion& aDirtyRegion)
{
AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Paint);
nsIFrame* frame;
nsresult rv = NS_OK;

if (mIsDestroying) {
NS_ASSERTION(PR_FALSE, "A paint message was dispatched to a destroyed PresShell");
return NS_OK;
}

NS_ASSERTION(!(nsnull == aView), "null view");

frame = static_cast(aView->GetClientData());
nscolor backgroundColor;
mViewManager->GetDefaultBackgroundColor(&backgroundColor);
for (nsIView *view = aView; view; view = view->GetParent()) {
if (view->HasWidget()) {
PRBool widgetIsTransparent;
view->GetWidget()->GetHasTransparentBackground(widgetIsTransparent);
if (widgetIsTransparent) {
backgroundColor = NS_RGBA(0,0,0,0);
break;
}
}
}

if (!frame) {
if (NS_GET_A(backgroundColor) > 0) {
aRenderingContext->SetColor(backgroundColor);
aRenderingContext->FillRect(aDirtyRegion.GetBounds());
}
return NS_OK;
}

nsLayoutUtils::PaintFrame(aRenderingContext, frame, aDirtyRegion,
backgroundColor);

return rv;
}

相关主要工作又转交给nsLayoutUtils::PaintFrame;其主要工作就是通过 nsDisplayListBuilder builder来建立一个nsDisplayList list,并调用nsDisplayList::Paint方法,其实现如下:

void nsDisplayList::Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx,
const nsRect& aDirtyRect) const {
for (nsDisplayItem* i = GetBottom(); i != nsnull; i = i->GetAbove()) {
i->Paint(aBuilder, aCtx, aDirtyRect);
}
nsCSSRendering::DidPaint();
}

在其中的for循环中会根据DisplayList Item构成的不同,会调用不同frame的Paint方法等,如
nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
nsIRenderingContext* aCtx, const nsRect& aDirtyRect) {
static_cast(mFrame)->
PaintText(aCtx, aBuilder->ToReferenceFrame(mFrame), aDirtyRect);
}
通过这样一个流程,就会显示出相关内容给用户。


六、总结分析
通过上面初步的整理,对整个过程有了初步的了解,其中关键在于了解到LoadURI的发起、nsIStreamListener数据的接收、nsParser及nsContentSink对文档的组织构成、nsPresShell结合nsFrame、nsView、nsViewManager、nsWindow对内容的显示。总的说来整个过程是相当的复杂,毕竟自从Web服务端获取数据后在内核中需要不断的动态创建很多
对应的类对象,但关键流程的流转在于对nsInputStreamReadyEvent事件和ReflowEvent两个事件的处理,有了这样基本的认识之后,对于css文档、image文档、js文档、plugin的处理,应该有了扎实的基础准备,至于具体每一html标签、xul标签具体对应哪些Node、Frame、Widget还须具体结合自身的不同加以分析。

七、参考网址
The Life Of An HTML HTTP Request

2008年6月27日星期五

浅谈Gecko关键部分之三线程管理及主要线程

作为一个浏览器内核,Gecko所要完成的任务是非常繁杂的,其主要任务就是根据用户提供的资源地址,通过http协议从Web服务器中取得页面文档,然后解析其内容,最后根据一定的约定在浏览器指定区域中显示出页面,其中往往涉及网络编程及图形界面编程,而大家通常都知道的是网络编程中的连接、读取数据等往往需要考虑到服务器端的情况,一般采用异步方式来确保有效处理服务端返回的数据包括连接不成功、错误处理等;而图形界面的处理往往需要采用一个主消息循环及回调函数的方式来处理用户的动作,为了给用户提供平滑的操作及兼顾后台服务器的不确定性,一个可行的浏览器内核必须充分利用多线程来协调处理复杂的应用场景,只有这样才能高效的完成其所要完成的任务,如果能够初步了解Gecko内核的线程模型及相关线程管理的知识,对了解Gecko是非常有帮助的,下面初步了解Gecko是如何进行线程管理及其主要线程实现。

一、Gecko线程模型
为了统一接口编程,Gecko将其线程模型按照组件的方式来处理,定义的接口主要有nsIRunnable、nsIEventTarget、nsIThread、nsIThreadManager、nsIThreadPool,其中nsIThreadManager由nsThreadManager来实现,它主要用于来管理所有的nsThread,包括创建nsThread实例,并通过维护一些系统原生线程的属性可以判断同一段代码是在什么线程的上下文中调用,从而可作出不同的处理,如GetIsMainThread可以判定当前执行线程是否为主线程,这一点在Gecko中应该得到了充分的应用,同时nsThreadManager的实例属于sington模式,在第一次启用XPCOM组件时由NS_InitXPCOM3实例化出来,以供以后管理nsThread使用;

nsThread代表一个一般意义上的原生线程,在其构造函数中会初始化它并启动原生线程,它同时增加了EventQueue及ThreadObserver的概念,其函数入口为nsThread::ThreadFunc,在线程的整个周期中也即nsThread::ThreadFunc中不断的处理外部线程或本身向其EventQueue中Dispatch来的Event,同时每处理一个Event之前都会检查其是否存在Observer,如存在则先让Observer调用其OnProcessNextEvent来处理该Event,然后调用Event本身提供的run方法,这样增加了nsThread处理Event的灵活性;同时nsThread统一由nsThreadManager来创建,通过调用其Shutdown方法来结束该线程;并提供了一组外部接口来进行线程管理操作,如
NS_NewThread、NS_GetMainThread、NS_DispatchToMainThread、NS_ProcessNextEvent等以供不同线程调用,同时保证线程安全;

nsThreadPool代表一组线程,当外部向其Dispatch一个Event时它先会向其中的EventQueue添加该Event,然后它会根据设置的参数来决定是否分配一个nsThread来处理该Event,一旦创建了一些nsThread则将其维护在mThreads队列中,同时向该nsThread Dispatch这个nsThreadPool实例以让该nsThread去执行该nsThreadPool的run方法,这样可保证线程池中的每个nsThread的执行体都是nsThreadPool中的run方法,在其run方法中会根据是否空闲等条件自动ShutDown一些暂时不用的nsThread,以达到线程池的目的,即当任务事件Event繁多的时候多开启一些线程来处理,一旦任务完成则释放大部分空闲线程,保持一小部分线程以等待新任务的分配及提交;nsThreadPool在异步读取网络流数据的时候会经常用到,以后有机会可具体分析其应用场景;

二、Gecko主要线程
MainThread是Gecko的主线程,也即进程启动时的执行序列,它主要处理图形界面的消息循环以及其他线程向它Dispatch过来的Event,这样可以有机的结合图形界面的处理及网络数据的读取、解析、渲染等。MainThread线程充分利用了Gecko线程模型特点,既能处理原生的窗口消息,又可及时处理其它线程通知给它的Event,其执行主体往往是Gecko的核心,据初步统计其执行时间往往占整个程序执行时间的90%以上,其效率的高低直接决定了是否会让用户产生阻塞停顿的感觉。其主要实现逻辑是在启动XPCOM当中会在nsThreadManager初始化时自动产生一个nsThread实例mMainThread,它代表进程启动时的执行序列,待该执行序列完成其他基本准备后,在nsBaseAppShell::Init中将其Observer设置为nsBaseAppShell实例本身,具体参考如下:

nsresult nsBaseAppShell::Init()
{ // Configure ourselves as an observer for the current thread:
nsCOMPtr threadInt = do_QueryInterface(NS_GetCurrentThread()); NS_ENSURE_STATE(threadInt);
threadInt->SetObserver(this);//mObserver=>this
nsCOMPtr obsSvc = do_GetService("@mozilla.org/observer-service;1");
if (obsSvc) obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
return NS_OK;
}

NS_IMETHODIMP nsBaseAppShell::Run(void)
{
nsIThread *thread = NS_GetCurrentThread();
NS_ENSURE_STATE(!mRunWasCalled); // should not call Run twice mRunWasCalled = PR_TRUE;
while (!mExiting) NS_ProcessNextEvent(thread);
NS_ProcessPendingEvents(thread);
return NS_OK;
}

PRBool NS_ProcessNextEvent(nsIThread *thread, PRBool mayWait)
{
nsCOMPtr current;
if (!thread) {
NS_GetCurrentThread(getter_AddRefs(current));
NS_ENSURE_TRUE(current, PR_FALSE);
thread = current.get();
}
PRBool val;
return NS_SUCCEEDED(thread->ProcessNextEvent(mayWait, &val)) && val;
}

NS_IMETHODIMP nsThread::ProcessNextEvent(PRBool mayWait, PRBool *result)
{
..........
nsCOMPtr obs = mObserver;
if (obs) obs->OnProcessNextEvent(this, mayWait && !ShuttingDown(), mRunningEvent);

mEvents->GetEvent(mayWait && !ShuttingDown(), getter_AddRefs(event));
*result = (event.get() != nsnull);
nsresult rv = NS_OK;
if (event) {
++mRunningEvent;
event->Run();
--mRunningEvent;
}
else if (mayWait)
{ .........}

if (obs) obs->AfterProcessNextEvent(this, mRunningEvent);
..........
return rv;
}

其中关键点在于MainThread的mObserver是nsAppShell实例,那么它的OnProcessNextEvent都作了些啥呢?主要是根据情形调用其ProcessNextNativeEvent方法,其实现如下:

PRBool nsAppShell::ProcessNextNativeEvent(PRBool mayWait)
{
PRBool gotMessage = PR_FALSE;
do
{
MSG msg; // Give priority to system messages (in particular keyboard, mouse, timer, // and paint messages).
if (PeekKeyAndIMEMessage(&msg, NULL) ::PeekMessageW(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) ::PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
gotMessage = PR_TRUE;
if (msg.message == WM_QUIT)
{
Exit();
}
else {
::TranslateMessage(&msg);
::DispatchMessageW(&msg);
}
}
else if (mayWait) {
// Block and wait for any posted application message
::WaitMessage();
}
} while (!gotMessage && mayWait);
return gotMessage;
}
这个函数是否觉得非常的面熟???

nsSocketTransportService作为异步处理socket读写的线程,在程序启动就会实例化,有时间可以仔细看看\mozilla\network\base\src\nsSocketTransportService2.cpp,其主要代码应该与一般多线程网络程序差不多。

至于其他关于TimerThread、ThreadPool及javascript垃圾回收线程等以后有时间再来分析。

三、参考网址
The Thread Manager
XPCOM:nsIThreadManager

2008年6月20日星期五

浅谈Gecko关键部分之二内存分配使用回收

2008年6月17日Firefox3Release版本已正式发布啦,可喜可贺,毕竟经历3年多的努力Mozilla社区终于又能给大家带来一些震撼、新希望,甚至冲动,其中包含很多人性化的东东以及最近几年发展起来的很多有趣的extensions,并且经过一些比较,似乎这个版本部分解决了困扰人们很久的内存使用大多及内存泄漏的问题,是否完全能化解人们心中的疑问可能还有待时间及实践来证明。借助人们很关注的问题,今天也来了解了解相关的内存分配使用回收问题。内存的使用及防止泄漏是c/c++程序员非常关心的问题,一旦有泄漏发生往往都感觉到非常头痛,所以我们也很有必要关心这方面的问题,看看Firefox这些Hacker们究竟是如何来面对这些问题的。

一、内存管理的主要方式
为了尽可能合理高效的使用内存,不同的系统使用了一些各具特色的内存管理机制,1、以java为代表的garbagecollector,所有的资源经由虚拟机来分配、回收,大大降低了java程序员对内存管理的困扰,但也带来了程序速度启动变慢,占用内存较多的问题,特别在程序使用了多种资源后,当然这种机制还在脚本语言如javascript、python、perl等的实现中也得到充分的使用,并且通过一些优化算法,已经达到了很好的效果;2、以linux内核为代表Slab allocation机制,它通过一些缓存及固定长度分配器来有效解决由于多次及不固定长度内存分配可能带来的内存碎片问题;3、以apache为代表的内存池机制,它往往会先malloc一大块内存,以供后续程序内存申请使用,特别在内存分配申请频繁的情况下大大降低程序对系统层的内存申请次数,同时可以起到类似部分garbagecollector的作用。

二、主要Gecko内存管理机制
1、组件的生命周期可简单归纳为首先在factory的createInstance中通过new出一个类实例,然后通过queryInterface进行接口转换及维护mRefCnt,并检查mRefCnt是否为0,如是则delete该实例,主要实现原理可参考XPCOM组件相关开发资料,对应源代码可参考\mozilla\xpcom\glue\nsISupportsImpl.h及\mozilla\xpcom\glue\nsISupportsUtils.h等。当然也有部分类override operator new方法,但往往都是作清零的处理,根本没涉及到更深层的内存管理,而new/delete动作往往分别直接对应系统层的malloc/free方法,所以几乎没有上面提到的垃圾回收、Slab及内存池的使用,当然对javascript的实现是肯定会有相关垃圾回收机制的,但它的出发点往往为了满足脚本语言的要求。

2、对于一些字符串及数组的内存使用往往采用NS_Alloc/NS_Free等,其实现代码对应于XPCom的FrozenFunctions中指定的AllocFunc/FreeFunc,其由\mozilla\nsprpub\pr\src\malloc\Prmem.c中对应的PR_Malloc及PR_Free来实现,其中根据是否使用use_zone_allocator来检查直接调用系统malloc及free还是采取一定类似内存池及slab allocation的方法来统一管理内存的分配。其具体实现代码可详见\mozilla\nsprpub\pr\src\malloc\Prmem.c及Prmalloc.c等。对于NS_Alloc/NS_Free的使用,往往用在一些atoms、tags等当中,总的说来使用不是很多。。

3、为了防止组件之间的循环引用,引入了nsICycleCollector来单独处理这个问题,以尽可能的避免无法释放由于程序逻辑造成的内存冗余,具体实现可参考\mozilla\xpcom\base\nsCycleCollector.cpp;同时为了让程序占系统内存达到一定比例后,采用momory reporter机制要求系统flush/swap一些内存,供程序继续使用;另外结合nsGarbageCollector及nsLeakDetector来扫描stack检测是否存在内存泄漏。

4、网上关于gecko的内存使用的讨论非常多,现提供一些相关资料以供大家参考,从多方面了解gecko内存管理等方面的问题,从自己的角度来了解它,以拓展我们的思路。