wwWfb3f显示“250ppcom页面升级浏览不到里面fb3fcom的内容”怎么解决?

热门车型:
努力加载数据中,请稍后...
热门品牌:
热门关注:
手机配件:
热门品牌:
热门关注:
电脑整机:
笔记本配件:
热门品牌:
热门关注:
热门关注:
智能穿戴:
智能家居:
装机硬件:
外设配件:
热门品牌:
热门关注:
相机配件:
数码产品:
数码配件:
热门品牌:
热门关注:
平板电视:
家庭影音:
白色家电:
厨卫/小家电:
办公打印:
投影设备:
网络设备:
无线网络:
汽车电子:
各地报价:
热门车系:
商家促销:
 |  |  |
热门地区推荐经销商
努力加载数据中,请稍后...
玩转IT必须懂
微信身份证正式上线:日,广州市公安局南沙区分局签发全国首张微信身份证“网...
随着三大运营商的流量之争日趋激烈,为抢夺新用户,迎合客户需求,联通纷纷联合各大互联网...
苹果电池膨胀事件还在不断发酵,到底是5%范围内的可接受意外,还是去年三星Note7爆炸门的...
努力加载数据中,请稍后...
公众号推荐
微信名:VR饭
简介:虚拟现实?福利资源?VR眼镜?你想知道的都在VR饭!
微信名:摄影部落
简介:欣赏数百套精美摄影作品,海量摄影技巧及PS教程分享
微信名:VRfine
简介:分享和推荐最好玩的VR玩法!
微信名:聚超值
简介:致力于帮助广大网友,买到更有性价比网购产品的分享平台
微信名:太平洋电脑网
简介:DIY装机进“数码帮”,发名称可查数码产品参数和报价
您可能感兴趣的车
努力加载数据中,请稍后...
太平洋电脑网
太平洋汽车网
太平洋时尚网
太平洋亲子网
太平洋家居网&figure&&img src=&https://pic2.zhimg.com/v2-cd050dcd89e445f0561feaca320e3611_b.jpg& data-rawwidth=&894& data-rawheight=&518& class=&origin_image zh-lightbox-thumb& width=&894& data-original=&https://pic2.zhimg.com/v2-cd050dcd89e445f0561feaca320e3611_r.jpg&&&/figure&&blockquote&作者简介 felix 蚂蚁金服·数据体验技术团队&/blockquote&&p&本文主要介绍preload的使用,以及与prefetch的区别。然后会聊聊浏览器的加载优先级。&/p&&p&preload 提供了一种声明式的命令,让浏览器提前加载指定资源(加载后并不执行),在需要执行的时候再执行。提供的好处主要是&/p&&ul&&li&将加载和执行分离开,可不阻塞渲染和 document 的 onload 事件&/li&&li&提前加载指定资源,不再出现依赖的font字体隔了一段时间才刷出&/li&&/ul&&h2&如何使用 preload&/h2&&h2&使用 link 标签创建&/h2&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&!-- 使用 link 标签静态标记需要预加载的资源 --&
&link rel=&preload& href=&/path/to/style.css& as=&style&&
&!-- 或使用脚本动态创建一个 link 标签后插入到 head 头部 --&
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'style';
link.href = '/path/to/style.css';
document.head.appendChild(link);
&/code&&/pre&&/div&&h2&使用 HTTP 响应头的 Link 字段创建&/h2&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Link: &https://example.com/other/styles.css&; rel= as=style
&/code&&/pre&&/div&&p&如我们常用到的 antd 会依赖一个 CDN 上的 font.js 字体文件,我们可以设置为提前加载,以及有一些模块虽然是按需异步加载,但在某些场景下知道其必定会加载的,则可以设置 preload 进行预加载,如:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&link rel=&preload& as=&font&
href=&https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff&&
&link rel=&preload& as=&script& href=&https://a.xxx.com/xxx/PcCommon.js&&
&link rel=&preload& as=&script& href=&https://a.xxx.com/xxx/TabsPc.js&&
&/code&&/pre&&/div&&h2&如何判断浏览器是否支持 preload&/h2&&p&目前我们支持的浏览器主要为高版本 Chrome,所以可放心使用 preload 技术。 其他环境在 &a href=&https://link.zhihu.com/?target=http%3A//caniuse.com& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&caniuse.com&/span&&span class=&invisible&&&/span&&/a& 上查到的支持情况如下:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-c6d511b432fd83f4d7632ded2d41c02e_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&932& data-rawheight=&418& class=&origin_image zh-lightbox-thumb& width=&932& data-original=&https://pic4.zhimg.com/v2-c6d511b432fd83f4d7632ded2d41c02e_r.jpg&&&/figure&&p&在不支持 preload 的浏览器环境中,会忽略对应的 link 标签,而若需要做特征检测的话,则:&/p&&p&&br&&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const isPreloadSupported = () =& {
const link = document.createElement('link');
const relList = link.relL
if (!relList || !relList.supports) {
return relList.supports('preload');
&/code&&/pre&&/div&&h2&如何区分 preload 和 prefetch&/h2&&ul&&li&preload
是告诉浏览器页面&b&必定&/b&需要的资源,浏览器&b&一定会&/b&加载这些资源;&/li&&li&prefetch 是告诉浏览器页面&b&可能&/b&需要的资源,浏览器&b&不一定会&/b&加载这些资源。&/li&&/ul&&p&preload 是确认会加载指定资源,如在我们的场景中,x-report.js 初始化后一定会加载 PcCommon.js 和 TabsPc.js, 则可以预先 preload 这些资源;&/p&&p&prefetch 是预测会加载指定资源,如在我们的场景中,我们在页面加载后会初始化首屏组件,当用户滚动页面时,会拉取第二屏的组件,若能预测用户行为,则可以 prefetch 下一屏的组件。&/p&&h2&preload 将提升资源加载的优先级&/h2&&p&使用 preload 前,在遇到资源依赖时进行加载:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-aebd247bc2e3a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&496& data-rawheight=&75& class=&origin_image zh-lightbox-thumb& width=&496& data-original=&https://pic4.zhimg.com/v2-aebd247bc2e3a_r.jpg&&&/figure&&p&使用 preload 后,不管资源是否使用都将提前加载:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-534c30e80ed_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&483& data-rawheight=&75& class=&origin_image zh-lightbox-thumb& width=&483& data-original=&https://pic4.zhimg.com/v2-534c30e80ed_r.jpg&&&/figure&&p&&br&&/p&&p&可以看到,preload 的资源加载顺序将被提前:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-93f8daac0dd02538eadc15a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&522& data-rawheight=&193& class=&origin_image zh-lightbox-thumb& width=&522& data-original=&https://pic3.zhimg.com/v2-93f8daac0dd02538eadc15a_r.jpg&&&/figure&&p&&br&&/p&&h2&避免滥用 preload&/h2&&p&使用 preload 后,Chrome 会有一个警告:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-294cc968f08d2eaf52a4acaa_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&782& data-rawheight=&34& class=&origin_image zh-lightbox-thumb& width=&782& data-original=&https://pic3.zhimg.com/v2-294cc968f08d2eaf52a4acaa_r.jpg&&&/figure&&p&&br&&/p&&p&如上文所言,若不确定资源是必定会加载的,则不要错误使用 preload,以免本末导致,给页面带来更沉重的负担。&/p&&p&当然,可以在 PC 中使用 preload 来刷新资源的缓存,但在移动端则需要特别慎重,因为可能会浪费用户的带宽。&/p&&h2&避免混用 preload 和 prefetch&/h2&&p&preload 和 prefetch 混用的话,并不会复用资源,而是会重复加载。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&link rel=&preload&
href=&https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff& as=&font&&
&link rel=&prefetch&
href=&https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff& as=&font&&
&/code&&/pre&&/div&&p&使用 preload 和 prefetch 的逻辑可能不是写到一起,但一旦发生对用一资源 preload 或 prefetch 的话,会带来双倍的网络请求,这点通过 Chrome 控制台的网络面板就能甄别:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-26cde7a379c6c55fa01fe_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&649& data-rawheight=&111& class=&origin_image zh-lightbox-thumb& width=&649& data-original=&https://pic4.zhimg.com/v2-26cde7a379c6c55fa01fe_r.jpg&&&/figure&&p&&br&&/p&&h2&避免错用 preload 加载跨域资源&/h2&&p&若 css 中有应用于已渲染到 DOM 树的元素的选择器,且设置了 @font-face 规则时,会触发字体文件的加载。 而字体文件加载中时,DOM 中的这些元素,是处于不可见的状态。对已知必加载的 font 文件进行预加载,除了有性能提升外,更有体验优化的效果。&/p&&p&在我们的场景中,已知 antd.css 会依赖 font 文件,所以我们可以对这个字体文件进行 preload:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&link rel=&preload& as=&font& href=&https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff&&
&/code&&/pre&&/div&&p&然而我发现这个文件加载了两次:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-49ecba5aac6bbbd1fac6fdb_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&712& data-rawheight=&111& class=&origin_image zh-lightbox-thumb& width=&712& data-original=&https://pic2.zhimg.com/v2-49ecba5aac6bbbd1fac6fdb_r.jpg&&&/figure&&figure&&img src=&https://pic3.zhimg.com/v2-0e37cedf5dfcb76a1e0806_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1039& data-rawheight=&75& class=&origin_image zh-lightbox-thumb& width=&1039& data-original=&https://pic3.zhimg.com/v2-0e37cedf5dfcb76a1e0806_r.jpg&&&/figure&&figure&&img src=&https://pic2.zhimg.com/v2-6dba2b3ea1c35b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1047& data-rawheight=&75& class=&origin_image zh-lightbox-thumb& width=&1047& data-original=&https://pic2.zhimg.com/v2-6dba2b3ea1c35b_r.jpg&&&/figure&&p&&br&&/p&&p&原因是对跨域的文件进行 preload 的时候,我们必须加上 crossorigin 属性:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&link rel=&preload& as=&font& crossorigin href=&https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff&&
&/code&&/pre&&/div&&p&再看一下网络请求,就变成一条了。&/p&&p&W3 规范是这么解释的:&/p&&blockquote&Preload links for CORS enabled resources, such as fonts or images with a crossorigin attribute, must also include a crossorigin attribute, in order for the resource to be properly used.&/blockquote&&p&那为何会有两条请求,且优先级不一致,又没有命中缓存呢?这就得引出下一个话题来解释了。&/p&&h2&不同资源加载的优先级规则&/h2&&p&我们先来看一张图:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-8f9b8f536b1d8e35cb278d718f894819_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&687& data-rawheight=&601& class=&origin_image zh-lightbox-thumb& width=&687& data-original=&https://pic4.zhimg.com/v2-8f9b8f536b1d8e35cb278d718f894819_r.jpg&&&/figure&&p&&br&&/p&&blockquote&这张表详见:Chrome Resource Priorities and Scheduling&/blockquote&&p&这张图表示的是,在 Chrome 46 以后的版本中,不同的资源在浏览器渲染的不同阶段进行加载的优先级。 在这里,我们只需要关注 DevTools Priority 体现的优先级,一共分成五个级别:&/p&&ul&&li&Highest 最高&/li&&li&Hight 高&/li&&li&Medium 中等&/li&&li&Low 低&/li&&li&Lowest 最低&/li&&/ul&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-ff5b64df5bf10d6bb245d7_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&689& data-rawheight=&136& class=&origin_image zh-lightbox-thumb& width=&689& data-original=&https://pic1.zhimg.com/v2-ff5b64df5bf10d6bb245d7_r.jpg&&&/figure&&p&&br&&/p&&h2&html 主要资源,其优先级是最高的&/h2&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-6d743a9fdefc342ffb300fdd730ee2dc_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&686& data-rawheight=&165& class=&origin_image zh-lightbox-thumb& width=&686& data-original=&https://pic1.zhimg.com/v2-6d743a9fdefc342ffb300fdd730ee2dc_r.jpg&&&/figure&&figure&&img src=&https://pic4.zhimg.com/v2-294ea3dcd_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&501& data-rawheight=&69& class=&origin_image zh-lightbox-thumb& width=&501& data-original=&https://pic4.zhimg.com/v2-294ea3dcd_r.jpg&&&/figure&&p&&br&&/p&&h2&css 样式资源,其优先级也是最高的&/h2&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-3d1a5fb3cda2e50e92daa14e5e928bb2_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&684& data-rawheight=&182& class=&origin_image zh-lightbox-thumb& width=&684& data-original=&https://pic3.zhimg.com/v2-3d1a5fb3cda2e50e92daa14e5e928bb2_r.jpg&&&/figure&&p&CSS(match) 指的是对已有的 DOM 具备规则的有效的样式文件。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-3c4dbf263e061d61b6baf4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&475& data-rawheight=&191& class=&origin_image zh-lightbox-thumb& width=&475& data-original=&https://pic2.zhimg.com/v2-3c4dbf263e061d61b6baf4_r.jpg&&&/figure&&p&&br&&/p&&h2&script 脚本资源,优先级不一&/h2&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-a6f2ad04f4d531acfadd5_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&686& data-rawheight=&200& class=&origin_image zh-lightbox-thumb& width=&686& data-original=&https://pic1.zhimg.com/v2-a6f2ad04f4d531acfadd5_r.jpg&&&/figure&&figure&&img src=&https://pic3.zhimg.com/v2-4e1d4a13ee8061cb6fcffa9ef7cd474f_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&476& data-rawheight=&247& class=&origin_image zh-lightbox-thumb& width=&476& data-original=&https://pic3.zhimg.com/v2-4e1d4a13ee8061cb6fcffa9ef7cd474f_r.jpg&&&/figure&&p&前三个 js 文件是写死在 html 中的静态资源依赖,后三个 js 文件是根据首屏按需异步加载的组件资源依赖,这正验证了这个规则。&/p&&p&&br&&/p&&h2&font 字体资源,优先级不一&/h2&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-ddf156e810e_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&686& data-rawheight=&164& class=&origin_image zh-lightbox-thumb& width=&686& data-original=&https://pic4.zhimg.com/v2-ddf156e810e_r.jpg&&&/figure&&figure&&img src=&https://pic4.zhimg.com/v2-f763eb751bd1b2e44535adec8836920b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&472& data-rawheight=&114& class=&origin_image zh-lightbox-thumb& width=&472& data-original=&https://pic4.zhimg.com/v2-f763eb751bd1b2e44535adec8836920b_r.jpg&&&/figure&&p&css 样式文件中有一个 @font-face 依赖一个 font 文件,样式文件中依赖的字体文件加载的优先级是 Highest; 在使用 preload 预加载这个 font 文件时,若不指定 crossorigin 属性(即使同源),则会采用匿名模式的 CORS 去加载,优先级是 High,看下图对比: 第一条 High 优先级也就是 preload 的请求:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-5ebc98d89_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2396& data-rawheight=&1188& class=&origin_image zh-lightbox-thumb& width=&2396& data-original=&https://pic4.zhimg.com/v2-5ebc98d89_r.jpg&&&/figure&&p&&br&&/p&&p&第二条 Highest 也就是样式引入的请求:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-95fc26caad7b97e23910_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2322& data-rawheight=&1162& class=&origin_image zh-lightbox-thumb& width=&2322& data-original=&https://pic4.zhimg.com/v2-95fc26caad7b97e23910_r.jpg&&&/figure&&p&&br&&/p&&p&可以看到,在 preload 的请求中,缺少了一个 origin 的请求头字段,表示这个请求是匿名的请求。 让这两个请求能共用缓存的话,目前的解法是给 preload 加上 crossorigin 属性,这样请求头会带上 origin, 且与样式引入的请求同源,从而做到命中缓存:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&&link rel=&preload& as=&font& crossorigin href=&https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff&&
&/code&&/pre&&/div&&p&这么请求就只剩一个:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-f9e34adab2da6d8799322_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&475& data-rawheight=&81& class=&origin_image zh-lightbox-thumb& width=&475& data-original=&https://pic1.zhimg.com/v2-f9e34adab2da6d8799322_r.jpg&&&/figure&&figure&&img src=&https://pic2.zhimg.com/v2-1c9a1a4257edbd2ebb46a79acda12d3b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&2298& data-rawheight=&1176& class=&origin_image zh-lightbox-thumb& width=&2298& data-original=&https://pic2.zhimg.com/v2-1c9a1a4257edbd2ebb46a79acda12d3b_r.jpg&&&/figure&&p&在网络瀑布流图中,也显示成功预加载且后续命中缓存不再二次加载:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-4f16d1e2ae33ca131b9a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&662& data-rawheight=&86& class=&origin_image zh-lightbox-thumb& width=&662& data-original=&https://pic2.zhimg.com/v2-4f16d1e2ae33ca131b9a_r.jpg&&&/figure&&p&&br&&/p&&h2&总结&/h2&&p&preload 是个好东西,能告诉浏览器提前加载当前页面必须的资源,将加载与解析执行分离开,做得好可以对首次渲染带来不小的提升,但要避免滥用,区分其与 prefetch 的关系,且需要知道 preload 不同资源时的网络优先级差异。&/p&&p&preload 加载页面必需的资源如 CDN 上的字体文件,与 prefetch 预测加载下一屏数据,兴许是个不错的组合。&/p&&p&参考资料:&/p&&ul&&li&&a href=&https://link.zhihu.com/?target=https%3A//www.w3.org/TR/preload/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&w3.org/TR/preload/&/span&&span class=&invisible&&&/span&&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//www.w3.org/TR/resource-hints/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&w3.org/TR/resource-hint&/span&&span class=&invisible&&s/&/span&&span class=&ellipsis&&&/span&&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//developers.google.com/web/updates/2016/03/link-rel-preload& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Prioritizing Your Resources with link rel='preload'&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//medium.com/reloading/preload-prefetch-and-priorities-in-chrome-bbf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Preload, Prefetch And Priorities in Chrome&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=https%3A//medium.com/reloading/a-link-rel-preload-analysis-from-the-chrome-data-saver-team-5edf54b08715& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&A Link: rel=preload Analysis From the Chrome Data Saver Team&/a&&/li&&/ul&&blockquote&团队提前给大家拜个早年,感谢大家对于专栏的支持~对团队感兴趣的同学可以关注专栏或者发送简历至'tao.qit####alibaba-inc.com'.replace('####', '@'),欢迎有志之士加入~&/blockquote&&p&原文地址:&a href=&https://link.zhihu.com/?target=https%3A//juejin.im/post/5a7fb09bf265da4e8e785c38& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&juejin.im/post/5a7fb09b&/span&&span class=&invisible&&f265da4e8e785c38&/span&&span class=&ellipsis&&&/span&&/a&&/p&
作者简介 felix 蚂蚁金服·数据体验技术团队本文主要介绍preload的使用,以及与prefetch的区别。然后会聊聊浏览器的加载优先级。preload 提供了一种声明式的命令,让浏览器提前加载指定资源(加载后并不执行),在需要执行的时候再执行。提供的好处主要是将加…
&p&正好最近正在翻译 Google 开发者中心在 Youtube 发布的“HTTP203”系列视频。&/p&&p&近期推出了 HTTP203 Advent 系列。Advent 就是“来临”、“即将到来”的意思。这是 HTTP203 圣诞节的特别版! Jake(@jaffathecake)和 Surma(@DasSurma)有 2 分钟的时间来描述他们对 2018 年的兴奋点。&/p&&p&我会不定期制作中文字幕,欢迎在 B 站或者优酷订阅:&/p&&ul&&li&B站:&a href=&//link.zhihu.com/?target=https%3A//space.bilibili.com//channel/detail%3Fcid%3D29740& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&HTTP 203 - 频道&/a&&/li&&li&优酷:&a href=&//link.zhihu.com/?target=http%3A//list.youku.com/albumlist/show/id_.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&HTTP 203 - 播单&/a&&/li&&/ul&&p&其中包括:&/p&&ul&&li&&a href=&//link.zhihu.com/?target=https%3A//github.com/WICG/background-fetch& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Background fetch&/a& - &b&后台获取&/b&。&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//github.com/WICG/animation-worklet& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Animation worklets&/a& - &b&动画 worklet&/b&&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//github.com/inexorabletash/web-locks/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Web locks&/a& - &b&Web 锁&/b&&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//dassur.ma/things/120fps/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Web architecture&/a& - &b&Web 架构&/b&&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//drafts.csswg.org/selectors-4/%23is& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Weightless CSS&/a& - &b&Weightless CSS&/b&&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//github.com/w3c/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Template instantiation&/a&&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//github.com/tc39/proposal-flatMap& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Flatmap & flatten&/a&.&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//jakearchibald.com/2017/async-iterators-and-generators/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Async iterators & generators&/a& - &b&异步迭代器和生成器&/b&&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//github.com/whatwg/fetch/issues/607& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Fetch observers&/a&.&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//developers.google.com/web/updates/2017/11/dynamic-import& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Dynamic imports&/a& - &b&动态导入&/b&&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//streams.spec.whatwg.org/%23ts& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Transform streams&/a&.&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//dassur.ma/things/120fps/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&The benefits of web workers&/a&.&/li&&/ul&&p&&/p&
正好最近正在翻译 Google 开发者中心在 Youtube 发布的“HTTP203”系列视频。近期推出了 HTTP203 Advent 系列。Advent 就是“来临”、“即将到来”的意思。这是 HTTP203 圣诞节的特别版! Jake(@jaffathecake)和 Surma(@DasSurma)有 2 分钟的时间来描述…
&figure&&img src=&https://pic3.zhimg.com/v2-a490ac0c9ef6eeaf9e4e3638edb4bcc0_b.jpg& data-rawwidth=&500& data-rawheight=&302& class=&origin_image zh-lightbox-thumb& width=&500& data-original=&https://pic3.zhimg.com/v2-a490ac0c9ef6eeaf9e4e3638edb4bcc0_r.jpg&&&/figure&&p&上篇讲到了整个框架大体思路,今天我想讲点细节,来点更有技术含量的。那就从CC(chromium 渲染合成层)开始讲吧,这块是chromium最复杂的部分,复杂到从一个 platform/graphics/cc 下的小目录单独提到最外层的根目录作为一个独立的组件。我打算分好几个章节来讲,实在是太庞大了这玩意。&/p&&p&&br&&/p&&p&讲解渲染层,就得先来讲点webkit时代的历史。在最原始webkit里,渲染是纯软绘的,意思是所有渲染都是cpu生成。因此绘制的机制也是很简单粗暴的提出刷新区域、从根节点(RendererObject)层层绘制。关于这套机制,网上有很多文章,这是最朴素的绘制机制,也是几乎所有我能见过的界面库的绘制方式。&/p&&p&&br&&/p&&p&然而这套机制在做各种css3动画的时候,难免会出现很多不必要的开销。比如一个小方块在网页上运动,小方块坐标每变化一次,就要调用一次绘制网页背景的函数。所以我们能想到的正常的优化逻辑是:分层。在webkit里,就是抽象出RendererLayer,并且把已经绘制好的网页背景放到单独一个层里(RenderLayer-&GraphicsLayer-&RenderLayerBacking的过程)。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-5c8152dbfb713c3d2f651fd_b.jpg& data-rawwidth=&554& data-rawheight=&341& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&https://pic2.zhimg.com/v2-5c8152dbfb713c3d2f651fd_r.jpg&&&/figure&&p&
图中是webkit的几个和层相关的class。&/p&&p&&br&&/p&&p&快速介绍下webkit里相关机制,打点基础,之后几篇才能进入难点:WebCore(webkit里的排版渲染相关组件)中有三棵树:DOM树,Render树及RenderLayer树。事实上远不止这三棵树,在开启硬件加速的情况下,WebView会构成一棵与RenderLayer树结构并行的Layer树,通常RenderLayer树中的一个或多个节点对于Layer树中的一个节点。这才是上图中的Layer的含义。&/p&&p&在早期版本的webkit android port中,Layer类是一个基类,BaseLayerAndroid和LayerAndroid类继承于它。Layer树中BaseLayerAndroid为根,其代表最大的surface,通常是一个普通的web页面。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-d54ba7c53b6ace3a568fd2d22e8b5f00_b.jpg& data-rawwidth=&556& data-rawheight=&353& class=&origin_image zh-lightbox-thumb& width=&556& data-original=&https://pic1.zhimg.com/v2-d54ba7c53b6ace3a568fd2d22e8b5f00_r.jpg&&&/figure&&p&比如上图中的Layer1;Layer树中其他节点为LayerAndroid,它代表一些特殊的surface,如video,插件等,比如上图中的其他Layer。关于这两个类的具体内容在后面有介绍。&/p&&p&在没有硬件加速的情况下,浏览器通常是依赖于CPU来渲染生成网页的内容,大致的做法是遍历这些层,然后按照顺序把这些层的内容依次绘制在一个内部存储空间上(例如bitmap),最后把这个内部表示显示出来,这种做法就是软件渲染(SoftwaRerendering)。&/p&&p&随着GPU硬件能力的增强,包括在很多小型设备上也是如此,浏览器可以借助于其处理图形方面的性能来对渲染实现加速。此时不再将所有层绘制到一起,而是进行分层渲染,合成之后再显示到屏幕上。&/p&&p&对于每一个RenderLayer,我们可以为其单独创建一块内部存储(有些情况下可以为多个layer创建同一块存储),这些存储会被用来保存该层中的内容,浏览器最后会把这些所有的层通过GPU合成起来,形成最终网页渲染的内容,这就是硬件加速合成。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-2d5dabbf77f8ad18a213a_b.jpg& data-rawwidth=&554& data-rawheight=&272& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&https://pic3.zhimg.com/v2-2d5dabbf77f8ad18a213a_r.jpg&&&/figure&&p&
随便找了个图&/p&&p&哪些元素会生成RenderLayer类呢,&/p&&ol&&li&Root object for the page&/li&&li&Explicit CSS position(relative,absolute or transform)&/li&&li&Transparent&/li&&li&Overflow,alpha mask or reflection&/li&&li&CSS filter&/li&&/ol&&p&&br&&/p&&p&GraphicsLayer类是一个抽象类,用于表示有后端存储(backing store)的渲染surface,同时,也包括作用于之上的各种变换(transformation)和动画(animation)。在android平台下的实现类为GraphicsLayerAndroid,此类中包含一个LayerAndroid,而真正进行合成的一般是LayerAndroid。&/p&&p&GraphicsLayerAndroid会构成一个跟RenderLayer结构并行的树结构,称之为GraphicsLayer树。而LayerAndroid所属的Layer树也是一个与GraphicsLayer树并行的结构(在刚才已经介绍过)。实际上RenderLayer树,GraphicsLayer树,Layer树依次关联起来。&/p&&p&哪些条件又会产生GraphicsLayer呢&/p&&ol&&li&Layer has 3D or perspective transform css properties&/li&&li&Composited plugin&/li&&li&CSS animation&/li&&li&CSS filter&/li&&/ol&&p&&br&&/p&&p&每个RenderLayer创建一个RenderLayerBacking,它用来管理和控制相对应的RenderLayer的合成行为,包含很多GraphicsLayer对象,这些对象用于表示层内容,前景(foreground)内容等等。 &/p&&p&RenderLayerCompositor类是WebKit中渲染部分‘掌控大局’的类, 管理RenderLayer树结构,它通过浏览器的设置来决定是否创建RenderLayer以及是否硬件加速合成,同时也决定是否为RenderLayer创建RenderLayerBacking。&/p&&p&&br&&/p&&p&GraphicsLayerClient是一个提供给GraphicsLayer的回调接口,GraphicsLayer主要通过这个回调接口:&/p&&ol&&li&notifyAnimationStarted - 通知页面该Layer启动一个CSS动画,用于实现硬件加速的CSS动画&/li&&li&notifySyncRequired - 通知页面该Layer的样式属性被改变,需要重新渲染和混合&/li&&li&paintContents - 把对应的RenderLayer上的内容绘制到自身内部的Backing Surface上&/li&&/ol&&p&&br&&/p&&p&GraphicsContext类在android下的实现为GraphicsContextAndroid,它负责最终绘制到后端存储(backing store)。&/p&&p&&br&&/p&&p&Webkit的硬件加速相关的基础组件介绍完了,其实懂行的小伙伴能看出上面讲的这一陀其实是很早期版本webkit 的android平台的代码了,因为我对这块比较熟,就以早期代码来讲,而且早期webkit android port的硬件加速代码逻辑非常清晰,效率又高,从代码性价比上来说远比chromium要优秀(谷歌的兄弟要来喷我了)。&/p&&p&下面就是如何在算法上优化滚动的效率,这就牵扯到tile(瓦片)算法以及miniblink的cc和原版cc层最大不同点了。&/p&
上篇讲到了整个框架大体思路,今天我想讲点细节,来点更有技术含量的。那就从CC(chromium 渲染合成层)开始讲吧,这块是chromium最复杂的部分,复杂到从一个 platform/graphics/cc 下的小目录单独提到最外层的根目录作为一个独立的组件。我打算分好几个章节…
&figure&&img src=&https://pic3.zhimg.com/v2-f907d89e3d66c76e8efd79_b.jpg& data-rawwidth=&1651& data-rawheight=&1000& class=&origin_image zh-lightbox-thumb& width=&1651& data-original=&https://pic3.zhimg.com/v2-f907d89e3d66c76e8efd79_r.jpg&&&/figure&&blockquote&简评:这是 Instagram Android 团队分享的 Android 应用模块化和懒加载经验,并且开源了他们的懒加载库,链接在文章结尾。: )&/blockquote&&p&随着 Instagram 的规模和开发人员数量的不断增加,也导致了不少的问题:&/p&&ul&&li&App 体积增大;&/li&&li&冷启动时间增加;&/li&&li&所占存储空间增大;&/li&&li&并且由于应用体积的增加导致构建时间的增长,减慢了开发人员的开发速度。&/li&&li&...&/li&&/ul&&p&为了应对这些问题,Instagram 开始了应用的模块化工作,以求在不同功能间建立起清晰的边界。这里就是 Instagram 他们自己分享的如何做应用模块化和懒加载的。&/p&&h2&应用模块化&/h2&&p&&b&什么是应用模块化?&/b&&/p&&p&模块化,顾名思义就是对代码根据业务逻辑进行分离和创建边界的过程。模块化的一个好处就是能优化应用的启动时间,在模块化之前,从一个功能到另一个功能的引用链可能会加载所有的代码,现在结合懒加载可以在需要的时候再加载模块。此外,对于构建时间,开发速度等等也有好处。&/p&&p&&b&怎么模块化?&/b&&/p&&p&模块化关键的就是考量各个模块所具有的依赖关系。对于每个依赖关系,判断其是应该删除还是保留。虽然要具体情况具体分析,但也还是有一些共通的考虑事项:&/p&&ul&&li&有没有办法将这个依赖重写为更通用的模块,而不是从特定业务模块中进行引用。&/li&&li&这些依赖所实现的功能一定需要在现在引用吗?还是说可以推迟到执行具体业务逻辑时再引用。&/li&&li&能否将逻辑简化到仅限于特定功能模块的范围内,以便更容易的判断依赖关系。&/li&&/ul&&p&当完成了模块化后,该功能的界面就应该只会包含一些关键的方法,例如:&/p&&ul&&li&生命周期相关的方法;&/li&&li&导航到该功能模块的方法;&/li&&li&...&/li&&/ul&&h2&模块的懒加载&/h2&&p&&b&什么是懒加载?&/b&&/p&&p&延迟加载能够将原来一大块的 dex 文件根据功能编译为独立的 dex 文件,这能带来的好处:&/p&&ul&&li&能够在真正需要某个功能时才加载到内存中,而不是在每次冷启动时加载。&/li&&li&如果某些模块一直是未使用状态,这些代码也就不会被解压缩,因此所占用的磁盘空间也减少了。&/li&&li&能让应用更方便的根据不同类型用户提供不同功能,缩小应用包的大小。&/li&&/ul&&p&并且,针对开发效率而言,我们增加了对懒加载的热插拔支持,意味着开发人员能够在编码时马上看到变化,而无需重新启动应用。&/p&&p&&b&什么时候触发懒加载?&/b&&/p&&p&一般来说,当我们预计一个模块在不久将来会使用时,就会对其加载。模块的加载因为不同的模块大小可能会有较小的延迟,因此需要采取不同的策略:&/p&&ul&&li&如果用户可能通过点击触发这个功能模块时,就在后台预先加载模块。当然,用户是有可能不点击的,但如果这个模块的点击概率很高,那么这是一个合适的解决方案。&/li&&li&在用户已经导航到该模块再进行加载。如果在测试中这个模块大多数时候加载延迟很小(小于 50ms),那么我们可以简单的直接等待加载完成。否则,可以显示一个进度条什么的,以便让应用不会被当成卡住了。&/li&&li&还有些模块本身是异步的,比如视频的加载和播放。对于这样的模块,懒加载会将其加载到辅助进程。&/li&&/ul&&p&Instagram 也开源了自己的懒加载库 - &a href=&https://link.zhihu.com/?target=https%3A//github.com/Instagram/ig-lazy-module-loader& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Instagram/ig-lazy-module-loader&/a& ,感兴趣的同学可以看看。&/p&&blockquote&原文:&a href=&https://link.zhihu.com/?target=https%3A//engineering.instagram.com/app-modularization-and-module-lazy-loading-at-instagram-and-beyond-46b9daa3fea4& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&App modularization and module lazy loading at Instagram and beyond&/a&&/blockquote&&p&日报扩展阅读:&/p&&ul&&li&&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&Android 实现颜色渐变的一个小 tip&/a&&/li&&/ul&&p&&/p&
简评:这是 Instagram Android 团队分享的 Android 应用模块化和懒加载经验,并且开源了他们的懒加载库,链接在文章结尾。: )随着 Instagram 的规模和开发人员数量的不断增加,也导致了不少的问题:App 体积增大;冷启动时间增加;所占存储空间增大;并且由…
&figure&&img src=&https://pic4.zhimg.com/v2-1cf6d64e0a16d7174018_b.jpg& data-rawwidth=&995& data-rawheight=&400& class=&origin_image zh-lightbox-thumb& width=&995& data-original=&https://pic4.zhimg.com/v2-1cf6d64e0a16d7174018_r.jpg&&&/figure&&p&若干年前,我写过一篇介绍浏览器渲染流水线的文章 - &a href=&http://link.zhihu.com/?target=http%3A//blog.csdn.net/rogeryi/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&How Rendering Work (in WebKit and Blink)&/a&,这篇文章,一来部分内容已经过时,二来缺少一个全局视角来对流水线整体进行分析,所以打算重新写一篇新的文章,从一个更高抽象层次和高度简化的方式对浏览器的渲染流水线进行解析,能让大部分页端同学都能够看的明白,并以此作为指引来分析和优化页面的渲染/动画性能。&/p&&blockquote&有些基本概念如图层,分块,光栅化基本没有发生变化,如果读者不理解的话请参考 &a href=&http://link.zhihu.com/?target=http%3A//blog.csdn.net/rogeryi/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&How Rendering Work (in WebKit and Blink)&/a&,本文不再过多解释。&/blockquote&&p&写这篇文章是始于年初&a href=&http://link.zhihu.com/?target=http%3A//blog.csdn.net/rogeryi/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&对页端开发高性能(交互/动画) Mobile WebApp 的一些思考&/a&,实际开始写已经是年中... 陆陆续续写了几个月才终于写完。感觉最难的还是开始的部分,要能够先把基础的部分讲解清楚,然后再循序渐进去讲解更复杂的概念。思考了很久,最终决定从帧的概念入手,然后把动画定义成&b&一个连续的帧序列的组合&/b&这种方式来对动画进行解析,最终成文的效果还是比较让自己满意的。&/p&&p&文章是在作业部落上书写,然后再把 Markdown 贴到其它网站,无法保证每个网站最终呈现的排版效果,如果读者觉得排版欠佳,可以访问在&a href=&http://link.zhihu.com/?target=https%3A//www.zybuluo.com/rogeryi/note/834994& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&作业部落上发布的原网址&/a&。&/p&&p&本文基于当前版本的 Chrome 浏览器写成(60 左右),理论上部分知识可以应用于其它浏览器(当然术语会有一定差别)或者 Chrome 后续的版本,但是并不完全保证这一点。&/p&&h2&1. 渲染流水线&/h2&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-810cb7ff67aba1d2dae309a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&650& data-rawheight=&540& class=&origin_image zh-lightbox-thumb& width=&650& data-original=&https://pic3.zhimg.com/v2-810cb7ff67aba1d2dae309a_r.jpg&&&/figure&&p&&br&&/p&&p&上图显示了 Chrome 一个高度简化后的渲染流水线示意图:&/p&&p&&br&&/p&&ol&&li&最底层的是 Chrome 最核心的部分 Blink,负责JS的解析执行,HTML/CSS解析,DOM操作,排版,图层树的构建和更新等任务;&/li&&li&Layer Compositor(图层合成器)接收 Blink 的输入,负责图层树的管理,图层的滚动,旋转等矩阵变幻,图层的分块,光栅化,纹理上传等任务;&/li&&li&Display Compositor 接收 Layer Compositor 的输入,负责输出最终的 OpenGL 绘制指令,将网页内容通过 GL 贴图操作绘制到目标窗口上,如果忽略掉操作系统本身的窗口合成器,也可以简单认为是绘制在显示屏上;&/li&&/ol&&blockquote&当我们说 Compositor,在没有加修饰语的情况下,一般都是指 Layer Compositor。另外术语 Child Compositor(子合成器)也是指 Layer Compositor,相对于作为 Parent 的 Display Compositor 而言。&br&&/blockquote&&h2&1.1 进程与线程&/h2&&p&一个 Chrome 浏览器一般会有一个 Browser 进程,一个 GPU 进程,和多个 Renderer 进程,通常每个 Renderer 进程对应一个页面。在特殊架构(Android WebView)或者特定配置下,Browser 进程可以兼作 GPU 进程或者 Renderer 进程(意味着没有独立的 GPU 或者 Renderer 进程),但是 Browser 跟 Renderer,Browser 跟 GPU,Renderer 跟 GPU 之间的系统架构和通讯方式基本保持不变,线程架构也是同样。&/p&&p&&br&&/p&&ol&&li&Blink 主要运行在 Renderer 进程的 Renderer 线程,我们通常会称之为内核主线程;&/li&&li&Layer Compositor 主要运行在 Renderer 进程的 Compositor 线程;&/li&&li&Display Compositor 主要运行在 Browser 进程的 UI 线程;&/li&&/ol&&blockquote&Display Compositor 后面应该会移到 GPU 进程的主 GPU 线程,当然对父子合成器进行调度的部分仍然是在 Browser 进程的 UI 线程,不太确定各个不不同平台的状况,Android WebView 平台是已经实现了。&br&&/blockquote&&h2&1.2 帧&/h2&&p&所有的渲染流水线都会有帧的概念,帧这个概念抽象描述了渲染流水线下级模块往上级模块输出的绘制内容相关数据的封装。我们可以看到 Blink 输出 Main Frame 给 Layer Compositor,Layer Compositor 输出 Compositor Frame 给 Display Compositor,Display Compositor 输出 GL Frame 给 Window。我们觉得一个动画是否流畅,最终取决于 GL Frame 的帧率(也就是目标窗口的绘制更新频率),而觉得一个触屏操作是否响应即时,取决于从 Blink 处理事件到 Window 更新的整个过程的耗时(理论上应该还要加上事件从 Browser 发送给 Compositor,再发送给 Blink 的这个过程的耗时)。&/p&&h2&1.1.1 Main Frame&/h2&&p&Main Frame 包含了对网页内容的描述,主要以绘图指令的形式,或者可以简单理解为某个时间点对整个网页的一个矢量图快照(可以局部更新)。当前版本的 Chrome,图层化的决策仍然由 Blink 来负责,Blink 需要决定如何根据网页的 DOM 树来生成一颗图层树,并以 DisplayList 的形式记录每个图层的内容(未来图层化决策应该会转移到 Layer Compositor,Blink 只输出 DisplayList 树和 DisplayList 节点的关键属性,同时 DisplayList 不再以图层作为单位,而是以每个排版对象作为单位)。&/p&&p&图层化决策一般由以下几个因素决定:&/p&&p&&br&&/p&&ol&&li&特殊元素如 Plugin,Video,Canvas(WebGL);&/li&&li&维护正确的层级关系来保证绘制顺序是正确的,&a href=&http://link.zhihu.com/?target=http%3A//blog.csdn.net/rogeryi/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&比如 Overlap 的计算&/a&;&/li&&li&减少图层树的结构变更,减少图层内容的变更(目前 Blink 网页内容的变更是以图层为原子单位的,如果以一个元素为根节点生成图层,该元素的某些 CSS 属性如 Transform 的变更不会引起所属图层内容的变更);&/li&&/ol&&blockquote&第三点是可以被页端所直接控制来优化图层结构及 Main Frame 性能,像传统的 translate3d hack 和新的 CSS 属性 will-change。&br&&/blockquote&&h2&1.2.2 Compositor Frame&/h2&&p&Layer Compositor 接收 Blink 生成的 Main Frame,并转换成合成器内部的图层树结构(因为图层化决策仍然由 Blink 负责,所以这里的转换基本上可以认为是生成一棵同样的树,再逐个对图层进行拷贝)。&/p&&p&Layer Compositor 需要为每个图层进行分块,为每个分块分配 Resource(Texture 的封装),然后安排光栅化任务。&/p&&p&当 Layer Compositor 接收到来自 Browser 的绘制请求时,它会为当前可见区域的每个图层的每个分块生成一个 Draw Quad 的绘制指令(矩形绘制,指令实际上指定了坐标,大小,变换矩阵等属性),所有的 Draw Quad 指令和对应的 Resource 的集合就构成了 Compositor Frame。Compositor Frame 被发送往 Browser,并最终到达 Display Compositor(未来也可以直接发给 Display Compositor)。&/p&&h2&1.2.3 GL Frame&/h2&&p&Display Compositor 将 Compositor Frame 的每个 Draw Quad 绘制指令转换一个 GL 多边形绘图指令,使用对应 Resource 封装的 Texture 对目标窗口进行贴图,这些 GL 绘图指令的集合就构成了一个 GL Frame,最终由 GPU 执行这些 GL 指令完成网页在窗口上占据的可见区域的绘制。&/p&&h2&1.3 调度&/h2&&p&Chrome 渲染流水线的调度是基于请求和状态机响应,调度的最上级中枢运行在 Browser UI 线程,它按显示器的 VSync(垂直同步)周期向 Layer Compositor 发出输出下一帧的请求,而 Layer Compositor 根据自身状态机的状态决定是否需要 Blink 输出下一帧。&/p&&p&Display Compositor 则比较简单,它持有一个 Compositor Frame 的队列不断的进行取出和绘制,输出的频率唯二地取决于 Compositor Frame 的输入频率和自身绘制 GL Frame 的耗时。基本上可以认为 Layer Compositor 和 Display Compositor 是生产者和消费者的关系。&/p&&h2&2. 网页动画&/h2&&p&&b&动画可以看做是一个连续的帧序列的组合&/b&。我们把网页的动画分成两大类 —— 一类是合成器动画,一类是非合成器动画(UC 内部也将其称为内核动画或者 Blink Animation,虽然这不是 Chrome 官方的术语)。&/p&&p&&br&&/p&&ol&&li&合成器动画顾名思义,动画的每一帧都是由 Layer Compositor 生成并输出的,合成器自身驱动着整个动画的运行,在动画的过程中,不需要新的 Main Frame 输入;&/li&&li&非合成器动画,每一帧都是由 Blink 生成,都需要产生一个新的 Main Frame;&/li&&/ol&&h2&2.1 合成器动画&/h2&&p&合成器动画又可以分为两类:&/p&&p&&br&&/p&&ol&&li&合成器本身触发并运行的,比如最常见的网页惯性滚动,包括整个网页或者某个页内可滚动元素的滚动;&/li&&li&Blink 触发然后交由合成器运行,比如说传统的 CSS Translation 或者新的 Animation API,如果它们触发的动画经由 Blink 判断可以交由合成器运行;&/li&&/ol&&blockquote&Blink 触发的动画,如果是 Transform 和 Opacity 属性的动画基本上都可以由合成器运行,因为它们没有改变图层的内容。不过即使可以交由合成器运行,它们也需要产生一个新的 Main Frame 提交给合成器来触发这个动画,如果这个 Main Frame 包含了大量的图层变更,也会导致触发的瞬间卡顿,页端事先对图层结构进行优化可以避免这个问题。&br&&/blockquote&&h2&2.2 非合成器动画&/h2&&p&非合成器动画也可以分为两类:&/p&&p&&br&&/p&&ol&&li&使用 CSS Translation 或者 Animation API 创建的动画,但是无法由合成器运行;&/li&&li&使用 Timer 或者 rAF 由 JS 驱动的动画,比较典型的就是 Canvas/WebGL 游戏,这种动画实际上是由页端自己定义的,浏览器本身并没有对应的动画的概念,也就是说浏览器本身是不知道这个动画什么时候开始,是否正在运行,什么时候结束,这些完全是页端自己的内部逻辑;&/li&&/ol&&p&合成器动画和非合成器动画在渲染流水线上有较大的差异,后者更复杂,流水线更长。上面四种动画的分类,按渲染流水线的复杂程度和理论性能排列(复杂程度由低到高,理论性能由高到低):&/p&&p&&br&&/p&&ol&&li&合成器本身触发并运行的动画;&/li&&li&Blink 触发,合成器运行的动画;&/li&&li&Blink 触发,无法由合成器运行的动画;&/li&&li&由 Timer/rAF 驱动的 JS 动画;&/li&&/ol&&p&长久以来,浏览器渲染流水线的设计都主要是为了合成器动画的性能而优化,甚至在某种程度上导致非合成器动画性能的下降,比如说合成器的异步光栅化机制。不过这两年,随着对 WebApp 渲染性能包括 WebGL 性能的重视,并且随着主流移动设备的硬件性能持续提升,合成器动画的性能也已经基本不成问题,Chrome 的渲染流水线已经更多地针对非合成器动画的性能进行优化,甚至会导致在某些特定状况下合成器动画性能的下降,比方说倾向于为了&a href=&http://link.zhihu.com/?target=http%3A//blog.csdn.net/rogeryi/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&维持图层树的稳定性,减少变更,而生成更多的图层&/a&。不过总的说来,目前 Chrome 的渲染流水线,在主流的移动设备上,大部分场景下,两者性能都能获得一个较好的平衡。&/p&&h2&3. 动画性能分析基础&/h2&&p&这里的性能分析主要是针对移动设备,以桌面处理器的性能,大部分场景下都不存在性能问题。目前移动设备的屏幕刷新率基本上都是 60hz,而浏览器跟其它应用一样,需要跟屏幕刷新保持垂直同步,也就是动画帧率的上限是 60 帧,这也是我们能够达到的最理想的结果。不过考虑浏览器本身的复杂程度,可能有很多后台任务在运行,而且操作系统本身也可能同时运行其它后台任务,并且移动平台要考虑能耗和散热,CPU/GPU 的调度策略会频繁地发生变化,要完全锁定 60 帧是非常困难的。&/p&&blockquote&如果上限超过 60 帧,实际平均帧率超过 60 反而不难,但是如果上限是 60 帧,垂直同步下要锁定 60 帧是非常困难的,要求每一帧的各个环节耗时都要保持非常稳定。&br&&/blockquote&&p&一般而言:&/p&&p&&br&&/p&&ol&&li&帧率在 55 ~ 60 之间已经可以认为是非常优秀的水平,这时用户几乎感觉不到卡顿;&/li&&li&帧率在 50 ~ 55 之间可以认为是良好的水平,用户感觉到轻微卡顿,但整体来说还是比较流畅;&/li&&/ol&&p&要达到 50 帧以上的水平,我们就需要对动画在渲染流水线的每个重要环节进行性能计算,需要知道这些环节最长允许的耗时上限和网页影响这些环节耗时的主要原因,虽然实际上很难完全锁定 60 帧,但是一般来说性能分析/优化还是会以 60 帧为目标来倒推各个环节的最大耗时。&/p&&blockquote&如果是场景比较复杂的 Canvas/WebGL 游戏,以 30 帧为目标帧率是一个合理的诉求。&br&&/blockquote&&h2&3.1 光栅化机制&/h2&&p&在对动画性能进行分析之前,需要先说明一下目前的 Chrome 的光栅化机制。合成器会监控是否需要安排新的光栅化任务,当需要光栅化调度时:&/p&&p&&br&&/p&&ol&&li&合成器找到所有在当前可见区域的图层;&/li&&li&合成器找到这些图层在当前可见区域的分块;&/li&&li&合成器检查这些分块是否需要光栅化,如果需要,生成一个对应的光栅化任务并分配所需要的 Resource 放入任务队列里面;&/li&&li&Renderer 进程会预先创建一个或者多个 Worker 线程(移动平台一般是两个),这些线程会从任务队列里面顺序取出每一个光栅化任务并运行;&/li&&li&光栅化任务运行后,会通知合成器,合成器根据需要检查哪些任务已经完成,已经完成的任务, Resource 会转交给对应的分块;&/li&&/ol&&blockquote&实际的光栅化区域会比当前可见区域要更大一些,一般是增加一个分块大小单位,对不可见区域的预光栅化有助于提升合成器动画的性能和减少出现空白的几率。&br&&/blockquote&&p&从上可知,合成器的光栅化调度完全是异步的,合成器在 Compositor 线程需要执行的就是安排光栅化任务和检查哪些任务已经完成,Compositor 线程本身不会被真正运行光栅化任务的 Worker 线程所阻塞。&/p&&h2&4 合成器动画性能分析和优化指南&/h2&&h2&4.1 动画流水线&/h2&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-8fec8eb541_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&995& data-rawheight=&400& class=&origin_image zh-lightbox-thumb& width=&995& data-original=&https://pic2.zhimg.com/v2-8fec8eb541_r.jpg&&&/figure&&p&&br&&/p&&blockquote&上图显示了合成器动画的渲染流水线示意图,根据 Android WebView 平台的实现进行绘制,其它平台可能略微不同,但对后面的性能分析,在大部分情况下影响不大&br&&/blockquote&&p&整个流水线的大概过程是:&/p&&p&&br&&/p&&ol&&li&位于 Browser 进程 UI 线程的窗口管理器接收到来自操作系统的屏幕刷新垂直同步信号(VSync),开始准备输出新的一帧,它首先给位于 Renderer 进程 Compositor 线程的 Layer Compositor 发送一个 &b&Begin Frame&/b& 消息;&/li&&li&Layer Compositor 接收到 &b&Begin Frame&/b& 消息后,更新合成器内部的状态机,开始准备输出 Compositor Frame,在这个过程中的一个重要动作就是 &b&Animate&/b&,合成器会检查当前是否有正在运行的动画,然后运行这些动画,并根据动画运行的结果改变关联图层的对应属性(比如惯性滚动动画改变图层的 Scroll Offset,Transform 动画改变图层的 Transform),&b&Animate&/b& 的结果会发送回给 UI 线程告诉其是否有动画正在运行,需要更新窗口;&/li&&li&如果 UI 线程确定合成器需要更新窗口,则会发送一个 &b&Draw&/b& 消息请求合成器输出下一帧 Compositor Frame;&/li&&li&合成器按下面的过程产生新的 Compositor Frame 并发送给 Display Compositor;&br&4.1 合成器找出在当前可见区域内显示的图层;&br&4.2 合成器找出这些图层在可见区域内的分块;&br&4.3 如果该分块已经有分配 Resource(说明此分块已经完成光栅化),则产生一个 Draw Quad 的命令置入 Compositor Frame 中,如果没有则跳过;&/li&&li&Display Compositor 接受到新的 Compositor Frame 后,对 Compositor Frame 进行 &b&Render&/b&,将每一个 Draw Quad 命令转换成一个 GL Draw Call,然后 &b&GPU&/b& 执行所有的 GL 指令完成最后的窗口绘制;&/li&&/ol&&p&上述流程的一些关键点是:&/p&&p&&br&&/p&&ol&&li&Draw 的过程中,合成器不会等待可见的分块光栅化完成,这让合成器充分利用了异步光栅化的机制来提升性能,但是也会造成动画过程中可能会出现空白的分块,比如快速滚动页面有时会看到空白区域;&/li&&li&在合成器动画过程中,Layer Compositor 和 Display Compositor 是异步并发的,在 Display Compositor 输出 GL Frame N 的时候,Layer Compositor 已经可以开始输出下一帧 Compositor Frame N + 1,这意味只要让 Compositor Frame N 和 GL Frame N 的耗时各自都在 16.7 毫秒以内就可以实现 60 帧的动画;&/li&&/ol&&h2&4.2 动画耗时分析&/h2&&p&&br&&/p&&ol&&li&Begin Frame 的耗时一般很短,大概 1 ~ 2 毫秒左右;&/li&&li&Draw 的耗时也不长,一般在 3 ~ 5 毫秒左右,耗时主要取决于网页的图层复杂度,总的来说合成器动画过程中 Compositor 线程的开销一般都不会构成性能瓶颈;&/li&&li&Render 的耗时也不长,大概在 3 毫秒左右,耗时主要取决于当前可见区域内的可见分块的数量;&/li&&li&GPU 部分的耗时比较长,耗时主要取决于当前可见区域内的可见分块的总面积,也就是绘制的总面积,一旦 Render + GPU 部分的耗时大于 16.7 毫秒,动画就会出现掉帧;&/li&&/ol&&p&总的来说影响合成器动画性能的最关键因素就是&b&过度绘制系数&/b&(Overdraw,可以理解为绘制的面积和可见区域面积的比例),如果网页本身存在大量图层堆叠情况,导致过度绘制系数过高,就会严重影响合成器动画的性能。经验显示,过度绘制系数比较理想的值是在 2 以内,一般建议不超过 3,这样可以保证在中低端的移动设备上也有不错的性能表现。&/p&&p&另外,合成器动画过程中,Compositor 和 GPU 线程是前台线程,它们虽然理论上不会被 Worker 和 Renderer 线程阻塞,但是在真实的运行场景中,移动设备的 CPU/GPU 和内存带宽等硬件资源是有限的,如果 Worker 和 Renderer 线程处于高负荷状态下,也会导致前台的 Compositor 和 GPU 线程阻塞,最终导致合成器动画掉帧。&/p&&p&这种现象常见于:&/p&&p&&br&&/p&&ol&&li&网页在合成器动画比如惯性滚动过程中,有大量的 JS 加载图片或者其它内容,并频繁地对 DOM 树进行操作;&/li&&li&网页的图层树非常复杂,并且其结构在合成器动画过程中频繁发生变化,导致大量的光栅化任务在 Worker 线程运行;&/li&&/ol&&h2&4.3 动画性能优化 Checklist&/h2&&p&&b&根据上述的耗时分析,我们可以给出一个页端优化合成器动画性能的简单 Checklist&/b&:&/p&&p&&br&&/p&&ol&&li&检查网页的图层结构是否合理,包括深度和数量,一般来说深度在 10 以内,数量在 100 以内是比较合理的值;&/li&&li&检查网页的合成器动画,包括网页的惯性滚动,各种图层的淡入/淡出等动画,在动画过程中,是否同时存在大量的网络加载和 DOM 操作,网页图层结构是否保持稳定;&/li&&li&当网页处于任一滚动位置上时,它的当前过度绘制系数是否合理;&/li&&/ol&&blockquote&如何判断网页的图层结构是否稳定,一般而言,如果是位于叶子节点的图层增加或者移除,对整个图层结构影响并不大,但是如果是中间节点的图层增加或者移除,对图层结构的影响就比较大了,并且越是接近根节点,影响就越大。&br&现在的页端都会大量使用异步加载来优化加载性能和流量,但是容易出现导致动画掉帧的现象。要平衡好这一点意味着需要实现一个加载和关联 DOM 操作的调度器,如果检查到动画正在运行,则停止加载或者通过节流阀机制降低加载的并发数量和频率,同时可以通过事先生成相应的 DOM 节点和图层作为占位符来避免加载后的图层结构发生剧烈变化。&/blockquote&&h2&5 非合成器动画性能分析和优化指南&/h2&&p&前面已经我们已经把非合成器动画区分为 Blink 触发,无法由合成器运行的动画和由 Timer/rAF 驱动的 JS 动画两类,因为前者可以认为是后者的一个简化版本,所以这一章主要讨论 Timer/rAF 驱动的 JS 动画。&/p&&h2&5.1 动画流水线&/h2&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-f8bd8328531_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1458& data-rawheight=&710& class=&origin_image zh-lightbox-thumb& width=&1458& data-original=&https://pic2.zhimg.com/v2-f8bd8328531_r.jpg&&&/figure&&p&&br&&/p&&p&从上图可以看出非合成器动画的流水线比合成器动画更长更复杂,并且非合成器动画的后半段跟合成器动画是一致的。&/p&&p&&br&&/p&&ol&&li&&b&JavaScipt&/b& 部分是页端实现的逻辑,可能包含了计算的部分,和调用浏览器提供的 API 的部分(修改 DOM 树,CSS 属性等),最终改变了网页的内容;&/li&&li&网页内容被改变会导致 Blink 生成新的 MainFrame,&b&MainFrame&/b& 包括了重排版,更新图层树,和重新记录发生变更的图层的内容,生成新的 DisplayList,等等;&/li&&li&Blink 生成新的 MainFrame 后需要向合成器发起 &b&Commit&/b& 的请求,合成器在 &b&Commit&/b& 过程中根据 MainFrame 生成自身的图层树,Blink 在 &b&Commit&/b& 的过程中保持阻塞状态,&b&Commit&/b& 完成后再继续运行;&/li&&li&合成器实际上有两棵图层树,新提交的 MainFrame 生成的是 Pending 树,用于绘制 &b&Draw&/b& 的是 Active 树,只有当 Pending 树当前可见区域部分的分块全部完成 &b&Rasterize&/b& 后,才会进入 &b&Active&/b& 步骤,在 &b&Active&/b& 的过程中,Pending 树相对于 Active 树的变更部分才会被同步到 Active 树;&/li&&li&&b&Active&/b& 后,合成器会向 UI 线程的窗口管理器发起重绘请求,窗口管理器会在下一个 VSync 的时候开始绘制新的一帧,后面的流程就跟合成器动画是一样的了;&/li&&/ol&&p&上述流程的一些关键点是:&/p&&p&&br&&/p&&ol&&li&在合成器动画中,分块没有完成光栅化,出现空白是被允许的,这样浏览器可以更好地保证合成器动画的帧率,但是在非合成器动画中出现空白是不被允许的,因为新的 MainFrame 常常会带来大面积的变更,如果允许空白的话可能会出现非常不好的视觉效果。这样就导致合成器需要使用两棵图层树来构建一个类似双缓冲的机制,只有当 Pending 树在后台完成可见区域的光栅化时才被允许同步到 Active 树;&/li&&li&在非合成器动画过程中,Main Frame N,Main Frame N Active + Compositor Frame N,GL Frame N 所属的三个线程基本上可以认为是可以并发运行的(唯一会阻塞的环节是 Commit,不过 Commit 耗时一般不长),理论上我们要实现 60 帧的非合成器动画,只需要保证其中每个线程包含的部分的耗时总和小于 16.7 毫秒即可。当然实际的状况下,在移动设备上很难实现这么多线程完全并发运行,加上过多线程带来的互相通讯的开销,使得每个线程的最大允许耗时实际上是小于 16.7 毫秒的;&/li&&/ol&&h2&5.2 动画耗时分析和优化指南&/h2&&p&&br&&/p&&ol&&li&JavaScipt 的耗时是由页端自己的逻辑决定的,一般超过 10 毫秒就基本上很难实现 60 帧的非合成器动画了;&/li&&li&MainFrame 的耗时主要取决于网页 DOM 树,图层树的复杂程度和变化程度,在变更很小,比如只有几个元素的内容发生变化,图层树不变的情况下,一般耗时都是在 3 ~ 5 毫秒左右,如果变更很大,几十甚至几百都是有可能的;&/li&&li&Commit 的耗时主要取决于图层树的复杂程度,一般耗时都很短,大概 2 ~ 3 毫秒上下;&/li&&li&Rasterize 的耗时范围变化极大,取决于网页内容的复杂程度和新 MainFrame 在当前可见区域内网页内容发生变化的总面积,另外图片解码也发生在这个阶段,而图片解码也是光栅化耗时最多的一个环节,光栅化的耗时从几毫秒到几百毫秒都有可能(图片在第一次被光栅化时被解码,一直在可见区域内的图片不会被反复重解码);&/li&&li&Active 跟 Commit 的耗时类似,主要取决于图层树的复杂程度,一般耗时很短,大概 2 ~ 3 毫秒上下;&/li&&/ol&&p&总的来说对非合成器动画性能影响最大的通常是 JavaScript 和 Rasterize,要实现高性能的非合成器动画,页端需要很小心地控制 JavaScript 部分的耗时,并避免在每一帧中引入大面积的网页内容变化和大幅度的图层结构变化。另外非合成器动画的后半段就是合成器动画,所以对合成器动画的性能优化要求也同样适用于非合成器动画。&/p&&p&另外对于 WebGL 来说,当在 JavaScript 里面调用 WebGL API 时,这些命令只是被 Chrome 缓存起来,并不会在 Renderer 线程调用真正的 GL API,所以 WebGL API 在 JavaScript 部分的耗时只是一个 JS Binding 调用的 Overhead,最终绘制 WebGL 内容的 GPU 耗时实际上是被包含在最后的 GPU 的步骤里面。但是在移动平台上一个 JS Binding 调用的 Overhead 是相当高的,大概在 0.01 毫秒这个范围,所以每一帧超过 1000 个 WebGL API 调用的 WebGL 游戏,性能阻塞的瓶颈有很大概率会出现在 JavaScript 也就是 CPU 上,而不是 GPU。&/p&&h2&6 浏览器渲染流水线未来的演化&/h2&&p&看完非合成器动画流水线分析的读者,第一感觉恐怕是觉得太过复杂了,比起合成器动画,更多的线程,更多的中间环节,有时即使每个环节都做到了完美,最终也有可能因为线程之间的通讯和等待而导致掉帧。正如前面所说的一样,长久以来,浏览器的渲染流水线都是为了合成器动画而优化的,它的主要特征是:&/p&&p&&br&&/p&&ol&&li&利用图层缓存作为中间媒介,将光栅化和合成分离,让光栅化完全独立;&/li&&li&异步光栅化的机制,避免合成器的阻塞;&/li&&/ol&&p&这两点实际上是有利于合成器动画而不利于非合成器动画的,浏览器渲染流水线当前和未来的演化理所应当地要解决这些问题。Firefox &a href=&http://link.zhihu.com/?target=https%3A//hacks.mozilla.org/2017/10/the-whole-web-at-maximum-fps-how-webrender-gets-rid-of-jank/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&新的渲染引擎 WebRender&/a& 比较激进,看起来是采用流行的 UI Toolkit 如 Qt,Android 的 DisplayList/Scene Graph + Direct Rasterize 的方式,这样当然是有利于非合成器动画的,但是否会造成合成器动画的性能衰退还很难说。&/p&&p&Direct Rasterize 的前提条件是浏览器渲染引擎要实现 GPU 光栅化,并且性能和兼容性要足够好。Chrome 当前也是在努力推进 GPU 光栅化,即使整个流水线的架构没有发生变化,GPU 光栅化也可以大幅度减少光栅化在 Worker 线程部分的 CPU 耗时,将这些耗时转移到 GPU 线程上去,这样 Main Frame N Active 这部分的耗时就可以大大减少了。从目前的统计数据来看,可以使用 GPU 光栅化的移动设备占比已经很高,大概 6 成以上的样子,随着老旧设备的淘汰,这个比例未来会越来越高,并且随着 GPU 性能的提升,GPU 光栅化的效果也会越来越好。&/p&&p&Chrome 另外一个重点改进渲染性能的项目是 Slim Painting,未来 Blink 跟 Firefox 类似只输出 DisplayList 树,并且每棵 DisplayList 不再是以图层为单位而是以排版对象为单位,这样合成器可以更自由地选择 Layerize 和 Rasterize 的策略,Async 或者 On Demand 或者 Direct Rasterize 都可以混用来最大化动画的性能,合成器可以变得更 Adaptive 而不像现在光栅化和合成区隔的那么泾渭分明。&/p&&p&除了浏览器持续改进自身渲染流水线外,提供更多 API 供页端使用来最大化 WebApp 的性能也是一个重要的方向,包括:&/p&&p&&br&&/p&&ol&&li&合成器实现更多的动画效果,然后通过 API 供页端直接使用,这样页端可以避免自己使用 JavaScript 实现,比如 &a href=&http://link.zhihu.com/?target=https%3A//developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap_Points& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&CSS Snap Point&/a&;&/li&&li&更多的有助于帮助页端将耗时的 JavaScript 部分从 Renderer 线程迁移到其它 Worker 线程,让 WebApp 可以更充分地利用硬件的多核能力,通过并发来提升性能,比如 &a href=&http://link.zhihu.com/?target=https%3A//developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Offscreen Canvas&/a&;&/li&&/ol&&p&但是这些新特性要真正应用起来,对页端对浏览器渲染流水线的理解要求就更高了。&/p&
若干年前,我写过一篇介绍浏览器渲染流水线的文章 - ,这篇文章,一来部分内容已经过时,二来缺少一个全局视角来对流水线整体进行分析,所以打算重新写一篇新的文章,从一个更高抽象层次和高度简化的方式对浏览器…
&p&对浏览器加载资源有很多不确定性,例如:&/p&&ol&&li&css/font的资源的优化级会比img高,资源的优化级是怎么确定的呢?&/li&&li&资源优先级又是如何影响加载的先后顺序的?&/li&&li&有几种情况可能会导致资源被阻止加载?&/li&&/ol&&p&通过源码可以找到答案。此次源码解读基于Chromium 64(10月28日更新的源码)。&/p&&p&下面通过加载资源的步骤,依次说明。&/p&&h2&1. 开始加载&/h2&&p&通过以下命令打开Chromium,同时打开一个网页:&/p&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&chromium --renderer-startup-dialog https://www.baidu.com
&/code&&/pre&&/div&&p&Chrome会在DocumentLoader.cpp里面通过以下代码去加载:&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span& &span class=&n&&main_resource_&/span& &span class=&o&&=&/span&
&span class=&n&&RawResource&/span&&span class=&o&&::&/span&&span class=&n&&FetchMainResource&/span&&span class=&p&&(&/span&&span class=&n&&fetch_params&/span&&span class=&p&&,&/span& &span class=&n&&Fetcher&/span&&span class=&p&&(),&/span& &span class=&n&&substitute_data_&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&页面资源属于MainRescouce,Chrome把Rescource归为以下几种:&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span& &span class=&k&&enum&/span& &span class=&nl&&Type&/span& &span class=&p&&:&/span& &span class=&kt&&uint8_t&/span& &span class=&p&&{&/span&
&span class=&n&&kMainResource&/span&&span class=&p&&,&/span&
&span class=&n&&kImage&/span&&span class=&p&&,&/span&
&span class=&n&&kCSSStyleSheet&/span&&span class=&p&&,&/span&
&span class=&n&&kScript&/span&&span class=&p&&,&/span&
&span class=&n&&kFont&/span&&span class=&p&&,&/span&
&span class=&n&&kRaw&/span&&span class=&p&&,&/span&
&span class=&n&&kSVGDocument&/span&&span class=&p&&,&/span&
&span class=&n&&kXSLStyleSheet&/span&&span class=&p&&,&/span&
&span class=&n&&kLinkPrefetch&/span&&span class=&p&&,&/span&
&span class=&n&&kTextTrack&/span&&span class=&p&&,&/span&
&span class=&n&&kImportResource&/span&&span class=&p&&,&/span&
&span class=&n&&kMedia&/span&&span class=&p&&,&/span&
&span class=&c1&&// Audio or video file requested by a HTML5 media element&/span&
&span class=&n&&kManifest&/span&&span class=&p&&,&/span&
&span class=&n&&kMock&/span&
&span class=&c1&&// Only for testing&/span&
&span class=&p&&};&/span&
&/code&&/pre&&/div&&p&除了常见的image/css/js/font之外,我们发现还有像textTrack的资源,这个是什么东西呢?这个是video的字幕,使用webvtt格式:&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span&&span class=&o&&&&/span&&span class=&n&&video&/span& &span class=&n&&controls&/span& &span class=&n&&poster&/span&&span class=&o&&=&/span&&span class=&s&&&/images/sample.gif&&/span&&span class=&o&&&&/span&
&span class=&o&&&&/span&&span class=&n&&source&/span& &span class=&n&&src&/span&&span class=&o&&=&/span&&span class=&s&&&sample.mp4&&/span& &span class=&n&&type&/span&&span class=&o&&=&/span&&span class=&s&&&video/mp4&&/span&&span class=&o&&&&/span&
&span class=&o&&&&/span&&span class=&n&&track&/span& &span class=&n&&kind&/span&&span class=&o&&=&/span&&span class=&s&&&captions&&/span& &span class=&n&&src&/span&&span class=&o&&=&/span&&span class=&s&&&sampleCaptions.vtt&&/span& &span class=&n&&srclang&/span&&span class=&o&&=&/span&&span class=&s&&&en&&/span&&span class=&o&&&&/span&
&span class=&o&&&/&/span&&span class=&n&&video&/span&&span class=&o&&&&/span&
&/code&&/pre&&/div&&p&还有动态请求ajax属于Raw类型。因为ajax可以请求多种资源。&/p&&p&MainResource包括location即导航输入地址得到的页面、使用frame/iframe嵌套的、通过超链接点击的页面以及表单提交这几种。&/p&&p&接着交给稍底层的ResourceFecher去加载,所有资源都是通过它加载:&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span&&span class=&n&&fetcher&/span&&span class=&o&&-&&/span&&span class=&n&&RequestResource&/span&&span class=&p&&(&/span&
&span class=&n&&params&/span&&span class=&p&&,&/span& &span class=&n&&RawResourceFactory&/span&&span class=&p&&(&/span&&span class=&n&&Resource&/span&&span class=&o&&::&/span&&span class=&n&&kMainResource&/span&&span class=&p&&),&/span& &span class=&n&&substitute_data&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&在这个里面会先对请求做预处理。&/p&&h2&2. 预处理请求&/h2&&p&每发个请求会生成一个ResourceRequest对象,这个对象包含了http请求的所有信息:&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-e3171c51bcb304d78d9d08a_b.jpg& data-caption=&& data-rawwidth=&1278& data-rawheight=&524& class=&origin_image zh-lightbox-thumb& width=&1278& data-original=&https://pic1.zhimg.com/v2-e3171c51bcb304d78d9d08a_r.jpg&&&/figure&&p&&br&&/p&&p&包括url、http header、http body等,还有请求的优先级信息等:&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-e688a17e54ccee5b9a63e1_b.jpg& data-caption=&& data-rawwidth=&850& data-rawheight=&202& class=&origin_image zh-lightbox-thumb& width=&850& data-original=&https://pic4.zhimg.com/v2-e688a17e54ccee5b9a63e1_r.jpg&&&/figure&&p&&br&&/p&&p&然后会根据页面的加载策略对这个请求做一些预处理,如下代码:&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span& &span class=&n&&PrepareRequestResult&/span& &span class=&n&&result&/span& &span class=&o&&=&/span& &span class=&n&&PrepareRequest&/span&&span class=&p&&(&/span&&span class=&n&&params&/span&&span class=&p&&,&/span& &span class=&n&&factory&/span&&span class=&p&&,&/span& &span class=&n&&substitute_data&/span&&span class=&p&&,&/span&
&span class=&n&&identifier&/span&&span class=&p&&,&/span& &span class=&n&&blocked_reason&/span&&span class=&p&&);&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&result&/span& &span class=&o&&==&/span& &span class=&n&&kAbort&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&k&&nullptr&/span&&span class=&p&&;&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&result&/span& &span class=&o&&==&/span& &span class=&n&&kBlock&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&ResourceForBlockedRequest&/span&&span class=&p&&(&/span&&span class=&n&&params&/span&&span class=&p&&,&/span& &span class=&n&&factory&/span&&span class=&p&&,&/span& &span class=&n&&blocked_reason&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&prepareRequest会做两件事情,一件是检查请求是否合法,第二件是把请求做些修改。如果检查合法性返回kAbort或者kBlock,说明资源被废弃了或者被阻止了,就不去加载了。&/p&&p&被block的原因可能有以下几种:&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span&&span class=&k&&enum&/span& &span class=&k&&class&/span& &span class=&nc&&ResourceRequestBlockedReason&/span& &span class=&p&&{&/span&
&span class=&n&&kCSP&/span&&span class=&p&&,&/span&
&span class=&c1&&// CSP内容安全策略检查&/span&
&span class=&n&&kMixedContent&/span&&span class=&p&&,&/span&
&span class=&c1&&// mixed content&/span&
&span class=&n&&kOrigin&/span&&span class=&p&&,&/span&
&span class=&c1&&// secure origin&/span&
&span class=&n&&kInspector&/span&&span class=&p&&,&/span&
&span class=&c1&&// devtools的检查器&/span&
&span class=&n&&kSubresourceFilter&/span&&span class=&p&&,&/span&
&span class=&n&&kOther&/span&&span class=&p&&,&/span&
&span class=&n&&kNone&/span&
&span class=&p&&};&/span&
&/code&&/pre&&/div&&p&源码里面会在这个函数做合法性检查:&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span& &span class=&n&&blocked_reason&/span& &span class=&o&&=&/span& &span class=&n&&Context&/span&&span class=&p&&().&/span&&span class=&n&&CanRequest&/span&&span class=&p&&(&/span&&span class=&cm&&/*参数省略*/&/span&&span class=&p&&);&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&blocked_reason&/span& &span class=&o&&!=&/span& &span class=&n&&ResourceRequestBlockedReason&/span&&span class=&o&&::&/span&&span class=&n&&kNone&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&n&&kBlock&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&CanRequest函数会相应地检查以下内容:&/p&&h2&(1)CSP(Content Security Policy)内容安全策略检查&/h2&&p&CSP是减少XSS攻击一个策略。如果我们只允许加载自己域的图片的话,可以加上下面这个meta标签:&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&p&&&&/span&&span class=&nt&&meta&/span& &span class=&na&&http-equiv&/span&&span class=&o&&=&/span&&span class=&s&&&Content-Security-Policy&&/span& &span class=&na&&content&/span&&span class=&o&&=&/span&&span class=&s&&&img-src 'self';&&/span&&span class=&p&&&&/span&
&/code&&/pre&&/div&&p&或者是后端设置这个http响应头。&/p&&p&self表示本域,如果加载其它域的图片浏览器将会报错:&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-bf28b28cd47bbab12ba1223_b.jpg& data-caption=&& data-rawwidth=&874& data-rawheight=&150& class=&origin_image zh-lightbox-thumb& width=&874& data-original=&https://pic2.zhimg.com/v2-bf28b28cd47bbab12ba1223_r.jpg&&&/figure&&p&&br&&/p&&p&所以这个可以防止一些XSS注入的跨域请求。&/p&&p&源码里面会检查该请求是否符合CSP的设定要求:&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span& &span class=&k&&const&/span& &span class=&n&&ContentSecurityPolicy&/span&&span class=&o&&*&/span& &span class=&n&&csp&/span& &span class=&o&&=&/span& &span class=&n&&GetContentSecurityPolicy&/span&&span class=&p&&();&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&csp&/span& &span class=&o&&&&&/span& &span class=&o&&!&/span&&span class=&n&&csp&/span&&span class=&o&&-&&/span&&span class=&n&&AllowRequest&/span&&span class=&p&&(&/span&
&span class=&n&&request_context&/span&&span class=&p&&,&/span& &span class=&n&&url&/span&&span class=&p&&,&/span& &span class=&n&&options&/span&&span class=&p&&.&/span&&span class=&n&&content_security_policy_nonce&/span&&span class=&p&&,&/span&
&span class=&n&&options&/span&&span class=&p&&.&/span&&span class=&n&&integrity_metadata&/span&&span class=&p&&,&/span& &span class=&n&&options&/span&&span class=&p&&.&/span&&span class=&n&&parser_disposition&/span&&span class=&p&&,&/span&
&span class=&n&&redirect_status&/span&&span class=&p&&,&/span& &span class=&n&&reporting_policy&/span&&span class=&p&&,&/span& &span class=&n&&check_header_type&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&n&&ResourceRequestBlockedReason&/span&&span class=&o&&::&/span&&span class=&n&&kCSP&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&如果有CSP并且AllowRequest没有通过的话就会返回堵塞的原因。具体的检查过程是根据不同的资源类型去获取该类资源资源的CSP设定进行比较。&/p&&p&接着会根据CSP的要求改变请求:&/p&&div class=&highlight&&&pre&&code class=&language-cpp&&&span&&/span&&span class=&n&&ModifyRequestForCSP&/span&&span class=&p&&(&/span&&span class=&n&&request&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&主要是升级http为https。&/p&&h2&(2)upgrade-insecure-requests&/h2&&p&如果设定了以下CSP规则:&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&p&&&&/span&&span class=&nt&&meta&/span& &span class=&na&

我要回帖

更多关于 250ppcom页面升级到 的文章

 

随机推荐