Refused to load the image '<ImageURL>' because it violates the following Content

699 阅读1分钟

背景

想在 GitHub 做一个自动注入 npm badge 的链接,点击即可跳转到 npm。 而无需查看 package.json name 手动拼接对应的 npm 链接。 但是因为 GitHub 设置了 CSP 导致没法直接插入除 GitHub 等规定域名以外的图片。

执行:

$('.markdown-body h1').insertAdjacentHTML('afterend', `<a href="https://badge.fury.io/js/ts-brand"><img src="https://badge.fury.io/js/ts-brand.svg" alt="npm version" height="18"></a>`);

报错: image.png

Refused to load the image 'badge.fury.io/js/ts-brand…' because it violates the following Content Security Policy directive: "img-src data: 'self' data: github.githubassets.com identicons.github.com collector.githubapp.com collector.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ *.githubusercontent.com".

解决方案

Inline image use createObjectURL and readAsDataURL

通过 createObjectURL 或 readAsDataURL 让图片内联到 DOM。

createObjectURL

async function fetchObjectURL(url) {
  const { URL, fetch } = window;
  
  const blob = await fetch(url).then((resp) => resp.blob());
  const objectURL = URL.createObjectURL(blob);

  return [objectURL, () => { return URL.revokeObjectURL(objectURL); }]
}

使用

const httpSrc = `https://img.shields.io/npm/v/verb-corpus.svg`
const [objectURL, revokeObjectURL] = await fetchObjectURL(httpSrc)

img.src = objectURL

// 虽然浏览器会在 DOM unload 自动销毁但建议使用完毕手动回收
img.onload = revokeObjectURL

readAsDataURL

async function fetchDataURL(url) {
  const reader = new FileReader()

  const blob = await fetch(url).then((resp) => resp.blob())

  reader.readAsDataURL(blob)

  return new Promise((resolve, reject) => {
    reader.addEventListener('load', () => {
      resolve(reader.result)
    });

    reader.addEventListener('error', event => {
      reject(event)
    });
  })
}

使用

const dataURL = await fetchDataURL(httpSrc)

img.src = dataURL

效果图

image.png

技术总结

下面我们开始技术总结:

  1. 可设置图片的load事件处理器来释放对象URL,当图片加载完成之后对象URL就不再需要了。可通过调用window.URL.revokeObjectURL()来释放。

  2. 返回释放函数,是一个经典的模式,让调用者尽可能的少感知逻辑,这个叫做『最小知识原则』也叫做『Law of Demeter』。否则得这么写:

    img.onload = () => {
      window.URL.revokeObjectURL(objectURL); // BAD => 抽象泄露
    }
    
  3. reader 不能重复使用,故每次都 new FileReader()

参考