显示标签为“Gecko”的博文。显示所有博文
显示标签为“Gecko”的博文。显示所有博文

2008年10月29日星期三

也谈WebKit、Gecko使用图形库

阅读了Graphics in Google Chrome之后,觉得作为浏览器内核WebKit、Gecko,为了能高效美观的显示页面的内容,选择适当的图形库非常重要。如果图形库选择不当,往往会导致页面上显示的文字、图片不美观,看起来总让人觉得别扭,更为糟糕的是排列布局出现紊乱,简直无法阅览。

从浏览器发展的历史来看,IE系列浏览器的网页布局、文字图片显示的美观程度还是相当高的,也许这与Microsoft图形显示方面的功力相关,到目前为止linux桌面显示还是与传统的windows桌面显示有相当的差距。

相比较Firefox1.5,Firefox3.0图形显示方面也有相当大的进步,这应该归功于完全采取Cario图形库来显示页面,目前应当完全达到了IE6的显示效果。可见图形显示的好与坏,直接决定了一款浏览器的质量以及用户接受程度。那究竟什么是图形库?其主要功能是什么?目前WebKit、Gecko可使用哪些图形库?它们在浏览器中是如何发挥其应有的作用呢?

一、图形库概述及其主要功能
A graphics library is a program designed to aid in rendering computer graphics to a monitor. This typically involves providing optimized versions of functions that handle common rendering tasks.

This can be done purely in software and running on the CPU, common in embedded systems, or being hardware accelerated by a GPU, more common in PCs. By employing these functions, a program can assemble an image to be output to a monitor. This relieves the programmer of the task of creating and optimizing these functions, and allows them to focus on building the graphics program.

目前主要的图形库有:
windows提供的GDI/GDI+、DirectX、OpenGL;
支持X的有Cario、GTK、QT、OpenGL;
其他的还有Skia(google提供)、Quartz 2D(apple提供)、wxWidget等;

一般说来图形库只提供绘画图形,渲染文字、图片等,不管是2D还是3D,其往往不提供消息处理,简单的说来就是如何高效的在一块指定的画布上将线条、文字、图片显示出来,其中往往涉及字体、颜色等;典型的图形库如GDI/GDI+、Cario、DirectX、Quartz 2D等;

而按钮、菜单、窗口等图形组件往往是基于图形库的基础上绘画出来的并有相对固定形状,同时一般具有消息处理功能;相关实现有GTK、QT、wxWidget、windows组件等;

其中GTK、QT、wxWidget、Skia等不仅提供图形组件,同时提供图形库的功能;而Cario则是一个纯粹的图形库,类似与Quartz 2D,目前GTK2则完全基于Cario来实现;

由于浏览器页面元素的数量存在不确定性,将页面上的一些元素对应成图形组件可能导致一个页面使用组件过多的问题(早期的IE就曾出现使用GDI对象过多的现象)。因此尽可能的将一个页面的所有元素显示在一个图形组件上,至于其显示交给图形库来处理,其消息响应交互交给DOM及原生窗口消息循环来完成。

从这里我们可以进一步的确认图形库在浏览器中的重要性,以及随着用户需求的增加及硬件的提升,浏览器中使用3D效果应该是一个大的方向。

二、Gecko中使用图形库Cario

1、Cario概述
Cairo is a 2D graphics library with support for multiple output devices. Currently supported output targets include the X Window System, Quartz, Win32, image buffers, PostScript, PDF, and SVG file output. Experimental backends include OpenGL (through glitz), XCB, BeOS, OS/2, and DirectFB.

Cairo is designed to produce consistent output on all output media while taking advantage of display hardware acceleration when available (eg. through the X Render Extension).

其主要优点在于其在X、Win32、Quartz的基础上统一了图形库的操作方式,同时支持PS、PDF、SVG、PNG/JPEG等图像格式的输出,极大的方便页面的再次利用,在glitz的支持下支持部分3D效果。

2、Cario在Gecko中的使用
首先提供一个gfxASurface抽象类,代表一块可以作画的画布;提供一个gfxContext类,它用来管理究竟如何作画,如画圆形、旋转,维护画布的状态、当前颜色、路径等,其往往需要一个gfxASurface子类实例来初始化;

其次不同的图形输出实现不同的gfxASurface子类如gfxWindowsSurface、gfxXlibSurface、gfxQuartzSurface、gfxGlitzSurface、gfxQuartzPDFSurface、gfxPSSurface等;

其次提供一个DeviceContextImpl类实现nsIDeviceContext接口,以描述指定原生Widget相关的字体属性及创建可以在该原生Widget上作画的nsIRenderingContext接口实现;

而nsThebesRenderingContext类实现了nsIRenderingContext接口,以供外部(如不同的DOM Node页面元素对应的不同Frame)在其上显示文字、图像、作图形等;

然后当解析、布局完DOM页面元素后需要画出不同的页面元素时则由DeviceContextImpl类实例来创建nsThebesRenderingContext类实现,并初始化它,其初始化代码如下:
nsThebesRenderingContext::Init(nsIDeviceContext* aContext, nsIWidget *aWidget)
{
nsThebesDeviceContext *thebesDC = static_cast(aContext);

mDeviceContext = aContext;
mWidget = aWidget;

mThebes = new gfxContext(aWidget->GetThebesSurface());

return (CommonInit());
}

nsThebesRenderingContext类中包含:
nsRefPtr mThebes;
nsCOMPtr mWidget;
而mThebes初始时需要的gfxASurface子类实例则由原生的Widget的GetThebesSurface()方法来实现,针对不同的图形输出实现返回不同的gfxASurface子类如gfxWindowsSurface、gfxXlibSurface、gfxQuartzSurface、gfxGlitzSurface的实例;

最后将nsThebesRenderingContext类实现实例返回,由不同的Frame自身决定如何在提供的画布上画出其表示的内容。

至于不同的Frame应该怎么画(显示)由其本身及CSS来决定,而nsIRenderingContext接口提供了所有可能用到的作画方式,具体每一个方法的含义需详细了解。

nsIRenderingContext接口提供了一组方法,向外部提供了图形库能实现的功能,其相当于一个中间层,从图形库的角度看,通过这个接口向外部提供了其能提供的功能;从需要图形功能的外部看,可不关心具体图形库的实现,而直接通过该接口使用图形库能提供的功能。

让我们来看看
nsIRenderingContext接口究竟有哪些主要方法?
// RenderingContext interface
class nsIRenderingContext : public nsISupports
{
public:
.........................................................................
/**
* Initialize the RenderingContext
* @param aContext the device context to use.
* @param aWidget the widget to hook up to
* @result The result of the initialization, NS_Ok if no errors
*/
NS_IMETHOD Init(nsIDeviceContext* aContext, nsIWidget *aWidget) = 0;
/**
* Get the DeviceContext that this RenderingContext was initialized
* with. This function addrefs the device context. Though it might
* be better if it just returned it directly, without addrefing.
* @result the device context
*/
NS_IMETHOD GetDeviceContext(nsIDeviceContext *& aDeviceContext) = 0;

/**
* Sets the forground color for the RenderingContext
* @param aColor The color to set the RenderingContext to
*/
NS_IMETHOD SetColor(nscolor aColor) = 0;

/**
* Get the forground color for the RenderingContext
* @return The current forground color of the RenderingContext
*/
NS_IMETHOD GetColor(nscolor &aColor) const = 0;

/**
* Sets the font for the RenderingContext
* @param aFont The font to use in the RenderingContext
*/
NS_IMETHOD SetFont(const nsFont& aFont, nsIAtom* aLangGroup) = 0;

/**
* Sets the font for the RenderingContext
* @param aFontMetric The font metrics representing the
* font to use in the RenderingContext
*/
NS_IMETHOD SetFont(nsIFontMetrics *aFontMetrics) = 0;

/**
* Get the current fontmetrics for the RenderingContext
* @return The current font of the RenderingContext
*/
NS_IMETHOD GetFontMetrics(nsIFontMetrics *&aFontMetrics) = 0;

/**
* Add in a translate to the RenderingContext's transformation matrix
* @param aX The horizontal translation
* @param aY The vertical translation
*/
NS_IMETHOD Translate(nscoord aX, nscoord aY) = 0;

/**
* Add in a scale to the RenderingContext's transformation matrix
* @param aX The horizontal scale
* @param aY The vertical scale
*/
NS_IMETHOD Scale(float aSx, float aSy) = 0;

/**
* Draw a line
* @param aXO starting horiztonal coord in twips
* @param aY0 starting vertical coord in twips
* @param aX1 end horiztonal coord in twips
* @param aY1 end vertical coord in twips
*/
NS_IMETHOD DrawLine(nscoord aX0, nscoord aY0, nscoord aX1, nscoord aY1) = 0;

/**
* Draw a rectangle
* @param aRect The rectangle to draw
*/
NS_IMETHOD DrawRect(const nsRect& aRect) = 0;

/**
* Draw a string in the RenderingContext
* @param aString The string to draw
* @param aLength The length of the aString
* @param aX Horizontal starting point of baseline
* @param aY Vertical starting point of baseline.
* @param aSpacing inter-character spacing to apply
*/
NS_IMETHOD DrawString(const char *aString, PRUint32 aLength,
nscoord aX, nscoord aY,
const nscoord* aSpacing = nsnull) = 0;

/*
* Tiles an image over an area
* @param aImage Image to tile
* @param aXImageStart x location where the origin (0,0) of the image starts
* @param aYImageStart y location where the origin (0,0) of the image starts
* @param aTargetRect area to draw to
* @param aSubimageRect the subimage (in tile space) which we expect to
* sample from; may be null to indicate that the whole image is
* OK to sample from
*/
NS_IMETHOD DrawTile(imgIContainer *aImage,
nscoord aXImageStart, nscoord aYImageStart,
const nsRect * aTargetRect,
const nsIntRect * aSubimageRect) = 0;
...............................................................................
};

其中DrawString、DrawTile方法最常用,其分别对应如何显示文字及图像。
针对图形库显示文字的基本原理可以参考Font technology and FreetypeFreeType Glyph Conventions

至于图形库如何显示不同格式的图像可参考如gifjpegpng等。

Gecko对Cario的使用还体现在对canvas标签的实现,具体可参考nsCanvasRenderingContext2D.cpp、nsHTMLCanvasElement.cpp等。

三、WebKit中使用图形库
1、WebKit支持的图形库
目前WebKit支持的图形库包括Cairo、Gtk、Qt、Wx、Cg、Mac、Skia等,虽然不同的图形库能支持不同的平台,但其在不同平台上的显示效果也不尽相同。至于在一个指定的平台上究竟使用何种库,则显示出很大的灵活性。就目前来看,在windows平台上可选的图形库有Cairo、Qt、Wx、Cg、Skia等,其中Graphics in Google Chrome阐述了Chrome关于图形库的选择。

其实从WebKit的角度来看,它通过提供一组与Gecko中nsIRenderingContext类似的公共图形接口,而不同的图形库则根据自身的不同实现了这些公共图形接口,以提供给WebCore元素使用,从而可以让WebKit支持不同的图形库。

2、WebKit支持不同图形库的实现
在WebKit中提供了一个GraphicsContext类,其中包括所有的图形接口,完全类似nsIRenderingContext,针对不同平台的特性,其定义中包含一些不同平台特有的
宏及元素定义。

在目录webcore\platform\graphics\下的子目录Cairo、Cg、Gtk、Mac、Qt、Win、Wx分别提供了GraphicsContext类部分方法的实现,而公共的实现则在webcore\platform\graphics\GraphicsContext.cpp中提供。

其中我们非常值得关注的方法有drawText与drawImage,其实现如下:
void GraphicsContext::drawText(const TextRun& run, const IntPoint& point, int from, int to)
{
if (paintingDisabled())
return;

font().drawText(this, run, point, from, to);
}

void GraphicsContext::drawImage(Image* image, const FloatRect& dest, const FloatRect& src, CompositeOperator op, bool useLowQualityScale)
{
if (paintingDisabled() || !image)
return;

float tsw = src.width();
float tsh = src.height();
float tw = dest.width();
float th = dest.height();

if (tsw == -1)
tsw = image->width();
if (tsh == -1)
tsh = image->height();

if (tw == -1)
tw = image->width();
if (th == -1)
th = image->height();

if (useLowQualityScale) {
save();
setUseLowQualityImageInterpolation(true);
}
image->draw(this, FloatRect(dest.location(), FloatSize(tw, th)), FloatRect(src.location(), FloatSize(tsw, tsh)), op);
if (useLowQualityScale)
restore();
}

最终的实现转交给类Font、Image的方法drawText、draw来实现,而不同实现如Cairo、Cg、Gtk、Mac、Qt、Win、Wx则会针对类Font、Image分别提供部分对应的实现,而公共的实现则在webcore\platform\graphics\Font.cpp及Image.cpp中提供。

3、不同平台GraphicsContext实例创建及使用
GraphicsContext创建的时机往往在对应平台的WebView获得Paint消息事件时,进而将该GraphicsContext类实例传递给FrameView及其不同的RenderObject实例,由不同的RenderObject实例来决定究竟如何来显示自身的内容,而GraphicsContext类实例提供了各种的显示文字、图形、图像的方法以供RenderObject实例调用。其调用关系基本上与Gecko中的不同Frame对象使用nsIRenderingContext接口方法类似。

创建GraphicsContext实例的示例如下:
//Gtk
static gboolean webkit_web_view_expose_event(GtkWidget* widget, GdkEventExpose* event)
{
WebKitWebView* webView = WEBKIT_WEB_VIEW(widget);
WebKitWebViewPrivate* priv = webView->priv;

Frame* frame = core(webView)->mainFrame();
GdkRectangle clip;
gdk_region_get_clipbox(event->region, &clip);
cairo_t* cr = gdk_cairo_create(event->window);
GraphicsContext ctx(cr);
ctx.setGdkExposeEvent(event);
if (frame->contentRenderer() && frame->view()) {
frame->view()->layoutIfNeededRecursive();

if (priv->transparent) {
cairo_save(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
cairo_restore(cr);
}

frame->view()->paint(&ctx, clip);
}
cairo_destroy(cr);

return FALSE;
}

//win
void WebView::paintIntoBackingStore(FrameView* frameView, HDC bitmapDC, const IntRect& dirtyRect)
{
LOCAL_GDI_COUNTER(0, __FUNCTION__);

RECT rect = dirtyRect;

#if FLASH_BACKING_STORE_REDRAW
HDC dc = ::GetDC(m_viewWindow);
OwnPtr yellowBrush = CreateSolidBrush(RGB(255, 255, 0));
FillRect(dc, &rect, yellowBrush.get());
GdiFlush();
Sleep(50);
paintIntoWindow(bitmapDC, dc, dirtyRect);
::ReleaseDC(m_viewWindow, dc);
#endif

FillRect(bitmapDC, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
if (frameView && frameView->frame() && frameView->frame()->contentRenderer()) {
GraphicsContext gc(bitmapDC);
gc.save();
gc.clip(dirtyRect);
frameView->paint(&gc, dirtyRect);
gc.restore();
}
}

//wx
void wxWebView::OnPaint(wxPaintEvent& event)
{
if (m_beingDestroyed || !m_impl->frame->view() || !m_impl->frame)
return;

wxAutoBufferedPaintDC dc(this);

if (IsShown() && m_impl->frame && m_impl->frame->document()) {
#if USE(WXGC)
wxGCDC gcdc(dc);
#endif

if (dc.IsOk()) {
wxRect paintRect = GetUpdateRegion().GetBox();

WebCore::IntSize offset = m_impl->frame->view()->scrollOffset();
#if USE(WXGC)
gcdc.SetDeviceOrigin(-offset.width(), -offset.height());
#endif
dc.SetDeviceOrigin(-offset.width(), -offset.height());
paintRect.Offset(offset.width(), offset.height());

#if USE(WXGC)
WebCore::GraphicsContext* gc = new WebCore::GraphicsContext(&gcdc);
#else
WebCore::GraphicsContext* gc = new WebCore::GraphicsContext((wxWindowDC*)&dc);
#endif
if (gc && m_impl->frame->contentRenderer()) {
if (m_impl->frame->view()->needsLayout())
m_impl->frame->view()->layout();

m_impl->frame->paint(gc, paintRect);
}
}
}
}

//Qt
void QWebFrame::render(QPainter *painter, const QRegion &clip)
{
if (!d->frame->view() || !d->frame->contentRenderer())
return;

d->frame->view()->layoutIfNeededRecursive();

GraphicsContext ctx(painter);
QVector vector = clip.rects();
WebCore::FrameView* view = d->frame->view();
for (int i = 0; i <>paint(&ctx, vector.at(i));
}

/*!
Render the frame into \a painter.
*/
void QWebFrame::render(QPainter *painter)
{
if (!d->frame->view() || !d->frame->contentRenderer())
return;

d->frame->view()->layoutIfNeededRecursive();

GraphicsContext ctx(painter);
WebCore::FrameView* view = d->frame->view();
view->paint(&ctx, view->frameGeometry());
}

4、WebKit 3D Port实现
Clutter WebKit port中提供了WebKit 对3D Port的支持与实现,其实现类似于Gtk+/Cairo图形库的实现,但其3D效果仅实现在Port层,没有对页面上的元素如文字、图像实现3D效果支持。

这里只是简单的了解WebKit中如何整合不同的图形库及其与WebCore的交互。要想更加深入的了解页面上的文字、图形、图像究竟是如何显示出来的,则需要更进一步的针对不同平台库进行学习与了解。

WebKit中也支持canvas标签,该标签提供的接口与Gecko能提供的几乎一致,其具体实现可参考webcore\html \CanvasRenderingContext2D.cpp,结合GraphicsContext类的实现,应该能对canvas标签的实现有充分的理 解。

四、总结
其实关于图形库及其使用的内容非常的多,而对浏览器内核来讲能对图形库进行高效使用也是非常重要的一部分,所以在这里所谈到的内容也许只是一些皮毛,但希望能在此开阔一下了解浏览器内核特别是图形库使用方面的思路。

五、参考资源
Wiki Rendering (computer graphics)
Wiki Cairo
Cairo HomePage
Wiki Qt
Wiki GTK+
Wiki wxWidgets
Wiki GDI
Wiki DirectX
Wiki Quartz 2D
Wiki OpenGL
Wiki OpenGL ES

Wiki gif
Wiki jpeg
Wiki png

Clutter Toolkit

Font technology and Freetype

2008年9月5日星期五

体验新鲜出炉的Google Chrome浏览器

2008年9月2日新一款浏览器终于诞生了,她的名字叫做Chrome,出自名门之家Google,早在N年前就听说Google想推出自己的浏览器,但经过多年的励精图治,其庐山真面目终于大白于天下,一时间网上各种测评、预言等等满天飞,经过这几天的学习研究,作为一个开源浏览器内核爱好者,从自身的角度来学习观察它,现将自己初步了解的Google Chrome浏览器总结如下:

一、学习Google Chrome Comic
Google为了有效宣传Chrome,通过漫画的形式来表达自己对当前浏览器的一些看法,以及他们的实现方案即Chrome,总结起来其主要内容如下:

1、Stability,Testing and the Multi-process Architecture
其中着重描述浏览器稳定性的要求及其独特的多进程架构,同时强调Chrome经过Google搜索引擎抓到的大量不同页面测试,其稳定性拥有独特的优势,这一点是其他浏览器的测试无法比拟的;

从技术的角度来讲,采取多进程架构来实现浏览器,目前来讲确实是独一无二的,是其他浏览器无法比拟的,但多进程架构本身在Yahoo!Widget及Apache等早有成功的应用案例,技术难度没有太多的创新,相对多线程架构来讲,增加了大量IPC方面的内容,其实IPC的实现与应用在OS的实现当中拥有大规模的应用。

但多进程架构是否真的象Google所宣称的那样稳定,会不会带来诸如CPU使用过高,整个浏览器资源是否太多?特别是window句柄是否过多?因为浏览器毕竟是个图形界面程序,而不仅仅是网络程序。

通过对其Process Models初步了解,其体系上也考虑到诸如Process-per-site-instance、Process-per-site、Process-per-tab、Single process等方式的选择,但其整体效用如何还需实践的考验,当然其出发点还是够强大的,正如N年前他们设计浏览器时所宣称的那样,浏览器就是一个互联网平台,作为一个互联网平台所能显示的页面及应用越来越多,其稳定性应该是首要解决的问题,他们采取的方案就是向OS学习,采取多进程,由一个主进程来管理浏览器的一切。

2、Speed,WebKit and V8
从漫画当中,我们可看到Google对浏览器的方方面面都早有相当程度的研究,但WebKit渲染内核是否速度最快,其实值得商榷,但其代码简洁,方便学习开发,倒觉得真的很对。

至于V8对javascript的实现是否真的很快也很难讲,虽然它对javascript的实现方式有所调整,但是否真的有那么高效率很难讲,毕竟似乎没听说过它在其他地方使用过并有良好表现。

其中网上就有很多人对其对其它javascript的实现如SpiderMonkey、JavascriptCore的评价特别是垃圾回收方面有很大的争议。

3、Search,UI
这方面Chrome本身应该没有太大的创新,虽然其提出标签页放在最上方,但其好多想法早在Firefox、Opera中有所实现。

4、Security,Sanboxing and safe Browsing
对于安全的考虑,其中sanbox及phishing等方面,Firefox早有相应的处理机制,只不过其sanbox可扩展到进程层,有了一定的提升,但其本质还是基于同源策略等,同时根据V8的介绍其中提供了对对象方法、属性权限的控制管理,这一点比JavascriptCore要强大,不过SpiderMonkey其实早有提供,至于其具体Security实现是否也有诸如Firefox中的SecrurityManager还须继续观察。

5、Gears,Standards and Open source
其中特别提到对Web应用的扩展是基于Gear来实现,由于前期对Gear的实现扩展方式未曾了解,可能以后须加强对这方面的学习。

但总的说来,其扩展性应该与Firefox相比,还是有很大差距,Firefox中提供了一整套的xpcom、extension、xul等可以让开发者从浏览器的方方面面来扩展,但Chrome因为其渲染部分使用了WebKit,要让开发者象扩展Firefox那样方便的扩展Chrome浏览器应该几乎不可能。目前来看要扩展Chrome需要使用Gear或相当通用的NP Plugin技术。

二、切身体验使用Chrome
2008年9月3日一早第一时间就下载试用了Chrome浏览器,感觉其体现了Google一贯的界面简单,操作简便的风格,并且省掉了很多操作如全屏、打印预览等,一时间觉得让人无所适从,同时页面加载速度及内存占用等方面并没有太突出的表现,特别是打开20个以上的页面后,再在不同页签之间切换,往往有停顿的感觉及空白页面的现象出现,这一点在Firefox3中那怕是50个页签也没有出现类似的现象。

通过地址栏在不同页签中打开20个不同的网站,对比Chrome及Firefox3的使用情况发现,Chrome的加载速度至少比Firefox3慢20%,而整体占用CPU及内存资源,Chrome比Firefox至少多20%,对计算机系统的影响也较大,在加载完网页后不进行任何鼠标操作的情况下,Chrome主进程或Plugin进程还继续占用大量CPU,而Firefox3中则几乎不占用CPU,或许Chrome整体架构或架构对Flash Plugin等方面的支持还存在或多或少的问题。

总之毕竟是第一个测试版本,能有不崩溃及正常使用的效果,比上次第一个Safari版本发布的效果好多啦,特别是对中文、英文等不同语言的支持方面。从初步使用的感受来看,应该还是挺不错的,应该可打80分以上。

三、初步研究Chrome源代码及相关网站
第一时间阅读了其开发网站Chromium developer Documentation中的相关内容,其中对相关内容的说明可谓专业而详细但不够全面,不愧为Google主推的Open Source,有兴趣的话可以好好阅读阅读。

其中对于Chrome的编译与调试一篇更是特别实用,按照其中的说明经过30-50分钟就可轻松的编译出一个Chrome,这一点在Firefox、WebKit等开源项目中是很难办到的,同时其中包含许多第三方库的编译,而不象一般的开源项目往往需要将第三方包从其他地方下载再编译,充分的说明了Google在Open Source方面的大力支持,对开源社区来讲应该是个福音,它极大的方便了开源项目的开发,从这点讲我们应该感谢Google。

通过初步分析其源代码及其架构说明,Chrome浏览器的主要特别之处在于多进程通讯、管理及V8,并将WebKit嵌入到其中。

而与WebKit相关的网络库使用的是WinHttp、图形库使用Google自己提供的skia,而不是苹果的CoreGraphic或者Cairo等,同时对WebKit的Port方面也是自己提供的一套Interface,这样看来Chrome对WebKit的使用主要是Render Layout及页面处理流程方面。

至于其他模块诸如libxml、libxslt、libjpeg、libpng、sqlite、pthread等与WebKit中使用的相一致。今后若有时间可对其进行进一步的分析。当然其中包含很多Google自己提供的V8、Gear等等。

四、总结
通过对漫画及相关文档的学习研究及实践等,虽然Google强调其开源、稳定、安全、扩展等特性,但其架构本身对Web方面并不象Gecko那样非常开放,Gecko通过对xpcom、xul的支持大大的扩展了Html及其应用支持,这一点Gecko应该还有相当的优势。

虽然从Google看来这正是其看不上Gecko内核而采取WebKit内核的原因,因为相对简单的WebKit内核还在Android方面也有优势,而Google对RIA方面的支持,也许没那么大,其支持方式应该更像Adobe AIR一样,而不象Gecko那样支持xul一样。

从整个Chrome浏览器中体现了Google一贯的作风,简单,代码也要简单,而Gecko它太复杂,复杂得让人难以掌控。

自从Google推出Chrome之后,各种评论特别多,尤其是Google与Mozilla的关系,以及Firefox的发展,有些人悲观地预计3年后Firefox将被Chrome超越甚至让人遗忘。

但是通过初步的学习研究,个人感觉Chrome的实现方式与Firefox的实现方式还是有本质上的差别,Firefox以前获得大家认可的优势如安全、快速、易扩展等优势依然存在,同时很早以前Mozilla的开发者就认识到其在开放平台及易扩展方面的巨大前景及无人可敌的优势,也许Mozilla的优势就在这里,只要坚守住啦,目前看来Google、Microsoft都短时间内难以超越Mozilla这一点。

一点个人看法,希望能激起大家一点共鸣,大家都能更好的利用Web技术。

好好研究研究Chrome,她毕竟是个新生儿,并出自名门之家。。。。。

五、参考资源
Google Chrome Home

2008年8月25日星期一

认识了解WebKit

WebKit作为一个浏览器引擎,其第一Release版本在2003年1月发布,虽然出现的相对比较晚,但以其一些优越的特性,获得了诸如Google、Apple、Adobe、Yahoo等公司的青睐,似乎大有赶超Gecko的势头,带着究竟什么是WebKit?其主要特点有哪些?如何应用它?它与其他浏览器内核有哪些不同等疑问,开始认识了解WebKit。。。

一、什么是WebKit?
WebKit is an open source web browser engine. WebKit is also the name of the Mac OS X system framework version of the engine that's used by Safari, Dashboard, Mail, and many other OS X applications.

WebKit is an open source application framework that provides a foundation upon which to build a web browser. WebKit was originally derived from the Konqueror browser’s KHTML software library by Apple, Inc. for use as the engine of Mac OS X’s Safari web browser, and has now been further developed by Apple, Nokia, Google and others. The framework is now used by Omniweb, Shiira, iCab, Adobe AIR, Iris Browser, mobile phones (including the iPhone), Nokia’s Series 60 browser, and Google’s Android platform.

WebKit's HTML and JavaScript code began as a branch of the KHTML and KJS libraries from KDE.

二、WebKit主要特点及其目标
Web Content Engine
The project's primary focus is content deployed on the World Wide Web, using standards-based technologies such as HTML, CSS, JavaScript and the DOM. However, we also want to make it possible to embed WebKit in other applications, and to use it as a general-purpose display and interaction engine.
Open Source
WebKit should remain freely usable for both open source and proprietary applications.

Performance
Maintaining and improving speed and memory use is an important goal. We never consider performance "good enough", but strive to constantly improve. As web content becomes richer and more complex, and as web browsers run on more limited devices, performance gains continue to have value even if normal browsing seems fast enough.

Portability
The WebKit project seeks to address a variety of needs. We want to make it reasonable to port WebKit to a variety of desktop, mobile, embedded and other platforms. We will provide the infrastructure to do this with tight platform integration, reusing native platform services where appropriate and providing friendly embedding APIs.
Compatibility
For users browsing the web, compatibility with their existing sites is essential. We strive to maintain and improve compatibility with existing web content, sometimes even at the expense of standards. We use regression testing to maintain our compatibility gains.

Standards Compliance
WebKit aims for compliance with relevant web standards, and support for new standards
Security
Protecting users from security violations is critical. We fix security issues promptly to protect users and maintain their trust.
WebKit is an engine, not a browser
We do not plan to develop or host a full-featured web browser based on WebKit.
WebKit is not the solution to every problem
We focus on web content, not complete solutions to every imaginable technology need.

三、WebKit主要组成
WebKit作为一个浏览器内核引擎,正如上面所描述,其主要内容专注在浏览器的核心部分Javascript的实现(JavaScriptCore)、布局渲染的实现(WebCore)以及对外接口的支持。

Gecko内核提供了一整套的与浏览器相关的解决方案如包括对NSPR、XPCOM、Network、Extension、XUL等的支持与实现等,但WebKit往往专注于核心部分,其他部分由外壳程序或其他公共库来实现,如其对Http请求的实现可由外部的公共库如libcurl、cf、soup等来提供实现,其本身不提供Http协议实现方面的代码,而Gecko中的Http请求完全由Gecko中的Network部分来实现。

为了跨越更多平台及图形库,WebKit提供了cairo、gtk、mac、win、qt、wxwidget等对外接口的支持,这方面并不比Gecko逊色多少。

对外部嵌入程序而言,WebKit提供了具有浏览器功能的、同时与外壳程序相对应的平台或图形库相关的动态库。如在Safari中WebKit内核仅以一个webkit.dll的形式出现。相对Gecko内核而言,虽然其功能比较少,但其使用起来简单方便,实现代码相对简洁,性能也不错。

四、参考资源
The WebKit Open Source Project
WiKi WebKit
Comparison of layout engines

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