Giter Club home page Giter Club logo

construct-style-sheets's Introduction

Constructible style sheets polyfill

CI npm version codecov

This package is a polyfill for the constructible style sheets/adopted style sheets specification. The full specificaiton is enabled by default in Google Chrome as of version 73. Firefox only has support as of version 101, and Safari as of version 16.4.

Use case

The constructible style sheets proposal is intended to allow for the dynamic creation and sharing of style sheets, even across shadow boundaries. By adopting a style sheet into a shadow root, the same sheet can be applied to multiple nodes, including the document.

How it works

This polyfill will create a new style element for every DocumentOrShadowRoot into which the sheet is adopted. This is counter to the current proposal, but updates to the style sheet using the replace or replaceSync methods should update the relevant style elements with the updated content across all adopters.

No changes will occur in a browser that supports the feature by default.

Support

This polyfill supports all modern browsers and IE 11.

For browsers that do not support the web components specification (currently IE 11 and Edge) only the document-level style sheets adoption works.

IE 11

To make this polyfill work with IE 11 you need the following tools:

Installation

This package is available on npm under the name construct-style-sheet-polyfill and can be installed with npm, yarn, unpkg or however else you consume dependencies.

Example commands:

npm:

npm i construct-style-sheets-polyfill

yarn:

yarn add construct-style-sheets-polyfill

unpkg:

import 'https://unpkg.com/construct-style-sheets-polyfill';

Usage

const everythingTomato = new CSSStyleSheet();
everythingTomato
  .replace(
    `
* {
    color: tomato;
}
`,
  )
  .then(console.log); // will log the CSSStyleSheet object

document.adoptedStyleSheets = [everythingTomato];

class TestEl extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.adoptedStyleSheets = [everythingTomato];
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `<h1>This will be tomato colored, too</h1>`;
  }
}

customElements('test-el', TestEl);

const testEl = new TestEl();
document.body.appendChild(testEl);

The polyfill will append new style tags to the designated DocumentOrShadowRoot. Manually removing the style node will cause a re-insertion of the styles at the designated root. To remove a style sheet, you must remove the style element from the element.adoptedStyleSheets array. The behavior here is supposed to emulate a FrozenArray, so modifying the array in question will have no effect until the value is changed using a setter.

A note about versioning

This packages doesn't necessarily follow semantic versioning. As the spec is still under consideration and implementation by browser vendors, the features supported by this package will change (generally following Chrome's implementation).

construct-style-sheets's People

Contributors

artur- avatar calebdwilliams avatar denyo avatar dependabot[bot] avatar designbyonyx avatar dyoder avatar hansottowirtz avatar jacecotton avatar jack-works avatar lodin avatar web-padawan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

construct-style-sheets's Issues

TypeError: "can't access dead object"

Envorioment: Firefox Extension

Reason:

const iframe = document.createElement('iframe');

iframe.hidden = true;
document.body.appendChild(iframe);
const frameBody = iframe.contentWindow.document.body;
document.body.removeChild(iframe);

https://github.com/calebdwilliams/construct-style-sheets/blob/master/adoptedStyleSheets.js#L39
https://github.com/calebdwilliams/construct-style-sheets/blob/master/adoptedStyleSheets.js#L64-L67

The polyfill adds the dom then remove it.

Firefox bans strong reference to DOM object in WebExtension, after .removeChild(iframe), frameBody become DeadObject

frameBody.appendChild(style);

https://github.com/calebdwilliams/construct-style-sheets/blob/master/adoptedStyleSheets.js#L88
Then this throws the TypeError: "can't access dead object"

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Dead_object

Mutating CSSStyleDeclaration (via `.setProperty()` for example) does not update injected style

Describe the bug
Mutating CSSStyleDeclaration (via .setProperty() for example) does not update injected style. When I change its value, only ConstructedStyleSheet is changed, but I don't see any changes in DOM

To Reproduce

  1. Create and add a Stylesheet with some rules to a Shadow DOM's adoptedStyleSheets
  2. Change some property with myRule.style.setPropety('width', '10px)3.
  3. Check the DOM and verify no changes have happened.

Expected behavior**
Injected style will update as it would on Chrome with native constructed style sheets support

Desktop (please complete the following information):

  • OS: macOS
  • Browser: Safari
  • Version: 15.5

Styles are not applied for elements created when document is loading

Describe the bug
An adoptedStyleSheets set during page load is not applied when initPolyfill is called when the document has finished loading.

To Reproduce

<!DOCTYPE html>
<html>
  <head>
    <script src="https://unpkg.com/[email protected]/dist/adoptedStyleSheets.js"></script>
  </head>
  <body>
    <script type="text/javascript">
      // This code needs to run after the polyfill has loaded but before initPolyfill has been run
      debugger;
      const div = document.createElement("div");
      const shadowRoot = div.attachShadow({ mode: "open" });
      const spanWithText = document.createElement("span");
      spanWithText.innerText = "I should be blue";
      shadowRoot.appendChild(spanWithText);
      const sheet = new CSSStyleSheet();
      sheet.replaceSync(" * { color: blue }");
      shadowRoot.adoptedStyleSheets = [sheet];
      document.body.appendChild(div);
    </script>

    <button
      onclick="javascript:s = document.querySelector('div').shadowRoot; s.adoptedStyleSheets = s.adoptedStyleSheets;"
    >
      Fix me
    </button>
  </body>
</html>

or the live version at https://artur.app.fi/stylesheet-polyfill-bug.html

Expected behavior
The text in the example case is blue when the style sheet is applied. When opening the page in Chrome, the text is blue (native implementation). When opening the page in Firefox or Safari, the text is black. Re-applying adoptedStyleSheets causes the text to turn blue also in Firefox and Safari.

Desktop (please complete the following information):

  • OS: [e.g. iOS] macOS
  • Browser [e.g. chrome, safari] Chrome (works), Safari Firefox (broken)
  • Version [e.g. 22]

adoptedStyleSheets does not applies on 'closed' shadowRoot

Describe the bug
adoptedStyleSheets does not apply on { type : 'closed' } shadowRoot

To Reproduce

const style = (new CSSStyleSheet).replaceSync(`:host { background: red }`)

class WebComponent extends HTMLElement {
   constructor () {
      super().attachShadow({ mode: 'closed' }).adoptedStyleSheets = [style]
   }
}

Desktop (please complete the following information):

  • Browser: Safari

Other micro tasks can be executed while initPolyfill is running

Describe the bug
This code in initPolyfill can be interrupted in the middle:

const iframe = document.createElement('iframe');
iframe.hidden = true;
document.body.appendChild(iframe);
frame.body = iframe.contentWindow.document.body;
frame.CSSStyleSheet = iframe.contentWindow.CSSStyleSheet;

It can be seen by running e.g. the following in a browser console

queueMicrotask(() => {
  console.log('micro task');
});
console.log('creating iframe');
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
console.log('iframe appended');

In Firefox you get

creating iframe
iframe appended
micro task

But in Safari you get

creating iframe
micro task
iframe appended

Is this an academic problem? Unfortunately not.

I see the problem in my application using Lit 2 + this polyfill. It so happens that rendering a Lit element triggers loading of the polyfill and scheduling rendering of that element. As a result of rendering the element, document.adoptedStyleSheets is used but fails when instanceOfStyleSheet in the polyfill is called as the polyfill is in use but only half-initialized

TypeError: Right hand side of instanceof is not an object

because frame.CSSStyleSheet is not defined yet. It is set on the row following document.body.appendChild(iframe); in initPolyfill

Interestingly enough, I see the issue also in Firefox even though the simple micro task test case indicates I should not.

To Reproduce
Wasn't able to extract a test case.

Expected behavior
Initialization of the polyfill would be non-interruptible so this would be a non issue.

[Feature Request] Usage with text editors

Is there a way to expose an import like
import { adoptedStyleSheets } from 'construct-style-sheets-polyfill';

Reason: Usage for vscode completion and warning free.
image

Lots of "Uncaught TypeError: observer is undefined" errors

Describe the bug

  function adoptStyleSheets(location) {

assumes that there is an observer on the location

    var observer = observerRegistry.get(location);
...
      if (elementToAdopt) {
        observer.disconnect();

An observe is registered for body in initPolyfill:

    createObserver(document.body);

and for each shadow root in attachShadow:

        createObserver(location);

But this code

      set: function set(sheets) {
        var oldSheets = adoptedSheetsRegistry.get(this) || [];
        checkAndPrepare(sheets, this);
        var location = this === document ?
        isDocumentLoading() ? this.head : this.body : this;
        var isConnected = 'isConnected' in location ? location.isConnected : document.body.contains(location);
        if (isConnected) {
          window.requestAnimationFrame(function () {
            adoptStyleSheets(location);
            removeExcludedStyleSheets(location, oldSheets);
          });
        } else {
          deferredAdopters.push(location);
        }
      }
    };

will actually use this.head i.e. document.head as the location when isDocumentLoading() is true.

This will always cause

Uncaught TypeError: observer is undefined

in

      var elementToAdopt = adopters.get(location);
      if (elementToAdopt) {
        observer.disconnect();

Desktop (please complete the following information):

  • OS: macOs
  • Browser Firefox 88

sheetMetadataRegistry fail to register sheet set asynchronously

Describe the bug

Set adoptedStyleSheets asynchronously when sheet.replace is resolved will cause the polyfill to throw _sheetMetadataRegistr is undefined in function adoptStyleSheets.

To Reproduce
Steps to reproduce the behavior:

const externalRef = `@import url('main.css')`;
const sheet = new CSSStyleSheet();
sheet.replace(externalRef).then(loadedSheet => {
    document.adoptedStyleSheets = [loadedSheet];
});

Expected behavior

No errors.

Although set the sheet synchronously solves the problem on Firefox. Chrome will crash with DevTools open and when there are webcomponents spreading document.adoptedStyleSheets to their own shadowRoot.adoptedStyleSheets. Also it seems setting Set adoptedStyleSheets asynchronously is the proper way.

The following works on Firefox but crashes Chrome.

const sheet = new CSSStyleSheet();
sheet.replace('@import url("main.css")');
document.adoptedStyleSheets = [sheet];

Desktop (please complete the following information):

  • OS: Ubuntu
  • Browser: Firefox
  • Version: 70.0

[Regression] Incorrect style embed priority.

Describe the bug
When a web component defines its own styles within the innerHTML, this polyfill prepends the styles above, giving the innerHTML stylesheet priority. This is opposite to the native behaviour of Chrome. When testing in Chrome 88 the styles defined by adoptedStyleSheets take priority.

To Reproduce

  1. Go to the following JSFiddle: https://jsfiddle.net/j06uL2np/
  2. Test in Chrome 88, it shows "IT WORKS!".
  3. Test in Chrome 63, it does not. (Can perhaps test within another build prior to constuctable stylesheets support).

Expected behavior
adoptedStyleSheets should take priority in this case. View the demo, it should read "IT WORKS!".

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop:

  • OS: macOS
  • Browser Chrome 88, Chrome 63

[question] Does the iframe work better than a template element?

I continue exploring the abilities of CSSStyleSheet object and this polyfill and found that the polyfill implementation uses the iframe element. Just wonder why not template element or even DocumentFragment? Is it connected with how browser parses and uses the CSS?

TypeError: Right hand side of instanceof is not an object

Describe the bug
TypeError: Right hand side of instanceof is not an object is raised from instanceOfStyleSheet when the polyfill is used with LitElement.

To Reproduce

git clone https://github.com/PolymerLabs/lit-element-starter-js.git
cd lit-element-starter-js
yarn install
yarn add construct-style-sheets-polyfill

Add the following to ./dev/index.html head

<script src="../node_modules/construct-style-sheets-polyfill/dist/adoptedStyleSheets.js"></script>

Start server yarn serve and visit http://localhost:8000/dev/index.html

Observe the error in the console, and my-element is not rendered.

Expected behavior
No error and working LitElement

Desktop (please complete the following information):

  • OS: Mac
  • Browser: Safari
  • Version: 12.1, 13.1

IE11 Support

We're recommending this polyfill for usage in the SystemJS 6.x (yet to be released) implementation of CSS modules (https://github.com/systemjs/systemjs/blob/6.x/docs/module-types.md#css-modules).

Because SystemJS supports IE11, we've had to note that this polyfill can't be used for CSS modules in IE11.

IE11 does fully support mutation observers though, so it may simply be that avoiding the spread operator, arrow functions, for-of and providing a simple Symbol fallback in the codebase that IE11 support could be completely possible.

Would you be open to a PR along these lines?

Potential Memory Leak?

Hi @calebdwilliams!

Thanks for the important polyfill! Rather than a confirmed bug report, this is more of a question about a potential memory leak in the current implementation.

I have only done a cursory review of the codebase so please correct me if I'm wrong, but it seems the following is true:

  1. a Location object holds a strong reference to a Document or ShadowRoot
  2. $locations in ConstructedStyleSheet.ts is a WeakMap where keys are instances of ConstructedStyleSheet and values are arrays of Location instances
  3. addAdopterLocation pushes a Location object (including its strong reference to a Document or ShadowRoot) to the Location array that is mapped in $locations (such mapping from the key of the specific ConstructedStyleSheet adopted by the same Document or ShadowRoot strongly referred to by the Location object being pushed to the array)
  4. addAdopterLocation is called when a stylesheet is initially adopted (or when any 'adopted' style node is removed)
  5. removeAdopterLocation is the only place that the Location objects (and their strong reference to a Document or ShadowRoot) are removed from the array that is mapped in $locations
  6. removeAdopterLocation is called when a ConstructedStyleSheet is removed from a Document or ShadowRoot's adoptedStylesheets but is not called in any other case

Based on the above, it seems that in the following scenario we would have a memory leak:

  1. Custom element instance 1 is constructed and a ConstructedStyleSheet is constructed and adopted by Custom element instance 1's shadowRoot
  2. Custom Element instances 2 - 1,000,000 are constructed and added and then later removed from the DOM, where each of custom element 2 - 1,000,000's constructors cause their shadow roots to adopt the same ConstructedStyleSheet from step 1

As a WeapMap's values are strongly held until the key is referred to only by the WeakMap itself, if Custom element instance 1 is still strongly held in the DOM after instances 2 - 1,000,000 are no longer held in the DOM or referenced by anything but this polyfill, it would seem that this polyfill would cause instances 2 - 1,000,000's not to be garbage collected. This is because each of instances 2 - 1,000,000's shadow root will still be referenced by the Location objects in the array that is the value of the $locations entry with the key of the ConstructedStyleSheet still adopted by custom element instance 1's shadow root. And because shadowRoot refers to the element it is attached to with the ShadowRoot.host property, the element itself will not be garbage collected.

If this is the case, this would seem to be a serious issue as adopting a StyleSheet when a custom element is constructed is a major use case for adopted stylesheets and constructable stylesheets so that all instances of a custom element constructor can share a single set of styles. At the very least a warning not to use this polyfill with short lived custom elements may be in order.

Please forgive me if I've missed something or if any of this is unclear. Also, unfortunately it doesn't seem we can force garbage collection so a test case where memory is not being garbage collected doesn't seem to be proof of a memory leak so we seem to be forced to report potential issues with analysis like the above: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management

Performance issues (in Firefox) with Web Components

Describe the bug
I'm using this with Webpack to load the CSS for my web components. I noticed on Firefox it inserts a style tag for each CSSStyleSheet that is loaded. I don't know if my idea below will make things faster, but if someone could test it, I'd appreciate it.

To Reproduce
Steps to reproduce the behavior:

Create a uicomponent.js and uicomponent.css. Fill the uicomponent.css with thousands of rules.

uicomponent.js

import uiComponentStyle from '!css-loader?exportType=css-style-sheet!./uicomponent.css' assert { type: "css" };

export default class UIComponent extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({mode: 'open'});
        this.shadowRoot.adoptedStyleSheets = [sharedStyle, uiComponentStyle];
        // ...
    }
}
customElements.define('ui-component', UIComponent);

uicomponent.css

a { color: red; }
... thousands more rules

Insert 1000 instances of ui-component into the body. It seems to take a while to process the style tags.

Expected behavior
Create one blob per CSSStyleSheet and just @import it? I think that would skip any CSS parsing or whatever Firefox is doing when constructing the web components.

Example of using blob with @import https://jsfiddle.net/na5tz49r/

<div>hello</div>
const blob = new Blob(['div { color: red }'], { type: 'text/css' });
const url = window.URL.createObjectURL(blob);

const $style = document.createElement('style');
$style.innerText = `@import url(${url});`;
document.head.appendChild($style);

I don't know if Firefox will import the blob and reparse it for every ui-component load. I'm thinking it won't, but I could be wrong. I expect it'll be drastically faster.

Additional context
This isn't a huge issue for me right now. I tell people to use Chrome and everything is fine. I'm rewriting my own code to limit the size of styles in my web components. Still if this works it might speed things up with very little changes.

Including the polyfill in the head gives an obscure error

When including the polyfill within the <head> tag synchronously, this throws a mutation observer error when trying to attach to document.body as body is null during page parsing.

It could be worth either supporting this case, better documenting it, or providing a more useful error message.

adopted style sheet is not overriding the styles declared in the custom element

Describe the bug
The poly

To Reproduce
Steps to reproduce the behavior:

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

Referential integrity is lost on replace()

Describe the bug
The object returned by replace is not the same as the original instance. I'm not sure if this is a bug, but it differs from Chrome's implementation.

To Reproduce

const sheet = new CSSStyleSheet();
const css = '/* all the css */';

sheet.replace(css).then(theSheet => {
  console.log(theSheet === sheet); // true in Chrome, false in Firefox
});

Expected behavior
Being that Chrome has native support for the CSSStyleSheet constructor, I would expect the polyfill should stay in line with their implementation until a more formal spec is in place.

removeExcludedStyleSheets doesn't remove any stylesheets after it finds a non-excluded sheet

Describe the bug

Short version: this line should be continue not return.

Slightly longer version: when checking for excluded stylesheets, removeExcludedStyleSheets returns after finding a stylesheet that should not be included. This prevents it from checking additional stylesheets.

To Reproduce

Given two stylesheets s1 and s2:

document.adoptedStyleSheets = [ s1, s2 ]
document.adoptedStyleSheets = [ s1 ]

will fail to remove s2 because it finds s1 first.

Expected behavior

The stylesheet s2 is removed.

Does not adopt styles of custom element if it is a child of element without adopted styles

If the custom element is rendered in the Shadow DOM of another custom element which does not have adoptedStyleSheet property set, it won't adopt the styles.

It happens because the MutationObserver is created in the adoptedStyleSheets property setter only. So, if you didn't set this property for the element, its ShadowRoot won't be observed, and any child added won't perform the adoption logic.

Here is the repro.

Add CI testing & coverage

To be sure the project has all tests passing in all target browsers and the coverage is OK, the CI service should be added. I personally use the Travis CI for testing and Codecov for coverage.

FOUC in Firefox

Describe the bug
I get a FOUC in Firefox.

To Reproduce
My setup is basically this (only with more app around it): http://jsfiddle.net/6scbyupm/

When loading the page, the icon is gigantic, before the size from the stylesheet is applied.

This seems to be related to the requestAnimationFrame() here:

requestAnimationFrame(() => {

If I remove that, it seems to work fine. But since I assume there's a good reason why requestAnimationFrame() is used, I'm wondering how to avoid the FOUC without patching the polyfill.

Expected behavior

The icon has the proper size from the start.

Desktop (please complete the following information):

  • OS: Linux
  • Browser: Firefox
  • Version: 98.0.1

Additional context
In my production case, the CSSStyleSheet object is created using Wepack's css-loader with exportType='css-style-sheet'.

background:none is applied as background-image:none in Safari

Describe the bug

.nobg { background: none !important;} 
.nobg {background: blue; }

shows a blue background in Safari.

To Reproduce
https://artur.app.fi/stylesheet-polyfill-bug3.html

Expected behavior
The element should have the defined CSS rules set and thus no background.

Screenshots
Styles in Chrome
image

Styles in Safari
image

Additional context
Not only is background replaced with background-image and background-color. It also seems like !important is lost

Does not adopt styles of the deeply nested custom element in the ShadowRoot

I continue experimenting with the polyfill in Firefox, and I found the critical issue that breaks the styling for many components.

The source of the issue is the following. When I build the shadow layout of my custom element, I can add an element into another with any nesting I need. E.g., I have a custom element x-foo and a custom element x-bar, and I want these elements to be the following:

<x-foo>
  #shadow-root
    <x-bar></x-bar>
    <div>
        <x-baz></x-baz>
    </div>
</x-foo>

However, if I put elements this way using, for example, the innerHTML, the current solution with MutationObserver would be able to catch only the nodes that are the direct children of the x-foo shadow root. Even adding a subtree: true rule does not help. Why? Because the div is not the child of the shadow root yet, so its children is not a source of changes for the MutationObserver. So in addedNodes in the mutation callback, we will receive only the first x-bar and the div, and the polyfill won't be able to run CSSStyleSheet adoption on nested elements.

<x-foo>
  #shadow-root
    <x-bar></x-bar> <!-- adopted style element is added to this element -->
    <div>
        <x-baz></x-baz> <!-- but not to this -->
    </div>
</x-foo>

Here is the repro.

[Suggestion] Remove support for CSSStyleSheet methods

While working on #28, I found that it is quite tricky to implement support for CSSStyleSheet methods like insertRule, deleteRule, etc. for ShadyCSS. The root of the problem is that ShadyCSS works with CSS text while the polyfill operates with HTMLStyleElement. So it is not easy to just apply CSSStyleSheet methods on the resulting style sheet the ShadyCSS produces.

So my suggestion is to drop the support for CSSStyleSheet methods. They are odd because do not change the visible CSS text, quite hard to implement, and speaking from my experience are very rare to use. Dropping them not only gives us the ability to be consistent between different browsers. It would also simplify the general code that has to be more complicated due to restrictions this support brings.

E.g., we probably would be able to remove createNodeIterator part because we can work with disconnected styles. Also, it won't be necessary to add mutation observer to all shadow roots because we could run adoption logic on the disconnected elements. BTW, the issue with Node.prototype.isConnected will simply disappear.

One more argument for my suggestion is that the ShadyCSS does not support these methods as well. It just sticks the styles together in one big stylesheet and appends it to the <head>. And it is ok for more than 99% of all requirements.

Edge bug

I just tested out the fixes included previously, and that seems to have worked in resolving the previous bug, but now a new error is being thrown in Edge:

TypeError: Failed to set the 'adoptedStyleSheets' property on Document: Failed to convert value to 'CSSStyleSheet'
   at adoptedStyleSheetAccessors.set (https://unpkg.com/[email protected]/adoptedStyleSheets.js:347:9)

adoptedStyleSheets#push doesn't work

Describe the bug
document.adoptedStyleSheets.push(new CSSStyleSheet()); doesn't work.

To Reproduce

Apply CSSStyleSheet via push

Expected behavior

document.adoptedStyleSheets = [ new CSSStyleSheet() ];
document.adoptedStyleSheets.push(new CSSStyleSheet());
should have the same effect

Desktop (please complete the following information):

  • OS: macOS
  • Browser Safari
  • Version 16.3

Find alternative to isConnected

Describe the bug
Node.isConnected is not supported by IE11.

To Reproduce
Run the package through Babel and then in IE11.

Expected behavior
The polyfill should work as expected

Desktop (please complete the following information):

  • IE11

RFC: Integrity version

It's been a while but I'm here with new thoughts about the polyfill implementation.

Integrity

The idea came from #45 and #46. Before, I thought that we should operate with real CSSStyleSheet classes to avoid any inconsistency that may come from using the wrapper class. However, after some investigation, I couldn't find any specific cases we aren't able to use the wrapper class for if we create it with the new operator. E.g., we cannot set it directly to the style element sheet property. All we can do is adding the class to adoptedStyleSheet array or call its methods. And any of this can be done in the scope of a single class. Please, correct me if I'm wrong.

This architecture promises to be more straight, and the package should become smaller.

Development tools

What about including typescript and eslint for this project? I think we could use their power to make the maintenance of the project easier.

For eslint I have a couple of suggestions:

  • I have my own set of rules. I created it following the idea: "Any rule should be included until it causes pain". Cons: it does not follow any specific style guide.
  • We can use the airbnb style guide. I'm not a big fan of this particular style guide (it is kinda opinionated) but since we are targeting IE, it should be ok.

UPD: BTW, I'm open to any other variants. I just don't know another style guide that can compete with Airbnb. Google, probably, but AFAIK it is full of strange decisions and is way more opinionated than Airbnb.

CSSStyleSheet instanceof check does not work

I tried to create a simple instanceof check for the CSSStyleSheet object, but it returns false.
Is it possible to pass the check?

const foo = new CSSStyleSheet();
console.log(foo instanceof CSSStyleSheet); // => false

npm version >7

Is there any reason why you need npm version to be >7 ?

HierarchyRequestError on Firefox

Describe the bug
Firefox throws HierarchyRequestError: Node cannot be inserted at the specified point in the hierarchy when adding style sheets to the document.

The errors occurs in function adoptStyleSheets at:

    if (location.firstChild) {
      location.insertBefore(newStyles, location.firstChild);
    } else {
      location.appendChild(newStyles);
    }

To Reproduce
In index.html:

    <script>
      const sheet = new CSSStyleSheet();
      sheet.replace('@import url("./styles/css/main.css")')
      document.adoptedStyleSheets = [sheet];
    </script>

Expected behavior
No errors.

Desktop (please complete the following information):

  • OS: Ubuntu
  • Browser: Firefox
  • Version: 69.0

Additional context
Add any other context about the problem here.

ReferenceError: document is not defined in SSR context

Describe the bug
The injected code on line 3 if ('adoptedStyleSheets' in document) { return; } fails in a node environment because document not defined

To Reproduce
Run this code in an SSR context

Expected behavior
Expected to catch this case and return early.

Exception while loading this polyfill together with webcomponents-lite polyfill

Describe the bug
An exception is thrown just by loading the polyfill.
This is the exception:

0: NotFoundError
lui-all.min.js (17875,366)

[object DOMException]: {code: 8, message: "NotFoundError", name: "NotFoundError"}
code: 8
message: "NotFoundError"
name: "NotFoundError"

__proto__: DOMExceptionPrototype

0: NotFoundError
lui-all.min.js (17875,366)

Screenshot
Screen Shot 2019-11-13 at 11 34 23

To Reproduce
Load this polyfill in addition: https://github.com/webcomponents/webcomponentsjs/blob/v1/webcomponents-lite.js

Expected behavior
No exception are thrown when loading the page with webcomponents-lite polyfill and construct-style-sheets polyfill.

Desktop (please complete the following information):

  • OS: Windows 10
  • Browser Edge
  • Version 44

Overriding HTMLElement.attachShadow breaks other polyfills

Describe the bug
attachShadow is defined (per spec) on Element but this polyfill sets it on HTMLElement.prototype
If anyone else later changes Element.prototype.attachShadow, their code is never called (on HTMLElements), because this library's polyfill takes precedence, and revert to the native attachShadow code (captured during init).

To Reproduce
https://codesandbox.io/s/attachshadow-bug-demo-w33wp
Loading it on Chrome shows "Should be called" on console (this library doesn't patch attachShadow).
Loading it on Firefox doesn't.
Switching the order of polyfills fixes the issue for Firefox (need to reload the sandbox for the prototypes to be reset)

Expected behavior
attachShadow being defined on Element as per the spec, that's where it should be defined to play fine with other polyfills/hooks.

Additional context
Polyfill confirmed to break if applied after this one:
@lwc/synthetic-shadow

Add typings for TypeScript

Describe the bug
Add a d.ts file that will add the replace and replaceSync methods to the global CSSStyleSheet interface for TypeScript.

Expected behavior
A TypeScript project importing this polyfill should not throw on the following

let sheet = new CSSStyleSheet();
sheet.replace(`* { box-sizing: border-box; }`);

Additional context
Add any other context about the problem here.

With the polyfill loaded, Firefox 91.0 no longer shows any styles in style inspector

Describe the bug
With the polyfill loaded, it is no longer possible to inspect any element's styles in the Firefox developer tools Inspector tab. No styles are presented at all. This not only applies to elements using constructed stylesheet, but also to all other regular HTML elements.

Current Firefox developer edition (92.0b2) also doesn't support constructed stylesheets, but does not have this issue.

To Reproduce
Load any page including JS with the polyfill in Firefox <= 91 on MacOS. Inspect any element in the page.

Expected behavior
Styles show up in inspector, at least for those elements not making use of constructed stylesheets.

Screenshots
Firefox 91.0:
grafik
grafik

Firefox Developer Edition 92.0b2 (still not supporting constructed stylesheets natively):
grafik

Desktop (please complete the following information):

  • OS: MacOS 12.0 (Monterey) Beta (21A5294g) // Windows 10 (1909 Enterprise N)
  • Browser: Firefox
  • Version: 91.0

Additional context
I've just had my windows colleague check, the same issue also exists on Firefox 91 / Windows 10.

SyntaxError in Safari

Describe the bug
When using this polyfill (>= v3.0.4) Safari throws a SyntaxError: The string did not match the expected pattern. which results in not applying any styles.
I can only reproduce this error when using the polyfill with twind. So it could also be a bug of twind but it works with [email protected] so I'm not really sure.

Safari is highlighting these lines in the error.

.forEach((command) =>
adopter.sheet![command.method].apply(adopter.sheet!, command.args),
);

To Reproduce
Steps to reproduce the behavior:
Preview with error in console
CodeSandbox

Expected behavior
To not throw SyntaxError: The string did not match the expected pattern.

Desktop (please complete the following information):

  • OS: [macOS]
  • Browser [safari]
  • Version [15.4]

Additional context
This error only occurs when using with twind so I'm not entirely sure which package is to blame ๐Ÿคทโ€โ™‚๏ธ
It is working up until version 3.0.3 of construct-style-sheets-polyfill.

How to use `construct-style-sheets` on iframe

When using construct-style-sheets for iframe, the current document has adoptedStyleSheets but not the iframe.
I've tried to fork a local repository and create a ConstructuredStyleSheet({ targetWindow }). Which will initiate the adoptedStyleSheet to the target window and its document. But it is just so buggy.

Is there any way the iframe will inherit adoptedStyleSheets or somehow to use it on an iframe?


To Reproduce

  1. Install construct-style-sheets as instruction
  2. use `import construct-style-sheets-polyfill
  3. Adopt stylesheets
iframeDocument.adoptedStyleSheets = [styleFile];

Expected behavior
The iframe should allow adopting new stylesheets

Actual behavior
On the iframe, adoptedStyleSheets is undefined. Error received:

TypeError: undefined is not an object

Additional context

Document need for Symbol polyfill

Describe the bug
Add language to README that details the needs for a polyfill for Symbol. core-js should do the job, maybe offer an alternative?

To Reproduce
Running this package through Babel and in IE11 will result in an error because Symbol isn't defined.

Expected behavior
We should detail for the consumer the need for a polyfill for Symbol in the project's README.

Desktop (please complete the following information):

  • IE11

Edge Support

Describe the bug
Edge Support is currently failing

To Reproduce
Run the polyfill script in Edge.

Expected behavior
Edge support should be possible?

Screenshots

Desktop (please complete the following information):

  • OS: [e.g. iOS] Windows 10
  • Browser [e.g. chrome, safari] Edge
  • Version [e.g. 22] 18

Additional context
Add any other context about the problem here.

I believe the error message was related to ShadowRoot being undefined.

Latest release on npm includes livereload script that breaks usage.

https://unpkg.com/construct-style-sheets-polyfill

which resolves to version 2.3.3 as of now includes (function(l, r) { if (l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (window.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.head.appendChild(r) })(window.document);
which breaks in my application when importing the polyfill.

https://unpkg.com/[email protected] does not have this issue.

Missing styles with v3 and Safari 14

Describe the bug
The CSS rules in adoptedStyleSheets are supposed to end up inside <style> tags inside the shadow root. In Safari in some cases, the <style> tags are empty instead.

To Reproduce
https://start.vaadin.com/app/ is running the polyfill v2. If you open the URL in Safari, you can see a small globe icon next to "globe-solid" in the "Icon" select.

This comes from CSS which you can see by running

root = document.querySelector("view-item").shadowRoot;
styleSheets = root.adoptedStyleSheets;
cssRules = styleSheets.flatMap(sheet => Array.from(sheet.cssRules))
css = cssRules.map(cssRule => cssRule.cssText);
css.filter(text => text.includes(".la-globe"));

which shows that there is a .la-globe rule in the adopted style sheets array and correspondingly

Array.from(root.querySelectorAll("style")).filter(style => style.innerText.includes(".la-globe"))

shows there is a .la-globe rule in the style tags inside the shadow root.

Now I deployed a version of the app using v3 of the polyfill at https://start-dev.herokuapp.com/app/

There you cannot see a globe icon in the UI and running the same code reveals that adoptedStylesheet still contains the expected CSS but the style tags inside the shadow root does not contain the corresponding CSS rule. In fact most or all of the style tags are completely empty.

This only reproduces in Safari.

In Firefox, the <style> tags actually are also empty but there the globe is shown in the UI, as expected. The inspector also shows that it finds the .la-globe CSS rule although it is unable to show where..

Stray console.log in published 2.4.0 pkg

Describe the bug
The recent 2.4.0 package logs unnecessary messages to the dev console. The line is not found in the repo source, just the published pkg.

To Reproduce
Steps to reproduce the behavior:
Install, import, use latest package in Firefox or Safari.

Expected behavior
Shouldn't log messages to the console.

Screenshots
image

Desktop (please complete the following information):

  • OS: all
  • Browser: FF, Safari

Move style sheet object to WeakMap

Long term, we should consider moving the metadata object for the constructed style sheet to a WeakMap to avoid polluting the nativeStyleSheet's interface.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.