来点难的:浏览器架构的前世今生(译文)

40 阅读17分钟

前端训练营:1v1私教,终身辅导计划,帮你拿到满意的 offer 已帮助数百位同学拿到了中大厂 offer。欢迎来撩~~~~~~~~

Hello,大家好,我是 Sunday。

前端一直是一个快速发展的领域,如果我们只是追随框架的应用,那么就会形成了一个“西西弗斯式”的怪圈。在这种无意义的循环之中,逐渐丧失斗志。

西西弗斯是古希腊神话中的一个人物,他被罚在地狱里推一块巨石到山顶,但每当他快要推到山顶的时候,巨石就会滚下来,迫使他不断地重复这个过程,形成了一个永恒的循环。

而前端的项目大多时候都需要在浏览器中进行展示,所以今天咱们就追本溯源,来看下 浏览器的架构原理,明确一下 浏览器架构的前世今生。内容有点长,可能也有点难,但是相信我,看完之后,你会满载而归。


以下内容为译文

浏览器架构经历了从单进程浏览器到多进程浏览器的转变。强调稳定性、流畅性、安全性,将流程分解为渲染、GPU、网络、插件,提高架构的整洁度。回顾浏览器的架构,需要进一步了解打开页面的流程、页面渲染流程以及浏览器插件机制。通过整理Chrome扩展版本的时间线,特别是从Manifest V1到Manifest V3的过渡,可以比较全面地了解浏览器的演变过程。

01:浏览器架构的演变

2007年之前,典型的浏览器架构是这样的:

单进程浏览器架构在单个操作系统进程中运行整个 Web 浏览器,将网络处理、插件、JavaScript 运行时、渲染引擎、页面管理和用户界面元素等任务整合到一个执行空间中。这种架构设计在简化资源管理的同时,主要存在以下问题:

  • 不稳定:处理网络视频和游戏等功能的插件和渲染引擎在同一进程中运行。插件或渲染引擎的崩溃可能会导致整个浏览器崩溃,这种不稳定性在处理复杂的 JavaScript 代码时尤其明显。
  • 缺乏流畅性:所有模块(包括页面渲染、JavaScript 执行和插件)共享一个线程。如果脚本变得非常耗时,它可能会独占整个线程,导致其他页面无响应,并导致整个浏览器出现延迟。
  • 不安全:在页面上运行的插件可以访问操作系统资源。恶意插件可以利用此访问权限来释放病毒,从而危及安全并可能窃取用户凭据等敏感信息。

单进程浏览器的优点是在一个进程内运行的所有浏览器组件简化了资源管理和协调。单进程浏览器通常表现出较低的内存使用率,这有利于资源效率的提高。任务在统一流程中按顺序运行。

2008年发布的Chrome进程架构就是多进程浏览器的一个例子,如下图所示:

早期的浏览器架构将功能分为三个主要进程:浏览器插件渲染。每个页面及其插件在专用渲染和插件进程中独立运行,通过IPC进行通信。

进程间通信(IPC)是一种允许进程在计算机上进行通信和同步操作的机制。它促进不同程序之间的有效数据交换和协调。关键的 IPC 机制包括共享内存,允许进程使用信号量同步访问共享公共内存区域。命名和未命名管道提供单向通信;Linux 中的 IPC 通常涉及使用信号量共享文件或共享内存存储。消息队列支持异步通信,有助于分离发送者和接收者进程。此外,进程可以通过信号进行通信以通知特定事件或请求。套接字使用网络协议在不同的机器上扩展 IPC。

多进程浏览器增强稳定性;独立的进程可以防止崩溃影响整个浏览器。页面或插件的崩溃只影响其特定进程,保证其他页面和浏览器本身的稳定性。在单独的渲染进程中运行 JavaScript 也可以隔离其影响;如果脚本阻止渲染过程,它只会影响当前页面,而浏览器的其余部分不受影响,因为每个页面都在其专用渲染过程中运行脚本。此外,Chrome 将插件和渲染进程放置在沙箱环境中,限制对数据的读/写访问 - 即使恶意软件在这些进程中执行,它也无法突破沙箱以获得系统权限 - 这是分隔架构模式的一个示例。

沙箱是一种测试环境,允许用户运行程序或打开文件而不影响整个系统的运行。在网络安全领域,沙箱分析潜在的恶意代码执行,检测威胁以达到缓解目的。

在最新版本的 Chrome 浏览器中,关键组件如下所示:

浏览器进程管理显示界面、用户交互和子进程协调,并提供存储功能。它充当协调其他进程的“调度程序”,例如在输入 URL 时调用网络进程。渲染过程将 HTML、CSS 和 JavaScript 转换为交互式网页并运行 V8 引擎。出于安全原因,Chrome 在沙盒模式下为每个选项卡创建单独的渲染进程。

GPU 进程最初是为 3D CSS 效果设计的,但后来扩展到绘制网页和 Chrome UI 界面。在Chrome的多进程架构中引入,以满足常见的浏览器需求。网络进程独立加载页面网络资源;最初是浏览器进程中的一个模块,现在它作为一个单独的进程运行。插件进程对插件进行管理,防止由于插件固有的不稳定性而影响浏览器和页面的崩溃。

现代浏览器架构如下图所示:

02:浏览器页面打开的背后

  • 添加选项卡会启动基本进程的创建:系统浏览器、渲染、GPU 和网络进程。
  • 用户输入触发浏览器进程进行检查、组装协议并形成完整的 URL。
  • 浏览器进程通过进程间通信将URL请求发送给网络进程。
  • 网络进程检查本地缓存中是否有请求的资源。如果在缓存中找到,则将其返回给浏览器进程。
  • 如果缓存中没有,则网络进程会向 Web 服务器发送 HTTP 请求。
  • 网络进程解析响应并检查状态码;非200状态码提示具体处理逻辑。
  • 对于 200 响应,浏览器进程会检查 Content-Type。字节流触发下载管理器,而 HTML 则发出渲染准备就绪信号。
  • 浏览器进程验证当前 URL 是否与现有渲染进程的根域匹配。如果不同,则启动新的渲染过程。
  • 浏览器向渲染进程发送“提交文档”消息,与网络进程建立数据传输管道。
  • 渲染进程接收到数据后,向浏览器返回确认信息。浏览器更新界面状态,包括安全指示器、地址栏 URL、浏览历史记录和网页内容。

03:渲染过程

现代浏览器使用延迟加载和缓存等策略来优先考虑性能。浏览器通过渲染过程显示网页内容。关键阶段包括HTML解析、CSS样式、布局创建和绘制,具体步骤如下:

  1. 用户输入 — 在浏览器地址栏中输入 URL。
  2. URL 解析 — 解析 URL 以识别协议、主机、端口和路径。
  3. DNS 查找 — 通过 DNS 查找将主机名转换为 IP 地址。
  4. 套接字连接 — 在用户和服务器 IP 之间建立连接。
  5. HTTP 请求 — 向指定协议的服务器发送 HTTP 请求。
  6. 服务器处理——服务器评估请求并确定处理插件(例如,PHP、Java)。
  7. 通过插件处理——作为 HTTP 响应的一部分访问数据库或其他资源。
  8. 浏览器响应 — 将 HTTP 响应发送回浏览器。
  9. 响应分析 — 分析浏览器响应中的 HTML 数据。
  10. DOM 树创建 — 从解析的 HTML 构建文档对象模型 (DOM) 树。
  11. 样式表解析 — 解析将演示数据链接到 DOM 节点的样式表。
  12. JavaScript 执行 — 执行 JavaScript 代码来修改 DOM 元素。
  13. 页面渲染——使用 DOM 和样式数据显示网页。

3.1 HTML解析

浏览器逐个字符地读取HTML,识别元素、属性和文本,然后构造表示网页结构的DOM树,以保证HTML代码的正确显示。

3.2 CSS对象模型

CSS 对象模型表达应用于 HTML 元素的样式,类似于 DOM 树的结构化层次结构,考虑到样式的特殊性和级联性质,允许样式的访问、操作和计算。

3.3 布局管理器

布局管理器将DOM和CSS对象模型结合起来形成渲染树,根据内容、填充等确定Box的大小,并使用各种方法构建特定位置。同时,堆叠上下文和 Z-index 用于处理重叠元素,批处理技术用于优化布局更改。最后,元素被绘制在屏幕上并在用户交互过程中不断更新。

04:插件机制

使用插件时,浏览器的操作比常规网页更简单。渲染进程负责运行网页。当页面打开时,contentscript.js 被加载并注入到网页环境中,通过操作 DOM 树和改变显示来类似于 JavaScript 进行操作。GPU进程支持渲染插件接口的硬件功能,而网络进程则管理插件内的外部资源请求,例如对外部JS资源的依赖。同时,存储过程为使用 chrome.storage.local 的插件提供本地存储功能,以在 Chrome 扩展程序中存储和检索数据。浏览器进程充当促进扩展页面和 contentscript.js 之间通信的桥梁。

4.1 插件发展历史

插件机制的开发流程如下:

  • 2012年8月——Manifest V1:Chrome插件最初基于Manifest V1,定义了基本功能和权限。
  • 2013 年 4 月 — Chrome26 稳定版本:包括对 Manifest V1 插件的支持。
  • 2014年5月——Chrome35稳定版:浏览器增量更新继续支持ManifestV1插件。
  • 2014 年 9 月 — Chrome 37 稳定版本:Manifest V1 插件的进一步改进和错误修复。
  • 2015 年 5 月 — Chrome43 稳定版本:继续支持 Manifest V1。
  • 2015年12月-Manifest V2:推出ManifestV2,带来更好的安全性和附加功能。
  • 2016年6月——Chrome51稳定版本:Manifest V2成为插件开发标准。
  • 2016年9月——Chrome53稳定版:Manifest V2持续改进和优化。
  • 2019 年 1 月 –Manifest V3 诞生:专注于安全性、性能和开发灵活性。
  • 2020 年 3 月 –Chrome80 稳定版本:虽然清单 v2 仍然是插件的标准。清单 v3 可用于测试,但尚未强制执行。
  • 2021 年 3 月 –Chrome89 稳定版:manifest v2 仍然默认,但注意力转向 Manifest v3
  • 2021 年 10 月 - Chrome94 稳定版本:manifestv3 开始强制执行一些功能,并向开发人员提供迁移指南和工具
  • 2022年3月——Chrome98稳定版:manifestv3成为默认新插件版本,同时继续支持manifestv2
  • 2022年8月——Chrome104稳定版本:所有插件全面过渡到manifestv3,正式宣布不再支持manifestv2
  • 2023年3月–chrome108稳定版:保持对manifestv3的全面支持,确保所有插件的平滑过渡
  • 2023 年 7 月 — ManifestV3 预览版:允许开发人员探索即将发生的变化并提供有价值的反馈
  • 2023 年 10 月 — 进一步改进对 ManifestV31nChromellZStableVersion 的支持,解决预览阶段报告的所有问题
  • 2024 年 1 月 — Manif est V31 的预稳定版本鼓励开发人员将其扩展迁移到 V31,并提供全面的文档和迁移指南
  • 2024 年 1 月——Manifest V3 预稳定版本发布:Manifest V3 已达到稳定状态,鼓励开发人员将其扩展迁移到 V3,并提供全面的文档和迁移指南。
  • 2024年3月——Chrome116稳定版:全面支持Manifest V3,开发者更新插件以确保兼容最新标准。

总体而言,Chrome 扩展(也称为附加组件)经历了 3 个主要版本的开发:

  • Manifest V1
  • Manifest V2
  • Manifest V3。

Manifest V1 (MV1) 是 Chrome 扩展清单的初始版本,已被弃用。Manifest V2 (MV2) 是当前 Chrome 扩展中广泛使用的主流版本,为构建具有增强的浏览器功能特性的扩展提供了强大的框架。Manifest V3是逐步取代MV2的最新版本。MV3的引入旨在通过加强安全措施并促进扩展开发中更好的性能来解决安全和性能问题。从 Chrome 127(2024 年 6 月)开始,Google 将开始在 Chrome 预稳定版本中禁用 Manifest V2 扩展,鼓励开发者过渡到 MV3。

4.2 Manifest的特性及版本迁移

清单V2的特点:

  • 使用script-src ‘self’; object-src ‘self’; Content-Security-Policy (CSP) 设置默认内容安全策略。
  • 插件包资源不再对外可用;web_access_resources白名单通过清单中的属性列出。
  • 对浏览器操作API和页面操作API进行了更改,chrome.extension代替了chrome.self指向插件本身,chrome.tension.getTabContentses替换为tension.getViewPort.tab替换为runtime.Port等。

清单V3的特点:

  • Manifest V3介绍Service worker,替换背景页面。
  • 网络请求修改使用新的声明式网络请求 API,而不是已弃用的 webRequest API。
  • 不允许远程执行代码;只能运行扩展包中的JS。
  • 许多方法中都添加了 Promise,并且仍然支持回调。
  • 浏览器操作 API 和页面操作 API 统一为单个操作 API。
  • Web 可访问的资源仅限于指定的站点和扩展。
  • 内容安全策略(CSP)允许为不同的执行上下文指定单独的CSP,其中executeScript只能运行脚本文件和函数,而不能运行任意字符串。

Manifest V3 代表了相对于 V1 和 V2 的重大转变,这是由 Chrome 致力于提高隐私、安全性和整体扩展性能所推动的。与之前的版本不同,Manifest V3 优先考虑资源效率,解决了人们对 Chrome 历史上高资源使用率的担忧。其核心目标是通过扩展限制系统资源消耗来优化浏览器性能。在施加额外限制的同时,Manifest V3 引入了显着的优势。ServiceWorker 功能允许扩展程序无需持续驻留在后台即可运行,从而实现扩展程序资源的回收并有效降低浏览器的整体开销。对规则计算的限制作为一种控制机制,以确保各个扩展不会过度消耗资源。这些变化共同为 Chrome 带来了更流畅的浏览体验,满足了用户对提高浏览器效率的期望。

从V2迁移到V3时,由于缺少配置页面背景的background.html,Windows对象上的XMLHttpRequest不再适用于V2版本中的background.html中构造AJAX请求。相反,必须使用提取方法来检索数据。

此外,服务工作者的生命周期较短,并且在不活动期间终止,导致整个插件生命周期偶尔启动、运行和终止,从而带来了不稳定。在MV2中,全局变量直接用于存储数据;因此,需要修改 backound.js 逻辑以增强这些情况下的稳定性和功能。此外,从 webRequest API 过渡到 statativeNetRequest API 需要大量的代码重构。

4.3 Chrome扩展的主要组件

4.3.1 清单文件

manifest.json 文件对于位于根目录的 Chrome 扩展非常重要。它用于配置所有插件设置,包括 Manif_version、名称和版本等基本参数。

Manifest V2的示例如下:

{
  "manifest_version": 2,
  // Plugin name
  "name": "...",
  // Plugin version
  "version": "1.0.0",
  // Plugin description
  "description": "...",
  "icons": {
    "16": "img/icon16.png",
    "48": "img/icon48.png",
    "128": "img/icon128.png"
  },
  // Persistent background JS or background page
  "background": {
    "scripts": ["js/background.js"]
  },
  // Browser icon settings :browser_action, page_action, app
  "browser_action": {
    "default_icon": "img/icon.png",
    "default_title": "...",
    "default_popup": "popup.html"
  },
  // Icon displayed only when specific pages are open
  "page_action": {
    "default_icon": "img/icon.png",
    "default_title": "...",
    "default_popup": "popup.html"
  },
  // JS directly injected into pages
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["js/content-script.js"],
    "css": ["css/custom.css"],
    // Code injection timing, default is document_idle
    "run_at": "document_start"
  }],
  // Permissions requested
  "permissions": [
    "contextMenus", // Right-click menu
    "tabs", // Tabs
    "notifications", // Notifications
    "webRequest", // Web requests
    "webRequestBlocking",
    "storage", // Plugin local storage
    "https://*/*" // Websites accessible via executeScript or insertCSS
  ],
  // List of plugin resources directly accessible by normal pages  "web_accessible_resources": ["js/inject.js"],
  "homepage_url": "...", // Plugin homepage
  "chrome_url_overrides": { // Override browser default pages
    "newtab": "newtab.html"
  },
  "options_ui": { // Plugin options page
    "page": "options.html",
    "chrome_style": true
  },
  "omnibox": { "keyword" : "..." }, // Register a keyword in the address bar for search suggestions, only one keyword can be set
  "default_locale": "en", // Default language
  "devtools_page": "devtools.html", // Devtools page entry, can only point to an HTML file    "content_security_policy": "...", // Security policy
  "web_accessible_resources": [ // Loadable resources
    "RESOURCE_PATHS"
  ]
}

Manifest V3的示例如下:

{
  "manifest_version": 3,
  "name": "...", 
  "version": "1.0.0",
  "description": "...", 
  "icons": {
    "16": "img/icon16.png",
    "48": "img/icon48.png",
    "128": "img/icon128.png"
  },
  "background": {
    "service_worker": "js/background.js"
  },
  "action": {
    "default_icon": "img/icon.png",
    "default_title": "...", 
    "default_popup": "popup.html"
  },
  "content_security_policy": {
    "extension_pages": "...",
    "sandbox": "..."
  },
  "web_accessible_resources": [
    {
      "resources": ["RESOURCE_PATHS"]
    }
  ],
  "permissions": [
    "contextMenus",
    "tabs",
    "notifications",
    "webRequest",
    "webRequestBlocking",
    "storage",
    "https://*/*"
  ],
  "web_accessible_resources": ["js/inject.js"],
  "homepage_url": "...", 
  "chrome_url_overrides": {
    "newtab": "newtab.html"
  },
  "options_ui": {
    "page": "options.html",
    "chrome_style": true
  },
  "omnibox": {
    "keyword": "..."
  },
  "default_locale": "zh_CN", 
  "devtools_page": "devtools.html",
  "content_security_policy": "...",
  "web_accessible_resources": ["RESOURCE_PATHS"]
}

4.3.2 内容脚本

Chrome扩展程序中的内容脚本通过配置将JS和CSS注入到指定页面中。它们与原始页面共享 DOM,但不共享 JavaScript。访问页面JS变量需要注入JS。内容脚本无法访问大多数 Chrome API,除了:

  • chrome. extension
  • chrome.i18n
  • chrome. runtime
  • chrome.storage

对于其他 API,需要与后台或 Service Worker 进行通信。

4.3.3 后台脚本

Chrome扩展程序中的后台脚本具有最长的生命周期,并且在浏览器打开时持续运行。它们拥有广泛的权限,允许访问大多数 Chrome 扩展 API 和跨源请求,而不受 CORS 限制。在 Manifest V3 中,后台页面被生命周期较短且基于事件执行的 Service Worker 所取代,使得它们不适合存储全局变量。

4.3.4 弹出窗口

弹出窗口是网页上的一个小窗口,当单击右上角的图标时会出现该窗口。当用户在网页之外进行交互时,它会快速关闭。通常用于临时交互,其权限级别与后台类似,但寿命较短。

4.3.5 注入脚本

开发人员在 Chrome 扩展开发过程中创造了术语“注入脚本”。它指的是通过 DOM 操作注入到页面中的 JavaScript。虽然内容脚本可以操作 DOM,但由于访问限制,无法直接访问它们。这种限制在事件绑定中尤其明显。为了满足在网页上添加按钮来触发插件的常见需求,开发人员采用了脚本插入的方式。

4.4 Chrome扩展的通信机制

在 Chrome 扩展中,通信依赖于五种类型的脚本:

  1. 注入脚本,代表动态注入网页的脚本,通常依赖于window.postMessage.
  2. 内容脚本,在特定网页上下文中执行的脚本,利用和window.postMessage进行脚本间通信。chrome.runtime.sendMessage``chrome.runtime.connect
  3. 弹出脚本,与插件的弹出界面相关,通常使用chrome.tabs.sendMessagechrome.tabs.connect进行通信。
  4. 后台脚本,在后台运行的独立脚本,涉及“ ”、“ chrome.tabs.sendMessage”、chrome.tabs.connect“ ”chrome.extension.getBackgroundPage等各种方法chrome.extension.getViews
  5. DevTools,使用特定 API(如chrome.devtools.inspectedWindow.eval和 )进行交互的开发工具的附加功能chrome.runtime.sendMessage

每个脚本都有不同的权限,强调它们通信的重要性。这种交互对于启用广泛的插件功能至关重要。

前端训练营:1v1私教,终身辅导计划,帮你拿到满意的 offer 已帮助数百位同学拿到了中大厂 offer。欢迎来撩~~~~~~~~