zaocha 2019-06-28
微信公众号:爱写bugger的阿拉斯加
如有问题或建议,请后台留言,我会尽力解决你的问题。
此文章是我最近在看的【WebKit 技术内幕】一书的一些理解和做的笔记。
而【WebKit 技术内幕】是基于 WebKit 的 Chromium 项目的讲解。
WebKit 的一个显著特征就是支持不同的浏览器,因为不同浏览器的需求不同,所以在 WebKit 中一些代码 可以共享,但是另外一部分是不同的,这些不同的部分称为 WebKit 的移植( Ports )。
上图的 WebKit 架构,虚线框表示该部分模块在不同浏览器使用的 WebKit 内核中的实现是不同的,也就是它们 不是普遍共享的。用实线框表示的部分,表示它们是基本上是共享的,但不是绝对,是因为它们中的一些特性可能并不是共享的,而且可以通过不同的编译设置改变它们的行为。
图中最下面的是操作系统,不同浏览器可能会依赖不同的操作系统,同一个浏览器使用的 WebKit 也可能依赖不同的的操作系统。
操作系统之上的就是 WebKit 赖以工作的众多第三方库,这些库是 WebKit 运行的基础。
在它们二者之上的就是 WebKit 项目了。
WebCore 包含了了目前被 各个浏览器所使用的 WebKit 共享部分,这些都是加载和渲染网页的基础部分,它们必不可少,包括 HTML (解释器)、CSS (解释器)、SVG、DOM、渲染树(RenderObject 树和RenderLqyer 树等),以及 Inspector(Web Inspector和调试网页)。这些共享部分有些是基础框架,其背后支持也需要各个平台的不同实现。
JavaScriptCore 引擎是 WebKit 中默认 JavaScript 引擎,也就是说一些 WebKit 的移植使用该引擎。而且它只是默认,并不是唯一的,是可以替换的。事实上,WebKit 中对 JavaScript 引擎的调用是独立于引擎的。在 Google 的 Chormium 开源项目中,它被替换成 V8 引擎。
WebKit Ports 指的是 WebKit 中的非共享部分,对于不同浏览器使用的 WebKit 来说,移植中的这些模块由于平台差异、第三方库和需求不同等原因,往往按照自己的方式来设计与实现,这就产生了移植部分,这也是导致众多 WebKit 版本的行为并非一到的重要原因。这其中包括硬件的 加速架构,网络栈,视频解码,图片解码等。
在 WebCore 和 WebKit Ports 之上的层主要是提供嵌入式编程接口,这些接口是提供给浏览器调用(当然也可以有其他使用者)。图中有左右两个部分分别是狭义 WebKit 的接口和 WebKit2 的接口。因为接口与具体的移植有关,所以有一个与浏览器相关的绑定层。绑定层上面就是 WebKit 项目对外暴露的接口层。实际上接口层的定义也是与移植密切相关的,而不是 WebKit 有什么统一接口。
WebKit 还有一个部分在图中没有展现出来,那就是测试用例,包括布局测试用例( Layout Tests )和性能测试用例( Performance Tests ),这两类测试包括了大量的测试用例和期望结果。为了保证 WebKit 的代码质量,这些用例被用来验证渲染结果的正确性。
Chromium 也是基于 WebKit ( Blink ) 的,而且在 WebKit 的移植部分中,所以可以通过 Chromium 可以了解如何基于 WebKit 构建浏览器。
在上面这些模块之上的就是著名 的 "Content 模块" 和 “Content API(接口)”,它们是 Chromium 对渲染网页功能的抽象。"Content 模块" 的本意是指网页的内容,这里是指用来渲染内容的模块。如果没有 Content 模块,浏览器的开发者也可以在 WebKit 的 Chormium 移植上渲染网页内容,但是没有办法获得沙箱模型。跨进程的 GPU 硬件加速机制、众多的 HTML5 功能,因为这些功能 很多是在 Content 层里面实现的。
“Content 模块” 和 “Content API” 将下面的渲染机制。安全机制和插件机制等隐藏起来,提供一个接口层。该接口目前被上层模块或者其他项目使用,内部 调用者包括 Chromium 浏览器、 Content Shell 等、外部包括 CEF (Chromium Embedded Framework)、Opera 浏览器等。
“Chromium 浏览器” 和 ”Content Shell“ 是构建在 Content API 之上的两个 ”浏览器“,Chromium 具有浏览器完整的功能,也就是我们编译出来能看到的浏览器式样。而 ”Content Shell“ 是使用 Content API 来包装的一层简单的 ”壳“,但是它也是一个简单的 ”浏览器“,用户可以使用 Content 模块来渲染和显示网页内容。Content Shell 的作用很明显,其一可以用来测试 Content 模块很多功能的正确性,例如渲染、硬件加速等。其二是一个参考,可以被很多外部的项目参考来开发基于 ”Content API“ 的浏览器或者各种类型的项目。
在 Android 系统上, Content Shell 的作用更大,这是因为同它并排的左侧的 ”Chromium 浏览器“ 部分的代码根本就没有开源,这直接导致开发者只能依赖 Content Shell。
”Android WebView“ 是为了满足 Android 系统上的 WebView 而设计的,其思想是利用 Chromium 的实现来替换原来 Android 系统默认的 WebView。
多进程模型至少带来了三点好处:
1、避免因单个页面不响应或者崩溃而影响整个浏览器的稳定性
2、当第三方插件崩溃时不会影响页面或者浏览器的稳定性,这时因为第三方插件也被使用单独的进程来运行
3、它方便了安全模型的实施,也就是说沙箱模型是基于多进程架构的。
Chromium 浏览器主要包括以下进程类型:
1、Browser 进程:浏览器的主进程,负责浏览器界面的显示、各个页面的管理、是所有其他类型进程的祖先、负责它们的创建和销毁等工作,它有且仅有一个。
2、Renderer 进程:网页的渲染进程,负责页面的渲染工作, Blink/WebKit 的渲染工作主要在这个进程中完成,可能有多个,但是 Renderer 进程的数量与用户打开的网页数量不一定一致,Chromium 设计了灵活的机制,允许用户配置。此外,在沙箱模型启动的情况下,该进程可能会发生一些改变。
3、NPAPI 插件进程:该进程是为 NPAPI 类型的插件而创建的。其创建的基本原则是每种类型的插件只会被创建一次,而且仅当使用时才会被创建。当有多个网页需要使用同一种类型的插件的时候,例如很多网页需要使用 Flash 插件, Flash 插件的进程会为每个使用者创建一个实例,所以插件进程是被共享的。
4、GPU 进程: 最多只有一个,当且仅当 GPU 硬件加速打开的时候才会被创建,主要用于对 3D 图形加速调用的实现。
5、Pepper 插件进程:同 NPAPI 插件进程,不同的是为 Pepper 插件而创建的进程。
6、其他类型的进程:图中还有一些其他类型的进程没有描述出来,例如 Linux 下的 “Zygote” 进程,Renderer 进程其实都是由它创建而来。另外一个就是名为 “Sandbox” 的准备进程,这在安全机制中作进一步的介绍。
对于桌面系统(Windows、Liunx、Mac OS)中的 Chormium 的浏览器,它们的进程模型总结后包括以下一些特征:
1、Browser 进程和页面的渲染分开的,这保证了页面的渲染导致的崩溃不会导致浏览器主界面的崩溃。
2、每个网页是独立的进程,这保证了页面之间相互不影响。
3、插件进程也是独立的,插件本身的问题不会影响浏览器主界面和网页
4、GPU 硬件加速进程也是独立的。
Browser 进程和 Render 进程是如何利用 WebKit 渲染网页的,这其中的代码层次由图 3-6 给出。
最下面的就是 WebKit 接口层,一般基于 WebKit 接口层的浏览器直接在上面构建,而没有引入复杂的多进程架构。
然后,在 WebKit 接口层上面就是 Chromium 基于 WebKit 的接口层而引入黏附层,它的出现主要是因为 Chromium 中的一些类型和 WebKit 内部不一致,所以需要一个简单的桥接层。
再上面的就是 Renderer,它主要是处理进程间通信,接受来自Browser 进程的请求,并调用相应的 WebKit 接口层,同时,将 WebKit 的处理结果发送回去,上面这些介绍的层都是在 Renderer 进程中工作的。
下面就进入了 Browser 进程,与 Renderer 进程相对应的就是 RendererHost, 其目的也是处理同 Renderer 进程之间的通信。不过 RendererHost 是给 Renderer 进程发送请求并接收来自 Renderer 进程的结果。
Web Contents 表示的就是网页的内容,因为网页可能有多个需要绘制的内容,例如弹出的对话框内容,所以这里是 “Contents”。它同时包括显示网页内容的一个子窗口(在桌面系统上),这个子窗口最后被嵌入浏览器的用户界面,作为它的一个标签页。
每个进程内部都有很多的线程。
多线程的主要目的就是为了保持用户界面的高响应度,保证 UI 线程(Browser进程中的主线程)不会被任何其他费用时的操作阻碍从而影响了对用户操作的响应。这些费时的其他操作很多,例如本地文件读写、socket 读写、数据库操作等。
既然文件读写等会阻碍其他操作,所以把它们放在单独的线程里面自己忙或者等待去吧。而在 Renderer 进程中,Chromium 则不让其他操作阻止渲染线程的快速执行。为了利用多核的优势,Chromium 将渲染过程管线化,这样可以让渲染的阶段在不同的线程执行。
网页的加载和渲染过程在图中模型下的基本工作方式如以下步骤:
1、Browser 进程收到用户的请求,首先由 UI 线程处理,而且将相应的任务转给 IO 线程,它随即将该任务传递给 Renderer 进程。
2、Renderer 进程的 IO 线程经过简单解释后交给渲染线程。渲染线程接受请求,加载网页并渲染网页,这其中可能需要 Browser 进程获取资源和需要 GPU 进程来帮助渲染,最后 Renderer 进程将结果由 IO 线程传递给 Browser 进程。
3、最后 Renderer 进程接收到结果并将结果绘制出来。
Content 接口不仅提供了一层对多进程进行渲染的抽象接口,而且它从诞生以来一个重要的目标就是要支持所有的 HTML5 功能、GPU 硬件加速功能和沙箱机制,这可以让 Content 接口的使用都们不需要很多的工作即可得到很强大的能力。
Content 接口按照功能分成六个部分。每个部分的接口一般也可以分成两类,第一类是嵌入者(embedder,这里可以是 Chromium 浏览器、CEF3 和 Content Shell )调用的接口,另一类是嵌入者应该实现的回调接口,被 Content 接口的内部实现所调用,用来参与具体实现的逻辑或者事件的监听等。
相比于狭义的 WebKit ,WebKit2 是一套全新的结构和接口,而不是一个简单的升级版。它的主要目的和思想同 Chromium 类似,就是将渲染过程放在单独的进程中来完成,独立于用户界面。
依旧是自底向上介绍,WebKit2 中也引入了插件进程,而且它还引入了网络进程。图中的 “Web 进程” 对应于 Chromium 中的 Renderer 进程,主要是渲染网页。在这之上的是 “UI 进程”,它对应于 Chromium 中的 Browser 进程。接口就是暴露在该进程中,应用程序只需要调用该接口即可。其中 “应用程序 ” 指的是浏览器或者任何使用该接口的程序。
希望本文对你有点帮助。
对 全栈开发 有兴趣的朋友可以扫下方二维码关注我的公众号 —— 爱写bugger的阿拉斯加
分享 web 开发相关的技术文章,热点资源,全栈程序员的成长之路
陛下...看完奏折,点个赞再走吧!