インラインスタイル属性をstyleMapディレクティブに置き換えてCSP違反を解消
4つのコンポーネントがインラインスタイル属性を使用していたことでContent Security Policy(CSP)の style-src 'unsafe-inline' 制約に違反していた問題が修正されました。この変更により、unsafe-inline を許可しない厳格なCSPポリシー下でもこれらのコンポーネントが正常に動作するようになります。
背景
#1937 で報告されたように、wa-animated-image、wa-carousel、wa-progress-ring、wa-slider の4つのコンポーネントは、文字列形式の style 属性を使用してスタイルを動的に設定していました。この実装方法は、CSPで style-src 'self' のように unsafe-inline を含めないポリシーを設定している環境では、セキュリティポリシー違反として動作がブロックされます。
文字列形式のスタイル属性はインラインスタイルとして扱われるため、CSPの style-src ディレクティブでインラインスタイルを許可する必要がありました。#1937 では、style-src 'self' のみを指定した環境で <wa-slider> や <wa-progress-ring> を使用すると、「Applying inline style violates the following Content Security Policy directive」というエラーが発生することが報告されています。
技術的な変更
全てのコンポーネントで、文字列形式の style 属性を Lit の styleMap ディレクティブ に置き換えました。styleMapはDOM APIを通じてCSSプロパティを直接設定するため、インラインスタイルとして評価されず、CSP違反を回避できます。
wa-animated-image(src/components/animated-image/animated-image.ts)では、静的なマージンスタイルを変換しました:
変更前:
style="margin-inline-start: 3px;"
変更後:
import { styleMap } from 'lit/directives/style-map.js';
style=${styleMap({ 'margin-inline-start': '3px' })}
wa-carousel(src/components/carousel/carousel.ts)では、CSS変数 --slides-per-page の設定を変換しました:
変更前:
style="--slides-per-page: ${this.slidesPerPage};"
変更後:
import { styleMap } from 'lit/directives/style-map.js';
style=${styleMap({ '--slides-per-page': this.slidesPerPage })}
wa-progress-ring(src/components/progress-ring/progress-ring.ts)では、--percentage CSS変数と stroke-dashoffset プロパティを変換しました:
変更前:
style="--percentage: ${this.value / 100}"
// ...
style="stroke-dashoffset: ${this.indicatorOffset}"
変更後:
import { styleMap } from 'lit/directives/style-map.js';
style=${styleMap({ '--percentage': this.value / 100 })}
// ...
style=${styleMap({ 'stroke-dashoffset': this.indicatorOffset })}
wa-slider(src/components/slider/slider.ts)では、--position、--start、--end の各CSS変数を変換しました。このコンポーネントでは6箇所の変更が行われています:
変更前:
style="--position: ${marker}%"
// ...
style="--start: ${Math.min(minThumbPosition, maxThumbPosition)}%; --end: ${Math.max(minThumbPosition, maxThumbPosition)}%"
// ...
style="--position: ${minThumbPosition}%"
// ...
style="--position: ${maxThumbPosition}%"
変更後:
import { styleMap } from 'lit/directives/style-map.js';
style=${styleMap({ '--position': `${marker}%` })}
// ...
style=${styleMap({
'--start': `${Math.min(minThumbPosition, maxThumbPosition)}%`,
'--end': `${Math.max(minThumbPosition, maxThumbPosition)}%`,
})}
// ...
style=${styleMap({ '--position': `${minThumbPosition}%` })}
// ...
style=${styleMap({ '--position': `${maxThumbPosition}%` })}
styleMapへの変換により、テストの期待値も更新されました。progress-ring.test.ts では、styleMap が生成するスタイル文字列の形式(セミコロン区切り、スペースなし)に合わせて期待値を調整しています:
// 変更前: '--percentage: 0.25'
// 変更後: '--percentage:0.25;'
expect(base).attribute('style', '--percentage:0.25;');
設計判断
文字列形式のstyle属性ではなく styleMap ディレクティブ が選択されました。
styleMapは、オブジェクトとしてスタイルプロパティを受け取り、DOM APIの element.style.setProperty() を使用して直接適用します。この方式では、スタイルシートやインラインスタイル属性として解釈されないため、CSPの style-src ディレクティブによる制約を受けません。
#1937 では、解決策として「Lit's styleMap directive rather than string attributes」の使用が提案されており、「This sets the CSS properties programmatically, which avoids the policy violation」と説明されています。このアプローチは、Web Awesomeの他のコンポーネントで既に実績があり、同様の手法を採用することで一貫性が保たれます。
まとめ
本PRは、4つのコンポーネントで文字列形式のstyle属性をstyleMapディレクティブに置き換えることで、CSPポリシーとの互換性を確保しました。DOM APIを通じた直接的なスタイル設定により、style-src 'unsafe-inline' の制約なしに動作可能になっています。この変更は、セキュリティ要件の厳しい環境でのコンポーネント利用を可能にし、Web Awesomeのコンポーネント群全体の堅牢性を向上させています。