whatwg / dom

DOM Standard
https://dom.spec.whatwg.org/
Other
1.58k stars 294 forks source link

Declarative Shadow DOM #510

Closed tabatkins closed 6 years ago

tabatkins commented 7 years ago

One of the promises of early Shadow DOM (and several other Parkour-descended products) was that while we would build the first version as an imperative (JS-based) API, for flexibility and composability, we'd come in later and backfill a declarative version that made it very easy to use for common cases. We haven't done this yet for Shadow DOM, and the spec is stable enough now that I think we can safely do it.


Rough proposal:

<div-or-something>
  <template attach-shadow shadow-mode="open | closed">
    ...shadow content...
  </template>
</div-or-something>

That is, using a <template> element with an attach-shadow attribute automatically (during parsing) attaches its contents to its parent element as a shadow root. The mode is selected by the optional shadow-mode attribute; if omitted, it defaults to "open" (or we can make it required, like the JS API does?).


Reasoning:

Today you can get something close to declarative with JS like:

<parent-element>
</parent-element>
<script>
"use strict";

const shadowRoot = document.currentScript.previousElementSibling.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `<child-element></child-element>`;
</script>

This has several downsides:

  1. Doesn't work nicely with syntax highlighting.
  2. Doesn't work nicely with any tooling that wants to be able to output HTML.
  3. Doesn't nest easily. (That is, having an element in the shadow contain another declarative shadow.)
  4. Can't include <script> in the shadow unless you remember to do the various tricks to avoid having a literal </script> in the string.
  5. Also have to be aware of which quoting character you're using, and remember to escape it when you're writing your page.
  6. Including user-generated content is now more complicated; it not only needs to be escaped as safe HTML, but as safe script and string contents, too, which are decently more complex (and not, afaik, supported by the core libraries in PHP or Python).
  7. Lots of non-trivial typing for something intended to be easy and widespread.
  8. Inline script isn't compatible with safe CSP practice, unless you go the extra mile to add nonces (more effort! must use crypto safely!)
  9. Inline script isn't run for .mhtml archives (at least in Chrome, for historical reasons).
  10. Users with JS turned off won't get any page at all, for no good reason - page might have just been wanting style isolation, but is now tied to JS evaluation.
  11. Inline script halts the speculative parser, I think?
  12. Doesn't allow for server-side rendering (doing as much of the page as possible on the server, only using client-side JS for final fixups and event hookups, etc). The page instead has to do all this work on client-side (which is apparently fairly slow, per https://github.com/whatwg/dom/issues/510#issuecomment-329179035)
domenic commented 7 years ago

I am not personally convinced of the need here (but I don't oppose it). Let me try to help with some details that could be problematic if people want to advance this.

annevk commented 7 years ago

I think I'd prefer <shadowroot mode="open|closed"> with the parser simply not treating this as a ShadowRoot insertion request if the mode attribute cannot be validated (until we can agree on a default).

This kind of feature also helps with first load where the server has done all the templating work to make rendering happen faster.

domenic commented 7 years ago

That would require more parser work and thus ecosystem churn though, right? On the same order of magnitude as that when we originally introduced template? Otherwise I agree it'd be nicer.

annevk commented 7 years ago

It should be less work now we have <template>, but probably still non-trivial. I'm not a big fan of overloading elements, that hasn't worked well historically (e.g., <object> and <embed> are still a poorly understood mess).

annevk commented 7 years ago

(Note that I suspect that overloading <template> would also require parser changes, due to the things you mentioned.)

treshugart commented 7 years ago

Hi! It's me again. Sorry. Some background:

I've tried to keep most of the history and rationale for our thoughts in the README of the skatejs/ssr repo, but some of it might be missing. It's worth a study to at least see where we're coming from.

The thing I've found most interesting in this work is that the imperative API is exclusive in its DOM behaviour. For example, <div>test</div> means something completely different when attachShadow() is imperatively invoked. Finding a declarative equivalent that doesn't break existing assumptions is difficult. For example:

<div>
  World
  <shadow-root>
    Hello, <slot />!
  </shadow-root>
<div>

Whether or not this is used with a <template /> element or something else, there's several problems with this:

  1. SEO and content order when read in engines that don't execute JS.
  2. What happens to <shadow-root />? Does it get removed?
  3. What happens to childNodes? Attaching a root modifies this.
  4. Does appendChild(document.createElement('shadow-root')) work?

Libraries like React (and many more) have based themselves on the pillar that a core subset of DOM works as intended. If appending a shadow root mutates the host and makes childeNodes behave differently, this breaks the web. Inversely, if this is only available to the initial parse, it seems inconsistent. Meaning if <div><shadow-root /></div> is only available to the parser, but means nothing when using the imperative alternatives (i.e. appendChild(shadowRootElement)), then expectations of declarative vs imperative APIs are inconsistent.

I'm sorry if my thoughts seem a bit disjointed right now. Somebody linked this to me and it's a bit late here, but I really wanted to lay out some of my thoughts otherwise I may have forgotten about it (I do web components in my spare time).

I'm excited to see traction on this!

matthewp commented 7 years ago

Thanks for starting this issue @tabatkins. To add some weight that this is a need, I tried the JS approach that you mentioned some time ago and it was fairly slow: https://discourse.wicg.io/t/declarative-shadow-dom/1904/8

tabatkins commented 7 years ago

We don't use hyphens in built-in attribute names. So attachshadow/shadowmode instead of attach-shadow/shadow-mode

Ah yeah, right.

It seems like these could be combined into a single attribute, e.g. shadow="open|closed". If shadow is not present then it's a normal template.

I did that at first, but separated it because the mode isn't a positional attribute of attachShadow, it's just a required entry in the options dict. I thought we might add more in the future, but now that I'm thinking, I guess we can't add more required options anyway as it would break the API. So sure, we can combine them.

Maybe the answer is nothing (as if you'd omitted the attribute), but at least for <template shadow> that seems unfortunate.

Strongly agree. I think there's an obvious answer - default to open, because you're just using this as a styling boundary; nothing more special is happening at all anyway, because no JS.

Template contents are parsed in a completely separate document, the template contents owner document. That prevents e.g. script from running or images from downloading. It sounds like that will no longer be the case here?

My intention is that it'll append the template contents to the auto-created shadow root. I think scripts'll run at that point, yeah?

What does templateEl.content return? Maybe it becomes an alias for templateEl.shadowRoot? Is that too weird?

Per the above, I guess the append automatically removes them from the template's doc fragment? The answer is then ".content is empty because its contents have been transferred out". Or is there a stamping operation that doesn't remove the contents? I'm not sure of the details here. I'm flexible, whatever answer falls out is fine.

What are the cloning and adopting steps?

I think in my conception this is a parser operation more or less, so cloning/adopting just does whatever those operations do for elements that have shadow roots attached in the JS way. I'm not sure what the implications of that are, tho.


This kind of feature also helps with first load where the server has done all the templating work to make rendering happen faster.

Ah yes, thank you, that's another tick in the Pro column.


[Adding a <shadowroot> element] would require more parser work and thus ecosystem churn though, right? On the same order of magnitude as that when we originally introduced template? Otherwise I agree it'd be nicer.

I completely agree - it would definitely be nicer, but given the non-trivial parser work, I was thinking leaning on template would be better. On the other hand, I guess now that template exists widely, one can lean on that for polyfilling (doing <template><shadowroot>...) and we could add a new element. I don't have a strong opinion on this, because I understand the tradeoffs. Whatever works best for everyone.


SEO and content order when read in engines that don't execute JS.

There's no JS here. Tools that read the DOM would have to update to know about declarative shadow roots, sure, but that's vastly easier than supporting JS.

What happens to <shadow-root />? Does it get removed?

I'd think it would just stay there, like template does. The shadow-attaching is a parse-time operation, it's a dead element afterwards.

What happens to childNodes? Attaching a root modifies this.

What do you mean? The element's childNodes are unaffected by the presence of a shadow root. The children get distributed, which matters for CSS and some other stuff down-stream, but the DOM doesn't care at all.

Does appendChild(document.createElement('shadow-root')) work?

No, this is a parse-time operation. Post-parsing, the shadowroot element is dead, identical to a plain template. If you have JS, just use attachShadow(). ^_^

robdodson commented 7 years ago

The answer is then ".content is empty because its contents have been transferred out". Or is there a stamping operation that doesn't remove the contents?

I think template.content.cloneNode leaves the content in place (sorry on my phone right now so can't check).

Really excited to see this discussion happening! @kevinpschaaf @samuelli

bedeoverend commented 7 years ago

There's no JS here. Tools that read the DOM would have to update to know about declarative shadow roots, sure, but that's vastly easier than supporting JS.

This for me is a potential issue - if we're relying on support from SEO engines / HTML scrapers to add support, it might hurt adoption as people are after a solution with current bots / tools - particularly given the ambiguity around what bots can do already.

Is this a concern for others? If so, it might be worth exploring a declarative 'composed' DOM, rather than declarative Shadow DOM, so current bots can 'see' what the user will see. Something we're exploring over at skatejs/ssr

Apologies if this is sidetracking from the main issue, but feel its worth mentioning. Thanks for getting this discussion going!

tabatkins commented 7 years ago

This for me is a potential issue - if we're relying on support from SEO engines / HTML scrapers to add support, it might hurt adoption as people are after a solution with current bots / tools - particularly given the ambiguity around what bots can do already.

That's a fully general counter-argument against any addition to HTML at all, and so can't really be used as a specific objection without more details.

If so, it might be worth exploring a declarative 'composed' DOM,

That's just... DOM, right? Like, normal ordinary DOM children.

bedeoverend commented 7 years ago

That's a fully general counter-argument against any addition to HTML at all, and so can't really be used as a specific objection without more details.

Yeah that's fair - I'll go into specific details and whether tradeoffs are worthwhile.

That's just... DOM, right? Like, normal ordinary DOM children.

What I meant by this was declarative way to present the resultant composed tree after shadow root is attached and light DOM nodes are distributed - apologies if I'm getting the terminology wrong here.

More concretely, what I was thinking was a composed flag to tell the parser to pull a part this elements DOM into Shadow and Light DOMs.

<div composed>
  Hello <slot><strong>World</strong></slot>
</div>

which the parser would turn into:

<div>
  #shadow-root
    Hello <slot></slot>
<strong>World</strong>
</div>

This wouldn't be a 1:1 declarative version of imperative Shadow DOM API, just a way to tell the parser to construct an element's Shadow and Light trees. Straight off - and I'm sure there's a lot more I'm not considering - downsides are:

I realise this may be way off base, but I just wanted to throw it out there as an idea, incase supporting current bots is worth the tradeoffs.

treshugart commented 7 years ago

That's just... DOM, right? Like, normal ordinary DOM children.

Yes, but this is useful. People outside of the WC community actually want this.

What we're proposing is two distinct modes:

  1. Shadow DOM and CSS encapsulation via attachShadow() (the current state of things)
  2. Only CSS encapsulation via the composed attribute (maybe also works via attachShadow({ composed: true }) but that can be worked out later. Declarative design first IMO).

To concretely illustrate this, React is a good example.

class Hello extends Component {
  static defaultProps = {
    name: 'World'
  }
  render () => (
    <div composed>
      <style>
        /* Works on <div composed>. */
        :host {}

        /* This works because of <slot />! */
        ::slotted(*) {}

        /* Only works for top-level span, not the slotted one. */
        span { font-weight: bold; }
      </style>
      Hello,{' '}
      {/* This span would be affected... */}
      <span>
        <slot>
          {/* ...but not this one. */}
          <span>{ this.props.name }</span>
        </slot>
      </span>!
    </div>
  )
}

The idea here is a lot like the old <style scoped /> form of encapsulation, but it differs because the full gamut of Shadow CSS works (:host, ::slotted(), etc).

The <slot /> element provides an encapsulation barrier that CSS selectors cannot cross but that's it (querySelector() still works). The assignedNodes() method does not return anything because nothing is being projected. In this mode, DOM accessors are not encapsulated. However, to turn this into full Shadow DOM, one can call attachShadow() and the composed tree is reverse engineered:

  1. The host receives a shadow root.
  2. childNodes of slots are attached to the host, thus are projected.
  3. Any content within a slot that has the default attribute (flagging for default content) is not attached to the host, and remains as default content.
  4. For named slots, this is declared as normal and is thus projected as normal.

This is literally the algorithm we're employing for declarative shadow DOM at the moment. The thing that's nice about it is that it's backward compatible and it fits very closely with the current model for shadow roots. @bedeoverend did I miss anything in that list?

EDIT

My proposal and @bedeoverend's proposal are slightly different. In his, he states that composed should reverse engineer the DOM tree whereas in mine it leaves it alone until attachShadow() is called.

treshugart commented 7 years ago

The "laugh" here along with the comment (not the comment alone) seems a bit condescending. We're outsiders of the W3C desperately trying to have our say in context of our extensive experience in working with these APIs and being in close working contact with other communities. Though, subtle, this downplays that and makes outsiders less motivated to participate which is bad for the specs. Can we please have a discussion here that places a bit more weight on non-W3C members' experience? Thanks!

screen shot 2017-09-14 at 10 04 47 am
bedeoverend commented 7 years ago

@bedeoverend did I miss anything in that list?

Don't think so - only thing is we're looking to handle undistributed nodes, but IMO that shouldn't be baked into platform.

FWIW I'm still unsure about whether composed should be an entirely different mode, or whether it should just trigger the HTML parser to behave differently while parsing that element. I'd be for a a different 'CSS-only' mode, but is it reaching too far right now? Perhaps there's an iterative way to get there? Keen to balance what's feasible now and what's going to garner adoption from wider web community.

bedeoverend commented 7 years ago

Though, subtle, this downplays that and makes outsiders less motivated to participate which is bad for the specs. Can we please have a discussion here that places a bit more weight on non-W3C members experience?

I agree with this. I appreciate it must be incredibly hard to work on specs and and the same time keep it open, but reactions which minimise external contribution only serve to ostracise the wider community. I want to try help, and I'm happy to be told my ideas aren't going to work, or I'm not communicating them well enough, but there's got to be a better way of doing that while keeping the community open.

treshugart commented 7 years ago

FWIW I'm still unsure about whether composed should be an entirely different mode, or whether it should just trigger the HTML parser to behave differently while parsing that element. I'd be for a a different 'CSS-only' mode, but is it reaching too far right now? Perhaps there's an iterative way to get there? Keen to balance what's feasible now and what's going to garner adoption from wider web community.

I've had discussions about this with several outside of the WC community. Since they have their own component model (whether or not they should be using Custom Elements is besides the point), they don't need the DOM encapsulation, they just want the CSS aspect. If what their internals expect changes underneath them (their declared DOM changes from what they think it looks like) then it precludes them from declarative usage. This is a barrier to entry for them because they must modify their internals.

I truly believe what I've proposed actually fits as two modes that can be composed together to achieve what you want here. It can service both use-cases while maintaining compatibility with libraries and frameworks, without requiring they update their internals.

dominiccooney commented 7 years ago

@annevk wrote:

I think I'd prefer <shadowroot ...

Using <template ...> would make this much easier to polyfill. For example one could write a custom element which does the parent node stuffing and the syntax would look like <template is="shadow-root" shadow="...

I guess it will be easier to add "popping the element stack and the element is a shadow-setting template" steps to the HTML parser than to separate out the context-inheriting stuff of the template to reuse it for shadowroot.

If the idea is to avoid the adoption tree walk by creating the nodes in the target document then, yeah, not complicating template is better. The inoperativeness of template parsing (specifically not running script and custom elements) would be tricky to preserve.

hayatoito commented 7 years ago

I tend to agree @domenic. I am not personally convinced of the need here, but I don't oppose it.

What happens to ? Does it get removed?

I'd think it would just stay there, like template does. The shadow-attaching is a parse-time operation, it's a dead element afterwards.

If it would just stay there as a dead element, the memory usage would increase, doubled or maybe more than that.

Suppose the following html, using declarative Shadow DOM via <shadowroot>,

<custom-a>
  <shadowroot>
    <slot></slot>
    <div></div>
  </shadowroot>
  <custom-b>
    <shadowroot>
      <slot></slot>
      <custom-c>
         <shadowroot>
            <slot></slot>
            <div></div>
         </shadowroot>
         <div></div>      
      </custom-c>
    </shadowroot>
  </custom-b>
</custom-b>

In this case, the composed tree which the rendering engine actually holds would be:

custom-a
  ::shadow-root
     slot
     div
  shadowroot
    slot
    div
  custom-b
    ::shadow-root
      slot
      custom-c
         ::shadow-root
            slot
            div
         shadowroot
            slot
            div
         div
    shadowroot
      slot
      custom-c
         shadowroot
            slot
            div
         div

I would prefer to remove a <shadowroot> element from the light tree after parsing and attaching it as a shadow tree, to save memory.

This can be:

custom-a
  ::shadow-root
     slot
     div
  custom-b
    ::shadow-root
      slot
      custom-c
         ::shadow-root
            slot
            div
dominiccooney commented 7 years ago

I would prefer to remove ` (sic) element from the light tree after parsing, to save memory.

Removing it would also make feature detection easier.

It's probably not a big deal but implementations should be able to avoid creating the template element because you won't be able to observe its brief existence (unless it is a custom element.)

sebmarkbage commented 7 years ago

I'll echo @treshugart's point that, for React, the shadow DOM is only overhead and complexity, when the only thing we'd really want out of this is scoped CSS. If we can't have that, we could work with a shadow DOM too. I'm sure @wycats had thoughts on this too.

I would expect many trees to result in code like:

<div>
  <template attach-shadow shadow-mode="open">
    <div>
      <template attach-shadow shadow-mode="open">
        <div>
          <template attach-shadow shadow-mode="open">
            <div>
              <template attach-shadow shadow-mode="open">
                ...
              </template>
            </div>
          </template>
        </div>
      </template>
    </div>
  </template>
</div>

Since that's semantically what a modular composed tree would be like. We'd just treat the shadow tree as the new children tree. Anything else would be an optimization (which might need to bail out).

What would be very interesting, however, is if you could target a particular ID that was defined earlier to attach the shadow lazily later in the document stream.

<section>
  <h1>Heading</h1>
  <p id="content">Loading...</p>
   ...
</section>
...
<template attachto="content">
  Hello world!
</template>

Even then, I'd prefer it if it replaced the children with the content of template, but we could live with the workaround to use shadow DOM.

slightlyoff commented 7 years ago

Given the extraordinary lift that was required to get <template> done (we had to get changes into XML's parsing behavior, e.g.), a new element for this seems like a non-starter.

hayatoito commented 7 years ago

From the perspective of implementation difficulty, whether <template newattribute> or <shadowroot>-ish new element shouldn't be a big deal, I guess, though it still needs non-trivial efforts.

From the perspective of spec, that might be a big deal.

annevk commented 7 years ago

@treshugart I think that by definition declarative shadow trees would have to be somewhat new as shadow trees are new. You cannot expect it to map to appendChild() just like you cannot expect that for contents of <template>.

Now, having a separate kind of feature where there is some kind of boundary that ends up applying to CSS, but doesn't involve creation shadow trees, might be worthwhile, but I don't think it's a good idea to conflate that request with the topic of this issue: declarative shadow trees. There's not even an imperative API for what you're suggesting. I have seen the request before to decouple more and I can certainly appreciate that though I haven't seen a good enough solution yet. In any event, please open a separate issue to discuss that.

@hayatoito I think we'll hit about as much complexity either way and I suspect that overloading <template> this way will make it harder to add new features to it in the future.

domenic commented 7 years ago

@treshugart I apologize, and have removed the offending emoji. I'll step back from this thread.

tabatkins commented 7 years ago

I would prefer to remove a <shadowroot> element from the light tree after parsing and attaching it as a shadow tree, to save memory.

Sure, I don't have an opinion on this. If removing it makes more sense, let's remove it. That might end up being less confusing for authors too, since the element doesn't do anything if it's sitting there.

It's probably not a big deal but implementations should be able to avoid creating the template element because you won't be able to observe its brief existence (unless it is a custom element.)

Another reasonable argument for removing it.


I've had discussions about this with several outside of the WC community. Since they have their own component model (whether or not they should be using Custom Elements is besides the point), they don't need the DOM encapsulation, they just want the CSS aspect. If what their internals expect changes underneath them (their declared DOM changes from what they think it looks like) then it precludes them from declarative usage. This is a barrier to entry for them because they must modify their internals.

The current concept of CSS encapsulation is built on Shadow DOM. It can't be separated from it in a reasonable way; doing so would be a completely new, totally separate feature that would need its own justification. (And I can tell you right now, it would be a hard sell to justify having two totally different ways to encapsulate CSS.) I'm not willing to try and tie declarative shadow DOM to this sort of limitation. As Anne said, this should be addressed via a separate issue.

(I also, personally, am 100% on the "stop rolling your own component models, they're not interoperable and they lock users in, just use WC" train.)

treshugart commented 7 years ago

Thank you, @domenic, and just to be clear - for posterity - you didn't necessarily "offend" me and it's not just you. This in isolation wouldn't be an issue. Community involvement in this working group has been pulling teeth for many, and this is one of the many forms of negativity (even if it's small) towards it. All we want is for our extensive experience to carry weight in the discussions.

I won't let this detract from the rest of the discussion. I'm over the moon this is even being discussed right now.

hayatoito commented 7 years ago

Sure, I don't have an opinion on this. If removing it makes more sense, let's remove it. That might end up being less confusing for authors too, since the element doesn't do anything if it's sitting there.

Regarding, template overloading (<template newattribute>) vs new element (<shadowroot>-ish),

if we can agree on "don't leave <template> element in the light tree", I prefer a new element, <shadowroot> or something, here because:

Descendant nodes of declarative shadow dom are effectively NOT inert, from user's perspective. e.g.

  <custom-a>
    <template newattribute> (or <shadowroot>)
      <img src='...'>
      <script src='...'>
    </template>
  </custom-a>

In this case, <img> or <script> look inside of <template> element, but they would reside in the shadow tree, as the final result, after parsing html. We fetch resources for them. That sounds a big difference to me, as compared to usual <template> element's usage pattern. newattribute is too weak as a visual sign of this big different semantics.

In addition to that, as Dominic suggested, the engine (or parser) might want to optimize.

Instead of:

  1. Parse a template element (and its descendants), as usual
  2. Call attachShadow on the parent element of the template
  3. shadowRoot.appendChild(template.content);
  4. Remove template element from the light tree

We might want to optimize:

  1. Parser encounters opening <shadowroot>
  2. Call attachShadow on the parent element of the <shadowroot>
  3. Parser continues to parse descendants of <shadowroot>, and build a shadow tree directly
  4. Parser encounters a closing </shadowroot>.
  5. Parser continues to parse html, and continue to build a light tree

I am not sure how this approach is feasible, but that could be an option as a starter. If we choose this, there might be no longer a good reason to use <template>.

justinfagnani commented 7 years ago

I can't speak to how difficult it would be to get a new element in, but @hayatoito's description of optimized <shadowroot> sounds perfectly aligned to the use-case, and not even an optimization, how how it should just work.

tjmonsi commented 7 years ago

Hi everyone,

I am just new here but I would like to get some clarifications. Given that you need define the shadow root of a given custom-element, I am really confused on how the proposed way of defining it in a declarative way should be. All I am seeing right now is:

 <custom-a>
    <template newattribute> (or <shadowroot>)
      <img src='...'>
      <script src='...'>
    </template>
  </custom-a>

or

<custom-a>
  <shadowroot>
    <slot></slot>
    <div></div>
  </shadowroot>
  <custom-b>
    <shadowroot>
      <slot></slot>
      <custom-c>
         <shadowroot>
            <slot></slot>
            <div></div>
         </shadowroot>
         <div></div>      
      </custom-c>
    </shadowroot>
  </custom-b>
</custom-b>

or

<div-or-something>
  <template attach-shadow shadow-mode="open | closed">
    ...shadow content...
  </template>
</div-or-something>

But wouldn't this confuse us (or the browser) or when to just use <custom-a> or <custom-b> as a tag and when to add the shadow root definition of <custom-a>?

What if this happens while parsing the html document below?

<html>
<head>
</head>
<body>
<custom-a>
  <shadowroot>
     Hello <slot></slot>!
  </shadowroot>
</custom-a>

<custom-a>
  <shadowroot>
     Hi <slot></slot>!
  </shadowroot>
</custom-a>

<custom-a>
  World
</custom-a>
</body>
</html>

If we mixed the definition of a custom-element's shadow root in the light child of the custom-element, then would that mean we can replace it while parsing the html (which is good if you want to change the shadowroot), but would at least confuse when is the time the custom-element is being used and when is the time the custom-element is being defined.

Would this be a better case?

<html>
<head>
</head>
<body>

<!-- usage phase -->
<custom-a>
  World
</custom-a>

<!-- definition phase -->
<shadowroot tag="custom-a">
  Hello <slot></slot>!
</shadowroot>

<!-- or -->
<template shadowroot tag="custom-a">
  Hi <slot></slot>!  
</template>

</body>
</html>

At least it defines when a custom-element is being used (when it is really being used as a tag) and when it is being defined (when you explicitly want to attach a shadow dom to a particular custom-element by using a defined tag like shadowroot or extend the template tag)

Just my thoughts.

robdodson commented 7 years ago

@tjmonsi I don't think anyone is proposing declarative syntax for defining a custom element, that may be where your confusion stems from. Today the only way to define a custom element is to call customElements.define(). They're discussing how to represent an element (and its shadow root) after it has already been defined. This would benefit use cases where you want to server side render elements with shadow roots. This would benefit things like bots/crawlers who may not run JavaScript and would have no other way of knowing what content lives inside of a <my-app> element.

tjmonsi commented 7 years ago

@robdodson Ah I see, so it means that we can now write a renderer that renders an output of a computed shadowRoot on the server and write it as a child of a custom element with a clearly defined tag (either shadowroot or template) so that it tells the browser that this how it should render the a particular custom-element at that moment (and once JS kicks in, it will eventually update the shadowRoot of that custom-element when the data also updates).

So now I get it when this happens

<html>
<head>
</head>
<body>
<custom-a>
  <shadowroot>
     Hello <slot></slot>!
  </shadowroot>
  World
</custom-a>

<custom-a>
  <shadowroot>
     Hi <slot></slot>!
  </shadowroot>
  TJ
</custom-a>
...

Because it will pretty much look like

<custom-a>
  #shadow-root (open)
     Hello <slot></slot>!
  World
</custom-a>

<custom-a>
  #shadow-root (open)
     Hi <slot></slot>!
  TJ
</custom-a>
hayatoito commented 7 years ago

If we can agree on using <shadowroot> (or whatever new element), what is the next action for us?

  1. Is it okay to introduce a new element name into HTML, without worrying about conflicts in wild?
  2. Who can confirm that it is feasible to update HTML Parser in HTML Standard in such a way? I think there is very few people who can update this part of HTML Standard. I guess we need more concrete proposal before that.
annevk commented 7 years ago
  1. No, we have to worry about conflicts as we'd like to maintain compatibility with existing content: https://www.w3.org/TR/html-design-principles/#support-existing-content.
  2. Folks from @whatwg/html-parser.
hayatoito commented 7 years ago

Thanks. Regarding 1, I checked the usage of <shadowroot> via httparchive. The usage is zero, as of now.

The query is:

 SELECT page, count(*)
 FROM [httparchive:har.2017_09_01_chrome_requests_bodies]
 WHERE REGEXP_MATCH(body, r'<shadowroot>')
 GROUP BY page
annevk commented 7 years ago

I think we should move this to whatwg/html at this point by the way. Everything proposed thus far (apart from CSS encapsulation without DOM encapsulation which there's no clear proposal for as of yet) requires changes to HTML, and I don't think would require changes to DOM at all.

inikulin commented 7 years ago

Regarding 2: I'm not quite familiar with Shadow DOM spec, so can someone clarify this for me, please: should <shadowroot> inherit parent element's parsing context? E.g. if I have a <table> element can I create a shadow root that will contain some rows of this table?

annevk commented 7 years ago

@inikulin I think that's what should happen. That's also how shadowRoot.innerHTML works (to be defined still: https://github.com/w3c/DOM-Parsing/issues/21). It gets the context element from the shadow host (<table> in your example).

inikulin commented 7 years ago

@annevk Thanks for the clarification. And another question: does any element can contain <shadowroot>, e.g. can <html>, <input> or <textarea> has a shadow root inside it?

matthewp commented 7 years ago

@inikulin No, the list of builtin elements that can have a shadow is here: https://dom.spec.whatwg.org/#dom-element-attachshadow

annevk commented 7 years ago

Ah indeed, that's probably why we'll need an issue against DOM anyway. We need to share that list somehow and make sure that whenever we add elements to it that's reflected on both sides.

inikulin commented 7 years ago

Since the list of allowed contexts doesn't contain any elements that require parser insertion mode adjustment it seems like we don't need <template> machinery for the <shadowroot> at all. From the first sight we just need to add amendments to node insertions logic, like we have for foster parenting at the moment: https://html.spec.whatwg.org/multipage/parsing.html#foster-parent, make <shadowroot> a scoping element, move to it to special category and maintain a stack of insertion points for nested roots.

annevk commented 7 years ago

Note that there's proposed additions for attachShadow() over at https://github.com/w3c/webcomponents/issues/511#issuecomment-224582561 though there hasn't been much interest in that lately. (And I think all those elements are okay from an insertion mode perspective too.)

inikulin commented 7 years ago

@annevk

And I think all those elements are okay from an insertion mode perspective too

Yes, for <a> we'll need to insert marker into active formatting element list once we encounter <shadowroot> to scope formatting element list reconstruction and adoption agency algorithms (like we do for <template>). Seems like everything else should work without additional modifications.

matthewp commented 7 years ago

@tabatkins

Does appendChild(document.createElement('shadow-root')) work?

No, this is a parse-time operation. Post-parsing, the shadowroot element is dead, identical to a plain template. If you have JS, just use attachShadow(). ^_^

So what should appendChild(document.createElement("shadowroot")) do? Throw?

inikulin commented 7 years ago

@matthewp I believe it should behave consistently with other cases when elements appended in locations that are not valid during parse phase: just attach an element without any additional machinery (e.g. inserting <div> in table).

tabatkins commented 7 years ago

Yeah, that's what I'd expect.

tjmonsi commented 7 years ago

Who can start opening this to whatwg/html?

hayatoito commented 7 years ago

Can I double-check an expected behavior?

// Construct a dom tree in an imperative way
const p = document.createElement('div');
const s = document.createElement('shadowroot');
p.appendChild(s);
assert(p.hasChildNodes());
assert(!p.shadowRoot);

// Then, we *serialize* and *deserialize* |p|.
const inner_html = p.innerHTML;
assert(inner_html === '<shadowroot></shadowroot>)';
p.innerHTML = inner_html;

// Now, |p| changed.
assert(!p.hasChildNodes());
assert(p.shadowRoot);  // Assuming shadow root's mode is "open" here.

Can I assume that this is an acceptable behavior?

inikulin commented 7 years ago

@hayatoito looks legit.