753 字
4 分钟
关于 Blob url 在火狐和 Chromium 上的行为不一致
概括
对于 revoked 的 blob url,火狐依然能通过元素访问,但 Chromium 不行。
起因
起因是在做一个功能的时候,火狐正常,但 Chromium 全系报错,具体代码是这样的(代码逻辑有误,但阴差阳错发现了这个问题):
useEffect(() => {
return () => {
if (imageA) URL.revokeObjectURL(imageA);
if (imageB) URL.revokeObjectURL(imageB);
if (mergedImage) URL.revokeObjectURL(mergedImage);
};
}, [imageA, imageB, mergedImage]);
功能是两张图片合并,这段代码本意是个清理函数,但当时脑子不清醒将他们写在了一起,这样就出现了一个逻辑问题:
- 用户先选择图 A,正常
- 用户选择了图 B,此时执行上一轮的清理函数,将图 A 的 Blob url 回收了
在图片合并时会使用图 A 和图 B 的 url,此时在 Chromium 系浏览器就报错了,提示 ERR_FILE_NOT_FOUND
,但火狐上依然正常。
复现
针对这个问题,我做了一个小 Demo 去测试:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test revoke</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<input type="file" id="file">upload</button>
<button>revoke</button>
<button>remove all</button>
<script>
const fileInput = document.getElementById('file');
const createBlobUrl = (file) => {
const blobUrl = URL.createObjectURL(file);
console.log(blobUrl);
return blobUrl;
};
const revokeBlobUrl = (blobUrl) => {
URL.revokeObjectURL(blobUrl);
console.log('revoke blob url:', blobUrl);
};
let blobUrl = null;
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
blobUrl = createBlobUrl(file);
const img = document.createElement('img');
img.src = blobUrl;
img.style.width = '300px';
img.style.height = '300px';
document.body.appendChild(img);
}
});
document.querySelector('button').addEventListener('click', () => {
revokeBlobUrl(blobUrl);
const img = document.createElement('img');
img.src = blobUrl;
img.style.width = '300px';
img.style.height = '300px';
document.body.appendChild(img);
setTimeout(() => {
const img2 = document.createElement('img');
img2.src = blobUrl;
img2.style.width = '300px';
img2.style.height = '300px';
document.body.appendChild(img2);
}, 5000);
});
// remove all img elements
document.querySelector('button:nth-of-type(2)').addEventListener('click', () => {
const imgs = document.querySelectorAll('img');
imgs.forEach(img => {
img.remove();
});
const img = document.createElement('img');
img.src = blobUrl;
img.style.width = '300px';
img.style.height = '300px';
document.body.appendChild(img);
});
</script>
</body>
</html>
使用流程是这样的:
- 使用 input 元素选择一张图片进行显示
- 点击 revoke 按钮,查看新的两张图片是否能显示
在火狐上,三张图片都是能够正常显示的,但在 Chromium 系浏览器上,只有第一张图片能正常显示,后面两张图片会显示为 ERR_FILE_NOT_FOUND。
但使用 fetch 访问 blob url 时,两种浏览器都能正常抛出错误,所以我认为是火狐浏览器出现了问题,是内核 Gecko 的。
目前已提交到 Bugzilla,等待回复。
更新
官方已确定是 Bug,已排上修复日程。
关于 Blob url 在火狐和 Chromium 上的行为不一致
https://blog.erio.work/posts/关于bloburl在火狐和chromium上的行为不一致/