最近发现一件有意思的事情,在Chrome 61 62 64 68版本上(也许不止这些版本),滚动事件会概率性Block住Ajax请求或者其它的异步事件,具体表现为,只要用户使用鼠标滚轮滚动页面,请求就一直Pending, Promise、setTimeout 这些异步行为也不会往下走。如果直接拖动滚动条,则不会出现这一问题。
这个问题多出现在滚动到底/顶触发请求的场景,比如聊天列表滚动到顶拉取IM消息,网页滚动到底拉取新的博客这种。
可以自己测试一下玩玩看,HTML代码如下(知乎竟然不支持上传html文件...)。
这个问题不是必现的,感觉上似乎得滚动得很快才能复现,本组测试妹子使用这个html可以多次复现,可以清楚地看到图片请求被Pending在那里,即便她停止滚动,请求依旧Pending,而通过滚动条触发的请求则不会被Pending。(那一刻我震惊了...)
<!--
Reproduction of https://bugs.chromium.org/p/chromium/issues/detail?id=661155
-->
<html>
<head>
<title>Scroll network throttling</title>
<style>
.fixed {
position: fixed;
}
img {
width: 120px;
height: 120px;
background: #777;
}
body {
background: repeating-linear-gradient(to bottom, #ddd, #ddd 40px, #fff 40px, #fff 80px);
}
</style>
</head>
<body>
<p>
Scroll the page and don't stop (easiest on devices with inertia scrolling or a free-scrolling wheel). After 1000px of scrolling the image src will be set.
</p>
<div class="fixed">
<img id="img" />
</div>
<div style="height: 20000%"></div>
<script>
// Bug only occurs if there is a touch listener attached to any element (even if marked as passive)
document.body.addEventListener('touchstart', () => {});
const initialScrollOffset = window.pageYOffset;
const img = document.getElementById('img');
const setSrc = () => {
img.src = 'http://placeimg.com/200/200/any';
}
const scrollCallback = (e) => {
if (window.pageYOffset - initialScrollOffset < 1000) {
return;
}
document.removeEventListener('scroll', scrollCallback);
setSrc();
}
document.addEventListener('scroll', scrollCallback)
</script>
</body>
</html>
所以,奇怪的知识增加了!开心!
——————————————————————————————————————
分割线后是详细的调查过程,主要研究目标是Content Download耗时过长问题。
事情的起因是这样的:
有用户报障说卡顿,远程看了一下发现请求的Timing里Content Download耗时特别久,虽然官方文档说Content Download只是Chrome从收到响应的第一个比特开始,到接收完所有数据的耗时,但是由于当时网络延迟很低,只有6ms,也没有丢包,所以我觉得3KB的东西不太可能传700ms,于是开始研究Content Download耗时这个问题。
有博客[1]的问题描述跟我们比较相似,大致意思是Chrome浏览器对于mousewheel实现的不标准,即便使用scroll事件也会相互影响,解决方案是干掉window下mousewheel事件的默认行为。但是这个结论怎么看怎么诡异,同时我这边也不能复现问题,所以没有办法验证。
于是继续查,在这篇博客[2]里,有对应的gif录像,大致意思是用鼠标滚轮触发请求之后,在Chrome里会一直转圈,在其它浏览器里都正常,同时如果用鼠标拖动滚动条也是正常的,所以怀疑Chrome有bug,但是他试验出来网络请求就是一直Pending,并不会触发Content Download时间过长。
另外,在某个提问[3]中,也叙述了Chrome中使用 鼠标的滚动事件block住异步行为的问题。
同时,在Chrome的某个issue页[4]中找到了相关的描述和复现方式,请测试妹纸复现成功,验证在我们QT 5.12编译出来的 Chrome 61版本的应用中确实可以复现这个问题。
所以我想了一下,第一篇博客那个诡异的结论可能是在服务端返回第一个比特之后,再触发的异步行为被block,不过,我实在是复现不了。我能够确认的是,Chrome真的有滚动block异步行为的问题,不过可能触发条件太tricky了,所以直到现在,这个问题还是没有修复的记录[5]。
相关链接:
[1] 关于ajax的content-download时间过慢问题的解决方案与思考
[2] https://github.com/TryGhost/Ghost/issues/7934
[3] https://stackoverflow.com/questions/35024301/xhr-settimeout-promise-not-finishing-until-scrolling-stops-in-chrome
[4] https://bugs.chromium.org/p/chromium/issues/detail?id=661155
[5] https://bugs.chromium.org/p/chromium/issues/detail?id=874836