问题背景

前段时间同事反馈了一个诡异的问题:页面打开后操作按钮被禁用,但是登录的账户是超管,所有权限都有,而且刷新页面后就恢复正常,也不能复现了。先说下权限控制的大致流程:通过 vue 路由 beforeEach,请求权限列表,然后存储在 sessionStorage,然后用的时候读取出来处理按钮的禁用。

由于不能复现,只偶现过几次,其中排查了后端返回,是正常的,问题大概率在前端,但由于没法复现看了代码也实在没什么漏洞,也就没有继续跟踪。结果今天偶然间复现了:关掉其他 tab 栏,在地址栏 Ctrl+V 粘贴有问题页面的地址,回车,就能复现。

问题排查

复现后第一件事先看 sessionStorage,果然是空的,但是权限的请求与存储是在 beforeEach 中,不存上按理说是不能 next()渲染页面的,百思不得其解,于是准备打开 devtools 再复现一遍,看下接口和控制台有没有报错。结果奇了怪了,就像薛定谔的错误,打开 devtools 就无法复现,关掉就大概率出现,即使关掉了禁用缓存也不行。

突破口是在我发现输入地址回车的时候,页面几乎是瞬间出现,没有任何空白与加载,让我想到 Chrome 的一个功能:预渲染。所以就关掉预渲染再试下,果然无法再现,页面正常。难道是预渲染的时候出现没有带上 cookie 或者没有请求权限接口等问题?既然不能打开 devtools,只能祭出 Wireshark,经过抓包,发现所有请求全部正常,难道预渲染时无法设置 sessionStorage? 于是在 set 后立马 get 并打印出来,居然是正常的,这就离了大谱了。

转去问了下 GPT,结合情况,得出了 预加载的页面和实际打开的页面是在不同的会话上下文中运行的 的结论,那这就麻烦了,由于项目使用 sessionStorage 是比较固定的方案,如果判断没有再请求一次或者更换存储位置甚至换掉整个方案的话,需要改动的地方太多,因为有很多子项目和其他的项目也使用了这种方案代码,而且让用户禁用预渲染也不现实,于是乎想寻求一种最好在设置时就能处理预渲染的情况的方案。

解决方案

通过 Copilot 找到了 Chrome 预渲染的相关文档, 学到了原来 document 还有prerendering 属性prerenderingchange 方法,他们两个都是实验性功能,只在 Chrome 和 Edge 等一些浏览器的较高版本才有,Firefox 和 Safari 都没有。

既然找到了文档那就好办了,先判断是不是预渲染状态,然后当预渲染结束,再设置 sessionStorage 和路由跳转即可:

1
2
3
4
5
6
7
8
9
10
11
12
const callback = () => {
sessionStorage.setItem(.......);
next();
};

if (document.prerendering) {
document.addEventListener('prerenderingchange', callback, {
once: true
});
} else {
callback();
}