前端性能精进之浏览器呈现:资源和渲染

2023-05-1014:37:57WEB前端开发Comments942 views字数 6594阅读模式

呈现一个页面时,在浏览器中会打开众多进程,包括浏览器、渲染、插件、GPU、网络等进程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

浏览器进程负责存储、界面、下载等管理。在渲染进程中,运行着熟知的主线程、合成线程、JavaScript 解释器、排版引擎等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

而呈现一个页面大致可分为 4 个步骤:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

  1. 浏览器进程处理用户在地址栏的输入,然后将 URL 发送给网络进程。
  2. 网络进程发送 URL 请求,在接收到响应数据后进行解析,接着转发给浏览器进程。
  3. 浏览器进程收到响应后,发送“提交导航”消息到渲染进程。
  4. 渲染进程开始接收网络进程发送的数据,并进行文档渲染。

基于上述步骤可以联想到,呈现的优化分为两部分:资源和渲染。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

像上一节的图像其实也属于资源部分,只是内容比较多就单独创建了章节。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

本文所用的示例代码已上传至 Github。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

一、资源

HTTP Archive 关于 2022 年页面大小的报告指出,按大小升序后,排在中间位置的移动页面大概有 70 个请求。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

包括 22 个图像、21 个脚本、7 个 CSS以及 2 个 HTML,脚本和 CSS 占了 40% 的请求。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

除了对这些资源进行尺寸优化之外,还可以对它们的加载进行优化。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

1)优先级文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

浏览器会给不同资源给予不同的请求优先级。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

以 Chrome 为例,分为多个等级,包括 Highest 、High、Low 和 Lowest 等,如下图所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

HTML 和 head 元素中的 CSS 优先级是最高的,head 元素中的脚本是高优先级,异步请求的脚本是低优先级。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

若优先级不符合预期,可以通过一些配置修改优先级,例如为 script 元素声明 async/defer,它的优先级就会变成低。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

在 img 元素中,新增了一个 fetchPriority 属性(如下所示),当值是 high 时,意味着这是一张重要的图像,浏览器会提升优先级立即开始请求。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

<img src="hero.png" fetchpriority="high" />

2)link 元素文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

link 元素常用来加载 CSS 文件,但它还支持些其他功能,接下来会一一介绍。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

当 link 的 rel 属性值为 preload 时,就能预加载资源,如下所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

<link rel="preload" href="demo.js" as="script" />

as 属性是告知浏览器加载的资源类型,包括 style、script、font、image 等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

预加载可提升资源的优先级,不过当资源在几秒后未使用时,浏览器会发出告警。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

当 link 的 rel 属性值为 preconnect 时,就能预连接站点,如下所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

<link rel="preconnect" href="https://www.pwstrick.com" />

另一个与连接相关的类型是 dns-prefetch(如下所示),用来处理 DNS 查询,即 DNS 预解析。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

<link rel="dns-prefetch" href="https://www.pwstrick.com" />

当 link 的 rel 属性值为 prefetch 时,就能预提取资源,如下所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

<link rel="prefetch" href="demo.js" />

预提取会让资源的优先级降为最低,用于让某些非关键资源提前请求,可为用户的下一步交互做准备。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

2023-03-23 当 link 的 rel 属性值为 prerender 时,就能预渲染指定的网站,如下所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

<link rel="prerender" href="https://www.pwstrick.com" />

不过,该参数的兼容性有限,Safari 和 Firefox 都不支持,如下图所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

有个名为 Tachyon 的开源库,基于 prerender,对页面之间的导航进行了提速。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

在用户将鼠标移动到链接时,会通过创建 link 元素,并赋予 prerender,实现指定地址的预渲染。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

3)script 元素文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

延迟(defer)和异步(async)的出现是为了解决 script 元素阻塞 HTML 解析的问题,下图描绘了 script 元素的 3 种运行机制。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

第一行是默认的运行机制,在解析HTML文档时,一遇到 script 元素就停止解析,改成下载外部脚本,然后执行脚本,执行完后才会继续解析。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

第二行是使用了 defer 属性后的运行机制,HTML 文档的解析和外部脚本的下载是同时进行的,解析完后才会执行脚本。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

第三行是使用了async 属性后的运行机制,HTML 文档的解析和外部脚本的下载也是同时进行,但下载完后就开始执行脚本,执行完后才会继续解析。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

4)数据预请求文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

在客户端的 WebView 中,每次请求后端接口大概要花 100~200ms,如果把这段时间省下来,那么也能减少白屏时间。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

数据预请求是将请求时机由业务发起提前到用户点击时,并行发送数据请求,缩短数据等待时间,如下图所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

这种改造需要客户端配合,现在简单介绍下我们公司当时实现的方案,流程图如下所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

首屏数据的接口信息,可以通过一些配置关联起来,比如一个单独的配置接口。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

客户端在拿到数据后,就会缓存到一个全局变量中,等待脚本读取。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

注意,到底是客户端先拿到数据,还是网页先拿到,这个无法确定,并且预请求只能以 get 方法通信。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

具体的实现方案如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

  • 客户端分析出当前 URL 中的路径和参数,其中 refresh 参数(有的话)是一个时间戳(秒),这个参数用来控制客户端是否需要重新请求配置接口。
  • 当分析的 URL 参数中无 refresh 字段时,访问 https://xxx.com/settings 接口,并将URL路径、客户端默认带的参数(包含用户ID等)和 URL 本身的参数全部传递过来(如下所示),然后本地缓存。
https://xxx.com/settings?path=game%2Fstrick&uid=xxxxx&refresh=1618451992
  • 客户端会将 settings 接口的响应数据缓存到本地,而 key 就是当前 URL,也就是说 URL 不变的话,默认就不会去请求 settings 接口。若要穿透缓存,那么加上 refresh 参数,赋一个与之前不同的值即可。
  • settings 接口返回的 JSON 格式,包含 urls 字段(如下所示),是个数组,由接口集合组成,已经拼接好参数。
{
    "urls": [
        "http://xxx.com/xx/xx?id=2",
        "http://xxx.com/yy/yy?uid=1"
    ]
}
  • 客户端将读取到的数据注入到 WebView 的全局对象中,可以用全局变量同步读取,名字可自行约定,例如叫 TheLClientResponse,读取方式:window.TheLClientResponse,JSON 格式如下,其中 key 是 api 的路径,如果无数据可以返回 null。
{
    "xx/xx": {
        code: 0,
        msg: "test",
        data: {
            list: []
        }
    },
    "yy/yy": {
        code: 0,
        msg: "test",
        data: {
            list: []
        }
    }
}

5)字体文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

CSS3 提供了 @font-face 规则允许为网页指定自定义字体,其声明和使用如下所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

@font-face {
  font-family: "iconfont";
  src: url("../font/iconfont.woff2") format("woff2"),
    url("../font/iconfont.woff") format("woff"),
    url("../font/iconfont.ttf") format("truetype");
}
.iconfont {
  font-family: "iconfont";
}

上述字体来源于 iconfont,为了兼容性考虑,往往会提供多个格式的字体。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

其中 ttf 是一种未压缩的格式,另外两种内部都做过压缩。在 2022 年大概有 75%~78% 的网页在使用 woff2 格式的字体。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

使用字体除了改变文字外形之外,还有一种普遍用法是用来显示 icon 小图标。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

CSS3 提供了 font-display 属性用于指定字体的渲染方式,在 @font-face 中声明,2022 年用的最多的值是 swap。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

swap 会让文字先按浏览器默认的字体展示,当字体加载完成后,再将其替换掉。在慢网中,会看到字体的前后变化。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

所以应该尽快加载字体,才能让用户享受到最优的体验。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

浏览器在解析 CSS 文件时,并不会马上下载 @font-face 中的字体文件。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

只有当发现 HTML 中有非空节点使用该字体时,才会开始下载。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

如果要提早下载,那么可以使用预加载,如下所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

<link rel="preload" href="../../assets/font/dakai.woff2" as="font" crossorigin="anonymous"/>

crossorigin 属性是必填的,表示允许跨域,若省略,就会有告警。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

还有一种优化方法是提取字体的子集(即有选择性的将需要的字符组合在一起),减小字体文件的尺寸,像图标就比较适合这样自定义。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

二、渲染过程

浏览器的渲染过程大致可分为 8 个阶段,如下图所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

下面的 1~5 步涉及主线程(main thread),6~8 步涉及合成线程(compositor thread)。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

  1. 将 HTML 解析成 DOM 树,并将其存储在内存中,同时下载解析到的资源。
  2. 将 CSS 解析成样式表(style sheets),即生成 CSSOM,在此阶段会计算节点样式,并把相对的值和单位都转换成像素。
  3. 通过 DOM 和样式表生成布局树(layout tree),在此阶段会计算元素的尺寸和坐标,并且在树中不包含隐藏元素,但会包含 CSS 中创建的内容。
  4. 对布局树进行分层,生成分层树(layer tree),可控制绘画顺序,裁剪元素内容,CSS 中的 transform、z-index、will-change 等属性都与层相关。
  5. 通过布局树和分层树生成绘制列表,并将其提交给合成线程。
  6. 通过绘制列表和图层生成图块(tile),因为渲染所有图块会比较昂贵,所以会划分优先级,例如视口中的可见图块优先级会高。
  7. 图块在提交到光栅化(raster)线程池后,会被转移到 GPU 中,加速光栅化处理,即转换成位图(bitmap),最终结果会存储在 GPU 内存中。
  8. GPU 将位图传送回合成线程后,就会生成合成帧,处理完所有位图后,合成器线程向浏览器发送 Draw Quad 命令,开始在屏幕上显示页面。

虽然这 8 个阶段的执行过程比较复杂,但是在现代浏览器中,它们会在 1/60 秒(即 16.67 毫秒)内完成,下图描述了整个渲染过程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

优化渲染过程的核心就是缩短某个阶段的执行时间,或者直接跳过某些阶段。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

1)流式渲染文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

HTTP/1.1 协议支持分块传输编码(chunked transfer encoding),允许服务器将网页数据分成多块后再进行传输。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

在响应头中设置 Transfer-Encoding: chunked 就会启用分块传输编码的响应格式。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

浏览器在知道 HTML 会被流式返回后,就不用等到 HTML 下载完成后再开始解析了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

不过,目前流行的客户端渲染(Client Side Render)其实并不需要专门的流式渲染,因为 HTML 的内容本来就少。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

若改成服务端渲染(Server Side Render),那就可根据实际情况进行流式渲染的优化了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

具体的实现过程,本文不再赘述,可参考网上相关的方案,例如 Vue SSR 指南中的流式渲染。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

2)DOM文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

HTML 在被解析时,一旦遇到 JavaScript,那么就会被阻塞,如下图所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

当遇到外部脚本时,还会停止 DOM 树的构建,转由网络进程去请求 JavaScript 脚本地址。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

CSS 本身并不会阻塞 DOM 树的构建,但在与 JavaScript 结合使用时,会出现阻塞。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

在下面的示例中,JavaScript 会修改 demo.css 文件中的样式。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

<link rel="stylesheet" href="demo.css" />
<div id='root'>内容</div>
<script>
  const root = document.getElementById('root');
  root.style.color = 'red';
</script>

主线程在执行脚本之前,需要先计算节点样式(即解析 CSS 文件),因此 DOM 树就无法被继续构建了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

若要优化 DOM 树的构建,除了尽量避免上述不科学的写法之外,还可以从两方面入手:减少关键资源请求的数量和大小。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

所谓关键资源(key resource),更确切的说就是网页首屏的核心资源,没有它们,那么首屏将无法正确的呈现。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

减少资源的请求数量可以通过 2 个方法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

  • 将 CSS 或 JavaScript 内联到 HTML 结构中,例如移动端的屏幕适配脚本就比较适合内联。
  • 脚本元素可以增加 async 或 defer 的标记,具体可以参考上一节的 script 元素。

关键资源的大小除了进行压缩外,就是只提取首屏需要的代码。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

将其他部分的代码合并到另一个文件,待需要时再加载,或者使用上一节所说的预提取。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

3)重排和重绘文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

重排(reflow)也叫回流,是指修改元素的几何属性后引起的重新渲染,涉及 7 个阶段,如下图所示,修改了元素的高度。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

触发重排的情况有添加或删除可见的元素、修改位置、边距或内容等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

重绘(repaint)是指修改元素的背景颜色后引起的重新渲染,但与重排不同,重绘将直接进入 Paint 阶段,如下图所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

重排和重绘都会降低渲染性能,因为它们都发生在主线程中,并且布局、分层和绘制 3 个阶段的计算过程比较昂贵。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

当在脚本中获取元素的尺寸、位置等排版相关的信息时,就有可能触发强制重排,例如调用 offsetTop、clientWidth、getComputedStyle() 等属性或方法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

优化它们的方式包括使用 cssText 或 CSS 类修一次性修改多个 CSS 属性,批量修改 DOM,例如使用文档片段 fragment、先隐藏元素再显示等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

在众多的 CSS 属性中,有两个 CSS 属性(transform 和 opacity)可以避开重排和重绘,直接进入合成阶段。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

例如用 transform 属性实现的元素变化,就不会占用主线程,而是由合成线程处理,如下图所示。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

前端性能精进之浏览器呈现:资源和渲染

值得一提的是,早期在脚本中实现动画,都会借助定时器,但定时器无法精确的配置动画帧之间的时间间隔。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

按屏幕刷新率为每秒 60 次计算,那么理论上每帧的间隔约等于是 16.67 毫秒。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

但实际情况比较复杂,间隔不一定是这个值,有可能出现丢帧,从而造成动画不够平滑流畅。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

为了解决动画问题,浏览器提供了 requestAnimationFrame() 方法,在每一帧的开始执行配置的回调。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

注意,只有当浏览器 GPU 生成位图和屏幕显示位图保持同步时,才会触发 requestAnimationFrame() 的回调。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

在下面的示例中,让绝对定位的 span 元素通过 requestAnimationFrame() 向右偏移。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

<span id='container' style="position:absolute">内容</span>
<script>
  let left = 0;
  const frame = () => {
    const container = document.getElementById('container');
    container.style.left = `${left++}px`;
    if (left > 100) return;
    requestAnimationFrame(frame);
  };
  requestAnimationFrame(frame);
</script>

注意,requestAnimationFrame() 也是运行在主线程中,如果主线程繁忙,那么也有可能延迟回调,造成动画的卡顿。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

并且如果其回调比较耗时(超过一帧),那么就会阻碍后续的任务。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

总结

本文的第一章节详细描述了资源的优化,并在开篇指出资源都存在着优先级,浏览器会按优先级进行请求。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

预加载可提升资源的优先级,预提取可降低资源的优先级,预连接可提前进行 TCP 连接或 DNS 查询。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

script 元素有延迟和异步两种运行机制,可有效地防止 HTML 解析的阻塞。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

数据预请求需要与客户端配合,本文给出了一份解决方案可供参考。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

自定义字体在页面开发中有着广泛的应用,常用的优化手段是预加载和减小尺寸。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

在第二章节中详细分析了浏览器的渲染过程,这个过程大致可分为 8 个阶段。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

围绕这些阶段,引出了流式渲染、DOM 树构建的优化。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

在重排和重绘中,详细说明了它们影响的阶段,并且列举了触发原因,以及优化手段。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

最后提到了合成动画,并且对比了 JavaScript 动画的两种实现方式。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/39064.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/gcs/39064.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定