拝啓 UserScript作者の皆様におかれましてはいかがお過ごしでしょうか。
わたしは以前書いたUserScriptが、サイト側のCSP導入によって <style>
の挿入だけ動かなくなっていました。
今日はその話。
いつものやつ
document.head.insertAdjacentHTML('beforeend', ` <style> #foo { display: none } #bar { cursor: pointer } </style> `);
…このシンタックスハイライト、Template literal 認識してなくない?
CSPでunsafe-inlineが許可されなくなると、こんな感じで蹴られるようになる。
Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self' https:". Either the 'unsafe-inline' keyword, a hash ('sha256-alRSRYviRMwuItyDEgtLXyYEpCVEYkqt5i6LJx1Oes4='), or a nonce ('nonce-...') is required to enable inline execution.
この時 HTMLStyleElement#sheet
が null
になっていることで判別ができる(普通は appendChild したところで CSSStyleSheet が入る)。
別解1. Constructable Stylesheet Objects を使う
https://wicg.github.io/construct-stylesheets/
Motivationを斜め読みした感じ…Web Components で Shadow Root の中にだけ適用されるスタイルシートが増えるなかで、スタイルシートの処理や内部表現を効率化したい。styleタグについてはそういうことをしたりもするけど、JS側でスタイルを変更することもあってどうしたものか。というところで、CSSStyleSheetをJS側で作って再利用できるようにしよう、ということらしい?
で現状Blinkでしか使えないこのインターフェイス、なんとCSPの制約を受けない。将来もそうなのかはどうなんだろうなと思いつつ。
const sheet = new CSSStyleSheet(); // syncでない、Promise返す版もある sheet.replaceSync(` #foo { display: none } #bar { cursor: pointer } `); // adoptedStyleSheets は FrozenArray なので要素の追加はできない。再代入はできる。 document.adoptedStyleSheets = document.adoptedStyleSheets.concat(sheet);
別解2. 既存の CSSStyleSheet にルールを追加する
CSPが効いていようと、既にドキュメントに関連付けられている CSSStyleSheet には(同一オリジンなら)ルールを追加できる。
// 同一オリジンで最も後ろにあるスタイルシートを探す const usableSheet = [...document.styleSheets].filter(x => x.href?.startsWith(location.origin)).slice(-1)[0]; usableSheet.insertRule('#foo { display: none }`, usableSheet.cssRules.length); usableSheet.insertRule('#bar { cursor: pointer }`, usableSheet.cssRules.length);
@media
などのat-rulesも書ける。惜しいのは複数のルールをまとめて追加できないことだけども、条件が常に満たされるようなグループに入れてしまえば大抵いけそうではある。
const usableSheet = [...document.styleSheets].filter(x => x.href?.startsWith(location.origin)).slice(-1)[0]; usableSheet.insertRule(`@supports (display:block) { #foo { display: none } #bar { cursor: pointer } }`, usableSheet.cssRules.length);
あとがき
最終的にきっかけとなったUserScriptは <style>
→ adoptedStyleSheets → insertRule の順に試すようにした。ただこれだと一度style要素を挿入してみる際にコンソールにエラーが出力されてしまうので、邪魔と言えば邪魔。サイレントに確認する方法を探るか、あるいはその方法はスキップしてしまってもいいかもしれない。