总体概览
Chromium分为3个大模块(包括第三方库):浏览器,渲染器,WebKit。浏览器负责主进程,UI展现和I/O。渲染器一般是由浏览器调用的标签子进程。它嵌入WebKit中做布局和渲染。
你应该熟读多进程架构和Chromium如何显示web页。
快速介绍sln文件
有2个sln文件,chrome.sln是使用V8引擎的普通版本,chrome_kjs.sln是使用Webkit的JavaScriptCore(JSC)引擎的版本。一般都是用V8,保留JSC就是为了看有bug时,这bug是不是跟V8有关。
-App/chrome_dll和App/chrome工程是启动代码。
-Libraries/base工程是通用库代码。这个代码是所有工程共用的,我们会尽量让它保持短小精悍。
-Browser/common中是浏览器相关的通用库代码。它是浏览器和渲染器共用的。
-Webkit(只读)目录下的工程是Webkit的代码。它的上层是Google的支持Windows接口的WebKit(自己的)/port,WebKit(自己的)/glue是嵌入层。
-glue与Browser/renderer工程相关,它用来展示每个子标签页里跑的子进程。
-Browser/browser工程提供了UI、存储、网络等功能。
顶层工程
拿到Chromium代码后,你会发现有很多顶级目录。有这些:
-base:所有子工程共用的通用代码。它包括字符串操作,通用库等。只有在所有顶层工程间共用时,才往这里加东西。
-gfx:共用图形类。它是Chromium的GUI的基础。chrome/common/gfx下还有一些图形类,它是Chromium应用程序专用的。
-breakpad:Google的开源crash报告工程。它是直接从Google Code的svn中拉过来的。
-build:所有工程共用的编译配置。
-chrome:Chromium浏览器(参考下面)。
-data:运行证书测试的data文件。
-gears:离线浏览。
-googleurl:Google的开源URL解析判别库。它是直接从Google Code的svn中拉过来的。
-net:Chromium中的网络库。在运行webkit里的简单test_shell时,它可以从Chromium中分离出来单独用。可以参考下chrome/common/net。
-sandbox:沙箱。它能防止恶意页面修改你的系统。
-skia:Google为Android开发的Skia图形库。它是从Android代码里复制过来的。base/gfx中的附加类封装了它。
-testing:包含Google的开源单元测试工具GTest。
-third_party:集中放第三方库,比如图片解码,压缩库之类的。也有些Chrome专用的第三方库放在chrome/third_party里。添加新包。
-tools:不解释,你知道的。
-V8:V8 js库。它是直接从Google Code的svn中拉过来的。
-webkit:所有跟Chromium的Webkit相关的东西:
-build:工程文件和其他工程相关的配置。
-data:大多数目录下的data都是给转换层的单元测试用的。layout_tests目录是直接从Apple拉过来的WebKit的布局测试套件。
-glue:glue层是个嵌入层。它负责Webcore类型和Chrome程序类型(大多STL)之间的转换,同时还提供了很多好用的函数来访问我们需要用到的Webcore的对象。
-tools
-layout_tests:运行WebCore布局测试的脚本。
-merge:帮助合并WebKit树的脚本。
-npapi_layout_test_plugin:专门用来测试插件层的插件。
-test_shell:一个非常简单的独立的浏览器。不用运行巨大的Chromium程序,就可以测试glue和转换代码。
"chrome/"下目录树的简要说明
-app:"app"是最基础的程序。它一开始就运行,根据当前进程的能力来分别分派到浏览器或渲染器代码中。它包括chrome.exe和chrome.dll 2个工程。一般你不需要修改它,顶多就是改改图片和字符串这些资源。
-locales:此工程用来生成本地化dll。
-resources:图标和光标。
-theme:窗体主题的图片。
-browser:前端包括主窗体和UI,后端包括I/O和存储。它与渲染器管理页面相关。
-common:这下面的文件在浏览器和渲染器之间共用,同时也给一些其他的工程用。这些代码是专门给Chromium(而不是基础程序)用的,但在chrome/浏览器和chrome/渲染器目录之间可以共用。
-gfx:Chromium专用的图形和渲染相关的助手代码。有些图形上的东西必须也提供给外面顶层的base/gfx使用。
-net:net上层的顶级模块上一些Chromium专用的东西。将会与browser/net合并。
-installer:制作安装包(MSI包)的源文件和工程文件。
-plugin:在其他进程中运行浏览器插件的代码。
test:
-automation:测试用来驱动浏览器UI的,比如test/ui,test/startup等。它会在浏览器中与browser/automation交互。
-page_cycler:执行页面循环测试的代码(比如性能测试)。参考tools/perf/dashboard。
-reliability:页面载入的分布式可靠性测试和crash定位。
-selenium:ajax和js等的第三方测试套件的选择性测试的代码。参考test/third_party/selenium_core。
-startup:测试启动性能。参考tools/perf/dashboard和tools/test/reference_build。
-ui:浏览器UI测试,比如打开标签页等。它使用test/automation来完成大部分操作。
-unit:单元测试的基础代码。测试代码一般和被测试文件放在一起,命名为*_unittest.cc。
-third_party:Chromium专用的第三方库。还有一些第三方库放在顶层的third_party里。
-tools
-build:编译相关的工具等。
-buildbot:编译机器人配置。编译机器人管理自动编译系统。参考third_pary/buildbot。
-win:在Windows下编译的东西,包括工程属性用的.vsprops文件和脚本。
-memory:内存工具。现在包括设置页面堆配置的gflags。
-perf/dashboard:将性能日志(比如test/startup_test)转换为数据和图形。
-profiles:历史数据随机生成器。用来生成测试概况(profile)。
-views:用来开发UI的简单框架,提供渲染、布局和事件处理。大多数浏览器UI都是用这个系统实现的。这个目录包括基础对象。另外还有些浏览器专用的对象在browser/views里。
通用操作的代码路径
附加信息和更多的例子在Chromium如何显示web页面里。
程序启动
1.WinMain函数在chrome/app/main.cc中,它在chrome工程中编译。
2.WinMain执行Google自动更新客户端。它会找到当前版本的子目录,然后载入chrome.dll。
3.调用刚载入的库里的ChromeMain,代码在chrome_dll工程里的chrome_main.cc。
4.ChromeMain初始化通用组件,然后如果命令行指定这是个子进程,就调用chrome/renderer/renderer_main.cc里的RendererMain,如果不是程序的新拷贝(也就是说程序第一次启动),就调用chrome/browser/browser_main.cc里的BBrowserMain。现在已经启动了,是时候启动浏览器了。
5.BrowserMain会初始化浏览器。不同的情况它会以不同的方式运行,比如如果是安装web应用程序就安装它,如果是在测试就连接到自动系统,等等。
6.chrome/browser/browser.cc里会创建一个新的浏览器对象,它会调用browser_init.cc里的LaunchWithProfile。这个对象会生成一个顶级窗口。这时会生成第一个标签页。
启动标签页并初始化导航
1.chrome/browser/browser.cc里的Browser::AppendTab会被调用,并创建一个新标签页。
2.创建一个browser/tab_contents/tab_contents.cc里的TabContents对象。
3.TabContents会通过chrome/browser/tab_contents/render_view_host_manager.cc里的RenderViewMostManager的初始化函数创建一个chrome/browser/renderer_host/render_view_host.cc里的RenderViewHost。通过SiteInstance,RenderViewHost会生成一个新的渲染进程(RenderProcess),或者重用一个已有的。RenderProcess是浏览器里的一个对象,负责一个单独的渲染子进程。
4.chrome/browser/tab_contents/navigation_controller.cc里的NavigationController会被标签页容器持有,并调用NavigationController::LoadURL来在新标签页打开一个URL地址。从这里到后面的3个步骤,描述的是从地址栏打开一个URL地址。
从地址栏打开一个URL地址
1.当用户输入,或者地址栏收到一个回车时,自动完成编辑框会算出最终目标URL,并传给AutocompleteEdit::OpenURL。(说用户输入可能不够确切——比如,搜索结果里的一个URL。)
2.NavigationController::LoadURL里的导航控制器(NavigationController)会去打开URL。
3.NavigationController调用TabContents::Navigate,并传入一个NavigationEntry来负责页面转换。这将会导致子进程如果需要,就创建一个RenderViewHost。如果是第一次导航,就不会有RenderView,否则如果渲染器crash了,会导致它被crash里的覆盖。
4.Navigate继续调用RenderViewHost::NavigateToEntry。NavigationController存储这个导航入口,但是会打一个"pending"的标记,因为它不知道到底会不会转换(可能主机无法解析)。
5.RenderViewHost::NavigateToEntry会给渲染进程里新的RenderView发一个ViewMsg_Navigate。
6.导航时,RenderView可能会导航,可能会失败,也可能会导航到其他地方(比如用户点了一个链接)。RenderViewHost会等待RenderView发一个ViewHostMsg_FrameNavigate。
7.当载入状态被WebKit改成"committed"时(server响应并发送了数据),RenderView就会发这个消息,并被RenderViewHost::OnMsgNavigate捕获处理。
8.过程中NavigationEntry会更新载入过程中的信息。例如点击的链接,之前浏览器是不知道的。如果导航是浏览器初始化的,就像启动时那样,在导航过程中可能会由于重定向而改变URL地址。
9.NavigationController给帐户把新的信息更新到导航列表中。
导航和会话历史
每个NavigationEntry都会存储一个页面ID和历史状态数据。页面ID用来唯一标识一个页面载入,这样我们就知道哪一个NavigationEntry与之对应。它在页面被提交时分配,所以一个pending状态的NavigationEntry的页面ID是-1。历史状态数据就是WebCore::HistoryItem序列化的一个字符串。它里面包含了页面URL,子frame URL,以及表格数据。
1.当浏览器初始化请求时(在地址栏中输入,或者点击后退/前进/刷新)
1.生成一个WebRequest来表示导航,它会一直为书记员(bookkeeping)带着页面ID之类的信息。新导航的ID是-1。当页面是第一次访问时,旧入口的导航会把ID分配给NavigationEntry。到后面载入提交时,还会查询这个信息。
2.主WebFrame会被调用来载入新的请求。
2.渲染器初始化请求时(用户点击链接,js改变了location,等):
1.WebCore::FrameLoader的各个载入方法会被调用来载入请求。
3.无论什么情况,当收到服务器返回的第一个包时,载入会被提交(不再是"pending"或"provisional"状态)。
4.如果有一个新的导航,会fork出一个新的WebCore,它会创建一个新的HistoryItem,并加到BackForwardList中。用这个方法,我们可以区分哪个导航是新的,哪个是会话历史导航。
5.RenderView::DidCommitLoadForFrame处理载入的提交。在这,前一个页面的状态通过ViewHostMsg_UpdateState消息被存储在会话历史中。这告诉浏览器去更新对应的NavigationEntry(通过RenderView的当前页面ID标识)为新的历史状态。
6.RenderView的当前页面ID被更新为提交页面的ID。对于新的导航,会生成一个新的唯一页面ID。对会话历史导航,页面ID是第一次访问时的ID,初始化导航时,已经把它存在WebRequest里了。
7.发送一个ViewHostMsg_FrameNavigate消息给浏览器,更新对应的NavigationEntry(由RenderView刚更新的页面ID标识)的新URL和其他信息。