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年10月18日星期六

Javascript调试工具

Javascript作为一门脚本语言,最近受到人们越来越多的关注,例如Chrome推出V8、Mozilla推出TracMonky、WebKit推出SquirrelFish等等Javascript引擎,使得Javascript实现的性能不断的得到提升,甚至更有Microsoft高级开发人员认为Silverlight的对手竟然是Javascript,而不是Flash,还有诸如Gears、prototype、dojo、jquery等JS库的大力发展,可见Javascript的应用前景相当光明。

但是其复杂性也会越来越高,Web开发人员遇到大量的Javascript往往会觉得头痛,甚至觉得Javascript比较诡异而又神奇,从而难以彻底掌握应用好。不过要掌握好Javascript的应用,其调试是个不得不提的话题,很早以前Microsoft就推出了Script Debugger工具,但Web开发人员使用起来还不是特别的方便,作为一个Web开发应用平台的推动者,Mozilla社区却为我们提供了更优秀的Javascript Debugger工具Venkman、Firebug,它们的出现极大的方便了Javascript的调试,让开发人员爱不释手,将它们比着Web开发的瑞士军刀一点都不为过。

今天我们从Venkman、Firebug的使用入手,进而分析其实现原理,同时了解了解WebKit内核的Javascript调试工具及其实现原理。

一、安装使用Venkman、Firebug
如果你还没使用过它们,现在就该动手了。赶快去Mozilla Addon中心下载JavaScript Debugger VenkmanFirebug吧。作为Firefox的extension,Venkman、Firebug不管是下载、安装、更新及使用,都已经与Firefox进行有机结合,使用起来非常的方便。

作为调试工具,其基本的功能不外乎设置断点、Step-in、Step-out、Step-over、Continue、查看参数及调用堆栈等。操作上与用VS调试C/C++代码几乎一致,但考虑各自特点的不同,下面总结一下个人对这两个工具的使用感受:

1、Firebug非常适合普通的Web开发人员,它可以非常方便、直观的查看html、js、dom等详细内容,查看js执行效率以及网络获取状态等,更值得称赞的是调试时动态更改html、js、dom的内容后,浏览效果会及时显现,从而大大的提供修改、调试效率。

网上一篇《初识Firebug 全文 — firebug的使用》很值得刚接触者参考参考。
有关Firebug更加详细的内容请参考Firebug网站

2、Venkman非常适合与Firefox开发相关的开发者如extension开发者等,虽然它的使用没有Firebug那么的直观方便动态更改内容,但其提供了完整的命令行调试模式,这一点与python的pdb很类似;更为突出的是它可以调试Firefox本身以及各种extensions(即chrome源文件,这一点目前Firebug还难以方便的处理),同时提供方便的过滤功能。有了Venkman,开发研究Firefox(当然包括各种有特色的extension及js库等)也就得心应手。

其中Learning the JavaScript debugger Venkman对Venkman有非常详细的介绍,很值得好好阅读阅读。

有关Venkman更加详细的使用请参考其HomePage

3、使用Venkman、Firebug调试Javascript有时会出现一些异常,没有象用Vs、gdb调试C/C++那样的稳定,也许与其本身的实现有些相关,毕竟是调试脚本语言。下面了解一下其实现也许能解除你的一些困惑。

二、Venkman实现
Venkman作为一个extension,其实现完全基于js+xul等相关技术实现。

1、UI
考虑所调试内容的多样性,Venkman UI方面的实现充分的利用了Gecko中的Overlay、XBL等技术要素,同时其显示的项目及其菜单等都是动态生成,界面风格类似于VS,各个显示区域可动态调整、组装。

可以具体关注一下Venkman-bindings.xml、Venkman.xul、Venkman-overlay.xul等实现;

2、调试逻辑控制
整个Gecko内核调试逻辑控制由一个实现了接口Components.interfaces.jsdIDebuggerService,CTRID为 "@mozilla.org/js/jsd/debugger-service;1"的XPCOM组件,其提供的主要接口如下:
interface jsdIDebuggerService : nsISupports
{
...........................................
/**
* Called when an error or warning occurs.
*/
attribute jsdIErrorHook errorHook;
/**
* Called when a jsdIScript is created or destroyed.
*/
attribute jsdIScriptHook scriptHook;
/**
* Called when the engine encounters a breakpoint.
*/
attribute jsdIExecutionHook breakpointHook;
/**
* Called when the engine encounters the debugger keyword.
*/
attribute jsdIExecutionHook debuggerHook;
/**
* Called when the errorHook returns false.
*/
attribute jsdIExecutionHook debugHook;
/**
* Called before the next PC is executed.
*/
attribute jsdIExecutionHook interruptHook;
/**
* Called when an exception is thrown (even if it will be caught.)
*/
attribute jsdIExecutionHook throwHook;
/**
* Called before and after a toplevel script is evaluated.
*/
attribute jsdICallHook topLevelHook;
/**
* Called before and after a function is called.
*/
attribute jsdICallHook functionHook;
attribute unsigned long flags;
attribute boolean initAtStartup;
readonly attribute boolean isOn;
/**
* Turn on the debugger.
*/
void on ();
[noscript] void onForRuntime (in JSRuntime rt);
void off ();
readonly attribute unsigned long pauseDepth;
unsigned long pause();
unsigned long unPause();
/**
* Force the engine to perform garbage collection.
*/
void GC();
void DumpHeap(in string fileName);
void clearProfileData();
/**
* Adds an execution hook filter. */
void insertFilter (in jsdIFilter filter, in jsdIFilter after);
/**
* Same as insertFilter, except always add to the end of the list.
*/
void appendFilter (in jsdIFilter filter);
....................................................
void enumerateContexts (in jsdIContextEnumerator enumerator);
void enumerateScripts (in jsdIScriptEnumerator enumerator);
void clearAllBreakpoints ();
jsdIValue wrapValue (/*in jsvalue value*/);

/**
* Push a new network queue, and enter a new UI event loop.
*/
unsigned long enterNestedEventLoop (in jsdINestCallback callback);
/**
* Exit the current nested event loop after the current iteration completes,
* and pop the network event queue.
*/
unsigned long exitNestedEventLoop ();
};

调试过程主要控制流程如下:
1、由Debugger(如Venkman)发起调试开始,调用DebuggerService.on方法,DebuggerService创建JSDContext,与当前JSRuntimer建立联系,并将DebuggerService中方法设置为当前JSRuntimer的globalDebugHooks对应元素的Hook(即设置回调函数指针);

2、Debugger将DebuggerService中的errorHook、scriptHook、breakpointHook、debuggerHook、debugHook、interruptHook、throwHook等Hook设置成Debugger提供的方法;

3、当JS引擎执行解析、调用JS脚本时,发现其globalDebugHooks中有对应回调Hook,如每解析完一个JS Function时就会调用newScriptHook,进而由DebuggerService调用Debugger提供的scriptHook,Debugger的scriptHook会根据JS引擎提供的文件名、函数名、行号等,读出对应文件显示出来;

4、当在指定文件对应行设置一个断点时,Debugger由DebuggerService调用JS引擎向指定的函数调用Frame中设置Trap操作码;

5、一旦JS引擎执行到Trap操作码,则会调用其globalDebugHooks中对应的回调Hook,进而调用DebuggerService breakpointHook,从而调用Debugger的debugTrap,其中会根据当时的调用上下文调用DebuggerService的DebuggerService的EnterNestedEventLoop方法,它往往会维护一个嵌套计数,然后作事件处理循环,其主要代码如下:
jsdService::EnterNestedEventLoop (jsdINestCallback *callback, PRUint32 *_rval)
{
// Nesting event queues is a thing of the past. Now, we just spin the
// current event loop.
nsresult rv;
nsCOMPtr
stack(do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv));
if (NS_FAILED(rv)) return rv;
PRUint32 nestLevel = ++mNestedLoopLevel;
nsCOMPtr thread = do_GetCurrentThread();
if (NS_SUCCEEDED(stack->Push(nsnull))) {
if (callback) {
Pause(nsnull);
rv = callback->OnNest();
UnPause(nsnull);
}
while (NS_SUCCEEDED(rv) && mNestedLoopLevel >= nestLevel) {
if (!NS_ProcessNextEvent(thread))
rv = NS_ERROR_UNEXPECTED;
}
JSContext* cx;
stack->Pop(&cx);
NS_ASSERTION(cx == nsnull, "JSContextStack mismatch");
}
else rv = NS_ERROR_FAILURE;
NS_ASSERTION (mNestedLoopLevel <= nestLevel,
"nested event didn't unwind properly");
if (mNestedLoopLevel == nestLevel)
--mNestedLoopLevel;
*_rval = mNestedLoopLevel;
return rv;
}

其中的事件处理循环很关键,在处理事件时被调试的JS脚本无法继续进行执行(即Block),而Debugger仍然可以处理UI事件,如显示当前CallFrame、查看变量值等;

6、当遇到断点时,用户通过Step-in或Step-out等触发JS引擎调用CallHook等,进而根据嵌套计数跳出上面的处理循环,而进入另一个循环或调试结束。

说明:整个过程与传统的windbg、gdb与OS、硬件协同完成Debug过程的实现非常类似,只不过这里的JS引擎相当与OS,它们都非常清楚源文件与具体执行点之间的对应和当时的上下文信息;一样由debugger来提供一些Hook给JS引擎或OS,当JS引擎或OS遇到Trap指令或异常等,它们会及时调用debugger提供的Hook,以由debugger给出下一步执行的决定。

但它们之间有个最大的不同在于JS debugger与运行的被debug的JS脚本都处于同一浏览器进程中;而windbg、gdb调试程序时debugger与被debug的程序往往处于不同的进程。

这里就导致JS debugger中需要过滤的问题,因为不可能在同一进程中调试自己(如用Venkman调试Venkman或用Firebug调试Firebug),这个过滤的动作往往由debugger来决定什么样的源文件可以设置断点、可对其函数进行Step-in、Step-out等,Venkman与Firebug的过滤机制不一样,导致Firebug不能调试chrome类的源文件。

Firebug有关调试方面的实现与Venkman很类似,一样需要利用DebuggerService来实现交互,有时间可具体参考参考。

三、WebKit Javascript调试实现
其实现逻辑与Gecko差不多,首先由Javascript实现提供一个Debugger接口类(相关于Gecko中的DebugHook),然后由一个实现了该Debugger接口的JavaScriptDebugServer来控制调试窗口与被调试页面之间的关系。

一旦JS引擎遇到解析完JS函数、设置的断点、开始函数调用、结束函数调用等则通知(或回调)调试窗口,以控制下一步的操作。

Drosera-Webkit-Trac对其中的设计有一定的说明。

四、总结
对一个开发人员来讲,不管是Web开发、还是Java、C/C++开发,乃至内核、驱动开发等,调试是一门必须掌握的技术,因为它不仅能让你提高工作效率,同时能让你更加深入的了解掌握计算机技术;如果能更一步的了解调试工作原理,则会让你如虎添翼。有空可以看看《软件调试》、《How debuggers work》等。。。

五、参考资源
Venkman HomePage
Learning the JavaScript debugger Venkman
Firebug HomePage
Drosera-Webkit-Trac

Wiki Debugger
Wiki GNU_Debugger(GDB)
Wiki WinDbg
Advanced Debugging(高端调试)

2008年10月15日星期三

认识了解Gears

前一段时间对Google Chrome有过简单的认识与了解,在体验新鲜出炉的Google Chrome浏览器中提到Google的Google Chrome Comic,其中提出通过Gears来扩展Web应用,但究竟什么是Gears以及其如何来扩展浏览器等,一直令人好奇。通过了解Gears应该可以了解Google将来究竟主推怎样的方式来扩展Web应用,当然包括Chrome浏览器及Android等,因为个人一直认为Gecko内核对扩展的支持非常方便、灵活、实用(具体可参考与Gecko相关的文档),但对基于WebKit内核的扩展相对来说不是那么的方便与直接,为了满足探究感,让我们开始了解Gears之旅吧。

一、什么是Gears?
Gears is an open source project that enables more powerful web applications, by adding new features to web browsers.

目前主要提供一组JS API以供Web应用使用,对应Web开发者来说其类似于prototype、dojo、jquery等JS库,但其实现是通过扩展浏览器来实现(不仅仅包含js代码还包含二进制代码等)。

主要的API模块有:
  • A Database module (powered by SQLite) that can store data locally.
  • A WorkerPool module that provides parallel execution of JavaScript code.
  • A LocalServer module that caches and serves application resources (HTML, JavaScript, images, etc).
  • A Desktop module that lets web applications interact more naturally with the desktop.
  • A Geolocation module that lets web applications detect the geographical location of their users.

通过Google Gears可以了解Gears的发展及目前开发状态等,其中有些已实现或打算实现的API还是非常有意思的如Geolocation、Audio、Camera等,一旦这些API能完善好的话确实非常有利于Web应用的扩展,比如说录音、摄像等。

如果对扩展Web应用如开发RIA等有兴趣的话,可以好好考虑哪些应用或业务,可以或需要扩展到Web上来,通过了解Gears,说不定Gears能给您很大的启发。。。。

二、如何利用Gears?
利用Gears,首先需要用户安装Gears如对Firefox来讲就是一个extension,对Chrome、Safari来讲是一个符合NPAPI的插件,对IE来讲就是一个ActiveX;其次利用Gears API开发相关应用页面,以供最终用户使用。

也许对最终用户来讲稍嫌麻烦,需要额外安装程序,但如果Gears能象Flash一样实用、好用,那时就不再会觉得很麻烦啦。

对开发者来讲利用Gears相对就简单啦。在页面包含
<script src="gears_init.js"></script>
<script>
if (!window.google || !google.gears) {
location.href = "http://gears.google.com/?action=install&message=<your welcome message>" + "&return=<your website url>";
}
</script>
然后就可以使用其中的API啦。

具体可参考Gears Resources and Tools中的gears_init.js、samples及其他API等文档。


三、初步探究Gears基本实现原理
通过初步了解Get gears_init.js可以基本了解Gears提供对IE、Firefox、Chrome、Safari等支持,但其实现方式各有不同。

1、对各浏览器的支持
对Firefox来讲,Gears扩展了一个GearsFactory JS对象,可供JS脚本直接调用,从JS的角度看相对于给window对象新增了一个对象,就像原先的document、navigator对象一样使用,只不过其功能不同而已。

对IE来讲,Gears扩展了一个Gears.Factory ActiveX对象,可通过JS脚本new ActiveXObject('Gears.Factory');来直接调用,从JS的角度看可以象使用XMLHttpRequest对象一样使用Gears.Factory。

对Chrome、Safari来讲,就有点特殊啦,也有点诡异的感觉。需要在当前文档动态添加一个MIMEType为application/x-googlegears的Object插件对象,而这个Object对象就是对应的google.gears.factory对象。

从这里就可以看出WebKit对扩展JS的支持,从架构设计不是那么的直接,需要由NPAPI插件机制来中转实现;而Firefox、IE都从架构上提供了相应扩展机制来扩展,相当的直接明了及与浏览器本身的统一。

2、google.gears.factory对Firefox支持的实现
浅谈Gecko关键部分之六认识javascript实现及应用中我们可以了解到Gecko中XPCOM组件通过xpconnect及对DOMCLASSINFO的定义,就可轻松的将XPCOM组件Binding给JS使用,就像Gecko中对Components、Components.Interfaces、xmlhttprequest等的实现。

在Gears中就是使用了类似的方式来Binding一个GearsFactory组件给JS环境使用。其中实现了一个实现了GearsFactoryInterface接口的名称为GearsFactory,ContractId 为"@google.com/gears/factory;1"的组件,具体代码详见gears\factory\Factory_ff.cc,然后在gears\base\firefox\Module.cc中注册该组件时使用
catMgr->AddCategoryEntry(JAVASCRIPT_DOM_CLASS,
kGearsFactoryClassName,
kDomciExtensionContractId,
PR_TRUE, PR_TRUE, NULL);
将该组件声明为需要由xpconnect Binding给JS使用。一旦启用xpconnect,xpconnect就会作相应处理,具体可参考nsDOMClassInfo::RegisterExternalClasses()等方法。这样就完成JS GearsFactory对象的实现。

3、google.gears.factory对Safari支持的实现
浅谈WebKit之JavaScriptCore/V8篇中我们可以知道WebKit中将DOM实现Binding给Javascript实现往往通过generate-bindings.pl生成的代码来静态Binding,这种方式无法满足动态Binding的需求。但是从浅谈Gecko关键部分之九Plugin中我们了解到NPAPI插件接口提供了一组接口可以将插件的接口动态Binding给JS环境,同时可以让插件与JS之间相互调用。

而WebKit中实现了对NPAPI插件接口的支持,这样Gears中实现了一个mimetype为application/x-googlegears的插件,将该插件对象当作google.gears.factory JS对象来提供给页面使用。

具体可参考gears\base\npapi\Module.cc、Npp_bindings.cc,其中的NPP_New()方法应该是主要入口,同时设置该插件为windowless。至于如何将插件的方法动态Binding给JS使用,可具体参考Webkit中对NPN_CreateObject、NPClass的实现支持等。

4、google.gears.factory对IE支持的实现
按照创建ActiveX及BHO等接口方法来实现Gears.Factory ActiveX,进而将该对象赋值给google.gears.factory。具体关于ActiveX及BHO相关的知识参考Microsoft文档。

5、对其他API接口的实现
虽然google.gears.factory实现了create、getBuildInfo、version等接口,但对通过create创建出来的提供给开发人员用的localserver、geolocation、httprequest、audio等对象是如何实现的呢?

体现Gears强大及方便的地方就在这里。Gears提供了一组统一的类及接口实现可以方便将自定义的类及接口Binding给对应浏览器的JS实现。针对Firefox,它通过扩展SpiderMonkey的方式直接扩展JS实现;针对Safari,它还是使用NPAPI、NPObject的方式来扩展。

具体可以参考一下gears\base\common\Js_runner_np.cc、Js_runner_ff.cc、Js_runner_ie.cc等;

这一组统一扩展JS实现的类及接口有:ModuleImplBaseClass、ModuleWrapperBaseClass、DispatcherInterface、Dispatcher、MarshaledModule、ModuleEnvironment等,其作用就是按照统一的方式方便的将自定义的类及接口Binding给不同的浏览器实现。

如果对这方面有兴趣,可以对这些类及接口之间的关系及实现进一步研究,看Gears究竟是如何将C/C++类实现Binding给JS。其实这一点对JS来讲非常有意义,因为一旦可轻松方便的扩展JS,那么JS的应用就会进一步的扩大,就象python一样,python脚本可以方便扩充其模块,C/C++类库也可方便的扩充成python模块,从而让python几乎无所不能。


四、如何扩展Gears API?
在初步了解Gears实现原理之后,正与Gears CreatingNewModules所描述,我们也可以利用Gears来扩展自己的API,在了解其他API的实现基础上,其实扩展一个自己的Gears API难度似乎并不太大。。。

期望大家有时间可以加入到Google Gears中来一起拓展Web应用。。。

五、总结
通过对Gears的初步了解,觉得Gears对浏览器的扩展主要体现在对JS方面的扩展,相当于JS功能库的扩充,并且主要体现在非UI方面,虽然其中有部分UI方面的内容,但相当少;而Adobe推出的flash、Microsoft推出的Silverlight主要是按照插件的方式基于html标准的embed/object进行扩充,往往涉及到UI方面,并都有自身的特色如内嵌脚本语言、播放video/audio、网络处理等等;而Gecko所拥有的extension确可以是全方位的(包括UI、xpcom、插件等等)。

目前随着Ajax及RIA的推广,各式各样的JS库如prototype、dojo、jquery等也层出不穷,但是针对JS功能库方面的扩展还没有一个相对通用的国际标准,也许Gears正好抓住这样一个发展的趋势才采取这种通过扩展JS接口,为开发人员提供API的方式来扩展Web应用。

Gears API一方面迎合了RIA等方面的需求,另一方面也推动浏览器的扩展。但是它似乎抛弃了UI方面的扩展如使用Gecko的XUL、Microsoft XAML等的可能,也许flash对UI方面有太多的优势,同时html5中的video/audio逐步提上日程,做一个类似于Silverlight的插件意义不大,还不如在JS方面下足功夫,同时全面拥抱html(如gmail、gmap、gdoc等),也许这就是Google目前对扩展Web应用的一点看法吧。。。


六、参考资源
Gears Project
Wiki Gears(software)

2008年10月8日星期三

浅谈WebKit之Port篇

WebKit作为一个浏览器引擎,其相对于Gecko而言一个较大的特点就是便于移植,嵌入到其他程序中,目前大家已了解使用WebKit引擎的应用包括Safari、iPhone、Chrome、Android、Nokia S60 Browser及KDE QT4.4等,同时还有其他方面的移植如Gtk、wxWidget、3D等,可以说WebKit从架构上讲其Port移植方面的设计及应用,是非常优秀的。这一点相对于Gecko有相当大的优势,有时间可以参考一下浅谈Gecko关键部分之十二Embedding。为了更深入的了解WebKit,我们现在就从WebKit有关Ports方面入手,了解其有关Port方面的设计,从而了解究竟如何能移植WebKit到自己的应用中。

一、有关Port方面的概述
在通过了解浅谈WebKit之WebCore篇之后,应该说WebKitPort方面的内容是可以很广的,例如可将不同的图形库、网络库与WebCore集成,提供不同的Port接口供外部程序使用等,例如同样在windows平台上可以运行的Google Chrome和Safari就是针对WebKit的不同移植。

我们想了解有关Port方面的主要内容在于提供不同的Port接口供外部程序使用以及如何与外部程序交互,因为WebKit中的其它两部分WebCore、Javascript实现,从逻辑上讲是不直接提供接口给外部程序使用的。同时为了完成浏览器的核心功能,WebKit也需要从外部程序中通过Port接口的方式获取一些支持。

从这个角度讲WebKit作为一个相对独立的整体,它与外部程序之间的交互也就有一组相对固定的接口来定义及维护它们之间的关系,它们之间的关系与插件跟浏览器引擎之间的关系完全类似,接口相当一组协议,有的是由WebKit来实现,而供外部程序调用;有的的正好相反。

通过前面的了解我们知道WebKit的主要功能集中在分析Html、渲染布局Web内容以及Javascript实现方面等,而这些Web内容显示在哪个窗口及消息处理的启动循环等都需要由外部程序来提供。

二、初步分析已有WebKit Port移植实现
1、与WebCore交互接口的实现
在WebKit源代码目录结构中WebKit目录下分别包含gtk、mac、qt、win、wx目录,其分别对应不同的Port移植方式,在每一个目录下面都包括WebCoreSupport目录,而在不同的WebCoreSupport目录下分别包含有对类接口WebCore::ChromeClient、WebCore::ContextMenuClient、WebCore::DragClient、WebCore::EditorClient、WebCore::FrameLoaderClient、WebCore::InspectorClient等的实现,它们代表外部程序提供给WebKit内部使用的接口实现,其中WebCore::ChromeClient、WebCore::FrameLoaderClient非常重要。

初步了解其接口定义能基本了解其对应的含义,这些接口往往需要由Port移植部分来提供实现,往往由WebKit内部根据一定的条件来调用。下面初步来了解几个主要接口:

WebCore::ChromeClient接口:
//往往在运行window.open脚本时调用,以便由外部程序决定如何打开一个新页面如新建一个窗口、新建一个Tab页签等;
virtual WebCore::Page* createWindow(WebCore::Frame*, const WebCore::FrameLoadRequest&, const WebCore::WindowFeatures&);

//通知外部程序显示页面;
virtual void show();

virtual bool canRunModal();

//通知外部程序以Modal的方式显示页面;
virtual void runModal();

//通知外部程序显示JS警告提示窗口;
virtual void runJavaScriptAlert(WebCore::Frame*, const WebCore::String&);

//通知外部程序显示JS警告确认窗口;
virtual bool runJavaScriptConfirm(WebCore::Frame*, const WebCore::String&);

WebCore::FrameLoaderClient接口:
//检查是否拥有主页面窗口;
virtual bool hasWebView() const;
//检查是否拥有页面窗口;
virtual bool hasFrameView() const;

//通知外部程序有关http请求开始、结束、获取数据等,如通常浏览器状态栏显示的信息;
virtual void dispatchDidReceiveResponse(WebCore::DocumentLoader*, unsigned long identifier, const WebCore::ResourceResponse&);
virtual void dispatchDidReceiveContentLength(WebCore::DocumentLoader*, unsigned long identifier, int lengthReceived);
virtual void dispatchDidFinishLoading(WebCore::DocumentLoader*, unsigned long identifier);
virtual void dispatchDidFailLoading(WebCore::DocumentLoader*, unsigned long identifier, const WebCore::ResourceError&);

//通知外部程序WebKit内部主要事件处理,以便外部程序及时响应或创建维护数据等
virtual void dispatchDidHandleOnloadEvents();
virtual void dispatchDidReceiveServerRedirectForProvisionalLoad();
virtual void dispatchDidCancelClientRedirect();
virtual void dispatchWillPerformClientRedirect(const WebCore::KURL&, double interval, double fireDate);
virtual void dispatchDidChangeLocationWithinPage();
virtual void dispatchWillClose();
virtual void dispatchDidReceiveIcon();
virtual void dispatchDidStartProvisionalLoad();
virtual void dispatchDidReceiveTitle(const WebCore::String&);
virtual void dispatchDidCommitLoad();
virtual void dispatchDidFinishDocumentLoad();
virtual void dispatchDidFinishLoad();
virtual void dispatchDidFirstLayout();

//告诉外部程序需要提供切换到一个新页面状态。此时外部程序往往会新建FrameView,并将FrameView与Frame关联,设置原生窗口句柄及其消息处理机制等等;
virtual void transitionToCommittedForNewPage();

//告诉外部程序创建一个新的Frame,如遇到html中iframe标签时,需要外部程序创建一个新的Frame及原生窗口句柄等;
virtual PassRefPtr createFrame(const WebCore::KURL& url, const WebCore::String& name, WebCore::HTMLFrameOwnerElement* ownerElement,
const WebCore::String& referrer, bool allowsScrolling, int marginWidth, int marginHeight);

//告诉外部程序需要创建一个Plugin实例,从而创建其原生窗口等等;
virtual WebCore::Widget* createPlugin(const WebCore::IntSize&, WebCore::Element*, const WebCore::KURL&, const Vector&, const Vector&, const WebCore::String&, bool loadManually);

2、对WebCore中的page/loader等方面的类提供对应Port的实现支持
如EventHandlerWin.cpp、FrameLoaderWin.cpp、DocumentLoaderWin.cpp、DocumentLoaderWin.cpp、WidgetWin.cpp、KeyEventWin.cpp等

3、实现WebView及WebFrame等以便外部程序嵌入WebKit
不同的Port移植对WebView及WebFrame的定义及实现有所不同,但其与WebCore中的Page、Frame之间的关系大致与浅谈WebKit之WebCore篇图一描述相一致。

具体关于WebView、WebFrame的定义与实现,特别是初始化时的动作可根据不同的Port移植而有所不同,同时初始化时会将上面提到的WebCore Port接口实现告诉WebKit内部。主要示例代码如下:
static void webkit_web_view_init(WebKitWebView* webView)
{
WebKitWebViewPrivate* priv = WEBKIT_WEB_VIEW_GET_PRIVATE(webView);
webView->priv = priv;
priv->corePage = new Page(new WebKit::ChromeClient(webView), new WebKit::ContextMenuClient(webView), new WebKit::EditorClient(webView), new WebKit::DragClient, new WebKit::InspectorClient);
priv->mainFrame = WEBKIT_WEB_FRAME(webkit_web_frame_new(webView));
priv->lastPopupXPosition = priv->lastPopupYPosition = -1;
priv->editable = false;
................................

priv->webSettings = webkit_web_settings_new();
webkit_web_view_update_settings(webView);
..................................
}

WebKitWebFrame* webkit_web_frame_new(WebKitWebView* webView)
{
g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), NULL);

WebKitWebFrame* frame = WEBKIT_WEB_FRAME(g_object_new(WEBKIT_TYPE_WEB_FRAME, NULL));
WebKitWebFramePrivate* priv = frame->priv;
WebKitWebViewPrivate* viewPriv = WEBKIT_WEB_VIEW_GET_PRIVATE(webView);

priv->webView = webView;
priv->client = new WebKit::FrameLoaderClient(frame);
priv->coreFrame = Frame::create(viewPriv->corePage, 0, priv->client).get();
priv->coreFrame->init();

return frame;
}

4、Chrome中对Port移植方面的实现
其基本上与其他Port移植类似,其主要代码在webkit\glue目录中,可重点关注带client_impl.cc后缀的文件、webview_impl.cc、webwidget_impl.cc等;但是其究竟如何创建原生windows窗口、如何创建Render进程、Render进程与创建的原生windows窗口的关系如何等需要更进一步深入研究Chrome,如果能从上面提到的Port部分入手也许很快就可得到答案,这一点以后有机会单独研究。

5、Android中对Port移植方面的实现
其实现有点特殊,
由于Andriod将WebKit以一个Java类接口的方式提供给Java环境使用(不像上面提到的Chrome、Safari等都是将WebKit以 一个C++动态或静态库的方式供C/C++外部程序调用),这样WebKit内部与外部即JavaVM的交互(如上面提到的ChromeClient、 FrameLoaderClient接口实现)需要一个Bridge类来协调处理,同时WebView、WebFrame接口绑定给JavaVM的jni接口实现也需要通过这个Bridge来支持协调处理。具体可详细参考android源码代码中WebCore\platform\android目录下的源文件。

6、通过进一步了解WebCore Port接口及其实现,可以加深这样一个认识:
如果
从MVC的角度来看整个基于WebKit的浏览器(当然不尽合理),WebKit的Port部分相当于V部分,它提供显示页面内容及其辅助信息(如提示状态)的场所(即原生窗口)以及控制该显示场所的状态变化及消息响应(如改变大小、鼠标移动等);而M部分往往由WebCore来实现,至于WebCore如何组织DOM则往往由htmlparser部分根据DOM定义来组织,如何在提供的显示场所显示Web内容则往往由WebCore中的layout部分来实现,其中充分利用了Css定义来布局显示该显示的内容;一旦涉及控制或动态处理往往由Port部分发起而由Javascript脚本来实现处理,其任务由JavascriptCore或V8来完成。

一般说来新打开一个页面,Port部分需要提供一个主显示场所(即原生窗口),如果页面中含有iframe标签,则需要在主显示场所内创建一个子显示场所,以显示iframe标签对应src的内容;如果页面中含有embed/object等插件标签同样往往也需要
在主显示场所内创建一个子显示场所(除非windowless),以交由插件实现在提供的显示场所中显示内容。

特别需要说明的是我们通常看到的页面表单元素input text field、textArea、button、radiobutton等往往不像window图形库中的按钮、菜单、输入框等会对应一个原生窗口,页面中的表单元素在一个显示场所(即原生窗口)中完全是利用Css等通过layout方式来达到我们所看到的类似原生按钮、输入框、列表框、滚动条等效果,其中特别是能准确定位元素大小、设置focus、光标显示、响应事件等,这充分的说明了浏览器引擎内部布局部分的威力所在。

从另外一个角度来看一个页面一般说来(除非遇到iframe或插件需要另外提供一块子画布)相当于一块画布,浏览器引擎能在其精确的位置绘制不同颜色的文字、图片、图标等,同时根据当前的鼠标及一个模拟的输入提示光标位置,接收键盘输入操作。页面中的绝大多数元素与原生的窗口元素几乎没有关联,完全通过组合、布局、准确定位来处理一切。。。

三、如何利用WebKit?
了解WebKit Port部分,对我们如何利用WebKit有非常现实的意义,目前已经将WebKit移植到多种平台如windows、qt、gtk、mac、wx、java、framebuffer等,甚至移植到python、ruby及3D等环境中去。通过借鉴或利用这些已有的WebKit Port实现,完全可以将WebKit发扬广大。

前一阶段正好得到一个网友抓取网页的需求,试想目前移植利用WebKit基本都用来显示页面,往往涉及图形显示方面,但随着ajax及动态页面的广泛使用,未来动态生成的页面越来越多,传统的搜索引擎仅仅抓取静态的页面内容显然是不够的,现代化的搜索引擎应该能抓取动态的页面内容,这样它从某种意义讲相当于一个能获取对应的动态页面但不真正显示出其内容的浏览器,这样一个搜索引擎不仅能分析DOM树,同时能运行Javascript脚本(如运行ajax),以真正完整获取页面内容,其实这样一个搜索引擎如果利用WebKit来实现的话,应该是个不错的选择,在我们了解WebKit Port部分之后,我们是否可以来模拟一个不真正具备图形显示方面的Port,进而充分利用WebKit中的WebCore及Javascript实现方面的功能呢?一点想法,今后有机会可以试试,或许Google、Yahoo的搜索引擎已经有了相关的实现,不知是否使用的就是WebKit?应该不会,有谁清楚的话,烦请通知一声。

但愿我们也能利用利用WebKit整出一个象模象样的东东如机顶盒浏览器、手机浏览器等等。。

四、参考资源
The WebKit Open Source Project
Google Chrome Home
Android - An Open Handset Alliance Project