Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when rendering the same stylesheet to two different documents (iframes, templates) #2609

Closed
jmrog opened this issue Dec 16, 2021 · 8 comments
Labels

Comments

@jmrog
Copy link

jmrog commented Dec 16, 2021

Description

This issue appears to affect Chrome only, and happens if (1) your lwc version is >= 2.3.7, (2) you render an LWC component using lwc:dom="manual", (3) your manually-constructed DOM also includes an LWC component, and (4) the LWC component in the manually-constructed DOM has its own CSS stylesheet. If any of those conditions are not satisfied (e.g., you use lwc version <= 2.3.4, or the LWC component in the manually-constructed DOM does not have its own stylesheet), the issue does not occur.

The issue: under the aforementioned conditions, in Chrome, the lwc library throws an uncaught DOMException, and the app fails to render (crashes). The DOMException is as follows:

Uncaught DOMException: Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot': Sharing constructed stylesheets in multiple documents is not allowed
    at insertConstructableStyleSheet (http://localhost:3001/app-d1335320964653800ef9.js:7691:35)
    at Object.insertStylesheet (http://localhost:3001/app-d1335320964653800ef9.js:7887:13)
    at createStylesheet (http://localhost:3001/app-d1335320964653800ef9.js:5973:18)
    at http://localhost:3001/app-d1335320964653800ef9.js:6209:71
    at ReactiveObserver.observe (http://localhost:3001/app-d1335320964653800ef9.js:597:7)
    at isUpdatingTemplate (http://localhost:3001/app-d1335320964653800ef9.js:6178:9)
    at runWithBoundaryProtection (http://localhost:3001/app-d1335320964653800ef9.js:7201:5)
    at evaluateTemplate (http://localhost:3001/app-d1335320964653800ef9.js:6163:3)
    at invokeComponentRenderMethod (http://localhost:3001/app-d1335320964653800ef9.js:6370:39)
    at renderComponent (http://localhost:3001/app-d1335320964653800ef9.js:6428:18)
    at rehydrate (http://localhost:3001/app-d1335320964653800ef9.js:6795:22)
    at connectRootElement (http://localhost:3001/app-d1335320964653800ef9.js:6551:3)
    at callNodeSlot (http://localhost:3001/app-d1335320964653800ef9.js:7919:9)
    at HTMLDivElement.appendChild (http://localhost:3001/app-d1335320964653800ef9.js:7929:16)
    at http://localhost:3001/app-d1335320964653800ef9.js:8437:15
    at NodeList.forEach (<anonymous>)
    at App.insertDocHtml (http://localhost:3001/app-d1335320964653800ef9.js:8428:14)
    at App.renderedCallback (http://localhost:3001/app-d1335320964653800ef9.js:8448:12)
    at callHook (http://localhost:3001/app-d1335320964653800ef9.js:6523:13)
    at http://localhost:3001/app-d1335320964653800ef9.js:6300:5
    at runWithBoundaryProtection (http://localhost:3001/app-d1335320964653800ef9.js:7201:5)
    at invokeComponentCallback (http://localhost:3001/app-d1335320964653800ef9.js:6299:3)
    at runRenderedCallback (http://localhost:3001/app-d1335320964653800ef9.js:6881:5)
    at patchShadowRoot (http://localhost:3001/app-d1335320964653800ef9.js:6853:5)
    at rehydrate (http://localhost:3001/app-d1335320964653800ef9.js:6796:5)
    at connectRootElement (http://localhost:3001/app-d1335320964653800ef9.js:6551:3)
    at callNodeSlot (http://localhost:3001/app-d1335320964653800ef9.js:7919:9)
    at HTMLDivElement.appendChild (http://localhost:3001/app-d1335320964653800ef9.js:7929:16)
    at http://localhost:3001/app-d1335320964653800ef9.js:25006:33
    at http://localhost:3001/app-d1335320964653800ef9.js:25007:3
    at http://localhost:3001/app-d1335320964653800ef9.js:25194:12

The error is thrown at the moment that the adoptedStylesheets property of the target is assigned in insertConstructableStyleSheet.

Steps to Reproduce

I created a very small GitHub repo that reproduces the issue: https://github.com/jmrog/lwc-stylesheets-bug. I was unable to reproduce the issue in the webcomponents.dev environment, but am unsure what version of lwc is being used there anyway. The steps to reproduce the issue are available on the just-mentioned repo, but here they are again:

  1. Clone the repo and run yarn install or npm install.
  2. Start the application using yarn watch or npm run watch.
  3. Direct Chrome to http://localhost:3001 to open the app.
  4. Observe that nothing renders.
  5. Open the browser's developer tools and observe the error mentioned above.

Again, the issue does not appear if you remove the child component's CSS file, or if you do not construct the DOM manually (e.g., change this.isManual = true; to this.isManual = false; in the constructor in app.js), or if you downgrade lwc enough (e.g., downgrading lwc-services to 3.1.0 in the aforementioned repo works), etc. It also does not appear in Firefox or Safari (only Chrome, as far as I know).

Expected Results

The app renders as expected, and no error is thrown. Visual from the above repo:

image

Actual Results

The app fails to render (crashes) and shows an uncaught DOMException in the browser console, in Chrome only. Visual from the above repo:

image

Browsers Affected

Chrome, latest version (96.0.4664.110 as of this writing).

Version

  • LWC: 2.3.7+

Possible Solutions

Not really a solution, but just want to note that the changes introduced in #2460 seem to have triggered this issue.

@nolanlawson
Copy link
Collaborator

Thanks for the very thorough bug report! I wrote a minimal repro (nolanlawson/lwc-barebone@431bec0) based on yours:

document.body.appendChild(createElement("x-app", { is: App }));

const template = document.createElement('template');
template.content.appendChild(createElement("x-app", { is: App })); // throws

The issue is that (apparently) adopted stylesheets cannot be reused across two documents – in this case, the main document and a <template>. Iframes have the same issue:

document.body.appendChild(createElement("x-app", { is: App }));

const iframe = document.createElement('iframe');
iframe.contentDocument.appendChild(createElement("x-app", { is: App }));  // throws

One workaround you can use is to avoid <template>s. For instance, here is a modified version of your repro (nolanlawson/lwc-stylesheets-bug@014de85) that doesn't throw an error.

We could try to track constructable stylesheets on a per-document basis, but I'm not sure if that's feasible. This is a tricky bug, and there's some more discussion of it here: WICG/construct-stylesheets#23

@uip-robot-zz
Copy link

This issue has been linked to a new work item: W-10323566

@nolanlawson nolanlawson changed the title Fails to render on Chrome with lwc:dom="manual" and component stylesheets LWC cannot render the same stylesheet to two different documents (iframes, templates) Dec 16, 2021
@nolanlawson nolanlawson changed the title LWC cannot render the same stylesheet to two different documents (iframes, templates) Error when rendering the same stylesheet to two different documents (iframes, templates) Dec 16, 2021
@jmrog
Copy link
Author

jmrog commented Dec 16, 2021

@nolanlawson Nice minimal repro, and thanks for the workaround. As far as my team is concerned, that workaround is probably sufficient -- and extremely minimal in cost -- for us to move to the latest version of lwc.

Thanks for the link to that discussion, too. Lots to think about there!

@nolanlawson
Copy link
Collaborator

So this is actually a really subtle issue. I have a minimal repro:

  const template = document.createElement('template')
  const div = document.createElement('div')
  div.attachShadow({ mode: 'open' })
  template.content.appendChild(div)

  const sheet = new CSSStyleSheet()
  div.shadowRoot.adoptedStyleSheets = [sheet]

In Chrome this throws:

DOMException: Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot': Sharing constructed stylesheets in multiple documents is not allowed

In Firefox with the flag layout.css.constructable-stylesheets.enabled enabled, it throws:

DOMException: ShadowRoot.adoptedStyleSheets setter: Each adopted style sheet's constructor document must match the document or shadow root's node document

Looking through various discussions on this (e.g. WICG/construct-stylesheets#133 and this Chromium issue), I'm not sure if there's a solution. There is no way AFAICT to associate the CSSStyleSheet's constructor document with the template's owner document. Apparently there are elaborate solutions involving adoptNode, but I haven't yet gotten them to work.

In iframes, I assume this would not be a problem, because assuming the LWC engine is running inside the iframe, it would be getting CSSStyleSheet from the window of the iframe, not the containing page. So maybe this just affects templates.

@nolanlawson
Copy link
Collaborator

nolanlawson commented Mar 1, 2022

Interestingly the same pattern works fine with Lit. The difference is this:

  const template = document.createElement('template');
  const div = document.createElement('x-foo');
  div.attachShadow({ mode: 'open' });
  template.content.appendChild(div); // LWC assigns adoptedStyleSheets here
  
  document.body.appendChild(template.content) // Lit assigns adoptedStyleSheets here

The reason for the difference is that LWC doesn't use the "real" connectedCallback, and we fire it at the incorrect time. Minimal repro:

Screen Shot 2022-03-01 at 1 54 27 PM

If we fix that bug, then assigning adoptedStyleSheets should "just work."

@nolanlawson
Copy link
Collaborator

nolanlawson commented Mar 1, 2022

Turns out you don't even need a <template>; you can repro with a <div>:

  const div = document.createElement('div');
  console.log('appending to div')
  div.appendChild(elm);
  console.log('appending div to document.body')
  document.body.appendChild(div);

Screen Shot 2022-03-01 at 3 34 27 PM

@nolanlawson
Copy link
Collaborator

Related to #1102

@pmdartus
Copy link
Member

Duplicate of #3198

@pmdartus pmdartus marked this as a duplicate of #3198 Jan 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants