24/7 twenty-four seven

iOS/OS X application programing topics.

Content-Security-Policy (CSP)でWeb Extension(Webブラウザ拡張機能)のCSSがブロックされる場合の対処

Webブラウザの拡張機能は本質的に第三者のWebサイトにJavaScriptやCSSをインジェクションします。

WebサイトによってはContent-Security-Policy (CSP)によってJSやCSSのインジェクションを制限している場合があります。

developer.mozilla.org

外部のユーザーエージェントによるJSやCSSを制限するContent-Security-Policyが設定されているWebサイトは多くはないのですが、AppleのDeveloperドキュメント以下のコンテンツにはかなりImageやCSSについて厳しめのCSPが設定されています。

developer.apple.com

Content-Security-Policy: default-src 'self' *.apple.com; script-src 'self' *.apple.com 'unsafe-eval' 'sha256-7njJh...' 'sha256-fgSWl...'; img-src 'self' *.apple.com data:; style-src 'self' *.apple.com 'sha256-8sYhe...';

レスポンスヘッダを見ると上記のようなContent-Security-Policyヘッダが設定されています。

このときstyle-src 'self' *.apple.comなのでapple.comドメイン以外のソースから提供されるCSSはブロックされます。

<link href="https://not-apple.com/styles/main.css" rel="stylesheet" />

<style>
  #inline-style {
    background: red;
  }
</style>

<style>
  @import url("https://not-apple.com/styles/print.css") print;
</style>

インラインのstyle属性もブロックされます。

<div style="display:none">Foo</div>

Webブラウザの拡張機能では、WebサイトのCSSに影響を与えないように拡張機能が挿入したHTMLにはインラインのstyle属性を使うことはわりとありますが、そのように書いている場合はスタイルがブロックされて適用されません。

JavaScript で直接style属性を設定したり、cssTextを設定したりしたスタイルも同様です。

document.querySelector("div").setAttribute("style", "display:none;");
document.querySelector("div").style.cssText = "display:none;";

以下のような、JavaScriptのstyleプロパティを使って直接設定する場合はスタイルが適用されます。

document.querySelector("div").style.display = "none";

詳しくは下記の解説を見てください。

developer.mozilla.org

ただ、すべてのCSSをJavaScriptのstyleプロパティを使って書いていくことは現実的ではないことが多いですし、DOM要素があらかじめ存在していなかったり擬似要素には適用できないなど、技術的に不可能な場合もあります。

対策としてはbrowser.runtime.getURL()関数で取得したURLはそのWebサイト自身のソース扱いになるので、下記のようにCSSを参照するLinkタグを追加するとContent-Security-Policyでstyle-src 'self'と設定されていてもそのCSSは問題なく読み込まれます。

衝突するようなスタイルがあるとWebサイトに影響を与えてしまうのでそこは注意する必要があります。

const link = document.createElement("link");
link.setAttribute("rel", "stylesheet");
link.setAttribute("href", browser.runtime.getURL("assets/style.css"));
document.head.appendChild(link);

Webコンポーネント(Shadow DOM)に対して同様のことを行うには下記のようにします。

const link = document.createElement("link");
link.setAttribute("rel", "stylesheet");
link.setAttribute("href", browser.runtime.getURL("assets/style.css"));
this.shadowRoot.appendChild(link);