chorme插件开发(一)

概览

谷歌浏览器插件是一种小型的用于定制浏览器体验的程序。每个插件必须在根目录包含 manifest.json 来描述插件,其文件结构如下

1
2
3
| 插件根目录
|--- manifest.json // 插件描述文件
|--- 其它文件(图标、js 、css 等)

manifest.json

用来描述插件信息,声明插件需要的权限及相关功能的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
{
// 必须
"manifest_version": 3, // 插件版本,建议使用最新版本 v3
"name": "My Extension", // 插件名称
"version": "versionString", // 插件版本

// Recommended
"action": {...},
"default_locale": "en", // 默认语言
"description": "A plain text description", // 插件描述
"icons": {...}, // 插件图标

// Optional
"author": ..., // 作者
"automation": ..., //
"background": {
// Required
"service_worker": "background.js", // background js
// Optional
"type": ...
},
// "chrome_settings_overrides": {...}, // 已不再支持
// "chrome_url_overrides": {...}, // 已不再支持
"commands": {...}, // 插件自定义右键菜单
"content_capabilities": ...,
"content_scripts": [{...}], // web 页面上下文中执行的 js ,可通过其实现页面与插件其它 js 的通信
"content_security_policy": {...}, // 安全策略
// "converted_from_user_script": ...,
// "cross_origin_embedder_policy": {"value": "require-corp"},
// "cross_origin_opener_policy": {"value": "same-origin"},
// "current_locale": ...,
// "declarative_net_request": ...,
"devtools_page": "devtools.html", // 自定义的开发者工具页面
// "differential_fingerprint": ...,
// "event_rules": [{...}],
// "externally_connectable": {
// "matches": ["*://*.example.com/*"]
// },
// "file_browser_handlers": [...],
// "file_system_provider_capabilities": {
// "configurable": true,
// "multiple_mounts": true,
// "source": "network"
// },
"homepage_url": "https://path/to/homepage", // 插件首页
// "host_permissions": [...],
// "import": [{"id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}],
// "incognito": "spanning, split, or not_allowed",
// "input_components": ...,
// "key": "publicKey",
// "minimum_chrome_version": "versionString",
// "nacl_modules": [...],
// "natively_connectable": ...,
// "oauth2": ...,
// "offline_enabled": true,
// "omnibox": {
// "keyword": "aString"
// },
"optional_permissions": ["tabs"], // 可选的权限
"options_page": "options.html", // option 页面
"options_ui": {
"chrome_style": true,
"page": "options.html"
},
"permissions": ["tabs"], // 插件需要的权限,需要在这里声明之后才能使用相关权限
// "platforms": ...,
// "replacement_web_app": ...,
// "requirements": {...},
// "sandbox": [...],
// "short_name": "Short Name",
// "storage": {
// "managed_schema": "schema.json"
// },
// "system_indicator": ...,
// "tts_engine": {...},
// "update_url": "https://path/to/updateInfo.xml",
// "version_name": "aString",
// "web_accessible_resources": [...]
}
content_scripts

Chrome 插件中向页面注入脚本的一种形式(虽然名为 script,其实还可以包括 css 的),借助 content-scripts 我们可以实现通过配置的方式轻松向指定页面注入 JSCSS

background

一个常驻的页面,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在 background 里面。

点击 browser_action 或者 page_action 图标时打开的一个小窗口网页,焦点离开网页就立即关闭,一般用来做一些临时性的交互。

homepage_url

开发者或者插件主页设置

开发

  1. 创建好插件文件夹,添加 manifest.json,输入如下内容
1
2
3
4
5
{
"manifest_version": 3,
"name": "插件测试",
"version": "0.0.1"
}
  1. 在浏览器中访问 chrome://extensions,打开开发者模式。
    charles 代理转发
  2. 加载解压的插件
    charles 代理转发

参考

代理

前端开发时,需要 mock 或请求对应的数据,选择一种合适的方式获取数据可以大大提高开发效率

获取数据方式

本地 mock

  • 页面接口请求到达 dev server 之后会转发到 mock 服务上,然后返回 mock 数据
  • 页面静态资源请求会到达 dev server ,然后直接返回静态资源

本地 mock 数据请求流程

优点

  • mock 数据只需要配置一次,无需后续的额外操作

缺点

  • 无法服用线上接口数据
  • 项目中可能会出现许多 mock 数据相关的代码
  • 接口之间无关联性

直接请求线上数据

  • 页面静态资源请求会到达 dev server ,然后直接返回静态资源
  • 通过绝对地址或者浏览器代理的方式转发请求到线上服务

直接请求线上数据

问题

  • 通过硬编码绝对路径的方式对代码入侵较大,可以通过浏览器代理插件的方式实现对接口的转发
  • 当接口需要登陆时,可在浏览器插件中实现注入对应环境 cookie 的功能

优点

  • 可以复用线上接口数据
  • 接口之间存在联动和业务逻辑

缺点

  • 需要安装额外的插件
  • 需要在项目之外做额外的配置

devServer 代理转发

  • 页面静态资源请求会到达 dev server ,然后直接返回静态资源
  • 通过 dve server 把接口代理转发到对应服务上

devServer 代理转发请求线上数据

问题

  • 当接口需要登陆时,可在浏览器插件中实现把对应环境 cookie 注入到开发服务页面下的功能

优点

  • 可以复用线上接口数据
  • 接口之间存在联动和业务逻辑

缺点

  • 需要安装额外的插件
  • 需要在项目之外做额外的配置

charles 代理转发

  • 页面静态资源会按照规则转发到 dev server上,然后直接返回静态资源
  • 接口请求会按照规则转发到远端服务

charles 代理转发

问题

  • 当接口需要登陆时,本地服务转发需要先注入对应环境的 cookie 才可以请求

优点

  • 可以复用线上接口数据
  • 接口之间存在联动和业务逻辑
  • 便于切换环境和账号

缺点

  • 需要安装额外的软件
  • 需要学习 charles 相关的配置

计算机网络-DNS

背景

通常通过 baidu.com 访问百度的服务,这个 baidu.com 就是百度持有的域名(Domain Name),然而在网络中能够识别的只有 IP ,那就需要把域名转化为对应的 IP 地址。

介绍

DNS 全名叫做 Domain Name System, 中文叫做 域名服务系统,主要作用是把域名转化为网络中对应的 IP 地址。

工作原理(以浏览器为例)

浏览器解析域名流程

  1. 浏览器解析出地址栏中的域名
  2. 在浏览器缓存中查找是否有缓存此域名的地址
  3. 在系统中是否有缓存此域名的地址
  4. 通过 UDP 的方式向系统中配置的 DNS 服务器查找域名对应的 IP 地址
  5. DNS 服务器通过 UDP 向根域名服务器查找域名地址,根域名服务器返回下一个查找的顶级域名服务器的 IP 地址
  6. DNS 服务器通过 UDP 向顶级域名服务器查找域名地址,顶级域名服务器返回下一个查找域名服务器的 IP 地址,不断查找,直到找到域名对应的 IP 地址为止
  7. DNS 服务器返回域名服务器的地址

总结

  • 域名分别在浏览器、操作系统、域名服务器三个层级做了缓存以提高访问速度
  • 获取域名时浏览器调用了操作系统的能力委托操作系统到操作系统配置的域名服务器上进行域名查找
  • 浏览器通过递归的方式分别去浏览器、操作系统和域名服务器上查找,而域名服务器是通过迭代的方式去各级域名服务器上进行查找的

各层级域名节点

  • 域名服务器具有层级关系
  • 每个层级的域名服务器都存储了根域名服务器的地址信息,这样方便快速查找域名服务的信息
  • 每个层级的域名服务器都需要注册到其对应的上一级域名服务器中

域名服务器节点关系图

域名服务数据更新

当域名服务系统数据有变化时就需要去更新对应的数据

  • 当根域名服务器地址有变化时,各级域名服务需要通过 TCP 的方式更新对应的数据
  • 当有新的子域名服务器注册或者域名信息注销时需要子域名通过 TCP 的方式把更新信息同步到当前域名服务

域名服务器更新数据图

服务端渲染

同构应用

环境区分

服务端和客户端所处环境有些许区别,打包时要根据不同环境打包出对应的资源

路由代码

  • 服务端从请求对象中获取路径,需要先从请求对象中提取出路径之后再通过上下文传递进应用之中
  • 客户端从全局路由对象中获取路径进行匹配

打包

  • 服务端的依赖可以直接从依赖中获取,而客户端则需要把所有依赖打包到对应的 chunk
  • 服务端不关心样式问题,而客户端需要加载对应的样式
  • 分环境进行打包时,会导致部分资源重复打包和覆盖

注水和脱水

服务端渲染时可能会通过接口请求数据,并保存准备好的数据状态,避免客户端做重复的请求

  • 脱水:服务端返回 HTML 时需要把数据状态通过字符串的形式保存在 HTML 字符串中。
  • 注水:客户端通过 HTML 中格式化后的数据初始化状态。

服务端请求

  • 通过配置文件的方式统一声明服务端需要请求的数据
  • 通过组件静态方法统一处理请求(需要框架遍历每个组件的属性去请求数据)

请求认证

服务端请求时不会自动携带客户端传递来的信息(例如 cookie) ,需要手动把请求中的字段提取出来放入服务端的请求中

样式处理

服务端渲染时不需要样式,但是客户端渲染时需要处理样式问题,若通过外部引入会造成在客户端样式的抖动

meta tags 处理

客户端和服务端渲染时有时需要动态修改 head 里的信息(SEO)

404 处理

当服务端匹配不到页面时,应该返回一个 404 的状态码和对应的内容

安全问题

上述注水和脱水过程中容易存在 script 脚本注入的风险,在序列化之前需要对对象做转义

性能优化

  • 缓存
  • 单服务改为服务集群
  • 服务压力过大时改成客户端渲染
  • 升级 Nodejs

简易的express中间件

express

基于 Node.js 平台,快速、开放、极简的 Web 开发框架

中间件

从请求到相应过程中执行的一系列函数被称为中间件,使其具有了极高的扩展性

注册

1
2
3
4
5
6
7
app.use(function middware1(req, res, next) {
// 业务逻辑
})

app.use('/test', function middware2(req, res, next) {
// 业务逻辑
})

使用

当请求 /test 时,会按照 middware1 -> middware2 的顺序依次执行注册的中间件

简易实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function isMathch(source, match) {
return source.startsWith(match)
}

const express = {
middwares: [],
use: function (path, fun) {
// 把注册的中间件放入 middwares 数组中
let handler = fun
let matchPath = path
if (typeof fun === 'undefined') {
matchPath = '/'
handler = path
}
this.middwares.push({ handler, path: matchPath })
},
call: function (req, res) {
// 从 middwares 中提取匹配的中间件执行
const { pathname = '/' } = req
const middwares = this.middwares.filter((item) =>
isMathch(pathname, item.path)
)
const len = middwares.length
let index = -1

// 通过 index 控制执行的中间件
function next(err) {
index++
if (index < len) {
middwares[index].handler(req, res, next)
}
}

return next()
},
}

参考

跨域

同源策略

当”协议+域名+端口”三者相同时,才能够相互访问资源。保证用户信息的安全,防止恶意的网站窃取数据。

限制

  • 当前域下的 js 脚本不能够访问其他域下的 cookielocalStorageindexDB 等本地存储
  • 当前域下的 js 脚本不能够操作访问操作其他域下的 DOM
  • 当前域下 ajax 无法发送跨域请求。

跨域访问

CORS

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器让运行在一个 origin (domain)上的 Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。

简单请求

简单请求不会触发 CORS 预检请求。需要满足以下条件才能成为简单请求

请求方法
  • HEAD
  • GET
  • POST
HTTP 的头信息

不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
请求过程
  1. 浏览器会直接发出 CORS 请求,它会在请求的头信息中增加一个 Orign 字段,该字段用来说明本次请求来自哪个源(协议+端口+域名)
  2. 服务器会根据 origin 值来决定是否同意这次请求
1
2
3
Access-Control-Allow-Origin: http://api.baidu.com  //  Orign 一致,必须要包含,可以为 *
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值

非简单请求过程

在正式通信之前进行一次 HTTP 查询请求,称为预检请求

请求过程
  1. 使用 OPTIONS 方法向服务端发起预检请求
1
2
Access-Control-Request-Method:该字段是必须的,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法。
Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段。
  1. 服务器在收到浏览器的预检请求之后,会根据头信息的三个字段来进行判断是否允许跨域
1
2
3
4
5
Access-Control-Allow-Origin: http://api.baidu.com  // 允许跨域的源地址(必须)
Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法(必须)
Access-Control-Allow-Headers: X-Custom-Header // 服务器支持的所有头信息字段(必须)
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Max-Age: 1728000 // 用来指定本次预检请求的有效期,单位为秒
  1. 默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie.
  2. Access-Control-Allow-Credentials 设置为 true
  3. Access-Control-Allow-Origin 设置为非 *

JSONP

利用<script> 标签没有跨域限制,通过<script>标签 src 属性,发送带有 callback 参数的 GET 请求,服务端将接口返回数据拼凑到 callback 函数中,返回给浏览器,浏览器解析执行,从而前端拿到 callback 函数返回的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let count = 0
function jsonp(src, callback) {
count++
const functionName = 'jsonpCallback' + count
// 回调执行函数
window[functionName] = function (res) {
callback && callback(res)
}
const script = document.createElement('script')
script.type = 'text/javascript'
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
const realSrc = src.includes('?')
? src + '&callback=' + functionName
: src + '?callback=' + functionName
script.src = realSrc
document.head.appendChild(script)
}

// 服务端实现
app.get('/test', (req, res) => {
const { callback } = req.query
res.send(`${callback}({ test: "a" })`)
})

通过 script 标签的形式获取到的数据会被自动执行,所以通过返回回调函数调用的方式自动执行代码

缺点

  • 具有局限性, 仅支持 get 方法
  • 不安全,可能会遭受 XSS 攻击

postMessage

特点

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的 iframe 消息传递
  • 上面三个场景的跨域数据传递

服务代理

服务端转发请求

document.domain + iframe

此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。

location.hash + iframe

a 欲与 b 跨域相互通信,通过中间页 c 来实现。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。

说明

  • A 与 B 不同域,A 向 B 传递消息时通过修改 B 页面的 hash 值,B 监听 hash 值的变化获取数据
  • A 与 C 同域,B 与 A 通信时通过修改 C 页面的 hash 值,C 通过各种手段通知 A hash 值的辩护

window.name + iframe

window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

WebSocket

通过 WebSocket 服务端中转通信

参考

术语

操作系统

  • 进程:资源分配的最小单位
  • 线程:CPU 调度的最小单位
  • 协程:协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。

网络

  • URL: Uniform Resource Locator, 统一资源定位符
  • URI: Uniform Resource Identifier, 统一资源标识符
  • FTP: File Transfer Protocol, 文件传输协议
  • CGI: Common Gateway Interface, 通用网关接口
  • DNS: Domain Name System, 域名服务系统
  • 协议栈: 操作系统内部的网络控制软件
  • MX: Mail eXchange, 邮件交换
  • UDP: User Datagram Protocol, 用户数据报协议
  • TCP: Transmission Control Protocol, 传输控制协议
  • ARP: Address Resolution Protocol, 地址解析协议
  • MTU: Maximum Transmission Unit, 最大传输单元
  • MSS: Maximum Segment Size, 最大分段大小
  • FCS: Frame Check Sequence, 帧校验序列
  • SFD: Start Frame Delimiter, 起始帧分界

其它

  • PaaS: Platform as a service, 平台即服务。PaaS 给用户提供的能力是使用由云服务提供商支持的编程语言、库、服务以及开发工具来创建、开发应用程序并部署在相关的基础设施上。用户无需管理底层的基础设施,包括网络、服务器,操作系统或者存储。他们只能控制部署在基础设施中操作系统上的应用程序,配置应用程序所托管的环境的可配置参数。常见的 PaaS 服务有数据库服务、web 应用以及容器服务。成熟的 PaaS 服务会简化开发人员,提供完备的 PC 端和移动端软件开发套件(SDK),拥有丰富的开发环境(Inteli、Eclipse、VS 等),完全可托管的数据库服务,可配置式的应用程序构建,支持多语言的开发,面向应用市场。
  • SaaS: Software as a Service, 软件即服务。SaaS 给用户提供的能力是使用在云基础架构上运行的云服务提供商的应用程序。可以通过轻量的客户端接口(诸如 web 浏览器(例如,基于 web 的电子邮件))或程序接口从各种客户端设备访问应用程序。 用户无需管理或控制底层云基础架构,包括网络,服务器,操作系统,存储甚至单独的应用程序功能,可能的例外是有限的用户特定应用程序配置设置。类似的服务有:各类的网盘(Dropbox、百度网盘等),JIRA,GitLab 等服务。而这些应用的提供者不仅仅是云服务提供商,还有众多的第三方提供商(ISV: independent software provider)。
  • IaaS: Infrastructure as a service, 基础设施即服务。用户可以在云服务提供商提供的基础设施上部署和运行任何软件,包括操作系统和应用软件。用户没有权限管理和访问底层的基础设施,如服务器、交换机、硬盘等,但是有权管理操作系统、存储内容,可以安装管理应用程序,甚至是有权管理网络组件。简单的说用户使用 IaaS,有权管理操作系统之上的一切功能。我们常见的 IaaS 服务有虚拟机、虚拟网络、以及存储。

参考

js沙箱

沙箱,即 sandbox,顾名思义,就是让你的程序跑在一个隔离的环境下,不对外界的其他程序造成影响,通过创建类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘产生永久性的影响。

使用场景

  • 解析或执行不可信的 JS
  • 隔离被执行代码的执行环境
  • 对执行代码中可访问对象进行限制

实现方式

with + new Function

with 的块级作用域下,变量访问会优先查找你传入的参数对象,之后再往上找,所以相当于你变相监控到了代码中的“变量访问”,结合 proxy 代理可以监听所有数据的修改和访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function proxyFanctory(obj) {
const target = new Proxy(obj, {
get(target, p) {
if (p === Symbol.unscopables) {
return undefined
}
if (target[p] === undefined) {
return undefined
}
return target[p]
},
set(target, p, value) {
target[p] = value
},
})

return target
}

function runWithSanbox(code) {
const context = proxyFanctory({ a: 'test', b: {} })
const codeSrc = `with(sandbox){${code}}`
return new Function('sandbox', codeSrc).call(context, context)
}

runWithSanbox(
'this.b.__proto__.toString=function(){console.log("__proto__.toString")};console.log(a, this)'
) // test Proxy {a: "test"}
const t = {}
t.toString() // __proto__.toString
需要解决的问题
  • proxy 无法监听深层次数据的访问,可以通过访问原型链的方式,实现沙箱逃逸(代码分析)
  • window 等固有对象调用(iframe 创建隔离的上下文)

借助 iframe 实现

sandboxh5 的提出的一个新属性, 启用方式就是在 iframe 标签中使用 sandbox 属性

1
<iframe sandbox src="url"></iframe>
限制
  • script 脚本不能执行, allow-scripts 属性
  • 不能发送 ajax 请求,allow-same-origin 允许同域请求

具体参考 iframe

Web Worker

Web Worker 子线程的形式也是一种天然的沙箱隔离

限制
  • 出于线程安全设计考虑,Web Worker 不支持 DOM 操作,必须通过 postMessage 通知 UI 主线程来实现。
  • Web Worker 无法访问 windowdocument 之类的浏览器全局对象。

通过代理将具体的渲染实现再转发给原 WorkerDomNodeImpl.js 逻辑来实现 DOM 的实际更新。

参考

浏览器渲染过程详解

渲染流程

  1. 浏览器通过网络请求后获取 html 数据,通过 tcp 传给浏览器进程,然后浏览器进程再传递给渲染器进程
  2. DOM - DOM 结构: 主线程将 html 解析构造 DOM
  3. style - 样式: 主线程解析页面的 CSS 从而确定每个 DOM 节点的计算样式(computed style)。
  4. layoutTree - 布局树: dom+style 根据 dom 树和样式生成 layoutTree
  5. paint - 绘制: 通过遍历 Layout Tree 生成绘制顺序表
  6. laryer - 布局: 主线程将 layoutTree 和绘制信息表传给合成器线程
  7. 合成器线程: 将得到的信息分图层分成更小的图块
  8. 栅格线程: 将更小的图块进行栅格化 raster,返还给合成器线程 draw quads 图块信息,存储在 GPU
  9. 合成线程会收集图块上面叫做绘画四边形(draw quads)的信息来构建一个合成帧(compositor frame)。
  10. 合成线程就会通过 IPC 向浏览器进程(browser process)提交(commit)一个渲染帧。
  11. 浏览器进程收到一帧的图像后传给 GPU 进行渲染

非快速滚动区域 - non-fast scrollable region

当一个页面被合成的时候,合成线程会将页面那些注册了事件监听器的区域标记为“非快速滚动区域”(Non-fast Scrollable Region)。当用户事件发生在这些区域时,合成线程会将输入事件发送给主线程来处理。如果输入事件不是发生在非快速滚动区域,合成线程就无须主线程的参与来合成一个新的帧。

问题

body 元素绑定了事件监听器后其实是将整个页面都标记为一个非快速滚动区域,这就意味着即使你页面的某些区域压根就不在乎是不是有用户输入,当用户输入事件发生时,合成线程每次都会告知主线程并且会等待主线程处理完它才干活。因此这种情况下合成线程就丧失提供流畅用户体验的能力了(smooth scrolling ability)。

解决办法

可以为事件监听器传递 passive:true 选项。 这个选项会告诉浏览器您仍要在主线程中侦听事件,可是合成线程也可以继续合成新的帧。

查找事件的目标对象(event target)

当合成线程向主线程发送输入事件时,主线程要做的第一件事是通过命中测试(hit test)去找到事件的目标对象(target)。具体的命中测试流程是遍历在渲染流水线中生成的绘画记录(paint records)来找到输入事件出现的 x, y 坐标上面描绘的对象是哪个。

相关概念

DOM 对象

既是浏览器对当前页面的内部表示,也是 Web 开发人员通过 JavaScript 与网页进行交互的数据结构以及 API

光栅化(rasterizing)

将以上文档结构,元素的样式,元素的几何信息以及它们的绘画顺序转化为显示器的像素的过程

绘画四边形

包含图块在内存的位置以及图层合成后图块在页面的位置之类的信息。

合成帧

代表页面一个帧的内容的绘制四边形集合。

参考