From 6c359d63fb3ab1953fe446759ef453e85710b1d4 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Fri, 22 Nov 2024 16:19:02 +0530 Subject: [PATCH 1/2] Add support for CSSStyleSheet instance in DomRenderer without relying solely on nonce This commit adds support for using a `CSSStyleSheet` instance provided by the user in the `DomRenderer` class. This enhancement allows JSS to inject styles directly into a `CSSStyleSheet` instance, which is particularly useful in CSP enabled applications where inline styles are restricted and the nonce value is not exposed. - Updated the constructor of `DomRenderer` to accept a `CSSStyleSheet` instance as the `insertionPoint`. - Modified the `insertStyle` method to handle `CSSStyleSheet` instances. - Maintained support for nonce while providing an alternative for environments where the nonce value is not exposed. This change improves the flexibility and security of style injection in JSS. --- packages/jss/src/DomRenderer.js | 47 ++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/jss/src/DomRenderer.js b/packages/jss/src/DomRenderer.js index 5151ca7f3..ec5a738db 100644 --- a/packages/jss/src/DomRenderer.js +++ b/packages/jss/src/DomRenderer.js @@ -210,6 +210,16 @@ function insertStyle(style, options) { return } + // if insertionPoint is instance of CSSStyleSheet then we can insert the rule at the end of the stylesheet + // and we don't need to create a new style element + if (insertionPoint instanceof CSSStyleSheet) { + try { + insertionPoint.insertRule(style.textContent, insertionPoint.cssRules.length) + } catch (err) { + warning(false, `[JSS] ${err.message}`) + } + return + } getHead().appendChild(style) } @@ -276,13 +286,19 @@ export default class DomRenderer { if (sheet) sheets.add(sheet) this.sheet = sheet - const {media, meta, element} = this.sheet ? this.sheet.options : {} - this.element = element || createStyle() - this.element.setAttribute('data-jss', '') - if (media) this.element.setAttribute('media', media) - if (meta) this.element.setAttribute('data-meta', meta) - const nonce = getNonce() - if (nonce) this.element.setAttribute('nonce', nonce) + const {media, meta, element, insertionPoint} = this.sheet ? this.sheet.options : {} + if (insertionPoint instanceof CSSStyleSheet) { + // If insertionPoint is an instance of CSSStyleSheet, use it directly. + this.element = insertionPoint + } else { + // Otherwise, create a new style element. + this.element = element || createStyle() + this.element.setAttribute('data-jss', '') + if (media) this.element.setAttribute('media', media) + if (meta) this.element.setAttribute('data-meta', meta) + const nonce = getNonce() + if (nonce) this.element.setAttribute('nonce', nonce) + } } /** @@ -345,14 +361,21 @@ export default class DomRenderer { * Insert a rule into element. */ insertRule(rule, index, nativeParent = this.element.sheet) { + // create a new variable to hold the nativeParent + let parentNode = nativeParent + // If the element is an instance of CSSStyleSheet, use it directly. + if (this.element instanceof CSSStyleSheet) { + parentNode = this.element + } + if (rule.rules) { const parent = rule - let latestNativeParent = nativeParent + let latestNativeParent = parentNode if (rule.type === 'conditional' || rule.type === 'keyframes') { - const insertionIndex = getValidRuleInsertionIndex(nativeParent, index) + const insertionIndex = getValidRuleInsertionIndex(parentNode, index) // We need to render the container without children first. latestNativeParent = insertRule( - nativeParent, + parentNode, parent.toString({children: false}), insertionIndex ) @@ -369,8 +392,8 @@ export default class DomRenderer { if (!ruleStr) return false - const insertionIndex = getValidRuleInsertionIndex(nativeParent, index) - const nativeRule = insertRule(nativeParent, ruleStr, insertionIndex) + const insertionIndex = getValidRuleInsertionIndex(parentNode, index) + const nativeRule = insertRule(parentNode, ruleStr, insertionIndex) if (nativeRule === false) { return false } From 492cdc7bc65e031cafe9d90eb2d6b49235998daa Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Fri, 22 Nov 2024 17:10:42 +0530 Subject: [PATCH 2/2] Update documentation to include CSSStyleSheet instance usage for CSP support This commit updates the documentation to include instructions for using a `CSSStyleSheet` instance with JSS in Content Security Policy (CSP) enabled applications. This addition complements the improvement introduced in this pull request by providing detailed guidance for secure style injection without relying solely on nonce attributes. - Added a new section, **Using a `CSSStyleSheet` instance for secure style injection**, under **Configuring Content Security Policy**. - Included an example demonstrating how to create and use a `CSSStyleSheet` instance with JSS. - Documented the benefits and notes of using a `CSSStyleSheet` instance as an alternative to nonce-based CSP compliance. To enhance user understanding and provide clear instructions on leveraging the newly introduced feature for improved flexibility and security in CSP-enabled environments. --- docs/setup.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/docs/setup.md b/docs/setup.md index 671b8b611..43011f86a 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -118,6 +118,48 @@ jss.setup({ }) ``` +## Using a `CSSStyleSheet` instance for secure style injection + +For environments with strict Content Security Policy (CSP) settings, JSS now supports injecting styles into a `CSSStyleSheet` instance. This approach is particularly useful when inline styles are restricted and a nonce value is unavailable or not exposed. + +### Example + +Create a `CSSStyleSheet` instance and pass it to JSS during setup: + +```javascript +import jss from 'jss' + +const sheet = new CSSStyleSheet() +document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet] + +jss.setup({ + insertionPoint: sheet // Pass the CSSStyleSheet instance +}) + +// Create your style. +const style = { + myButton: { + color: 'green' + } +} + +// Compile styles, apply plugins. +const jssSheet = jss.createStyleSheet(style) + +// Inject styles directly into the provided CSSStyleSheet. +jssSheet.attach() +``` + +### Benefits + +- Enables secure style injection into a `CSSStyleSheet` instance, avoiding inline styles. +- Works seamlessly in CSP-enabled applications where the nonce attribute cannot be used or accessed. + +### Notes + +- This feature is an alternative to nonce-based CSP compliance. Both approaches are supported. +- Ensure the provided `CSSStyleSheet` instance is valid and adopted by the document where styles need to be applied. + ## Configuring Content Security Policy You might need to set the `style-src` CSP directive, but do not want to set it to `unsafe-inline`. See [these instructions for configuring CSP](csp.md).