概念
raster(栅格化):将矢量图转位像素图
工具
主要内容
浏览器架构
- Browser Process: 控制应用程序的“chrome”部分,包括地址栏、书签、后退和前进按钮。还处理网络浏览器中不可见的部分,例如网络请求和文件访问
- Render Process: Controls anything inside of the tab where a website is displayed
多进程的好处
每个选项卡的都开一个渲染进程,当一个进程没有响应时,不会影响其他选项卡内容的渲染
节省更多内存-Chrome中的服务化
当 Chrome 在强大的硬件上运行时,它可能会将每个服务拆分为不同的进程以提供更高的稳定性,但如果它在资源受限的设备上,Chrome 会将服务整合到一个进程中以节省内存占用
Per-frame renderer processes -Site isolation
Site isolation为每个站点iframe运行单独的渲染器进程。同源策略是网络的核心安全模型,它确保一个站点在未经同意的情况下无法访问其他站点的数据
导航
一个简单的导航
- 处理输入,UI线程询问这是搜索查询还是url
- 开始导航,UI线程发起网络调用以获取站点内容,网络线程通过合适的协议查找并建立TLS链接
- Read Response,当响应主体开始返回时,网络线程就会查找MIME信息,确定返回信息是什么类型,并对其进行不同的处理,如果它是html文件,就交给渲染器进程,如果是zip,就是下载。同时这一步也是进行安全浏览检查的地方,此外,跨域站点数据不会进入渲染器进程
- find a render process,一旦完成所有检查并且网络线程确信浏览器应该导航到请求的站点,网络线程就会告诉 UI 线程数据已准备好。UI线程然后找到一个渲染器进程来进行网页的渲染
由于网络请求可能需要数百毫秒才能得到响应,因此应用了加速此过程的优化。当 UI 线程在第 2 步向网络线程发送 URL 请求时,它已经知道他们正在导航到哪个站点。UI 线程尝试主动查找或启动与网络请求并行的渲染器进程。这样,如果一切按预期进行,当网络线程接收到数据时,渲染器进程已经处于待机位置。如果导航重定向跨站点,则可能不会使用此备用进程,在这种情况下,可能需要不同的进程
- commit navigation,现在数据和渲染器进程已准备好,从浏览器进程向渲染器进程发送 IPC 以提交导航。它还传递数据流,因此渲染器进程可以继续接收 HTML 数据。一旦浏览器进程听到在渲染器进程中发生提交的确认,导航就完成了,文档加载阶段就开始了。
此时,地址栏更新,安全指示器和站点设置 UI 反映了新页面的站点信息。该选项卡的会话历史记录将被更新,因此后退/前进按钮将逐步浏览刚刚导航到的站点。为了在您关闭选项卡或窗口时促进选项卡/会话恢复,会话历史记录存储在磁盘上。
额外步骤:初始加载完成
提交导航后,渲染器进程会继续加载资源并渲染页面。我们将在下一篇文章中详细介绍此阶段发生的事情。一旦渲染器进程“完成”渲染,它会将 IPC 发送回浏览器进程(这是在 onload页面中的所有帧上触发所有事件并完成执行之后)。此时,UI 线程停止选项卡上的加载微调器
渲染器的内部工作
渲染器进程负责选项卡内的所有工作。
解析
- DOM的构建
- 子资源加载
网站通常使用图像、CSS 和 JavaScript 等外部资源。这些文件需要从网络或缓存中加载。主线程可以在解析构建DOM的过程中找到它们时一一请求,但为了加快速度,“预加载扫描器”是并发运行的
- JavaScript脚本执行会阻止解析过程(因为都在主线程)
Style calculation
- 起源: 主线程解析css并添加计算后的样式
即使不添加样式,dom结构自己也有默认样式
- 过程:dom tree => dom with style tree
Layout
- 起源:dom的几何位置,要根据composer tree去确定在页面的位置,想要详细了解这部分内容可以阅读这里
- 过程:composer tree=>layout tree
Paint
- 起源:You know the size, shape, and location of elements, but you still have to judge in what order you paint them
- 过程 layout tree=>paint records
- Updating rendering pipeline is costly,javascript的执行可能会导致掉帧,如果是动画的绘制,可以通过
requestAnimationFrame()来执行,这样就可以让画面平滑的渲染。
Composing
- How would you draw a page?
光栅化:dom tree + style tree + layout tree + paint record => 像素
developers.google.com/web/updates…
- What is composing?
developers.google.com/web/updates…
Compositing is a technique to separate parts of a page into layers, rasterize them separately, and composite as a page in a separate thread called compositor thread
Dividing into layers
主线程通过遍历layout tree来分层,如果效果不如你所愿,可以通过will-change这个css属性来提示浏览器进行分层
Raster and composite off of the main thread
- 起源:Once the layer tree is created and paint orders are determined, the main thread commits that information to the compositor thread. The compositor thread then rasterizes each layer. A layer could be large like the entire length of a page, so the compositor thread divides them into tiles and sends each tile off to raster threads. Raster threads rasterize each tile and store them in GPU memory
Once tiles are rastered, compositor thread gathers tile information called draw quads to create a compositor frame
Input is coming to the Compositor
Input events from the brower's point of view
当用户进行输入事件时,浏览器进程首先感知,但是浏览器只知道该输入发生的位置,所以将事件类型及坐标发送到渲染器进程,渲染器进程通过查找事件目标并运行附加的事件侦听器来适当地处理事件。
⬆️通过浏览器进程路由到渲染器进程的输入事件
Compositor receives input events
非快速滚动区域
附加事件处理程序的区域被称为非快速滚动区域,当事件发生在该区域,compositor thread会将事件发送到主线程,如果发生在之外,就继续合成新帧,无需等待主线程
Finding the event thread
当input event被send到main thread时,main thead会进行hit test来查询event target,hit test通过paint record来获取事件发生位置处是什么元素
Minimizing event dispatches to the main thread
为了减少对主线程的过多调用,chrome会合并连续事件并将调度延迟到下一个requestAnimationFrame,而对离散事件确实立即分派的
学为战
非快速滚动区域的优化
事件委托会将目标元素区域都标记为非快速滚动区域,这样合成器线程每次都要等待主线程工作后才会工作,就丧失了流畅的用户体验,可以通过在监听事件加passive:true解决,这向浏览器提示您仍想在主线程中收听事件,但合成器也可以继续合成新帧
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true});
获取完整的事件触发
对于大多数 Web 应用程序,合并事件应该足以提供良好的用户体验。但是,如果您正在构建诸如绘图应用程序并根据 touchmove坐标放置路径之类的东西,则可能会丢失中间坐标以绘制平滑线。在这种情况下,可以使用getCoalescedEvents指针事件中的方法来获取有关这些合并事件的信息
window.addEventListener('pointermove', event => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// draw a line using x and y coordinates.
}
});