5.10 Netbit Browser的缺点分析及改进办法
基础的GUI设计上存在缺陷
Netbit Browser目前的页面输出实际上还采用了简单的画图机制,无法在主窗体内放置如按钮、编辑框、单选框等控件,也无法处理页面元素的消息响应,(Netbit Browser 0.0.1-013版已进行了改进),而使用GTK作为开发平台是完全可以实现这些要求的,GZILLA就是最好的实例,它通过对现有控件的组合,开发了自己的文档视图控件,实现了上述功能。但之所以目前没有采用先进的文档视控件,是因为要实现这样的有较强实用性的自画文档视图控件,是需要很大的工作量的,仅GZILLA为实现其核心的DW文档视图控件,就动用了超过7000行的代码,比Netbit Browser目前的总代码量还大。而Netscape由于考虑到支持多个GUI平台,还需要一个抽象的中间层文档视图控件,这个中间层也在万行以上。
改进措施
固然,能容纳百川,一触即发的文档视控件的开发是很繁重的工作,但原理却并不复杂。下面加以阐述。
大多数的GUI平台都提供了方便用户进行控件组合的机制,例如有的控件能包含其它的控件,通常称之为container(容器),以GTK为例说明其原理。
GTK控件是以流行的控件组件的观念来设计的。 不过, 依然是以C来写的。 比起用C++来说, 这可以大大改善可移植性及稳定性。 但同时, 这也意味著widget 作者需要小心许多实际操作上的问题。 所有同一类别的控件的一般声明 (例如所有的按钮控件)是放在 class structure。 只有一个这样的结构。 在这个结构中储存类别信号的声明。 要支撑这样的继承, 第一栏的资料结构必须是其父类别的资料结构。例如GtkButton的类别的声明看起来像这样:
struct _GtkButtonClass{ GtkContainerClass parent_class; void (* pressed) (GtkButton *button); void (* released) (GtkButton *button); void (* clicked) (GtkButton *button); void (* enter) (GtkButton *button); void (* leave) (GtkButton *button);};当一个按钮被看成是个container(容器)时(例如, 当它被缩放时), 其类别结构可被传到GtkContainerClass, 而其相关的栏位被用来处理信号。
具体而言,比如我们使用一个基础的layout控件来作为我们自画的文档视图控件的基础控件,layout = gtk_layout_new(NULL, NULL);
接下来我们就可以使用gtk_layout_put函数将其它的允许被包含的控件放进去,就是这么简单,那难度在哪呢?其实,对于网页显示而言,能放进去多少个按钮、编辑框、单选框并不是最主要的,这很容易实现,只要采用了类似layout这样的基础控件,我们原则上可以组合出来很多种效果。我们迫切关心的是那些需要用画图方法来实现的页面元素,如文字、图片、表格、直线是如何产生的。下面加以阐述。
实际上,无论在什么情况下,我们要作画都需要合适的画布,要在可以作画的控件上才可以施展拳脚,drawing_area正是这样的控件,如此,我们只要将画画在drawing_area上,然后再使用gtk_layout_put函数将drawing_area放置到layout控件上,不就万事大吉了?不错,但只对了一半,原来drawing_area本身并没有实现自我重画的机制,当最小化窗口或打开对话框时,原有的界面就被破坏,只有进行重画才能恢复原貌,重画又是怎样实现的呢?原来,我们在将drawing_area放置于layout之前,是做了手脚的,使用以下函数
gtk_object_set_data(GTK_OBJECT(drawing_area), "layout", partp);
来将画在drawing_area上的信息对应的数据封装给了drawing_area控件。同时使用函数
gtk_signal_connect(GTK_OBJECT(drawing_area), "expose_event", (GtkSignalFunc)render_line_event, NULL);
来将expose_event这个重画消息,捆绑给了drawing_area控件。render_line_event即是用于重画的函数,到了需要重画的时候,该函数取出封装给drawing_area的数据进行重画。
如此这般费神,终于可以让线条、文字、表格、图片得见天日,完美的展现在众人面前(当然,所有的画与重画的函数都要自己写好了才行)。不要高兴太早,超级链接都不可点击,GIF动画也不会动,界面还是死的,原来忘了画龙点睛,怎么办?加消息。
加消息的步骤通常如下,以文字的超级链接为例。
首先新建一个消息盒子,event_box = gtk_event_box_new();
如果着急的话先把盒子放到layout上,当然用gtk_layout_put函数。若是最后再放上去,效果是同样的。
将drawing_area装到盒子里。
gtk_container_add(GTK_CONTAINER(event_box),drawing_area);
下面指定消息:
if(partp->parent && partp->parent->type == LAYOUT_PART_LINK) {
gtk_signal_connect(GTK_OBJECT(event_box), "enter_notify_event",
(GtkSignalFunc)activate_text_link, drawing_area);
gtk_signal_connect(GTK_OBJECT(event_box), "leave_notify_event",
(GtkSignalFunc)activate_text_link, drawing_area);
gtk_signal_connect(GTK_OBJECT(event_box), "button_press_event",
(GtkSignalFunc)select_text_link, drawing_area);
gtk_signal_connect(GTK_OBJECT(event_box), "button_release_event",
(GtkSignalFunc)select_text_link, drawing_area);
gtk_widget_add_events(event_box,
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK);
gtk_widget_realize(event_box);
如此这般,超级链接终于可点了(同样,消息引发的函数都得老老实实写好了)。可以实现移到上面就自动变色,点击就跳转到其它网页。
别忘了,指定一个漂亮的小手,这样点超级链接的时候更有情调。
gdk_window_set_cursor(event_box->window, gdk_cursor_new(GDK_HAND2));
综上所述,如果每一个消息对应的函数都能按部就班的完成,超级自画文档视图控件的完成也就指日可待了。
若只支持简单的字、线、图,程序大致可以控制在千行以内,当然这不包括文字布局算法、图像解码算法的部份。除此之外,我们还要拟定比较完善的数据结构来存储我们画在drawing_area上的信息,通常这些信息是语法分析的结果,也可以是词法分析的结果,在自画过程中融入语法分析的过程。
5.11 Netbit Browser未来的展望
Netbit Browser作为一个毕业设计课题,还是接近圆满的。通过Netbit Browser的开发,对常见的浏览器开发技术有了深入了解和实践机会,但若作为实际的软件开发项目来讲,还差的很远,为了使其能继续得以生存,决定将其变为开放源码项目,遵循GPL协议开发,现已成为中国Linux论坛(http://www.linuxforum.net)的MyLinux计划的一个子项目。
下面主要讨论一下Netbit Browser在技术上亟待解决的问题。
(1)超级链接的支持,实际上在对技术的了解的基础上。如前面的详细分析可见,该部份的实现已没有障碍,应该尽快实现,根本上是个编程序的毅力问题。
(2)图片的显示, 图片显示因为有很多开放的开发包或源程序,只是显示的话难度不大,但要考虑到多个图片的同时传输、JPG的逐步显示、GIF的动态显示就愈显复杂,仍需要对图像算法、网络传输、线程、消息等技术多加研究。
(3)TABLE等复杂的元素的布局,这实际是一个众多浏览器共同的问题,目前除了IE以外,其它浏览器都还存在较大的问题,如果能实现类似Netfront的效果,就比较实用了。开放源码的ZEN浏览器的table布局算法也很值得借鉴。
市场展望:Netbit Browser使用标准C编程,可以移植到Windows平台。也可以利用相关技术实现有市场价值的应用,如“HTML智能分析”这个软件,就是利用了Netbit Browser的词法分析技术进行了有益的尝试。
第六章 其它浏览器分析
6.1 Dillo(Gzilla)浏览器分析
Gzilla的最新版本改名为Dillo,Netbit Browser的界面设计主要借鉴了Dillo,其模块划分也受了Dillo很大的影响,Dillo浏览器是学习GTK程序开发的优秀范例,下面就其主要的模块加以说明。
6.1.1主函数流程分析
主要是对main函数进行分析。
gtk_true();
gtk_init(&argc, &argv); /* gtk初始化的一般方式 */
a_Prefs_init(); /* 初始化 preference ,preference包括: http_proxy, no_proxy, home, link_color, bg_color, text_color, allow_white_bg, force_my_colors。 函数a_Prefs_init()调用Pref_load()分析文件dillorc,并初始化全局变量。*/
a_Dns_init(); /* 初始化DNS模块。 此模块具体工作过程请参阅下文*/
a_Url_init(); /* 该函数首先初始化全局变量http_proxy, 和 no_proxy; */
a_Mime_init(); /*从网络(a_Http_get)或本地(a_File_get)取得文件后,不同的文件需要不同的方法打开。该函数定义了如下几种打开文件的方法: */
a_Dicache_init(); /* Dicache用于图形文件的处理。*/
a_Interface_init(); /* 初始化几个有关界面的全局变量 */
a_Dw_image_init(); /* 图形信息的初始化 (没有分析)*/
bw = a_Interface_new_browser_window(); /* 生成一个browser window(bw),初始化用户图形界面。这些代码是基于gtk开发的。主要的回调函数都是在这里定义*/
a_Bookmarks_init(); /* 初始化书签功能。这是比较独立的一个模块 */
……………
gtk_main(); /* gtk消息循环 */
/* 以下为内存释放 */
a_Cache_freeall();
a_Dicache_freeall();
a_Http_freeall();
a_Dns_freeall();
a_Prefs_freeall();
6.1.2文件的取得
当用户发出一个Url 请求时,Dillo 首先要取得Url所指向的文件;然后根据文件类型,选择相应的解释器。一个Url所指向的文件可能在本地,也可能在网络上。这节将主要介绍Dillo如何从网上取得文件。
模块interface定义了Dillo的图形用户界面。当用户发出一个Url请求时(例如open 一个网页),相应的回调函数将调用函数a_Nav_push()。模块Nav中的函数主要是维护每一个bw中的Url 堆栈(bw 是Browser Window的简写,是浏览器窗口的数据结构)。函数a_Nav_push()的主要功能是将当前的请求记录到bw 中的expecting域:
bw->nav_expect.url = g_strdup(url);
bw->nav_expect.title = NULL;
bw->nav_expecting = TRUE;
随后,调用函数Nav_open_url() 。该函数首先判断请求是否就在当前页中(如anchor 就在当前页中),如果是,则直接跳到当前页中指定位置;如果否,调用函数a_Cache_open_url() 并修改用户界面(状态条和一些buttons)。
模块Cache 是Dillo中的关键模块。模块HTTP 和模块File负责取得文件,模块Cache负责将取得的各种文件送到不同的解释器;同时模块Cache负责管理缓冲区,缓冲区中存在的文件不需要再通过网络下载。函数a_Cache_open_url() 首先搜索缓冲区,判断请求的文件数据是否已经存在;如果存在,调用Cache_process_queue()处理Cache中的数据;否则,调用a_Url_open ()从网上取文件。
函数a_Url_open()的主要功能是调用合适的opener,该函数一般调用Url_open()。Url_open()将调用合适的method:a_File_get()或者a_Http_get()。这里只讨论a_Http_get()。
函数a_Http_get()主要是创建一个http连接,发出DNS请求。它首先根据全局变量HTTP_Proxy和 No_Proxy,修改用户Url;然后创建非阻塞socket;最后通过调用a_DNS_lookup()发出DNS请求。
Dillo中的DNS请求将由多个线程完成。域名解析完成后,回调函数Http_dns_callback()将被调用。它首先发送Http请求(send query);然后接收回答(receive answer)。发送、接收操作都是通过调用a_IO_submit()实现的。函数a_IO_submit()通过gtk的支持,实现了阻塞IO操作。当数据被接收到,回调函数a_Cache_callback()将被调用。
函数a_Cache_callback()主要功能是处理接收到的数据,判断文件是否下载完毕。接收到的数据将由函数Cache_process_queue()处理。
6.1.3 选择合适的解释器
函数Cache_process_queue()的主要功能是为下载的文件选择一个合适的解释器。
(1)该函数首先判断协议头(header)(根据Http协议,body前为header)是否下载完毕,如果没有下载完,直接返回。
(2)随后,调用函数a_Web_dispatch_by_type()选择解释器。
(3)调用此解释器。
(4)如果文件下载完毕,通知解释器,并处理相应的Cache队列。
函数a_Web_dispatch_by_type()首先调用a_Mime_set_viewer(),该函数根据文件类型选择相应的viewer(即:主函数中a_Mime_init()初始化的几种打开方式: a_Gif_image() a_Jpeg_image() 和a_Html_text())。对于Html文件,a_Html_text()将会被调用。a_Html_text()主要生成一个DilloHtml结构和一个DwPage结构。
DilloHtml中有几个比较重要的结构:
Dw 指向DwPage结构,DwPage是Dillo自定义的文档视图。
堆栈 维护一个堆栈,记录当前正在处理的tag,用于语法分析。
Bw 记录当前的窗口。
然后,函数a_Web_dispatch_by_type()调用a_Dw_gtk_scroller_set_dw(), 为DwPage(dw)设置边界,为相应的窗口(bw)设置滚动条,并将页面(dw )嵌入到窗口中(bw)。此函数里的操作是基于gtk的。
函数a_Html_text()将会指定Html文件的解释器为Html_Callback().
6.1.4 Html 文件的显示
widget
word
word
word
Line
Line
Line
Page
一般Html文件的显示需要经过词法分析、语法分析和布局(layout),显示等几步。在Html显示方面,Dillo处理的比较简单。它在语法分析的同时,进行布局。
这里有两个概念:word 和line。一个word相当于一个Html文件中的一个tag ,若干个word形成一个line,而若干line形成一个page。在语法分析的过程中,发现一个tag,则生成一个word( 可能还会生成一个widget,如img),同时进行布局。当一行布满时,一个新的line将会生成。这种处理方式简单,但是具有较大的局限性。Dillo 中不支持Table 标记就是这种局限性的一种体现。
Html_write( )
函数Html_callback()通过调用Html_write()实现Html文件的分析、布局。Html_write()是对已经下载完毕、还没有处理的一段数据的处理。
Html_write()的主要过程是
(1)调用a_Dw_page_update_begin()。
(2)处理缓冲区中的数据
当前字符是空格,而且不在Tag “ pre”中处理空格
当前字符是“<”,
如果是注释,则跳过注释
如果是一个Tag,则处理此Tag. (Html_process_tag())
如果是word 则处理此word (Html_process_word()).
(3)将当前字符的位置,记录到对应DilloHtml结构中Start_Ofs (相对于整个文件头,未处理数据的起始地址,下一次调用此函数时使用)。
(4)处理状态条,显示浏览器当前的工作状态。
(5)a_Dw_page_update_end( )。对DwPage结构(页结构)中,word、line、widget等的改变,都应该放在这两个函数:a_Dw_page_update_begin() 和a_Dw_page_update_end()之间。在第二个函数调用之后,画面将会更新。
Tag处理一例: Html_tag_open_a()
此函数用于处理Tag( anchor)的开始标签。
(1)将此tag压入对应DilloHtml中的堆栈,
(2)取得属性 ”href”,创建一个attr,调用函数a_Dw_page_add_attr(page, &attr),将其加入相应的page.
(3)取得属性”name”,调用函数a_Dw_page_add_anchor()。此函数的主要功能:
创建一个word,记录word的属性是anchor,
以name为索引将此anchor插入当前page的anchor Hash表。
设置它在页面中的位置。
结尾标签由函数Html_tag_close_default()处理。它的主要功能是将此标签从DilloHtml栈中弹出。
Html_process_word()
此函数处理网页中,所要显示的文本。此函数首先根据当前DilloHtml堆栈中的ParseMode对文本进行处理。ParseMode有如下几种:STASH、VERBATIM、 PRE。
处理完后,调用函数a_Dw_page_add_text(),将这些文本插入页中。
此函数的主要流程为:
(1)设置字体
(2)计算文本宽度
(3)调用Dw_page_new_word(),在此页中创建一个新的word,记录此word的属性为Text.
6.1.5 Dillo分析总结
Dillo是运行在Linux平台上基于gtk的图形网络浏览器。具有规模小的优点。但是Html 页面的显示质量较差,不支持JavaScript、Java。基于Dillo的嵌入式浏览器改造需要考虑如下几个方面:
(1)从整体结构来看,从取得文件到文件处理,大量使用回调函数,代码的可读性差,结构不清晰。建议改成消息机制,这样既增强了各模块的独立性,同时提高了并发性。
(2)Html文件的显示模块需要改写。Dillo不能支持比较常用的标签Table,而这种缺陷又是因为设计结构本身的缺陷,所以该模块需要做较大的改写。
(3)Dillo对于gtk的依赖较强,由于gtk不适合于嵌入式系统,需要改成基于fltk或embedded QT等嵌入式ToolKits,因此改造的工作量较大。
6.2 Thunder浏览器分析
Thunder浏览器是一个XML浏览器,用于制作多媒体演示软件,支持多媒体格式包括文字、图像、音频、视频。采用标准C编程,在WINDOWS下使用VC编译,软件规模较大,源程序达到2.5M。
主要模块功能介绍:
JavaScript: JavaScript API函数库
JsEngine: 浏览器JavaScript支持引擎
TStream:流式文件处理
TXml:XML的词法语法分析与布局
Widget:widget控件库
TDevice:图形、声音等的驱动函数库
TKernel:核心系统函数,包括内存分配、消息解析宏以及接口类型定义等
Thunder浏览器值得称道的是对JavaScript支持的效果良好,通过JavaScript来达到对图片的操作,实现了较好的动态效果。是学习JavaScript支持的良好范例。
另外,Thunder浏览器的词法分析也比较完善,Neibit Browser的设计也参考了Thunder的设计,并借鉴了部份字符串处理函数。
Thunder浏览器的布局算法十分简单,因为其支持的XML是自己定义的,在图片等元素的属性里不仅定义了其应该出现的绝对位置,还定义了图片的长度,宽度,使得布局工作易如反掌,没有算法可借鉴。
6.3 Mozilla浏览器分析
6.3.1 COM
COM是由Microsoft提出的组件标准,它不仅定义了程序之间进行交互的标准,并且也提供了组件程序运行所需的环境。COM所定义的模块之间的接口标准是二进制可执行代码级标准,因此模块之间独立性更强,具有语言无关性。 在Mozilla中,COM机制利用C++语言实现,几乎所有的对象都是COM对象,若干COM对象又组合成COM模块。COM对象在初始化时,都要注册到COM运行环境(COM库)中;当其它模块需要调用该对象的方法时,先要通过COM运行环境,以该COM对象的全球唯一ID为参数,创建该COM对象,取得对象接口指针;最后通过该对象接口指针调用方法。
COM机制使各个模块之间独立性非常高,为裁减提供了便利;但是,为了实现这套机制,Mozilla(Linux环境下)必须要实现相应的运行环境(COM库),所有的COM对象都要继承实现一些COM标准接口,这些都增加了代码量,使得单一模块(对象)代码规模增大。
6.3.2 XML
Mozilla对于XML提供了强大的支持,甚至可以说整个软件包是对XML理论的一种实现。Mozilla 的用户界面主要是由XML文本描述(在Mozilla中称为XUL: XML-base User Interface Language);所描述界面中的控件,被解释转换成DOM(Document Object Model)对象;与控件相关的操作大部分由JavaScript实现。DOM 和RDF是Mozilla软件包中比较大的核心模块,同时两者也是XML的一部分。DOM 是程序访问和维护HTML 和XML文档的API;RDF ( Resource Description Framework) 提供了对整个资源(包括本地资源)的描述体系;在Mozilla中,JavaScript与C++代码的互操作,也需要这两个模块的支持。可以说,XML在整个软件包中起到了纲举目张的作用。
对XML的支持增强了系统的功能,但是客观上也增加了系统的复杂性,增大了系统规模,为裁减造成了困难(最早曾尝试裁减Mozilla)。
6.3.3 Layout
在对HTML脚本语法分析结束后,要对页面进行布局。在整个浏览器中,Layout属于关键技术之一。Mozilla提供了强大的布局功能,在系统自带的测试用例中,有非常复杂的页面(如:CSS styles, Deeply Nested Tables, Frames, DHTML等),MozillaV14可以对这些页面进行出色的布局;而RedHat Linux6.2自带的Netscape4.7x在显示这些页面时,错误比较多,甚至不能显示。
强大的功能是以规模巨大的代码量为代价的,MozillaV14中layout模块达到了3.8M,这是整个裁减过程中最难处理的模块之一。
6.4 ZEN浏览器分析
正如第一章简介中指出的,ZEN是一个不知名的优秀开放源码浏览器,拥有可换界面的优异特性,用户可改换GUI平台,设定自己的界面风格,极符合嵌入式的要求。
主要模块说明:
parser:词法语法分析。
layouter:布局。
ui:不同的GUI界面和控件库,包括GTK、SVGA、字符模式,也可自定义。
image:图像处理。
protocol:协议,包括file和http。
下面给出其语法分析的数据结构示意:
page
|
X- text <-> link <-> text <-> table -X
| |
text -X table_row -X
|
table_cell <-> table_cell -X
| |
text -X image –X
可见,节点之间不仅有先后次序,还有父子关系,正确的语法分析和良好的布局算法,使得ZEN可以支持表格等复杂元素,格式基本正确。
关于使用GTK来支持超级链接的消息响应,第五章已有详细阐述,其参考对象就是ZEN浏览器。
让我们记住作者Konfucius的座右铭吧:
"The reward of studying, lies in the studies themselves."
6.5 浏览器分析工作的总结
浏览器分析工作是一项十分耗费精力的工作,由于浏览器的源代码大多比较庞大,模块的独立性也较差,往往环环相扣,上下相关,涉及的技术也十分繁杂,分析起来十分费力,进展也比较缓慢,大约占了整个开发工作的1/3时间。但这项工作一定要踏识的完成,要多借鉴成熟的技术,这样在自己的开发工作中才可以事半功倍,做到心中有数也就能够按部就班。
致 谢
在本课题的研究设计的过程中,得到了许多老师和同学的帮助,借此机会向他们表示诚挚的谢意。
首先要感谢我的导师张丽芬老师。在整个课题过程中,张老师对我热心指导、严格要求,在选题、系统总体设计与技术方案上,给予我宝贵的建议,帮助我建立了正确的设计思想,保证了课题的研究和开发工作的顺利完成。我从她那里学到的不仅是学术方面的知识,还有优良品格和严谨的治学态度。
感谢.....等同学在前期的分析和准备中所做的大量工作,为课题的开发建立了良好的基础。
感谢李洋同学送来了宝贵的GDK、GTK的详细资料。
最后感谢..同学的密切配合,和给予我的热情帮助和鼓励。
本课题所涉及的相关技术包含了一个广泛的技术集合,半年多的研究与设计工作只是管中窥豹。未来,随着技术的不断发展,相信后人会有更出色的表现。也希望我的工作能给国内的浏览器开发工作者带来一定的参考价值。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/wushuan10141/archive/2008/07/31/2747790.aspx
作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:https://jackxiang.com/post/1919/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!