本文主要介绍Chromium从下到上渲染和显示一个网页的各个主要组件,并对这些组件的功能给予阐述。在阅读本文之前,建议先阅读本人另外一篇博文“Chromium浏览器的多进程架构”。原文出自Chromium的官方文档,本文对原文一些内容进行了直接翻译,由于有些内容已经过时,所以跟据自己的理解,对原文做了一些修改和补充。
Chromium的层次架构
上图中每个方格代表一层,并且Chromium在设计上要求每一层都不依赖上一层的接口。
WebKit/Blink:在2013年5月之前Chromium还是采用WebKit作为渲染和布局引擎,之后Chromium开始采用Google自主研发的Blink来替代WebKit,当然Blink也是来自于WebKit。关于Google为什么要从WebKit分离开发自己的渲染引擎,请参考本人另外一篇博文“浅议Google从WebKit的分离”。WebKit作为目前市场上最流行的浏览器渲染布局引擎,被广泛应用在许多流行的浏览器上,例如Chrome, Safari, 手机上的安卓原生浏览器,QQ浏览器,UC浏览器等。WebKit Port是WebKit在某个平台上的移植层,开发者在移植WebKit在某个平台上时,都要实现这个Port层,它包括画图库,字体和网络模块。
Glue:WebKit和Chromium采用了不同的数据类型和API接口,这就需要有一个适配层,把WebKit中的数据类型转化成Chromium中使用的数据类型。该层存在于渲染进程中。
Renderer / Render host: 该层实际上是一个跨越进程的虚拟层,Renderer存在于渲染进程中,Render host存在于浏览器进程中,他们通过IPC实现渲染进程中WebKit与上层浏览器进程中某个标签页的通信。
WebContents: 该层称为Content层,运行在浏览器进程中,WebContents是content层最核心的类,关于为什么要开发Content模块和Content API,可以参看本人写的另外一篇博文“Chromium源码浅析--- Content API (一)”。Chrome浏览器就是基于Content API实现的,自从Android 4.4以后,安卓设备上的webview也是基于Content API实现的。
Browser: 该层同样存在于浏览器进程,包含浏览器的UI模块,代表用户看得见的浏览器窗口,浏览器窗口中可以存在都个标签页,每个标签页都对应一个WebContents对象。
Tab Helpers: 该层对应于浏览器窗口上的标签页,主要用来装饰WebContents对象,由于Content API创建出来的WebContents对象功能比较简单,Chrome浏览器想添加一些高级功能,例如自动填充功能(autofill),书签(Bookmark),通知(infobar)等。
渲染进程
渲染进程中存在两个线程,一个是主线程(Main Thread)主要负责完成与浏览器进程的进程间通信(IPC);另外一个是渲染线程(Render Thread)主要负责页面的解析、渲染和布局工作,WebKit/Blink渲染引擎就运行在渲染进程的渲染线程中,当WebKit需要与浏览器通信时,WebKit第一步先把消息发给渲染进程的主线程,再由主线程统一与浏览器进程进行IPC。为了不阻塞其他消息的传递和执行,消息几乎都是异步传输和执行的,当然了也有例外,比如JavaScript在查询cookie时就需要采用同步的方式,因为它阻碍了JavaScript的执行。在此期间主线程收到的消息请求都会被暂时缓存起来,等待同步查询的数据返回之后再继续执行。
浏览器进程
浏览器进程存在许多线程,与本文相关的线程有两个,一个是UI线程(也是主线程),浏览器窗口时间的响应和处理都是在该线程中完成。另外一个是IO线程,该线程负责处理从渲染进程发过来的IPC请求,除此之外,该线程还处理所有的网络资源请求。
RenderProcessHost存在于主线程中,当有消息要传递给渲染进程时,就先把消息传给IO线程中的IPC::ChannelProxy对象,再由IO线程统一传递给渲染进程。同样IO线程会第一时间收到渲染进程传递过来的IPC,ResourceMessageFilter会先对消息进行过滤,如果是网络资源请求相关的消息,就之间在IO线程中处理,如果是视图相关的消息就会转发给主线程的RenderProcessHost去处理。
实例分析
设置光标(set cursor)消息的运行路径
该案例主要分析从渲染进程中WebKit传递消息到浏览器进程的处理过程。
渲染进程中:
1. 设置光标消息最开始是由WebKit出发,设置光标消息最先起源于RenderWidget::didChangeCursor,该函数存在于文件content/renderer/render_widget.cc中。
2. 调用RenderWidget::Send函数发送该消息,该函数最后调用RenderThread::Send
3. 最后通过IPC::SyncChannel发送给浏览器进程。
浏览器进程中:
1. IPC::ChannelProxy会收到渲染进程中发送过来的消息,该对象存在于IO线程中。
2. 在ChannelProxy把消息传递给RenderProcessHost之前需要经过一系列的过滤器,这些过滤器是在RenderProcessHost初始化时就已经构建完毕,其中ResourceMessageFilter就是其中一个比较重要的过滤器,如果ResourceMessageFilter检测到消息是网络资源请求消息,那么ResourceMessageFilter就直接相应该消息,此时消息的处理过程就结束。以此类推,当经过所有的过滤器后,消息都没有被处理掉,此时就会交给RenderProcessHost来处理。
3. 由于该消息类型是视图相关的,所以RenderProcessHost本身不处理该消息,而是转发给RenderViewHost类处理。许多消息都是在该对象中处理的,但是我们这次的ViewHostMsg_SetCursor消息没有在该类中处理,而是转发给RenderWidgetHost类来处理,这也很好理解,毕竟是渲染进程中的RenderWidget发送的消息,所以我们在浏览器进程中的RenderWidgetHost类中来处理,这也符合当初架构的设计。最后RenderWidgetHost::OnMsgSetCursor函数来处理该消息,并调用相应的UI方法来做画图或更新处理。
详细流程图如下: