インラインスタイル属性をstyleMapディレクティブに置き換えてCSP違反を解消

shoelace-style/webawesome

4つのコンポーネントがインラインスタイル属性を使用していたことでContent Security Policy(CSP)の style-src 'unsafe-inline' 制約に違反していた問題が修正されました。この変更により、unsafe-inline を許可しない厳格なCSPポリシー下でもこれらのコンポーネントが正常に動作するようになります。

背景

#1937 で報告されたように、wa-animated-imagewa-carouselwa-progress-ringwa-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-imagesrc/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-carouselsrc/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-ringsrc/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-slidersrc/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のコンポーネント群全体の堅牢性を向上させています。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
4回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術的変更・設計判断(各論)→まとめ(結論)という「総論→各論→結論」の構成が明確に適用されており、非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(例: ```typescript:src/components/animated-image/animated-image.ts)やGitHubのIssue/PRリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

CSP、Lit、styleMapディレクティブといった専門用語を前提としており、専門知識を持つエンジニアという対象読者に適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論で構成され、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が徹底されています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内のコードブロックは、提供されたDiff情報と完全に一致しており、変更内容を正確に引用しています。テストコードの変更点にまで言及している点も的確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「styleMapディレクティブ」「Content Security Policy (CSP)」「DOM API」などの技術用語が、文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「styleMapはDOM APIを通じてCSSプロパティを直接設定するため、インラインスタイルとして評価されず、CSP違反を回避できる」という説明は技術的に正確かつ明快です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(修正対象のコンポーネント、原因、解決策)は、PRのDescriptionやDiffの内容によって裏付けられており、ハルシネーションは検出されませんでした。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#1980)、Issue番号(#1937)などの固有名詞や数値は、提供された情報と一致しています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「インラインスタイル属性をstyleMapディレクティブに置き換えてCSP違反を解消」は、PRのタイトル「updated style attribute to styleMap」の意図を汲み取り、より具体的に表現しており、内容と完全に一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR情報に記載のない外部知識(バージョン情報、リリース予定など)の追加はなく、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「修正されました」「報告された」といった時間表現は、完了した変更を扱うPRの内容と一致しており、正確です。