lit / lit-element

LEGACY REPO. This repository is for maintenance of the legacy LitElement library. The LitElement base class is now part of the Lit library, which is developed in the lit monorepo.
https://lit-element.polymer-project.org
BSD 3-Clause "New" or "Revised" License
4.49k stars 318 forks source link

Bug/Question: LitElement rendering into multiple iframes no styles with constructable stylesheets #1139

Closed daKmoR closed 3 years ago

daKmoR commented 3 years ago

I'm rendering a component into multiple iframes

render(html`<foo-el></foo-el>`, iframe1.contentDocument.body);
render(html`<foo-el></foo-el>`, iframe2.contentDocument.body);

and I'm already amazed that I don't need to redefine all custom elements in those iframes... however due to LitElement using constructable stylesheets... those stylesheets are only available in the "parent" window... any way I can bring them along?

funny enough it works fine in browser that don't support constructable stylesheets as then it will create style tags in the shadow dom 😅

===> Live Demo at https://codepen.io/daKmoR/pen/XWjwdRb?editors=1000 <<===

Question

is that the intended behaviour? or should it register the stylesheet in every document it get's added 🤔

justinfagnani commented 3 years ago

Well... this isn't really an intended use of render(). Not that it's completely wrong, but lit-html hard-codes use of the main document for importing from templates. This is why you don't need to redefine the elements in the iframe - they're created in the main document, then moved to the iframe. This is also why the stylesheets are removed. Because constructible stylesheets have a base URL and origin that matches the document they're created in, it's not transparent or safe to move them to a frame with another origin, so they're removed by the browser.

I think the way to handle this is to implement and adoptedCallback() and trigger re-attaching the constructible stylesheet, like:

  adoptedCallback() {
    this.adoptStyles();
  }

I'm not sure that'll fully work because the css tag caches the CSSStyleSheet instance and it might not be adoptable in the new document. This might also double up the styles in browsers that don't support adoptedStyleSheets because it resets _needsShimAdoptedStyleSheets. Worth a try though.

@sorvell

daKmoR commented 3 years ago

that gives me 😅

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

so I assume I need to find some way to "duplicate" them without being the same and then attach them somehow 🤔

justinfagnani commented 3 years ago

I was afraid of that. @rakina, do you know of a way to create a CSSStyleSheet in one frame and adopt it in another?

justinfagnani commented 3 years ago

@daKmoR can you define the elements in the iframe?

daKmoR commented 3 years ago

what I would like to archive is to have a demo shown in multiple iframes to simulate multiple devices...

I probably can analyse the code, find all imports and webcomponents and recreate them in the iframe... it's just way more complicated.

And creating the element and moving it later is actually very useful in this case... and it all works if you use the good old <style> tag... I mean I could disable constructible stylesheets for the whole page... that would solve it... but then again this basically means you can never move any custom elements into another document (as you will always loose your styling)

is that a general limitation of constructible stylesheets? seems rather unfortunate 🤔

kevinpschaaf commented 3 years ago

See https://github.com/WICG/construct-stylesheets/issues/23 and https://bugs.chromium.org/p/chromium/issues/detail?id=1111864 for some context; there are a lot of constraints at play afaiu.

kevinpschaaf commented 3 years ago

Still, with some work, it seems like we might be able to have LitElement/ReactiveElement implement an adoptedCallback does a better thing here (i.e. run a clone algorithm over the flattened styles list that re-generates them in the new document)

kevinpschaaf commented 3 years ago

Just to see if this approach would work, something roughly like this?

  adoptedCallback() {
    if (supportsAdoptingStyleSheets) {
      // Assumes styles are made with `css` tag (and thus s.cssText is available)
      // To support `CSSStyleSheet` in static get styles would require generating the cssText from the CSSOM
      // To make it efficient, would want some sort of document-based cache, based on the new
      // `this.ownerDocument` after adoption
      this.adoptedStylesheets = (this.constructor as typeof LitElement)._styles.map((s as CSSResult) => {
        const sheet = new CSSStyleSheet();
        sheet.replaceSync(s.cssText);
        return s;
      });
    }
  }
calebdwilliams commented 3 years ago

Hey, I expanded on @kevinpschaaf's example and have a working demo that could be helpful.

Here instead of using CSSStyleSheet directly I accessed the object through this.ownerDocument.defaultView.CSSStyleSheet. While it's a bit of a nuisance maybe the LitElement class could use this reference instead of directly calling CSSStyleSheet?

adoptedCallback() {
  if ('adoptedStyleSheets' in document) {
    const CSSStyleSheet = this.ownerDocument.defaultView.CSSStyleSheet;
    this.shadowRoot.adoptedStyleSheets = this.constructor._styles.map(s => {
      const sheet = new CSSStyleSheet();
      sheet.replaceSync(s.cssText);
      return sheet;
    }); 
  }
}

This has something broken in Firefox (I'm not going to mess with figuring out what's happening there), but figured I'd post it as a possibility any way.

Edit

I'd also like to see this addressed somehow because I'd like to make (or use if someone else wants to do the hard work) a web component version of SEEK's Playroom.

sorvell commented 3 years ago

We're not inclined to address this in ReactiveElement because this seems like a rare/advanced use case, and it would be a chunk of work to get this to work with ShadyCSS. We'd need polyfill support to put this into the core code.

Instead, we recommend either disabling constructible stylesheets in the main document or loading the code in the iframes and creating the elements there. Will one of those approaches work for you?

daKmoR commented 3 years ago

I went with loading the code in the iframes and creating the elements there...

It's more complex to implement but you also gain isolated pages you can reload work with...

closing this as it's sort of a wont-fix