From 5b5631d4ee983ccd68ab3038539b45d9ce37217f Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Wed, 1 Jun 2022 08:16:25 -0700 Subject: [PATCH 1/8] Remove css-tag stylesheet caching by css text --- .changeset/loud-dragons-hang.md | 7 ++ packages/reactive-element/src/css-tag.ts | 19 +++--- .../reactive-element/src/test/css-tag_test.ts | 65 +++++++++---------- 3 files changed, 47 insertions(+), 44 deletions(-) create mode 100644 .changeset/loud-dragons-hang.md diff --git a/.changeset/loud-dragons-hang.md b/.changeset/loud-dragons-hang.md new file mode 100644 index 0000000000..c8cf65172d --- /dev/null +++ b/.changeset/loud-dragons-hang.md @@ -0,0 +1,7 @@ +--- +'@lit/reactive-element': patch +'lit': patch +'lit-element': patch +--- + +Changed the caching behavior of the css`` template literal tag so that same-text styles do not share a CSSStyleSheet. Note that this may be a breaking change in some very unusual scenarios on Chromium browsers only. diff --git a/packages/reactive-element/src/css-tag.ts b/packages/reactive-element/src/css-tag.ts index 47a63fbdbb..9aacc3975f 100644 --- a/packages/reactive-element/src/css-tag.ts +++ b/packages/reactive-element/src/css-tag.ts @@ -31,8 +31,6 @@ export type CSSResultGroup = CSSResultOrNative | CSSResultArray; const constructionToken = Symbol(); -const styleSheetCache = new Map(); - /** * A container for a string of CSS text, that may be used to create a CSSStyleSheet. * @@ -44,6 +42,7 @@ export class CSSResult { // This property needs to remain unminified. ['_$cssResult$'] = true; readonly cssText: string; + private _styleSheet?: CSSStyleSheet; private constructor(cssText: string, safeToken: symbol) { if (safeToken !== constructionToken) { @@ -54,17 +53,15 @@ export class CSSResult { this.cssText = cssText; } - // Note, this is a getter so that it's lazy. In practice, this means - // stylesheets are not created until the first element instance is made. + // This is a getter so that it's lazy. In practice, this means stylesheets + // are not created until the first element instance is made. get styleSheet(): CSSStyleSheet | undefined { - // Note, if `supportsAdoptingStyleSheets` is true then we assume - // CSSStyleSheet is constructable. - let styleSheet = styleSheetCache.get(this.cssText); - if (supportsAdoptingStyleSheets && styleSheet === undefined) { - styleSheetCache.set(this.cssText, (styleSheet = new CSSStyleSheet())); - styleSheet.replaceSync(this.cssText); + // If `supportsAdoptingStyleSheets` is true then we assume CSSStyleSheet is + // constructable. + if (supportsAdoptingStyleSheets && this._styleSheet === undefined) { + (this._styleSheet = new CSSStyleSheet()).replaceSync(this.cssText); } - return styleSheet; + return this._styleSheet; } toString(): string { diff --git a/packages/reactive-element/src/test/css-tag_test.ts b/packages/reactive-element/src/test/css-tag_test.ts index 7d2737b33a..79539decea 100644 --- a/packages/reactive-element/src/test/css-tag_test.ts +++ b/packages/reactive-element/src/test/css-tag_test.ts @@ -4,7 +4,12 @@ * SPDX-License-Identifier: BSD-3-Clause */ -import {css, CSSResult, unsafeCSS} from '../css-tag.js'; +import { + css, + CSSResult, + unsafeCSS, + supportsAdoptingStyleSheets, +} from '../css-tag.js'; import {assert} from '@esm-bundle/chai'; suite('Styling', () => { @@ -14,46 +19,43 @@ suite('Styling', () => { const cssValue = css; const makeStyle = () => cssValue`foo`; const style1 = makeStyle(); - assert.equal( - (style1 as CSSResult).styleSheet, - (style1 as CSSResult).styleSheet - ); - const style2 = makeStyle(); - assert.equal( - (style1 as CSSResult).styleSheet, - (style2 as CSSResult).styleSheet - ); + if (supportsAdoptingStyleSheets) { + assert.isDefined(style1.styleSheet); + assert.strictEqual(style1.styleSheet, style1.styleSheet); + const style2 = makeStyle(); + assert.notStrictEqual(style1.styleSheet, style2.styleSheet); + } else { + assert.isUndefined(style1.styleSheet); + } }); - test('css with same values always produce the same stylesheet', () => { + test('css with same values produce unique stylesheets', () => { // Alias avoids syntax highlighting issues in editors const cssValue = css; const makeStyle = () => cssValue`background: ${cssValue`blue`}`; const style1 = makeStyle(); - assert.equal( - (style1 as CSSResult).styleSheet, - (style1 as CSSResult).styleSheet - ); - const style2 = makeStyle(); - assert.equal( - (style1 as CSSResult).styleSheet, - (style2 as CSSResult).styleSheet - ); + if (supportsAdoptingStyleSheets) { + assert.isDefined(style1.styleSheet); + assert.strictEqual(style1.styleSheet, style1.styleSheet); + const style2 = makeStyle(); + assert.notStrictEqual(style1.styleSheet, style2.styleSheet); + } else { + assert.isUndefined(style1.styleSheet); + } }); test('unsafeCSS() CSSResults always produce the same stylesheet', () => { // Alias avoids syntax highlighting issues in editors const makeStyle = () => unsafeCSS(`foo`); const style1 = makeStyle(); - assert.equal( - (style1 as CSSResult).styleSheet, - (style1 as CSSResult).styleSheet - ); - const style2 = makeStyle(); - assert.equal( - (style1 as CSSResult).styleSheet, - (style2 as CSSResult).styleSheet - ); + if (supportsAdoptingStyleSheets) { + assert.isDefined(style1.styleSheet); + assert.strictEqual(style1.styleSheet, style1.styleSheet); + const style2 = makeStyle(); + assert.notStrictEqual(style1.styleSheet, style2.styleSheet); + } else { + assert.isUndefined(style1.styleSheet); + } }); test('`css` get styles throws when unsafe values are used', async () => { @@ -75,10 +77,7 @@ suite('Styling', () => { margin: ${spacer * 2}px; } `; - assert.equal( - (result as CSSResult).cssText.replace(/\s/g, ''), - 'div{margin:4px;}' - ); + assert.equal(result.cssText.replace(/\s/g, ''), 'div{margin:4px;}'); }); test('`CSSResult` cannot be constructed', async () => { From 1853709d0c5538291b345d0bba3aba90be5a9951 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 17 Jun 2022 10:34:42 -0700 Subject: [PATCH 2/8] Adds `preserveExisting` option to `adoptStyles` Adds ability to add to an existing set of styles via adoptStyles. Fixes #3010. --- packages/reactive-element/package.json | 6 +- packages/reactive-element/src/css-tag.ts | 125 +++++++++++++++--- .../reactive-element/src/test/css-tag_test.ts | 79 +++++++++++ .../reactive-element/src/test/test-helpers.ts | 24 +++- 4 files changed, 212 insertions(+), 22 deletions(-) diff --git a/packages/reactive-element/package.json b/packages/reactive-element/package.json index 581823c4da..3c251ea1da 100644 --- a/packages/reactive-element/package.json +++ b/packages/reactive-element/package.json @@ -89,7 +89,8 @@ "prepublishOnly": "npm run check-version", "test": "wireit", "test:dev": "wireit", - "test:prod": "wireit" + "test:prod": "wireit", + "test:dev:watch": "MODE=dev node ../tests/run-web-tests.js \"development/**/*_test.(js|html)\" --config ../tests/web-test-runner.config.js --watch" }, "wireit": { "build": { @@ -111,6 +112,7 @@ ], "output": [ "development", + "!development/test/decorators-babel", "tsconfig.tsbuildinfo", "tsconfig.polyfill-support.tsbuildinfo" ] @@ -226,7 +228,7 @@ "@webcomponents/template": "^1.4.4", "@webcomponents/webcomponentsjs": "^2.6.0", "chokidar-cli": "^3.0.0", - "internal-scripts": "^1.0.0", + "@lit-internal/scripts": "^1.0.0", "mocha": "^9.1.1", "rollup": "^2.70.2", "typescript": "^4.3.5" diff --git a/packages/reactive-element/src/css-tag.ts b/packages/reactive-element/src/css-tag.ts index 9aacc3975f..aa5affa696 100644 --- a/packages/reactive-element/src/css-tag.ts +++ b/packages/reactive-element/src/css-tag.ts @@ -73,10 +73,22 @@ type ConstructableCSSResult = CSSResult & { new (cssText: string, safeToken: symbol): CSSResult; }; +// Type guard for CSSResult +const isCSSResult = (value: unknown): value is CSSResult => + (value as CSSResult)['_$cssResult$'] === true; + +// Type guard for style element +const isStyleEl = ( + value: unknown +): value is HTMLStyleElement | HTMLLinkElement => { + const {localName} = value as HTMLElement; + return localName === 'style' || localName === 'link'; +}; + const textFromCSSResult = (value: CSSResultGroup | number) => { // This property needs to remain unminified. - if ((value as CSSResult)['_$cssResult$'] === true) { - return (value as CSSResult).cssText; + if (isCSSResult(value)) { + return value.cssText; } else if (typeof value === 'number') { return value; } else { @@ -123,6 +135,44 @@ export const css = ( return new (CSSResult as ConstructableCSSResult)(cssText, constructionToken); }; +// Markers used to determine where style elements have been inserted in the +// shadowRoot so that they can be easily updated. +const styleMarkersMap = new WeakMap(); +const getStyleMarkers = (renderRoot: ShadowRoot) => { + let markers = styleMarkersMap.get(renderRoot); + if (markers === undefined) { + const start = renderRoot.appendChild(document.createComment('')); + const end = renderRoot.appendChild(document.createComment('')); + styleMarkersMap.set(renderRoot, (markers = [start, end])); + } + return markers; +}; + +/** + * Clears any nodes between the given nodes. Used to remove style elements that + * have been inserted via `adoptStyles`. This allows ensures any previously + * applied styling is not re-applied. + */ +const removeNodesBetween = (start: Node, end: Node) => { + let n = start.nextSibling; + while (n && n !== end) { + const next = n.nextSibling; + n.remove(); + n = next; + } +}; + +/** + * Applies the optional globally set `litNonce` to an element. + */ +const applyNonce = (el: HTMLElement) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const nonce = (window as any)['litNonce']; + if (nonce !== undefined) { + el.setAttribute('nonce', nonce); + } +}; + /** * Applies the given styles to a `shadowRoot`. When Shadow DOM is * available but `adoptedStyleSheets` is not, styles are appended to the @@ -131,27 +181,64 @@ export const css = ( * the shadowRoot should be placed *before* any shimmed adopted styles. This * will match spec behavior that gives adopted sheets precedence over styles in * shadowRoot. + * + * The given styles can be a CSSResult or CSSStyleSheet. If a CSSStyleSheet is + * supplied, it should be a constructed stylesheet. + * + * Optionally preserves any existing adopted styles, sheets or elements. */ export const adoptStyles = ( renderRoot: ShadowRoot, - styles: Array + styles: CSSResultOrNative[], + preserveExisting = false ) => { - if (supportsAdoptingStyleSheets) { - (renderRoot as ShadowRoot).adoptedStyleSheets = styles.map((s) => - s instanceof CSSStyleSheet ? s : s.styleSheet! - ); - } else { - styles.forEach((s) => { + // Get a set of sheets and elements to apply. + const elements: Array = []; + const sheets: CSSStyleSheet[] = styles + .map((s) => getSheetOrElementToApply(s)) + .filter((s): s is CSSStyleSheet => !(isStyleEl(s) && elements.push(s))); + // By default, clear any existing styling. + if (!preserveExisting) { + if ((renderRoot as ShadowRoot).adoptedStyleSheets) { + (renderRoot as ShadowRoot).adoptedStyleSheets = []; + } + if (styleMarkersMap.has(renderRoot)) { + removeNodesBetween(...getStyleMarkers(renderRoot)); + } + } + // Apply sheets, Note, this are only set if `adoptedStyleSheets` is supported. + if (sheets.length) { + (renderRoot as ShadowRoot).adoptedStyleSheets = sheets; + } + // Apply any style elements + if (elements.length) { + const [, end] = getStyleMarkers(renderRoot); + end.before(...elements); + } +}; + +/** + * Gets compatible style object (sheet or element) which can be applied to a + * shadowRoot. + */ +const getSheetOrElementToApply = (styling: CSSResultOrNative) => { + // Converts to a CSSResult when `adoptedStyleSheets` is unsupported. + if (styling instanceof CSSStyleSheet) { + styling = getCompatibleStyle(styling); + } + // If it's a CSSResult, return the stylesheet or a style element + if (isCSSResult(styling)) { + if (styling.styleSheet !== undefined) { + return styling.styleSheet; + } else { const style = document.createElement('style'); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const nonce = (window as any)['litNonce']; - if (nonce !== undefined) { - style.setAttribute('nonce', nonce); - } - style.textContent = (s as CSSResult).cssText; - renderRoot.appendChild(style); - }); + style.textContent = styling.cssText; + applyNonce(style); + return style; + } } + // Otherwise, it should be a constructed stylesheet + return styling; }; const cssResultFromStyleSheet = (sheet: CSSStyleSheet) => { @@ -162,6 +249,10 @@ const cssResultFromStyleSheet = (sheet: CSSStyleSheet) => { return unsafeCSS(cssText); }; +/** + * Given a CSSStylesheet or CSSResult, converts from CSSStyleSheet to CSSResult + * if the browser does not support `adoptedStyleSheets`. + */ export const getCompatibleStyle = supportsAdoptingStyleSheets ? (s: CSSResultOrNative) => s : (s: CSSResultOrNative) => diff --git a/packages/reactive-element/src/test/css-tag_test.ts b/packages/reactive-element/src/test/css-tag_test.ts index 79539decea..bd1133fb50 100644 --- a/packages/reactive-element/src/test/css-tag_test.ts +++ b/packages/reactive-element/src/test/css-tag_test.ts @@ -9,7 +9,9 @@ import { CSSResult, unsafeCSS, supportsAdoptingStyleSheets, + adoptStyles, } from '../css-tag.js'; +import {html, getComputedStyleValue, createShadowRoot} from './test-helpers.js'; import {assert} from '@esm-bundle/chai'; suite('Styling', () => { @@ -100,4 +102,81 @@ suite('Styling', () => { assert.equal(bodyStyles.replace(/\s/g, ''), '.my-module{color:yellow;}'); }); }); + + suite('adopting styles', () => { + let container: HTMLElement; + + setup(() => { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + teardown(() => { + if (container && container.parentNode) { + container.remove(); + } + }); + + test('adoptStyles sets styles in a shadowRoot', () => { + const host = document.createElement('host-el'); + container.appendChild(host); + const root = createShadowRoot(host); + root.innerHTML = html`
`; + const div = root.querySelector('div')!; + adoptStyles(root, [ + css` + div { + border: 2px solid black; + } + `, + ]); + assert.equal(getComputedStyleValue(div), '2px'); + }); + + test('adoptStyles resets styles in a shadowRoot', () => { + const host = document.createElement('host-el'); + container.appendChild(host); + const root = createShadowRoot(host); + root.innerHTML = html`
`; + const div = root.querySelector('div')!; + adoptStyles(root, [ + css` + div { + border: 2px solid black; + } + `, + ]); + adoptStyles(root, []); + assert.equal(getComputedStyleValue(div), '0px'); + }); + + test('adoptStyles can preserve and add to styles in a shadowRoot', () => { + const host = document.createElement('host-el'); + container.appendChild(host); + const root = createShadowRoot(host); + root.innerHTML = html`
`; + const div = root.querySelector('div')!; + adoptStyles(root, [ + css` + div { + border: 2px solid black; + } + `, + ]); + adoptStyles(root, [], true); + assert.equal(getComputedStyleValue(div), '2px'); + adoptStyles( + root, + [ + css` + div { + border: 4px solid black; + } + `, + ], + true + ); + assert.equal(getComputedStyleValue(div), '4px'); + }); + }); }); diff --git a/packages/reactive-element/src/test/test-helpers.ts b/packages/reactive-element/src/test/test-helpers.ts index e63388bbfc..d33dff2aca 100644 --- a/packages/reactive-element/src/test/test-helpers.ts +++ b/packages/reactive-element/src/test/test-helpers.ts @@ -12,10 +12,14 @@ export const generateElementName = () => `x-${count++}`; export const nextFrame = () => new Promise((resolve) => requestAnimationFrame(resolve)); -export const getComputedStyleValue = (element: Element, property: string) => - window.ShadyCSS +export const getComputedStyleValue = ( + element: Element, + property = 'border-top-width' +): string => + (window.ShadyCSS ? window.ShadyCSS.getComputedStyleValue(element, property) - : getComputedStyle(element).getPropertyValue(property); + : getComputedStyle(element).getPropertyValue(property) + ).trim(); export const stripExpressionComments = (html: string) => html.replace(/|/g, ''); @@ -62,3 +66,17 @@ export const html = (strings: TemplateStringsArray, ...values: unknown[]) => { strings[0] ); }; + +export const createShadowRoot = (host: HTMLElement) => { + if (window.ShadyDOM && window.ShadyDOM.inUse) { + host = window.ShadyDOM.wrap(host) as HTMLElement; + if (window.ShadyCSS) { + window.ShadyCSS.prepareTemplateStyles( + document.createElement('template'), + host.localName + ); + window.ShadyCSS.styleElement(host); + } + } + return host.attachShadow({mode: 'open'}); +}; From 72853f8a4bbc9ba9903733f8bd4d8a59000d315c Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 17 Jun 2022 10:36:57 -0700 Subject: [PATCH 3/8] Add changeset. --- .changeset/six-pets-confess.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/six-pets-confess.md diff --git a/.changeset/six-pets-confess.md b/.changeset/six-pets-confess.md new file mode 100644 index 0000000000..28ae909ace --- /dev/null +++ b/.changeset/six-pets-confess.md @@ -0,0 +1,5 @@ +--- +'@lit/reactive-element': minor +--- + +Adds optional boolean argument to `adoptStyles` allowing existing styling to be preserved. From ffbc3aae060246dec34f2b581eceac41ba28ddc7 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 17 Jun 2022 12:04:16 -0700 Subject: [PATCH 4/8] Revert unintended package.json change --- packages/reactive-element/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/reactive-element/package.json b/packages/reactive-element/package.json index 3c251ea1da..1e1039b542 100644 --- a/packages/reactive-element/package.json +++ b/packages/reactive-element/package.json @@ -112,7 +112,6 @@ ], "output": [ "development", - "!development/test/decorators-babel", "tsconfig.tsbuildinfo", "tsconfig.polyfill-support.tsbuildinfo" ] @@ -228,7 +227,7 @@ "@webcomponents/template": "^1.4.4", "@webcomponents/webcomponentsjs": "^2.6.0", "chokidar-cli": "^3.0.0", - "@lit-internal/scripts": "^1.0.0", + "internal-scripts": "^1.0.0", "mocha": "^9.1.1", "rollup": "^2.70.2", "typescript": "^4.3.5" From 39ca5ba9b42451335d94fb89d80a96a3b8cc58fe Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Sat, 18 Jun 2022 07:52:29 -0700 Subject: [PATCH 5/8] Merge fix. --- packages/reactive-element/src/css-tag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reactive-element/src/css-tag.ts b/packages/reactive-element/src/css-tag.ts index 494a372237..f12bdf2b1a 100644 --- a/packages/reactive-element/src/css-tag.ts +++ b/packages/reactive-element/src/css-tag.ts @@ -82,7 +82,7 @@ export class CSSResult { } } } - return this._styleSheet; + return styleSheet; } toString(): string { From 5fdddb9a89a5b410a6664125c332980875c1470b Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Sat, 18 Jun 2022 09:19:18 -0700 Subject: [PATCH 6/8] Fix and add test for adding to adopted styles. --- packages/reactive-element/src/css-tag.ts | 23 ++++++---------- .../reactive-element/src/test/css-tag_test.ts | 26 +++++++++++++++++-- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/reactive-element/src/css-tag.ts b/packages/reactive-element/src/css-tag.ts index f12bdf2b1a..8d40ddca3d 100644 --- a/packages/reactive-element/src/css-tag.ts +++ b/packages/reactive-element/src/css-tag.ts @@ -228,19 +228,16 @@ export const adoptStyles = ( .map((s) => getSheetOrElementToApply(s)) .filter((s): s is CSSStyleSheet => !(isStyleEl(s) && elements.push(s))); // By default, clear any existing styling. - if (!preserveExisting) { - if ((renderRoot as ShadowRoot).adoptedStyleSheets) { - (renderRoot as ShadowRoot).adoptedStyleSheets = []; - } - if (styleMarkersMap.has(renderRoot)) { - removeNodesBetween(...getStyleMarkers(renderRoot)); - } + if (supportsAdoptingStyleSheets && (sheets.length || !preserveExisting)) { + renderRoot.adoptedStyleSheets = [ + ...(preserveExisting ? renderRoot.adoptedStyleSheets : []), + ...sheets, + ]; } - // Apply sheets, Note, this are only set if `adoptedStyleSheets` is supported. - if (sheets.length) { - (renderRoot as ShadowRoot).adoptedStyleSheets = sheets; + // Remove / Apply any style elements + if (!preserveExisting && styleMarkersMap.has(renderRoot)) { + removeNodesBetween(...getStyleMarkers(renderRoot)); } - // Apply any style elements if (elements.length) { const [, end] = getStyleMarkers(renderRoot); end.before(...elements); @@ -252,10 +249,6 @@ export const adoptStyles = ( * shadowRoot. */ const getSheetOrElementToApply = (styling: CSSResultOrNative) => { - // Converts to a CSSResult when `adoptedStyleSheets` is unsupported. - if (styling instanceof CSSStyleSheet) { - styling = getCompatibleStyle(styling); - } // If it's a CSSResult, return the stylesheet or a style element if (isCSSResult(styling)) { if (styling.styleSheet !== undefined) { diff --git a/packages/reactive-element/src/test/css-tag_test.ts b/packages/reactive-element/src/test/css-tag_test.ts index 913595bdf2..c9d89da37f 100644 --- a/packages/reactive-element/src/test/css-tag_test.ts +++ b/packages/reactive-element/src/test/css-tag_test.ts @@ -121,16 +121,24 @@ suite('Styling', () => { const host = document.createElement('host-el'); container.appendChild(host); const root = createShadowRoot(host); - root.innerHTML = html`
`; + root.innerHTML = html`
+

`; const div = root.querySelector('div')!; + const p = root.querySelector('p')!; adoptStyles(root, [ css` div { border: 2px solid black; } `, + css` + p { + border: 4px solid black; + } + `, ]); assert.equal(getComputedStyleValue(div), '2px'); + assert.equal(getComputedStyleValue(p), '4px'); }); test('adoptStyles resets styles in a shadowRoot', () => { @@ -154,8 +162,10 @@ suite('Styling', () => { const host = document.createElement('host-el'); container.appendChild(host); const root = createShadowRoot(host); - root.innerHTML = html`
`; + root.innerHTML = html`
+

`; const div = root.querySelector('div')!; + const p = root.querySelector('p')!; adoptStyles(root, [ css` div { @@ -176,7 +186,19 @@ suite('Styling', () => { ], true ); + adoptStyles( + root, + [ + css` + p { + border: 6px solid black; + } + `, + ], + true + ); assert.equal(getComputedStyleValue(div), '4px'); + assert.equal(getComputedStyleValue(p), '6px'); }); }); }); From 9e1fe6d889b7ee741a413f1f408090038dbe913b Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Sat, 18 Jun 2022 09:46:27 -0700 Subject: [PATCH 7/8] Test adopting CSSStyleSheet Tests adopting CSSStyleSheet and tests that we cover the case of using a constructed stylesheet when polyfilling via ShadyDOM/CSS. --- packages/reactive-element/src/css-tag.ts | 5 +++++ .../reactive-element/src/test/css-tag_test.ts | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/reactive-element/src/css-tag.ts b/packages/reactive-element/src/css-tag.ts index 8d40ddca3d..aaff3e4918 100644 --- a/packages/reactive-element/src/css-tag.ts +++ b/packages/reactive-element/src/css-tag.ts @@ -249,6 +249,11 @@ export const adoptStyles = ( * shadowRoot. */ const getSheetOrElementToApply = (styling: CSSResultOrNative) => { + // Converts to a CSSResult if needed. This is needed when forcing polyfilled + // ShadyDOM/CSS on a browser that supports constructible stylesheets. + if (styling instanceof CSSStyleSheet) { + styling = getCompatibleStyle(styling); + } // If it's a CSSResult, return the stylesheet or a style element if (isCSSResult(styling)) { if (styling.styleSheet !== undefined) { diff --git a/packages/reactive-element/src/test/css-tag_test.ts b/packages/reactive-element/src/test/css-tag_test.ts index c9d89da37f..ad4ef14cc2 100644 --- a/packages/reactive-element/src/test/css-tag_test.ts +++ b/packages/reactive-element/src/test/css-tag_test.ts @@ -141,6 +141,27 @@ suite('Styling', () => { assert.equal(getComputedStyleValue(p), '4px'); }); + test('adoptStyles can adopt CSSStyleSheet when supported', () => { + const host = document.createElement('host-el'); + container.appendChild(host); + const root = createShadowRoot(host); + root.innerHTML = html`
`; + const div = root.querySelector('div')!; + let sheet: CSSStyleSheet | undefined; + try { + sheet = new CSSStyleSheet(); + sheet.replaceSync(`div { + border: 12px solid black; + }`); + } catch (e) { + // unsupported + } + if (sheet !== undefined) { + adoptStyles(root, [sheet]); + assert.equal(getComputedStyleValue(div), '12px'); + } + }); + test('adoptStyles resets styles in a shadowRoot', () => { const host = document.createElement('host-el'); container.appendChild(host); From 4cff3780914ad33a7d6bf18b04b5432ee3aa1b4f Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 30 Sep 2022 14:48:48 -0700 Subject: [PATCH 8/8] Simplify slightly. --- packages/reactive-element/src/css-tag.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/reactive-element/src/css-tag.ts b/packages/reactive-element/src/css-tag.ts index 1e220967f7..4b03301153 100644 --- a/packages/reactive-element/src/css-tag.ts +++ b/packages/reactive-element/src/css-tag.ts @@ -174,9 +174,13 @@ const styleMarkersMap = new WeakMap(); const getStyleMarkers = (renderRoot: ShadowRoot) => { let markers = styleMarkersMap.get(renderRoot); if (markers === undefined) { - const start = renderRoot.appendChild(document.createComment('')); - const end = renderRoot.appendChild(document.createComment('')); - styleMarkersMap.set(renderRoot, (markers = [start, end])); + styleMarkersMap.set( + renderRoot, + (markers = [ + renderRoot.appendChild(document.createComment('')), + renderRoot.appendChild(document.createComment('')), + ]) + ); } return markers; }; @@ -218,7 +222,7 @@ const applyNonce = (el: HTMLElement) => { * The given styles can be a CSSResult or CSSStyleSheet. If a CSSStyleSheet is * supplied, it should be a constructed stylesheet. * - * Optionally preserves any existing adopted styles, sheets or elements. + * Optionally preserves any existing adopted styles. */ export const adoptStyles = ( renderRoot: ShadowRoot,