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]);

功能是两张图片合并,这段代码本意是个清理函数,但当时脑子不清醒将他们写在了一起,这样就出现了一个逻辑问题:

  1. 用户先选择图 A,正常
  2. 用户选择了图 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>

使用流程是这样的:

  1. 使用 input 元素选择一张图片进行显示
  2. 点击 revoke 按钮,查看新的两张图片是否能显示

在火狐上,三张图片都是能够正常显示的,但在 Chromium 系浏览器上,只有第一张图片能正常显示,后面两张图片会显示为 ERR_FILE_NOT_FOUND。

但使用 fetch 访问 blob url 时,两种浏览器都能正常抛出错误,所以我认为是火狐浏览器出现了问题,是内核 Gecko 的。

目前已提交到 Bugzilla,等待回复。

更新#

官方已确定是 Bug,已排上修复日程。

关于 Blob url 在火狐和 Chromium 上的行为不一致
https://blog.erio.work/posts/关于bloburl在火狐和chromium上的行为不一致/
作者
Dupfioire
发布于
2025-04-15
许可协议
CC BY-NC-SA 4.0