webcomponents / polyfills

Web Components Polyfills

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[ShadyCSS] Constructible Stylesheets and adoptedStyleSheets support for browsers with native shadow DOM

justinfagnani opened this issue · comments

Constructible Stylesheets

We want to support and API as close as possible to native Constructible Stylesheets, so that this works:

const s = new CSSStyleSheet();
s.replaceSync(':host { display: block } ');

This may be achievable by overwriting the CSSStyleSheet constructor with one that creates a stylesheet in a shadow root. Something like:

const container = document.createElement('constructible-stylesheets');
container.attachShadow({mode: 'open'});
document.append(container);

const originalCSSStyleSheet = CSSStyleSheet;
CSSStyleSheet = class extends originalCSSStyleSheet {
  constructor() {
    this._styleElement = document.createElement('style');
    container.shadowRoot.append(this._styleElement);
    Object.setPrototypeOf(this, this._styleElement.sheet);
  }

  replaceSync(styleText) {
    this._styleElement.data = styleText;
  }
}

(I haven't tried this code)

adoptedStyleSheets

ShadowRoot.adoptedStyleSheets could be implemented with a setter that only accepts arrays of stylesheets created as above with a _styleElement property. It would then clone those style elements and insert them last into the shadow root.

To protect against mutations of the shadow root that remove the styles, we can put them in a custom element called <adopted-stylesheets> that adds itself back to the shadow root if it's ever removed.

const allAdoptedStylesheets = new Set();
class AdoptedStylesheetsElement extends HTMLElement {
  constructor() {
    super();
    allAdoptedStylesheets.add(this);
  }

  connectedCallback() {
    this._previousRoot = this.getRootNode();
  }

  disconnectedCallback() {
    this._previousRoot.append(this);
  }
}

allAdoptedStylesheets can be used to update cloned style tags incase replace or replaceSync is called on a CSSStyleSheet.

cc @azakus

While implementing this enhancement, please mind the CSP (Content-Security-Policy). Some applications may have a very strict policy (e.g. forbid inline anonymous <style> elements), which may cause a lot of problems. One of possible ways of working around this issue would be to provide a possibility of using a nonce attribute on <style> element:

CSSStyleSheet = class extends originalCSSStyleSheet {
  (...)
  fixCsp(nonce) {
    this._styleElement.nonce = nonce;
  }
}

This would result in attaching <style nonce="..."> element to (shimmed?) shadow DOM, and this could satisfy CSP's needs. (Of course that nonce value must be provided by code that uses ShadyCSS.)

@justinfagnani, the construct-styles-sheets-polyfill project may be interesting for you. It supports the adoptedStyleSheets functionality in the following ways:

  • For all the browsers with native support for web components, it supports document-level and shadow root-level style sheets adoption.
  • For browsers without support for web components (IE 11 and stable Edge for now), it implements only the document-level style sheet adoption.

It can also be used along with the ShadyCSS's prepareAdoptedCssText because their functionalities do not overlap.

const originalCSSStyleSheet = CSSStyleSheet;
CSSStyleSheet = class extends originalCSSStyleSheet {
  constructor() {
    //...
  }
}

CSSStyleSheet is not instantiable, so it's not extensible either ...

commented

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.