chromium源代码结构

本篇文章翻译自chromium官方的源代码结果说明

http://www.chromium.org/developers/how-tos/getting-around-the-chrome-source-code

由于目前我对chromium的代码本身还没有特别深入的了解,只希望能尽可能还原原文的意思。

目录
1 总览
2 关于解决方案文件的简要说明
3 顶级工程目录
4 "content/"下目录树的简要说明
5 "chrome/"下目录树的简要说明
6 个人的学习计划
7 常规操作的代码路径
  7.1 程序启动
  7.2 打开标签页和初始化导航
  7.3 从地址栏中导航
  7.4 导航页和会话历史

总览

chromium分为三个主要的部分(不包括第三方库):浏览器browser, 渲染器renderer和Webkit。browser是主进程,它涉及所有的UI和IO。renderer是由browser驱动的子进程,通常一个标签页对应一个子进程。renderer中嵌入了Webkit用于网页的布局和渲染。

关于解决方案文件的简要说明

解决方案文件位于chrome/chrome.sln

  • App/chrome_dll和App/chrome:代码的入口在这两个目录下。
  • Libraries/base:共享库的代码,由于这部分代码所有工程中都会使用,所以会尽量保证它是轻量级的。
  • Browser/common:浏览器相关的代码库,这部分代码是浏览器和渲染器共用的。
  • Webkit:工程目录下是Webkit的代码,WebKit/port是调用Windows的接口(port集成了一些平台相关的系统调用,如资源加载或者绘制相关的东西),如WebKit/glue是嵌入层(chromium并不是直接调用Webkit的,而是在 Webkit上边再封装了一层,可以是为了考虑将来有更高效的渲染引擎能替换Webkit)
  • glue由Browser/renderer工程来调用,renderer是为每一个标签页运行的子进程。
  • Browser/browser工程定义了用户接口、存储和网络请求相关的东西。

关于webkit更加详细的说明

WebKit
我们使用 WebKit这个开源项目来展示网页。此代码主要是由Apple编写的并存放在/third_party/WebKit目录中。WebKit主要包括两部分:“WebCore”负责核心布局功能,“JavaScriptCore”用来执行JavaScript。我们只将JavaScriptCore用于测试目的,通常我们使用高性能的V8 JavaScript引擎取代它。我们实际不使用苹果称之为“WebKit”的软件层(译注:就是WebKit/Source/WebKit目录下的内容,Webkit/Source目录下同样有WebCore和JavaScriptCore目录),这个软件层用在如Safari这样的应用程序中,用来衔接WebCore和OS X。为了方便,我们通常将从Apple获取的代码称作“WebKit”。(其实只使用了WebCore)


The WebKit Port
在最底层,我们有我们的WebKit“Port”。这是我们实现的平台相关的代码,它用来衔接平台和WebCore。这些文件位于WebKit目录中,通常在Chromium目录中或者以Chromium为后缀名。实际上Port的大部分代码不是和操作系统相关的:你可以把它看成是WebCore的Chromium Port(用来衔接WebKit和Chromium的)。有些部分,如字体渲染,必须针对每个操作系统平台分别处理。

  • 网络流量是由我们的多进程资源加载系统处理的,而不是由渲染进程直接调用操作系统完成。
  • 图形使用为Android开发的Skia图形库。这是一个跨平台的图形库,原生的处理除了文字以外的所有图形、图像。Skia位于/third_party/skia。图形操作的主要入口点是 / WebKit/port/platform/graphics/GraphicsContextSkia.cpp。它使用了同目录及/base/gfx目录中的许多其他文件 。
WebKit glue
Chromium使用了和WebKit代码不同的类型,编码方式和代码结构。WebKit“glue”提供一个更方便的嵌入WebKit的API,以便使用谷歌编码约定和数据类型(例如我们使用std::string,而不是WebCore::String,使用GURL,而不是KURL)。glue代码位于 /WebKit/glue 。glue对象命名通常和WebKit对象类似,但以“Web”开头。例如, WebCore::Frame 成为WebFrame。
WebKit的“glue”将WebCore的数据类型和Chromium其余代码隔离,以帮助减少WebCore对Chromium代码库编程风格的影响。因此,Chromium从来不会直接使用WebCore的数据类型。glue也增加了一些API,用于Chromium直接访问WebCore对象。
“test shell”是一个用于测试WebKit Port和Glue代码的简单浏览器。它和Chromim一样使用Glue与WebKit通信。它为开发人员提供了一个测试新代码的简单方法,而不必使用许多复杂的浏览器特性,多线程以及多进程。此应用程序也被用来运行WebKit的自动测试。

顶级工程目录

在你下载chromium的源码后,你看到一系列的顶级工程目录,这些工程如下:

  • android_webview:为了能更好的集成到android平台上对src/content进行的一层facade封装,并不是为了在单独的android应用中使用。
  • apps:chromium中用来支持网页应用chrome package apps的代码。
  • base:所有子工程使用的通用代码,包含大量的框架代码的实现,比如进程、线程以及消息循环的封装,对字符串的处理等。只有代码需要在顶级工程目录中共享时,才往里边添加代码。
  • breakpad:Google的一个开源项目,用于进行程序崩溃处理,捕获异常,处理崩溃现场数据。它是直接从GoogleCode的svn中拉过来的。
  • build:所有工程共用的编译配置。
  • cc:Chromium的合成器,用在网页的合成和chromium主界面的合成。
  • chrome:Chromium浏览器
  • chrome/test/data:运行测试使用的data文件
  • components:组件目录,一些模块化的功能在这个目录中,这些模块可以被"content"层所使用
  • content:content模块的实现与接口,它是与浏览器页面处理相关的部分,
  • 多进程沙盒浏览器使用到的核心代码(很牛逼有木有。。。)。
  • net:为chromium开发的网络库,在运行webkit目录下的样例test_shell时可以单独使用,参考chrome/common/net。这部分实现了一些网络栈和网络协议,如SPDY,QUIC
  • sandbox:沙箱(sandbox)能阻止恶意网页渲染过程中篡改系统。
  • skia:google为android系统开发的图型库,它是直接从android的代码树中直接复制过来的,在ui/grx中增加的一些类对Skia进行了封装。
  • sql:该目录是包含Chrome数据库方面的模块。在该模块中包含了对SQLite的封装以及对SQL语句的封装和处理。
  • testing:包含Google的开源测试工具GTest,用来进行单元测试。
  • third_party:为第三方库建的一个工程,包含图片解码、压缩算法库等一些第三方库。也有一些Chrome专用的第三方库放在chrome/third_party里。该目录包含Chrome项目所使用的工具模块,比如堆栈调用、内存监测钩子等等。
  • tools:该目录包含Chrome项目所使用的工具模块,比如堆栈调用、内存监测钩子等等。(好吧,就是tools,不用我说你也懂的)
  • ui/gfx:共享图形库,这是渲染chromium ui图形的基础。
  • ui/views:进行UI开发的一个简单框架,提供渲染、布局和事件处理的功能。
  • 浏览器大部份的UI都在这个框架中实现,该目录包含基础对象,一些流览器特定的对象在chrome/browser/ui/views目录下。
  • url:google开源的url解析和规范化库。
  • V8:解析javascript的v8库,这是从google code的svn中直接拉取的。
  • Webkit:chromium webkit相关的东西都在这里。
    • appcache:
    • base:
    • blob:
    • build:其它工程的工程文件和配置
    • Data:大多数目录是Port层使用的单元测试数据。Layout_tests是从苹果上直接拉取的webkit布局测试包。
    • glue:glue层是个嵌入层。它负责Webcore类型和Chromium类型(大多数是stl类型)之间的转换,同时还提供了很多函数,可以很方便地访问需要使用的Webcore的对象。
    • tools:
      • Layout_tests: 运行WebCore布局测试的脚本
      • Merge: 把对象合并到WebKit树的脚本
      • npapi_layout_test_plugin:在一些测试中用来启动插件层的特定插件。
      • test_shell: 一个非常简易的浏览器,它可以独立运行用于测试glue和port代码,而不用编译运行重量级的Chromium程序

下边是一个依赖关系图,下游的模块不能直接包含上游的模块代码(例数,content不能包含chrome头文件),但可以使用嵌入的api进行调用。




"content/"下目录树的简要说明
  • Browser:Browser进程的代码,它是浏览器的后端,处理与子进程相关的I/O与通信。它同时与render进行通信,管理网页的绘制。
  • Common:被进程共享的代码,这部分代码在多个进程的间都被用到(如browser进行和renderer进程,browser进程和plugin进程等)这些代码是Chromium专用的(不合适放到base下边)。
  • Gpu:GPU进程的代码,用来进行3D合成,还包括一些3D相关的API。
  • Plugin:在其他进程中运行浏览器插件的代码。
  • ppapi_plugin:Pepper插件进程的代码
  • Renderer:标签页渲染子进程的代码,它嵌入了WebKit并从浏览器进程获取I/O
  • utility:在沙箱进程中运行任意操作的代码,浏览器进程用它对不可信数据进行操作。
  • Worker:运行HTML5 Web Workers的代码

“chrome/”下目录树的简要说明

  • app:app是代码中最基础的部分,它在启动时的执行,并根据当前进程是browser还是renderer去调度到相应的代码。它包含生成chrome.exe和chrome.dll的工程,除非是需要更新图片和字符串等资源,不然你并不需要改变该目录。
    • Locales:此工程用来生成本地化dll。
    • Resources:图标和光标
    • theme:窗口的主题图片
  • Browser:在前端负责主窗口和UI,在后端负责I/O和存储的处理,它同时与renderer进程通讯管理页面的渲染。
    • Ui:定义UI特性和功能相关的模型、视图和控制器
  • common:在browser进程和renderer进程间共用于代码文件,它是chrome模块特有的。
    • Net:chromium对网络顶层模块进行的特有的封装,它将来会和browser/net分支合并。
  • Installer:制作安装包(MSI包)的源文件和工程文件。
  • renderer:chrome运行render进程的代码,它为content模块增加了chrome的特性,如autofill、translate。
  • test:
    • automation:在测试时用来驱动浏览器UI,用在test/ui,test/startup等测等。它会在browser进程中与browser/automation交互。
    • page_cycler: 执行页面循环测试的代码(为了测试性能)
    • reliability:页面加载的分布式测试,这是为了测试可靠性和进行问题跟踪。
    • selenium:运行selenium测试的代码,这是一个第三方测试包,用于测试Ajaxy和JavaScript。
    • Startup:测试启动性能的代码。
    • Ui: UI测试,比如点击浏览器UI、打开标签页等。它使用test/automation来完成大部分操作。
    • Unit:单元测试的基础代码。测试代码一般和被测试代码放在一起,并以*_unittest.cc的格式命名。
  • third_party:Chromium专用的第三方库。还有一些第三方库放在顶层的third_party里。
  • tools
    • Build:编译相关的工具和一些杂七杂八的东西
      • buildbot:Buildbot配置。Buildbot管理自动编译系统
      • Win:在Windows下编译的东西,包括定义工程属性的.vsprops文件和一些脚本。
    • Memory:内存工具。目前包括用于设置页面堆配置的gflags。
    • perf/dashboard:将性能日志(比如test/startup_test)转换为数据和图形的代码。
    • Profiles:历史数据生成器。用来生成测试概况时用到。


个人学习计划

当你最终完成编译环境,并准备开始工作,理想的情况是在开始写你自己的代码前,你有足够的时间阅读并理解每一行代码。但是实际上,你会发现就算你一天内什么事情都不干,光把每天的更新的代码看完也是一件很困难的事情,所以你不想能通读所有的代码。那么应该怎样做呢?我们建议你针对你要学习的东西建立你自己的学习计划,你可以从以下这些地方入手:

幸运的是,chromium有一些一流的设计文档,虽然有一些旧(比如,当往下看时会发现一些文档的链接已经被迁移、重命名或者由于重构已经消失),但是与代码结合起来看会更容易理解代码。

阅读重要的开发文档

multi-process-architecture

displaying-a-web-page-in-chrome

inter-process-communication

threading

阅读你们团队拥有的一些入门文档。可能有一些文档只有在你需要在某个代码上进行开发才需要了解它们,否则你并不用太在意它们。

学习一些代码风格:

important-abstractions-and-data-structures

smart-pointer-guidelines

chromium-string-usage

然后,如果时间充许,简单过一遍所有的设计文档,阅读跟你相关的部分。
熟练代码查找(或者是你选择的代码阅读工具)。
要想知道有问题问谁或者代码是怎么工具的,点击这里。
如果可能的话,使用调试工具调试你需要学习的代码,否则通过打日记并在日记中grep来跟踪代码的运行。
分析代码中你正在了解的部分和你自己掌握知识之间有什么不同。比如,你的团队做了很多GUI编程方面的工作,那么你可以投入一些时间放在学习GTK+、Win32或者Cocoa编程上边。
常规操作的代码路径

启动程序

  1. WinMain函数在代码chrome/app/main.cc中,它已经被chrome工程链接进来。
  2. WinMain启动Google更新客户端,它的代码在installer/autoupdater,它会找到当前版本的子目录,并导入那里的chrome.dll文件。
  3. WinMain调用chrome.dll中的ChromeMain函数,它的代码在chrome_dll项目的chrome_main.cc。
  4. ChromeMain初始化常规组件,如果在启动的命令行中标识这是一个子进程,那么就调用chrome/renderer/renderer_main.cc中的RendererMain,否则会调用chrome/browser/browser_main.cc中的BrowserMain启动一个新的程序。从这之后,就开始正式启动browser进程。
  5. BrowserMain中会做一些浏览器常规的初始化操作,它有许多种模式,主要是为了运行安装的webapps,或者在测试browser进程时连接到自动化系统。
  6. BrowserMain调用browser_init.cc里的LaunchWithProfile函数,它会建立一个定义在chrome/browser/ui/browser.cc中的Browser对象,该对象里边封装了程序中最顶层的窗口,同时会生成第一个标签页。


标签页的启动以及导航的初始化
  1. 代码chrome/browser/ui/browser.cc中的Browser::AddTab函数用来启动一个tab页。
  2. 它会new一个browser/tab_contents/tab_contents.cc中定义的TabContents对象。
  3. TabContent通过RenderViewHostManager的Init函数(位于chrome/browser/tab_contents/render_view_host_manager.cc)新建一个RenderViewHost(位于chrome/browser/renderer_host/render_view_host.cc)对象。根据SiteInstance,RenderViewHost会启动一个新的渲染进程或者复用已经创建的RenderViewHost。RenderViewHost是browser进程中负责一个单独的渲染子进程的对象。
  4. TabContent里边包含有NavigationController(chrome/browser/tab_contents/navigation_controller.cc)对象,它通过NavigationController::LoadURL函数为标签页导航到一个url。接下来的步骤3“地址栏导航”描述这个过程发生了什么。
地址栏导航
  1. 当用户在地址栏中输入或者选择地址栏中提示的url时,编辑框会完成自动补全功能得到目标URL,并把它传给AutocompleteEdit::OpenURL函数(不保证就是用户输入的url,例如会为用户的搜索请求生成一个转到google的url)
  2. 导航控制器通过NavigationController::LoadURL函数导航该url
  3. NavigationController调用TabContents::Navigate函数,它的参数里会有一个它生成的NavigationEntry对象,表示需要加载的页面。它会根据需要实例化一个RenderViewHost对象,进而在渲染进程中生成一个RenderView。如果这是启动后的第一次导航或者是renderer进程崩溃时,都不会有RenderView对象,因此这个操作也能使RenderView从崩溃中恢复过来。
  4. Navigate函数进而调用RenderViewHost::NavigateToEntry。NavigationController会保存该函数生成的导航项,但是这个导航项NavigationEntry会被打上一个"pending"的标记,因为它不确定到底会不会加载(可能主机无法解析)。
  5. RenderViewHost::NavigateToEntry发送一个消息ViewMsg_Navigate到render进程来生成一个新的RenderView。
  6. 该消息告诉RenderView进行导航时,RenderView可能导航,也可能导航失败或者导航到其它地方(比如,用户点击了一个链接),RenderViewHost会等待从RenderView发来的ViewHostMsg_FrameNavigate消息。
  7. 这个消息是当WebKit确认加载完成,NavigationEntry打上committed标记(网页服务器响应并给我们发送数据),RenderView才会发送,这个消息将会在RenderViewHost::OnMsgNavigate函数中进行处理。
  8. 导航项NavigationEntry会根据加载的信息进行更新。需要更新的两种情况,点击了一个浏览器从来没有处理过的URL。如果导航是浏览器启动时默认的,可能已经比较旧,会被重定向到新的url上了。
  9. NavigationController根据新的消息更新导航列表。

导航和会话历史


每一个导航项NavigationEntry都保存有一个page iD和历史状态数据,网页ID用来唯一标识一个加载的网页,因此我们知道它对应哪个导航项。
它会在Webkit打上committed标记时附值,所以标记为"pending"的导航项的page id为-1,历史状态数据就是WebCore::HistoryItem经过序列化得到的字符串,
它包含网页url,子窗体的url和表单数据。

  1. 当浏览器初始化一个请求时(在地址栏中输入或者点击前进、倒退、重新加载等按钮)
    1. 首先生成一个WebRequest表示这次导航,并附带一些额外信息如page id用来做标记。新导航的page id值为-1,而访问过的页面的id值是初次访问该页面时导航项NavigationEntry的page id。这些额外信息会在加载页面时用到。
    2. WebFrame被告知加载新的请求
  2. 当renderer进程初始化该请求(用户点击一个链接,javascript改变窗口对象打开的网页地址)
    1. WebCore::FrameLoader会通过它的某一个加载函数加载该请求。
  3. 无论哪种情况,当服务器返回的第一个包被接收,加载会被“committed”(而不是处于pending或者provisional状态)
  4. 如果这是一次新的导航,WebCore会新建一个新的历史信息HistoryItem并添加到BackForwardList(WebCore的一个类型)。这样我们可以区别哪个导航是新的,哪个是会话历史导航
  5. RenderView::DidCommitLoadForFrame用于确认加载,上一个页面的加载状态通过ViewHostMsg_UpdateState保存在会话历史中,这会通知浏览器更新对应的导航项(通过RenderView当前页面的page id标识)
  6. RenderView当前页面的page id会更新为确认加载完的的页面,对于一个新的导航,会生成一个新的page id。对于会话历史中的导航,这个page id为初次访问该页面时赋给的page id,page id已经在初始化该导航时被保存在WebRequest中。
  7. ViewHostMsg_FrameNavigate消息会被发送到browser进程,并更新相应的导航项NavigationEntry(通过RenderView新更新的page id标识)中的url和其它信息。


  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值