综述
这篇文章描述chroumium从下到上如何显示网页,假设你读过多进程结构的设计文档。你特别希望理解各个组件的块状图;你也许会对多进程资源加载这篇文章感兴趣,它解释了如何从网络获取页面;
概念应用层次如:
每一个箱子都描述了一个概念应用层,层次之间互不感知和互不依赖。
webkit:是Safari、Chroumium和其他基于webkit浏览器的渲染引擎;Port是webkit的一部分,它集成了平台系统依赖服务如:资源装载和图形。
glue:把webkit的类型转化为Chromium的类型,这是我们的“webkit嵌入层”;它是Chroumium和test_shell(用来测试webkit的工具)两个浏览器的基础;
Renderer / Renderer host:这是Chroumium的“多进程嵌入层”,它代理进程间的通知和命令;
webContents:一个重复利用的组件,它是内容(content)模块中的一个主类,它容易嵌入,可以让多个进程的页面渲染到一个视图中;从内容模块页面可以了解更多的信息。
Browser:代表浏览器窗口,它包含多个webContents。
Tab Helpers:用来连接webContents的单个对象(通过混入WebContentsUserData),浏览器的webContents持有各种各样的Tab Helper(比如一个favicons,一个infobar等)
webkit
webkit port
在最低的层次上有我们自己的webkit 'port',这是我们实施所需的特定于平台的功能接口与平台无关的WebCore的代码;这些文件位于webkit目录树下,通常在chromium目录下或者那些以chromium作为前缀的文件;我们大部分的port不是特定于操作系统的,你可以认为它是webcore的'chromium port',某些部分比如字体渲染,平台不同其实现也不同;
- 网络通信是交由多进程资源加载系统处理,而不是直接从渲染进程交由OS来处理;
- 为android平台开发的skia图形库来担当图像系统,这是一个跨平台的图形库,除了文字处理所有的图像和图形基元;skia定位在/third_party/skia,图像功能的主要入口在/webkit/port/platform/graphics/GraphicsContextSkia.cpp,它调用了除来自/base/gfx目录下同目录下的其他很多文件。
webkit glue
相对第三方软件webkit的代码来说,chromium应用程序使用很多不同的类型、代码样式、代码布局;webkit glue提供了很多方便的嵌入API以便webkit使用google的代码惯列和数据类型(比如我们使用std::string而不是webcore::string,使用GURL而不是KURL);glue的代码在/webkit/glue下,glue的对象典型命名类似于webkit的对象,但是以'Web'开头,例如WebCore::Frame变成了WebFrame;
The render process
chromium的 render 进程使用glue接口嵌入了我们的 webkit port,它的代码量不大;相对于浏览器来说它的主要工作是ipc通道一侧的renderer;
在renderer中主要的类是RenderView,位于/content/renderer/render_view_impl.cc;这个对象描述一个web page;它处理来往于browser的导航相关命令;RenderView继承RenderWidget,RenderWidget的主要功能是处理重画和输入事件;RenderView通过全局的RenderProcess对象(每个 render process有一个全局RenderProcss对象)来和Browser通信;
FAQ: What's the difference between RenderWidget and RenderView?
RenderWidget通过实现glue层的抽象接口WebWidgetDelegate映射了一个WebCore::Widget;基本上是一个我们绘制的接收输入事件的屏幕窗口;继承RenderWidget的RenderView,是标签或者弹出窗口的内容对象;RenderView除了处理Widget的重画和输入事件外,还处理导航命令;只有一种情况下RenderWidget脱离RenderView存在,那就是网页上的选择框;这些带有下拉箭头的框弹出一个选项列表;选择框必须以本地窗口呈现,以便它可以在任何其他事物之上,必要时可以在框架之外弹出;这些窗口需要绘制和输入,但没有一个单独的“网页”(RenderView);
Threads in the renderer
每个Render有两个线程(参见多进程结构图或如何给chromium线程编程);类似RenderView的主要对象和所有的webkit代码在Render线程中运行;当它和Browser通信时,消息首先发送给主线程,然后依次轮流分发给Browser进程;除其他事列外,允许我们从Render发送同步消息给Browser,这种情况发生在来自Browser的结果要求一小部分操作继续;比如要求javascript为一个页面得到cookies;Render线程会阻塞,主线程会把所有消息排队直到确认响应发现;在此期间收到的任何消息会依次邮寄给Render线程处理。
The browser process
Low-level browser process objects
所有的和渲染进程的进程间通信都在Browser的I/O线程中,该线程同时处理网络通信防止用户界面的干扰;当一个RenderProcessHost在主线程(用户界面运行的地方)中初始化,它创建一个新的渲染进程和一个对应渲染的命名管道的ChannelProxy进程间通信对象;ChannelProxy对象运行在浏览器的I/O线程中,监听渲染器的命名管道,自动回转所有消息给UI线程的RenderProcessHost;一个ResoureMessageFilter将安装在这个隧道上,过滤某些I/O线程可以直接处理的消息,比如网络请求;过滤发生在ResourceMessageFilter::OnMessageReceived。
UI线程侧的RenderProcessHost负责分发所有的视图相关消息给相关的RenderViewHost(处理和自己相关的非视图消息),分发发生在RenderProcessHost::OnMessageReceived.
High-level browser process objects
特定的视图消息进入RenderViewHost::OnMessageReceived,大部分消息在这里处理,其余的转发给RenderWidgetHost基类;这两个对象映射到Render中的RenderView和RenderWidget(参见上图的‘Render Process’理解其中含义);每个平台有一个视图(View)类(RenderWidgetHostView[Aura|Gtk|Mac|Win])实现和本地视图系统的集成;
以上的RenderView/Widget 是WebContents的对象,大部分消息最后调用该对象的函数;一个WebContents描述了一个网页的内容,它是内容模块的最顶层对象,它的职责是在一个矩形视图中显示一个网页,参见内容模块页查看更多的信息;
WebContents对象包含在一个 TabContentWrapper中,在chrome/中负责一个标签(tab);
Illustrative examples
更多的例子覆盖导航和启动在围绕chromium源代码
Life of a "set cursor" message
设置光标是一个典型消息的例子,它从Render发送到Browser,在Render中发生了以下情况:
- 设置光标消息在Webkit的内部产生,一般是相应输入事件,设置光标消息在RenderWidget::SetCursor(应该是didChangeCursor)开始,这个函数在content/renderer/render_widget.cc中;
- RenderWidget::SetCursor调用RenderWidget::Send分发该消息,这个函数也被RenderView用来发送消息给Browser,接下来调用RenderThread::Send;
- 调用IPC::SyncChannel内部代理消息给render的主线程,主线程投递消息给命名管道,从而发送给Browser;
然后Browser采取控制:
- 在RenderProcessHost中的IPC::ChannelProxy接收Browser的I/O线程的所有消息,首先通过ResourceMessageFilter发送,在I/O线程里直接分发网络和相关消息;既然我们的消息没有被过滤掉,它们继续送到Browser的UI线程(IPC::ChannelProxy在内部处理);
- 在content/browser/renderer_host/render_process_host_impl.cc的RenderProcessHost::OnMessageReceived收到对应Render进程的所有视图的消息,它直接处理部分消息,其余的转发给发送消息的原RenderView对应的RenderViewHost;
- 消息到达位于content/browser/renderer_host/render_view_host_impl.cc的RenderViewHost::OnMessageReceived,许多消息在这里处理;但'set cursor'消息不是在这里处理,因为它是从RenderWidget发送,要被RenderWidgetHost处理;
- 所有未在RenderViewHost处理的消息自动转发给RenderWidgetHost,包括我们的设置光标消息(set cursor message);
- 在content/browser/render_host/render_view_host_impl.cc的消息映射最后在RenderWidgetost收到该消息,调用适当的界面(UI)函数来设置鼠标光标
Life of a "mouse click" message
发送鼠标点击是一个典型的从浏览器发送消息到渲染进程(renderer)
- 窗口消息在浏览器的界面线程中的RenderWidgetHostViewWin::OnMouseEvent被接收到,然后调用同类的函数ForwardMouseEventToRender;
- 该转发函数把输入事件封包成跨平台的WebMouseEvent,最后发送给相关的RenderWidgetHost;
- RenderWidgetHost::ForwardInputEvent创建一个进程间通信消息ViewMsg_HandleInputEvent,序列化进WebInputEvent,并呼叫RenderWidgetHost::Send;
- RenderWidgetHost::Send正好转发给RenderProcessHost::Send函数,轮流发送消息给IPC::ChannelProxy;
- 在内部IPC::ChannelProxy代理消息给浏览器的I/O线程并写进对应的渲染(renderer)进程的命名管道
注意许多其他类型的消息都在WebContents中创建,特别是导航系列,都遵循一个从WebContents到RenderViewHost的简单路径;
然后渲染进程采取控制
- 渲染主线程侧的IPC::Channel读取浏览器发送过来的消息,IPC::ChannelProxy代理给渲染线程;
- RendView::OnMessageReceived获取这个消息,许多消息都在这里处理;然而点击消息不在这里处理,它和其他未处理的消息直接到达RenderWidget::OnMessageReceived,再轮流转发给RenderWidget::OnHandleInputEvent;
- 输入事件转给WebWidgetImpl::HandleInputEvent,在这里转变为一个WebKit platformMouseEvent类,在传入给webkit的内部WebCore::Widget类;
完