Konqueror-Embedded之结构分析

来源:互联网 发布:jq数组push键和键值 编辑:程序博客网 时间:2024/04/29 14:40
 

Konqueror-Embedded之结构分析


  1. KIO

KIO 是一个网络使能的文件管理类库。这个库包含了几乎所有的你可能要用到的文件管理的方法。事实上,KDE中的文件管理器、Konqueror浏览器都使用KIO来提供它们网络使能的文件管理。。


把网络透明性移入到一个库文件中解决了在KDE1.x中曾经遇到的一个主要问题:应用程序通常依赖一个正在运行的kfm去为它们处理下载任务。显然,使用一个自治的应用去代替那个用户可能不愿意运行的什么东西要好的多,而且那个东西有可能因为一些不知道的原因而崩溃。


应用程序使用这个库的最简洁的方法是通过类KIO::NetAccess(针对简单的同步访问)或是通过类KIO::Job(针对负责的异步任务)。.


同步传输方式:它是为那些只需要简单网络应用的程序比如说下载一个文件。大多数情况下,同步传输方式已经是足够了,类KIONetAccess就提供了这方面的支持,并且KIONetAccess 提供了简单的API如:downloaduploadremoveTempFile.它同时也提供交互方式如通过对话框来显示目前的进度。


异步传输方式:类KIOJob是为象浏览一张网页等的高级应用而设计的。在这种情况下,我们希望一个异步传输的方式。


对于每一种协议的实现如:ftphttpsmbpop3imap4gophergziptar等等都是由独立的进程完成的,称之为Kioslaves.kio_ftp 实现FTP协议,kio_http实现http协议等等。使用“协议管理器”,KIOJob会根据给定的协议查找匹配的Kioslave,然后启动它。

二、KPart

2.1 Part的基本概念

部件的主要思想是重用性。通常一个应用程序需要使用另外一个应用程序的功能。虽然可以通过在两个应用之间使用共享库的方法来解决这个问题,但是如果一个应用程序要使用另外一个库的时候,将会产生矛盾。


KDE 中,部件被称为 part,这和 MS 中的 OLE组件是一个概念。一个 part 通常包含三个部分:一个窗口、功能、以及使用该功能的用户接口。应用在浏览器项目中,part 包含了多个窗口(一个页面对应于一个 part,而一个页面有可能包含多个窗口)、part 提供的功能包括:页内、页间的跳转、重定向、窗口的创建和删除、用户的输入处理、剪切、粘贴等,同时为使用这些功能提供了菜单、工具条、状态条等用户接口。其他的应用程序可以使用该 part 把浏览器窗口嵌入自己的应用中。


KDE 中,为了方便的实现将现有应用作为一个控件插入到另外一个应用中去,采用了 part 对象负责管理整个应用和窗口。对于每个窗口又采取了类似 mfc 中的 Doc—View 结构。因此在将应用嵌入到另外一个应用中的时候,只需要获得 part 中的接口和数据即可。对窗口的一切操作的响应由被嵌入应用的 part 对象来完成,而类 Doc—View 结构只简单的负责显示和保存显示数据就可以了。


Kpart 定义了一系列简单的类:partpluginmainwindowpart manager。其中:

  • Part:是 KDE 中的部件。为了定义一个Part,你需要提供widget,当然还有提供访问Part功能的Actions,还要有一个XML文件来描述在UIActions的布局。

  • Plugin:它是一小段功能程序,不通过嵌入一个Widget来实现。但是定义了应用程序用户接口对应的动作。它可以是图形的,比如弹出的对话框、拼写检查等。

  • Mainwindow:它是一个特殊的KTMainWindow,它的具有Action定义的UI是由XML文件来描述的,这样它可以通过弥合XML文件来嵌入Part对象。

  • Part manager:它是一个抽象的类,用来处理激活还是禁止 part。它只有在一个 mainwindow 中包含多个 part 的时候才有用。

2.2 Part的类型

KDEPart框架中定义了三种Part类型。通常的的一类是Part,它提供基本的Part功能包括WidgetXMLActions


Read-Only Parts:这个类型的Part为实现各种各样的Viewer提供了一个一般框架。一个Text Viewer、一个PostScript Viewer和一个Web BrowserViewer,他们共同的特性就是他们都访问一个URL,并且以一种Read-Only的方式。在各种可能情况下KDE都提供透明的网络访问,因此大部分的KDE应用使用URL而不使用文件名。如果文件在本地就直接打开,如果文件在远端服务器就激发网络去读取过来。这样应用就可以以一种统一的方式嵌入Part对象,并且以一种一致的方法来对它们进行控制。而这些不是一般的Part对象所提供的。


Read-Write Parts:这个类型的Part是对Read-Only Part的一个扩充,增加了修改和保存文档的方法。这在一个文本编辑类型的Part对象Kwrite中得到实际应用,Koffice亦是如此。在这个Read/Write的框架中,提供了另一半的网络通明性-当保存后重新载入文档(对于远程文件)。一个Read-Write Part也必须可以以一个Read-Only的方式来操作,当KwriteKoffice嵌入Konqueror中的时候,他们只能进行文档的查看,但不能进行修改。更一般的,任何一个Editor都可以看作一个Viewer

2.3 创建Part

对理解Konqureror没有太多意义,略。

2.4 使用动态库来获得Part

当你创建了上面的哪个Kpart之后,你可以自己把它嵌入到自己之中了。但是要让其他的应用来获得你的Part就需要动态连接你的Part库。第一步是把你的Part编译为共享库,这很简单。


现在你的Part在共享库中是可获得的了,但这还不够。你必须提供一种方式给其他的应用来动态打开这个库并创建Part。这是由使用一个Factory来实现的,它继承于KLibFactory。一个应用将使用KLibLoader来打开一个共享库,它负责装载和打开这个库并且调用一个初始化函数-init_lib….()。这个函数是由你的Part实现的并返回给KLibLoader,然后KLibLoader可以创建这个厂类了。


你的厂类需要实现一个create的方法,它将创建你的Part对象。它还有一个静态的实例(instance),你可以使用这个实例而不需要为每个Part创建自己的实例。在你的Part对象中你应该调用setInstance()来设置这个实例,而不需要自己创建一个。


基本上你所要做的就是定义一个函数作为库的入口。它需要以C函数的方式进行链接来避免C++命名规则。C链接意味着符合名和函数名是一致的。


注意:KPart提供了一个厂基类:KPart::Factory,它增强了KLibFactory可以允许Part的父亲和Widget的父亲是不同的。


为了提供和Konqueror更好的集成,你可以为你的Part提供一个KParts::BrowserExtension,在kparts/browserextension.h中有定义。我们应该记得ReadOnlyPart只有一个openURL(KURL),而没有其他的设置。对一个全功能的浏览器来讲,我们需要更多的属性,包括许多打开URL的众多参数(参看URLArgs),允许Parts来存储和恢复他们的数据到前进/后退的历史记录中,允许Parts来控制地址栏中的URL显示,请求作为宿主的Browser来打开URL等。


Part的开发者需要定义自己的类,从BrowserExtension派生,实现其中的虚方法(包括标准动作的槽)。把BrowserExtensionPart联系起来的方法非常简单,创建BrowserExtension作为Part的一个孩子。宿主应用会自动的寻找它。Browser集成的另一个方面是由Browser提供的一组标准动,但是由Part来实现。下面的标准活动是由Viewer的宿主来定义的:

  • 选择类活动:CutPasteCopy

  • 一般类活动:PrintreparseConfigurationrefreshMimeTypes

这些Viewer定义了以这些Action命名的槽来实现这些ActionBrowser将自动的检测并连接这些槽。


BrowserHostExtension是容器类Parts的扩展,KHTMLPart就是这样的一个Part对象。

三、DCOP

DCOP的主要动机是提供KDE应用程序的进程间通讯的功能。KDE已经有了一种简单的IPC机制叫做KWMcom,用来进行应用程序和窗口管理器之间的通讯。他通过X Window系统中的原子(Atom)来进行数据的交互。它在数据容量和复杂度上都产生了诸多限制。KDE曾考虑使用CORBA的解决方案,但是通过一年的尝试,他们认识到CORBA对简单的应用场合,它是一个有一点慢并且消耗内存很大的系统。而且它还不具备认证的功能。


DCOP是一个简单的IPC/RPC机制,建立在Socket的操作之上的。支持UD SocketTCP/IP socketDCOP建立在Inter Client Exchange(ICE)协议之上,ICEX11R6的一部分并成为标准。DCOP只依赖于QT


DCOP的模式也很简单,每个使用DCOP的应用都是一个Client。他们通过一个DCOPServer继续相互间的交互。Server的作用就是派发消息和呼叫给恰当的目标。


两种操作在DCOP是可能的,”Send and forget” messages(不会阻塞)和”calls”(阻塞直至等待数据的返回)。所有的数据都通过QT对象中的QDataStream操作符进行序列化,然后被发送。另外有一个IDL类似的编译器(dcopidldcopidl2cpp)可以生成Stubs(存根)和Skeletons(基干)。


KApplication中提供了一个方法叫做dcopClient(),它会返回一个DCOPClient实例的指针,在第一次调用的时候会创建一个ClientDCOP的客户端有唯一的标识,这些标识基于KApplication::name()的返回值来确定。实际上,如果运行的程序只有一个实例,那么这个应用的标识等于KApplication::name()


你需要调用DCOPClient::attach()来开始DCOP的通讯。这将会尝试去连接DCOPServer。如果没有查找到Server,那么将会返回一个错误,attach将会返回一个错误。在这种情况下,KApplication将会收到一个DCOP的信号并显示一个错误对话框。


在通过DCOPClient::attach()连接到服务器之后,你需要注册你的ID来让Server知道。否则你将会是一个匿名的通讯。使用DCOPClient::registerAs(const QCString &name)来达成这一点。AppId = client->registerAs( kapp->name() );你可以通过DCOPClient::detach()来断开与服务器的连接,如果你需要再次连接,那么你需要重新注册。如果你只是想要改变它的标识,只需要简单的重新注册它的ID就好了。


KuniqueApplication会自动注册自己。如果你使用KuniqueApplication,你不用attach或者register你自己了,它们已经做好了。


对实际的通讯来说,你可以使用sendcall的方法,这里就不详细介绍它们的使用了。需要介绍一个新的概念就是DCOPObject,如果你要从DCOP中接收数据,那么你需要多重继承一般类和DCOPObjectDCOPObject提供了一个非常重要的纯虚的方法,DCOPObject::process(),如果你要处理接收的信息你必须实现这个方法。想象一下DCOPObject是一系列的dispatch的代理(agent)。在将来,将会通过预编译的手段来完成这部分代码。


有时一个组件希望通过DCOP去发送通告到别的组件,但并不知道哪个组件将会对这些通告感兴趣。这时,你需要使用广播的方法,当然这是一个很粗糙的方法。比较高级的一个方法将会被介绍,那就是DCOPSignalDCOP的信号和QT的信号很相似,尽管它们还是有区别的。一个DCOP的信号可以连接到一个DCOP的函数。在DCOP的信号被激发时,DCOP相应的函数就会得到调用。它们都不提供返回值。DCOP的信号产生于DCOP对象和DCOP客户的组合(发送者)。它将会连接到一个函数或者另一个DCOP对象和DCOP客户的组合(接收者)。DCOPSignal连接和QT中的Signal连接有两个主要的不同:在DCOPSignal连接可以有匿名的发送者,一个DCOPSignal连接可以是长效的(它不会像QT中的信号连接一样,在发送或者接收的任意一方被析构时也被删除)。


Konq-emb使用的是一个简化的DCOP方案。在这个方案中,有几个部分组成:DCOPClientDCOPDispatcherDCOPObjectConnectionDCOPDispatcher是一个派发器,相当一个ServerDCOPObjectDCOPDispatcher在一个主进程进程中。DCOPClient运行在子进程中,它和DCOPDispatcher通过Connection定义的机制,按照协议和进行通讯。DCOPClient可以提出请求或者报告数据,通过Dispatcher查找相应的DCOPObject,调用它的process()虚函数进行处理。

四、QT中的SignalSlot机制

通过一些简单的试验和代码的跟踪,我们发现一般的QObject或者你继承的简单子类,操作SignalSlot时,激发信号(emit signal)将会陷入到Slot函数中。这和一般认为QT中的SignalSlot是异步处理的认识相悖。


为此,我询问了KDE的一个开发人员曾经告诉我说:

QT is a callback system and it can be used to simulate asynchronousmessage processing.  In Qt applications there is an event loop that runsand handles signals.


我们做了下面的例子。通过下面的实例,我们可以分析得出,QT中的标准控件的Signal发送也不是异步机制。

#include

#include

#include

#include

#include

#include


class MyMainWindow : public QWidget

{

Q_OBJECT

public:

MyMainWindow();

protected slots:

void slotClicked(){while(1);}

private:

QPushButton *b1;

QLabel *label;

};


MyMainWindow::MyMainWindow()

{

setGeometry( 100100200170 );


b1 = new QPushButton( "Quit"this );

b1->setGeometry( 202016080 );

b1->setFont( QFont( "Times"18QFont::Bold ) );

label = new QLabel( this );

label->setGeometry( 1011018050 );

label->setText( "push the button" );

label->setAlignment( AlignCenter );


connect( b1SIGNAL( clicked() )thisSLOT( slotClicked() ) );

}

#include "test.moc"


void main( int argcchar** argv )

{

QApplication a( argcargv );

MyMainWindow w;

a.setMainWidget( &w );

w.show();

a.exec();

}


如果QT中的标准控件类内部的SignalSlot处理是异步的话,按钮在按下之后应该恢复,而在本例中由于陷入了Slot的无限循环中,因此按钮无法恢复原有状态。


signalslot只是对于回调函数一个比较安全的封装(wrapper.其中,slot对应回调函数,signal则相当于触发回调函数的方法。


Event与回调函数毫无关系,它们是底层的Window系统发来的消息(linux下应该是XServer或者是Window Manager发来的?),其对应的具体的member function是对于这个event的处理函数。所以在这类function一般都是完成对于鼠标、键盘和窗口事件的处理。


刚开始比较容易弄混,可能是signalslot看起来也是发消息。(从对象的观点上来讲,也是这么回事,如UML中的面向对象的sequence图中,对象之间是互相发消息来通信,直到对象映射为类后,消息才映射为函数)。

通过阅读KonquerorQT的源代码,我们越来越发现Qevent应该是我们不明白的异步处理的答案。QT内部的事件处理是通过Qevent来进行的,SendQEvent等都是异步的。Event在传入控件内部处理时会激发Signal,然后接着通过查找MetaObject的注册表找到相应的对象的Slot执行。SignalSlot就是立即的函数调用,而QT内部有事件的Loop


QT中还有一个Qsignal的类,它是对Qobject的一个简单的扩充,可以发送不带参数的朴素的信号。如果你想在一个不是从Qojbect类继承而来的类中发送信号,你可以创建一个内部的Qsignal的对象来激发信号。你必须另外提供一个函数连接到这个信号到一个外部对象的槽上。这就是我们如何实现QmenuData类中的信号,它就不是从Qobject中派生的。

QSignal::block(bool b):如果b为真的话就阻塞住这个Signal,也就是说如果blockSig为真的话在你激发信号时就不会进行处理。它是通过Qobject中的blockSignals()函数来处理的。注意QSignal是从Qobject中私有继承的,你不能使用任何的Qobject的函数。


Qaction类抽象了一个用户接口的行为,可以出现在菜单或者工具栏上。这里有两种基本的UI行为,命令行为或者是选择。Qaction通常被塑造为一个命令行为,比如打开一个文件。当一个实际的行为将被执行,它激发activated()的信号。选择行为,例如在一个绘图应用中的绘制工具,经常是一些Toogle的行为。一个Toogle的行为激发一个toogle()的信号,无论何时它改变了自己的状态。多个Toogle行为将被合成为一个QactionGroup。一个行为可能被一些快捷键来触发。因为快捷键是由Window来指定的,应用的窗口必须是这个行为的祖先,每次创建行为都作为主窗口的孩子将是个明智的选择。


下图是事件结构的UML图:

五、Konqueror-Embedded中的Part结构UML

下面的UML图是Konqueror-Embedded中的Part结构示意图。










总体上来讲Konq-Embed是一个Doc-View的结构。它定义了一个从QObject类派生而来的View类。这就是Konq-Embed中的视图了,而它的视图对应的Doc是一个叫做HTMLView的类,这个类是从KHTMLPart类派生而来的。视图类组合于MainWindowBase的类中,它是应用的主窗口,继承于QmainWindow


KHTMLPartKHTMLView的关系又像是一种Doc-View的关系,KHTMLView是组合于KHTMLPart类中的,它是KHTMLView中的KHTMLPartPrivate类中的一个成员,由KHTMLPart来初始化并进行设置。KHTMLView继承于QScrollView


这样,当我们在Konq-Emb中的File菜单中新建一个窗口,就生成一个View的对象,这个视图类接着创建他的文档类(也就是KHTMLPart),KHTMLPart再创建KHTMLView的对象,这个对象其实是一个QWidget的类型,然后设置它为当前窗口活动的控件。


前面我们已经在介绍了KPart的大体内容,其中提到一个Part如果需要和浏览器进行更好的集成,那么它需要实现BrowserExtension。因而KHTMLPart也实现了BrowserExtensionKHTMLPartBrowserExtension,它是聚合于KHTMLPart中并由其的私有数据类(KHTMLPartPrivate)管理的。(在这里需要说明的是KHTMLPartKonqueror是两个不同的概念。Konqueror通过KHTMLPart来进行HTML渲染,Konqueror应该说是KHTMLPart的宿主外壳,它还可以是KWrite等其他ReadOnly Part的宿主。)RenderFormElement:: eventFilter()中,当有FocusOut或者FocusIn的事件发生时,它通过得到m_element-> view->part()->browserExtension()得到一个Extension对象,然后设置焦点的变化。


KPart::BrowserInterface这个类为一个KPart和宿主浏览器的外壳之间提供一种直接的访问方式。外壳应实现这样一个接口,并在嵌入该外壳的KPart组件中进行传递,通过调用BrowserExtension对象的setBrowserInterface方法。这个接口看起来并不丰富,但是主要的功能是通过callMethod这个方法来实现的,它使用QT中的属性来允许一个Part明确的查询外壳的信息。因此KParts::BrowserInterface是聚合于BrowserExtension的。


但是KHTMLPart被赋予了一种权力或者是任务,就是由KHTMLPart创建其他的ReadOnly Part对象。KHTMLPart::createPart()方法检查MIME类型,然后查找相应的Part对象名,最后交给KLibFactory::createPart()方法进行创建,创建完毕的Part对象,KHTMLPart把它存放在其内部维护的PartManager中。因此,ReadOnlyPart聚合于PartManagr中,PartManager组合于KHTMLPart之中。


因而KHTMLPart还是一个包容器的Part对象,它可能包容其他的Part对象,因此它需要实现BrowserHostExtensionKHTMLPartBrowserHostExtensionHTML显示Frame的时候,KHTMLPart为每一个Frame都生成一个KHTMLPart的对象。(可能我们还应该区分Container Parthost shell的区别)


View::childFrameNames()中,通过调用Kparts::BrowserHostExtension::childObject(part)得到一个hostExtension的指针,调用hostExtension->frames()得到一个PartList,对每一个Part递归调用childFrameNames() 把得到的Frame的名字记录在一个字符串列表中。View中还有一个方法,给出frame的名字,返回给你View的指针和一个BrowserExtension的指针。在MainWindowBase类中,在新创建一个View时,首先如果传入的URLArgs中名字不是空并且名字的小写不等于”_blank”,那么就去搜索相应的View指针(通过自己的成员函数findChildView),如果没找到,则创建一个新的View,最后调用view->document()得到一个Part对象,为传入的Part指针的引用赋值。在你切换窗口的时候,先把信号(主要是前进、后退、停止、刷新之类)与当前View中的槽断开,然后和新的View中的槽相连。


Konq-Embed定义了一个BrowserInterface,它是从KParts::BrowserInterface中派生而来的,它由View创建,并提交给HTMLView(也就是KHTMLPart)管理。BrowserInterface有一个goHistory的槽,在KJSHistoryFunc::tryExecute()中,通过part得到它的BrowserExtension,然后再得到该扩展的BrowserInterface对象iface,调用iface-> callMethod(“goHistory(int)”-1)通知iface调用goHistory()函数,参数为-1(后退)。在BrowserInterface中保存有View的对象指针,然后goHistory()会调用View中的goHistory()方法。在KJSHistory::get()中,它通过iface->property(“historyLength”)得到历史记录的长度。


View中的goHistory()通过调用KHTMLPartBrowserExtension::resotreState()Stream流入一些数据,最后进入KHTMLPart::resotreState()

六、Konq-Emb中的Cache方案

通过前面的分析,我们可以知道授权信息和CookieCache都是由“运行”在主进程中的CacheServer进行的,独立的SlaveBase进程把Cache的请求发送给DCOPDispatcher,然后由DCOPDispatcher找寻相应的DCOPObject(这些CacheServer派生于DCOPObject)进行数据的处理(通过DCOPObject的虚函数process)。


KHTMLPageCache缓冲从KIO读来的缓冲区,KHTMLPageCache是存储在主进程中的。主要是由KHTMLPart对它进行调用,加入数据和取出数据,需要注意的是只有在数据读取完毕时,那里面的KHTMLPageCacheEntry才是有效的。加入数据和取出数据需要一个ID作为KHTMLPageCache中字典的索引值


Cache为那些显示Html页面的对象提供缓冲和装载。这些对象一般是样式单、脚本和图象。需要使用CachedObject的类需要从CachedObjectClient中进行派生,需要重载它的三个虚函数。如果一个客户希望从web中装载样式单、图象和脚本,那么它需要从CachedObjectClient中继承并重载三个方法中的任意一个。DocLoader处理为一个确定文档装入脚本、样式单和图象的事宜。它聚合在DocumentImpl类中,并由它进行创建。对于那些不在缓冲中的请求,将由Loader创建一个Job去读取。


Loader中有一个Request的队列,在Loader::load()中,新建一个Request,然后调用servePendingRequest()Pending是悬而未决的意思。这个函数从队头取出一个Request,然后调用KIO::get()创建一个Job,连接Job的信号到自己的槽上,然后调用Scheduler::schedulerJob(job)进行job的调度,把正在Loading的请求加入到m_requestLoading的队列中。


从这里我们可以看出,似乎PageImage等是分开存储的,如果Page中制定了一个ImageSrc,那么就从Cache中获取,如果没有命中进建立一个Job去读取。


SSLCacheKSSLCertificateCache类,在它里面维护一个认证的链表。它聚合在TCPSlaveBase中,运行在孙进程空间中。在SlaveBase析构时会撤销KSSLKSSLCache实体存在于外部磁盘文件中的,每加入一个认证就进行一次saveToDisk()的操作,因此,KSSLCache对所有的孙进程都是共享的。


七、Konq-Emb运行时模型

通过上面的整体分析,我们可以知道,Konq-Emb是一个多进程模型,有三层父子关系。UIKHTMLPart(包括RenderCachePageCacheDOMHTML等等)、KJSDCOPDispatcherSlaveInterfaceSlave)、DCOPObjectsCacheServer)、Launcher的主进程部分,它们运行在主进程空间。主进程建立了一个Launcher的子进程,它和Launcher在主进程中的部分有UDS通讯。Launcher可以创建子进程,相对主进程来说就是孙进程,这些进程是Slave进程,用来进行网络访问的,其中还有SSL认证的Cache也运行在Slave进程中。这些Slave进程与祖父进程中的DCOPDispatcher进行相互通讯,同Slave进行交互、向CacheServer提出加入Cache请求等等。

八、面向对象的JavaScript以及解释引擎

JavaScript是一种面向对象的脚本语言,它符合Java的语法,由浏览器解释执行。

JS中,Object是基本数据类型的一种,而且是最重要的数据类型之一.比较难理解的是JS中的基于原型的继承机制。

首先我们介绍一下JS中的构造函数,例如:
function Rectangle(w, h)
{
   this.width = w;
   this.height = h;
}
var rect1 = new Rectangle( 10, 20 );
var rect2 = new Rectangle( 40, 20 );

this
是隐式传入的一个数值.C++中的有一些小的区别。

JS1.1
中引入了原型的概念,每个Object都有一个原型对象,而且一个Object可以继承它的原型Object的所有属性。也就是说,原型Object的所有属性都将成为其构造Object的实例的属性。构造函数定义了Object的类,并且初始化了类中的状态变量的属性,而原型Object是和构造函数关联在一起的。每个构造函数都有一个prototype的属性,在使用构造函数创建时,该prototype就会隐式关联到所创建的对象上。

举个例子:
function circle(x, y, r)
{
   this.x = x;
   this.y = y;
   this.r = r;
}

circle.prototype.pi = 3.14159;
function circle_circumference() { return 2*this.pi*this.r; }
circle.prototype.circumference = circle_circumference;
circle.prototype.area = new Function( "return this.pi*this.r*this.r;" );
//
JSFunction也是一个,可以进行创建。

var c = new circle( 0.0, 1.0, 10 );
var a = c.area();
var p = c.circumference();

也就是说,多个类的实例将共享同一个原型对象,类的属性首先在类实例内查询,如果没有,向该类的原型查询,如果没有,向原型的原型查询,一直到最后.这样组成了一个Prototype的链表.继承是在查询对象的一个属性时自动发生的。

当设置一个的属性时,JS并不使用原型,它会在实例上新添加一个同名属性,来存储设置的数值。这个方法有点Copy-On-Write的味道。

这样,我们就基本清楚JS中类的继承关系网络了。

ECMAScript
定义的JS规范有内建的若干对象:GlobalObjectFunctionArrayStringBooleanNumberMathDataRegExpErrorEvalError等。

JS
在一个Browser中运行时,需要Browser这个宿主提供若干的宿主对象(Host Object),比如说document(是一个DOM对象)window(一般被设置为一个Global对象)等等。

JS
引擎就是编译执行JS代码的一个解释器,它实现了内建的Object类型,至于外部的Host Object需要你通过它们提供的API进行注册。DOM规范提供了和ECMA的绑定,我们在实现JS的时候,主要是实现这新绑定。KJSKDEJS引擎,是一个自包含的系统,可以独立编译,它采用的是面向Object的结构。SpiderMonkeyMozillaJS引擎,它也是一个自包含的系统,可以独立编译,采用C语言进行编写。SpiderMonkey的文档比较丰富,在IBM中国开发网站上有唐新华的文章,文中大部分是翻译的Mozilla网站上JS Embeded Guide,但最后提供了一个具体的实例,很有参考价值,而且对于理解JS引擎很有帮助.本文中的很多实例也是从Oreilly的《JavaScript权威指南》摘录的。

九、相关性调查

因为KHTMLPart是整个程序的中枢,与它的相关有很多,我们为这些相关性划分一下类别和等级:

  • 深度相关:大量使用KHTMLPart的成员函数或者引用它的成员变量,有继承关系或者组合关系的。包括KHTMLBrowserExtensionKHTMLHostBrowserExtensionKHTMLViewHTMLView

  • 一般相关:使用KHTMLPart的成员函数或者成员变量,而且使用集中,有一般聚合关系。包括Cache相关类、KHTMLFactoryRender相关类等、KIO中的相关类、PartEvent相关类、KJS相关类。

  • 浅相关:较少使用KHTMLPart的成员函数或者成员变量,调用单一,有一般关联。包括DOMHTML相关类


一般看来,深度相关的都是我们在移植过程中需要剪裁掉的;一般相关中的一些部分例如KHTMLFactory也是不需要的,一般相关中的KIO部分是需要我们重新建立模型的,KJS是我们移植的一个重点(或者是重建模型),需要分析好它们的交互的接口;浅相关的部分中则是我们移植的重点。


khtml目录内,dom目录实现了DOM规范的Level1Level2的部分。Dom的每个对象都保存有一个指向其实现类的一个指针。DOM Core的实现类基本在XML这个目录中,DOM Html部分的实现类在html这个目录中,CSS部分的实现类在css这个目录中,每个目录都有相关的Parser类和其他的一些相关辅助类。实现类都是从DOMShared这个类派生的,在Clone DOM对象时,不重新构建新的Impl类,而对指针赋值。DOMShared内部有一个引用计数,在计数为0是释放自己。


DOM目录内的基本类基本不与QT相关(处理引用QT的一些数据类型外),与KHTMLPart的相关性也不大,主要是什么KHTMLPart是这些类的友元类。XML目录中的Doc和分词类是以Q_OBJECT来声明的,具有SignalSlot成员。HTML中的实现类中的Doc类是以Q_OBJECT来声明的。


ECMA目录下定义了另一套对象关系,和DOM的基本对应。相信相应的控制接口定义在这里面。上层目录下的KJSJS的解释引擎。KJS目录下没有Q_OBJECT的声明类。


Render目录下的RenderWidget使用Q_OBJECT进行了声明,从RenderWidget派生的类都是Q_OBJECT声明的,它们都是从CacheObject类派生而来的。


MISC目录下收集了一些辅助类。

十、ViewML项目调研

下面是View的体系结构图:

虽然View是基于KDE1.x的,但是它的一些做法还是值得我们去借鉴的。


模拟QObject类实现SignalSlot,使用Std C++实现QT中的数据容器类,使用Fltk实现QT的兼容层提供给KHTMLWidget使用,使用LibWWW进行网络访问。

十一、Part移植要点

移植要考虑的问题:

  • 图形平台独立性,尽量抽象出标准接口,主要是事件处理和渲染部分。

  • 核心机制的掌握和功能模块提取,尽量提供组件模型

  • 尽量好的自适应性(不随着Konqueror的升级而大部分的工作都要重新开展)。

  • 采用glue layer的方法进行系统模拟


Part是和一大堆的Signal/SlotQEventQAction等这些QT相关的内容紧密关联的。对SignalSlot建议采用提取QT类库实现QObject的做法来进行,前面我们已经分析过了SignalSlot的机制问题,它是一个安全的Callback机制。对于QEvent需要实现QApplicationSendEvent的方法,需要一个事件循环处理的机制。QAction无非也是一些消息,它们都可以用MiniGUI中的消息处理机制来模拟实现。


Part周围还包括一堆的辅助类,像BrowserExtensionBrowserHostExtensionBrowserInterface等等。这些对单独的浏览器应用没有什么太大的作用,如果考虑将来的组件式结构的话,还应该对其进行抽象总结。

原创粉丝点击