From 5546c8b9a0caa59de139eef8d4e204717edd17c1 Mon Sep 17 00:00:00 2001 From: Vlad Rindevich Date: Mon, 26 Aug 2019 23:42:53 +0300 Subject: [PATCH] fix: add fixes for 2 issues * Allow styling component if it is placed in the shadow root of the component without styles * Update prototype of CSSStyleSheet in the iframe. --- adoptedStyleSheets.js | 28 +++++++++++------- test/polyfill.test.js | 68 ++++++++++++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 31 deletions(-) diff --git a/adoptedStyleSheets.js b/adoptedStyleSheets.js index 4acb13f..a48fb97 100644 --- a/adoptedStyleSheets.js +++ b/adoptedStyleSheets.js @@ -85,7 +85,6 @@ frameBody.append(basicStyleElement); const nativeStyleSheet = basicStyleElement.sheet; - updatePrototype(nativeStyleSheet.constructor.prototype); // A support object to preserve all the polyfill data nativeStyleSheet[$constructStyleSheet] = { @@ -134,6 +133,12 @@ } } + updatePrototype(OldCSSStyleSheet.prototype); + + // Since we get the sheet from iframe, we need to patch prototype of the + // CSSStyleSheet in iframe as well. + updatePrototype(iframe.contentWindow.CSSStyleSheet.prototype); + const adoptStyleSheets = location => { const newStyles = document.createDocumentFragment(); @@ -197,9 +202,7 @@ continue; } - const styleElement = sheet[$constructStyleSheet].adopters.get( - location, - ); + const styleElement = sheet[$constructStyleSheet].adopters.get(location); location[$observer].disconnect(); styleElement.remove(); @@ -291,12 +294,6 @@ const location = this.body ? this.body : this; const uniqueSheets = [...new Set(sheets)]; - - if (!location[$adoptedStyleSheets] && location instanceof ShadowRoot) { - // Observer for document.body is already launched - createObserver(location); - } - const oldSheets = location[$adoptedStyleSheets] || []; location[$adoptedStyleSheets] = uniqueSheets; @@ -309,6 +306,17 @@ }, }; + const oldAttachShadow = HTMLElement.prototype.attachShadow; + + // Shadow root of each element should be observed to add styles to all + // elements added to this root. + HTMLElement.prototype.attachShadow = function(...args) { + const location = oldAttachShadow.apply(this, args); + createObserver(location); + + return location; + }; + Object.defineProperty( ShadowRoot.prototype, 'adoptedStyleSheets', diff --git a/test/polyfill.test.js b/test/polyfill.test.js index cb3075c..2ff70fd 100644 --- a/test/polyfill.test.js +++ b/test/polyfill.test.js @@ -120,7 +120,11 @@ describe('Constructible Style Sheets polyfill', () => { constructor() { super(); const root = this.attachShadow({mode: 'open'}); - root.adoptedStyleSheets = sheets; + + if (sheets) { + root.adoptedStyleSheets = sheets; + } + root.innerHTML = html; } } @@ -160,26 +164,6 @@ describe('Constructible Style Sheets polyfill', () => { checkCss(element, {...defaultChecker, height: '82px'}); }); - it('applies styling to deeply nested web components', async () => { - const [tag1] = createCustomElement([css]); - const [tag2] = createCustomElement( - [css], - `
-
-
- <${tag1} id="nested"> -
-
-
`, - ); - - const element = await fixture(`<${tag2}>`); - checkCss(element, defaultChecker); - - const nested = element.shadowRoot.getElementById('nested'); - checkCss(nested, defaultChecker); - }); - it('restores styles if innerHTML is cleared', async () => { const [tag] = createCustomElement([css]); const element = await fixture(`<${tag}>`); @@ -210,6 +194,48 @@ describe('Constructible Style Sheets polyfill', () => { checkCss(element, {...defaultChecker, height: '82px'}); }); + describe('detached elements', () => { + const detachedFixture = async (rootTag, ...nestedTags) => { + const detachedElement = nestedTags.reduceRight((acc, tag) => { + const element = document.createElement(tag); + + if (acc) { + element.append(acc); + } + + return element; + }, null); + + const rootElement = await fixture(`<${rootTag}>`); + rootElement.shadowRoot.append(detachedElement); + + return rootElement; + }; + + it('applies styling to deeply nested web components', async () => { + const [tag1] = createCustomElement([css]); + const [tag2] = createCustomElement([css]); + + const element = await detachedFixture(tag2, 'div', 'div', 'div', tag1); + checkCss(element, defaultChecker); + // await null; // MutationObserver is asynchronous + + const nested = element.shadowRoot.querySelector(tag1); + checkCss(nested, defaultChecker); + }); + + it('applies styling to deeply nested web components even if host component does not have adoptedStyleSheets set', async () => { + const [tag1] = createCustomElement([css]); + const [tag2] = createCustomElement(); + + const element = await detachedFixture(tag2, 'div', 'div', 'div', tag1); + await null; // MutationObserver is asynchronous + + const nested = element.shadowRoot.querySelector(tag1); + checkCss(nested, defaultChecker); + }); + }); + describe('Polyfill only', () => { it('does not re-create style element on removing the sibling node', async () => { ignore();