Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1636742
  • 博文数量: 584
  • 博客积分: 13857
  • 博客等级: 上将
  • 技术积分: 11883
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-16 09:34

分类: LINUX

2010-11-11 11:35:04


 

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论坛()的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 文件的显示


一般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时间。但这项工作一定要踏识的完成,要多借鉴成熟的技术,这样在自己的开发工作中才可以 事半功倍,做到心中有数也就能够按部就班。

阅读(1155) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~