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

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年9月25日星期四

浅谈WebKit之JavaScriptCore/V8篇

WebKit作为一个浏览器引擎,其中Javascript实现包括JavaScriptCore和V8,为了能更全面的了解WebKit,我们需要深入的了解Javascript实现的基本原理、其在WebKit中的作用以及与其他部分之间的交互,同时与Gecko中的Javacript实现作初步的对比。让我们开始了解WebKit之Javascript实现JavaScriptCore、V8之旅吧。

一、Javascript实现的作用
正与浅谈Gecko关键部分之六认识javascript实现及应用部分对什么是javascript的描述那样,在WebKit中其Javascript实现,同样相当于一个符合ECMAScript标准的动态库,其往往依附于浏览器引擎,由浏览器引擎来提供运行环境,并控制或发起javascript实现进行编译、解析执行脚本、垃圾回收等,同样需提供对浏览器引擎扩展的支持如Dom Binding等;

由于Web2.0的提出,动态网页的交互如运行ajax更加的频繁,Javascript脚本运行的总体效率以及安全往往成为浏览器内核的关键,而其Javascript实现就担负着如此重任。

二、JavaScriptCore实现特点
相对于其他的Javascript实现,JavaScriptCore提出了虚拟机的概念,在编译脚本时生成高效的bytecode,bytecode统一在一个虚拟机的环境中执行。而其高效的虚拟机实现常称为SquirrelFish,通过Announcing SquirrelFishIntroducing SquirrelFish Extreme可更进一步了解关于SquirrelFish的相关内容。

三、V8实现特点
Fast Property Access
To reduce the time required to access JavaScript properties, V8 does not use dynamic lookup to access properties. Instead, V8 dynamically creates hidden classes behind the scenes. This basic idea is not new - the prototype-based programming language Self used maps to do something similar. (See for example, An Efficient Implementation of Self, a Dynamically-Typed Object-Oriented Language Based on Prototypes). In V8, an object changes its hidden class when a new property is added.

Dynamic Machine Code Generation
V8 compiles JavaScript source code directly into machine code when it is first executed. There are no intermediate byte codes, no interpreter. Property access is handled by inline cache code that may be patched with other machine instructions as V8 executes.

During initial execution of the code for accessing a property of a given object, V8 determines the object's current hidden class. V8 optimizes property access by predicting that this class will also be used for all future objects accessed in the same section of code and uses the information in the class to patch the inline cache code to use the hidden class. If V8 has predicted correctly the property's value is assigned (or fetched) in a single operation. If the prediction is incorrect, V8 patches the code to remove the optimisation.

Efficient Garbage Collection
V8 reclaims memory used by objects that are no longer required in a process known as garbage collection. To ensure fast object allocation, short garbage collection pauses, and no memory fragmentation V8 employs a stop-the-world, generational, accurate, garbage collector. This means that V8:

  • stops program execution when performing a garbage collection cycle.
  • processes only part of the object heap in most garbage collection cycles. This minimizes the impact of stopping the application.
  • always knows exactly where all objects and pointers are in memory. This avoids falsely identifying objects as pointers which can result in memory leaks.

In V8, the object heap is segmented into two parts: new space where objects are created, and old space to which objects surviving a garbage collection cycle are promoted. If an object is moved in a garbage collection cycle, V8 updates all pointers to the object.


四、JavaScriptCore、V8如何与WebCore交互
在WebCore::Frame的数据结构中包含数据成员KJSProxy* m_jscript;而在Chrome的代码中调整为JSBridge* m_jscript;而针对不同实现JavaScriptCore、V8,分别有:
class KJSBridge : public JSBridge {
public:
KJSBridge(Frame* frame) : m_proxy(new KJSProxy(frame)) { }
virtual ~KJSBridge() { delete m_proxy; }
........................
private:
KJSProxy* m_proxy;
};

class V8Bridge : public JSBridge {
public:
explicit V8Bridge(Frame* frame);
virtual ~V8Bridge();
.......................
private:
V8Proxy* m_proxy;
};
V8Bridge::V8Bridge(Frame* frame) {
m_proxy = new V8Proxy(frame);
}
V8Bridge::~V8Bridge() {
delete m_proxy;
}

而不同的KJSProxy与V8Proxy分别对应不同的Javascript实现,它们分别实现了与WebCore之间的共同接口,其主要数据结构分别如下:
class KJSProxy {
Frame* m_frame;
KJS::ProtectedPtr< KJS::JSDOMWindow> m_globalObject;
int m_handlerLineno;
.........................................
};

class V8Proxy {
Frame* m_frame;
v8::Persistent<v8::context> m_context;
v8::Persistent<v8::object> m_global;
// Special handling of document wrapper;
v8::Persistent m_document;
int m_handlerLineno;
...........................
};
具体不同Javascript实现如何实现与WebCore的接口,需了解不同Javascript实现逻辑;

如对Javascript实现逻辑及基本原理感兴趣,可具体参考其提供的api及sample等等;

至于Dom Binding的实现,JavaScriptCore与V8通过通过同样的方式来实现,可参考浅谈WebKit之WebCore篇 中所描述的Javascript实现如何与WebCore集成等;

具体Dom Binding的实现可参考generate-bindings.pl生成的代码,其中的内容一定会让你受益非浅,同时为将Javascript实现嵌入到其他应用中去提供非常有益的参考。如对window的实现,特别是open方法的实现,很值得研究研究。。。

五、初步对比JavaScriptCore、V8、SpiderMonkey等
具体JavaScriptCore、V8、SpiderMonkey、TracMonkey执行效率对比如何,不同的测试方法会有不同的测试结果,在这里不再阐述。

就个人了解而言,觉得JavaScriptCore关于对象的方法、属性的安全访问控制方面略有欠缺;

SpiderMonkey作为最早一批实现Javascript的引擎,其代码使用C语言来实现,稍现复杂,没有象后来的实现如JavaScriptCore、V8等借鉴了最新的虚拟机技术如JVM等;

V8作为新近推出的Javascript实现,正与其特点所描述,拥有很多优势,同时基于C++实现,充分利用了C++ template,代码相对简洁,便于学习使用;

关于TracMonkey请参考Firefox to get massive JavaScript performance boost

六、参考资源
Wiki Javascript
V8
Announcing SquirrelFish
Introducing SquirrelFish Extreme
SpiderMonkey Internals
Tamarin

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