1

In outline, the DOM spec defines the concept of shadow trees as follows:

  • A shadow tree is a node tree that is distinct from the current document one (light tree).
  • Its root (shadow root) is connected with the light tree node by means of host reference - this is not the same as the descendants/ancestor relation in terms of DOM tree.
  • Moreover, the shadow tree can have slot nodes and they, in turn, can be connected with light tree nodes (slottables) through the assigned nodes relation (yet again it's considered distinguishable from the DOM tree structure).

I find this picture a bit involved. It seems we in effect have not two different trees (light and shadow) but all three. Actually, a user agent renders another one which named flattened tree. In this tree the shadow root is a child of its host and slots are ancestors of their slottables:

enter image description here

One may argue flattened tree is just a "virtual idea". However, it is used in CSS and event listening and probably this fact bears out its usefullness.

Loosely, the shadow tree is treated as the shadow host’s contents instead of its normal light tree contents. However, some of its light tree children can be "pulled into" the shadow tree by assigning them to slots. This causes them to be treated as children of the slot for CSS purposes. (CSS non-normative explanation)

Are there any reasons to introduce the concepts of shadow trees and especially slot assigned nodes when the state of matter can be described with a single flattened tree where nodes are connected with well-known descendants/ancestor references instead of having the peculiar reference named "assigned nodes"? Of course, if we crave for incapsulation, API functions like findElementById() would remain the "shadow" subtrees of the flattened tree intact.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Ilya Loskutov
  • 1,967
  • 2
  • 20
  • 34
  • Are you asking why the HTML standards committee made a design decision? You would have to ask them. – Robert Harvey Apr 20 '21 at 14:31
  • @Robert Harvey Maybe there are well-known reasons I'm not aware of – Ilya Loskutov Apr 20 '21 at 14:33
  • [Nodes within the shadow tree are not affected by anything applied outside the shadow tree, and vice versa. This provides a way to encapsulate implementation details, which is especially useful for custom elements and other advanced design paradigms.](https://developer.mozilla.org/en-US/docs/Glossary/Shadow_tree#content:~:text=Nodes%20within%20the%20shadow%20tree%20are,elements%20and%20other%20advanced%20design%20paradigms.) – Robert Harvey Apr 20 '21 at 14:41
  • `where nodes are connected with well-known descendants/ancestor references` -- That doesn't sound like a "shadow tree." References implies that changes to your data structure would affect the original tree. – Robert Harvey Apr 20 '21 at 14:46
  • _"However, it [Flattened DOM] is used in CSS and event listening"_ Can you explain that further? There is only lightDOM or shadowDOM. Slotted/assigned nodes are **reflected** from lightDOM to shadowDOM. Both are explained in detail [in this answer](https://stackoverflow.com/questions/61626493/slotted-css-selector-for-nested-children-in-shadowdom-slot/61631668#61631668) – Danny '365CSI' Engelman Apr 20 '21 at 15:27

1 Answers1

0

TL;DR Flattened trees are an implementation detail.

Consider this code snippet

<html>
    <body>
        <style>
            my-element {
                font-style: italic;
                color: green;
            }

            p {
                color: darkorange;
            }
        </style>
        <p>Document Para</p>
        <my-element>
            <p>Light Child Para</p>
        </my-element>
    </body>
    <script>
        customElements.define('my-element', class extends HTMLElement {
            constructor() {
                super();
                this.attachShadow({mode: 'open'}).innerHTML =
                `
                    <p>Shadow Para</p>
                    <slot></slot>
                    <style>
                        :host > p {
                            color: brown;
                        }
                        slot {
                            color: blue;
                        }
                    </style>
                `;
            }
        });
        const shadowHost = document.querySelector('my-element');
        const shadowRoot = shadowHost.shadowRoot;
        const shadowPara = shadowRoot.querySelector('p');
        const slot = shadowRoot.querySelector('slot');
        const lightPara = document.querySelector('my-element > p');
        console.log(`Is Shadow Host the shadow Para's parent? ${shadowPara.parentNode === shadowHost}`);
        console.log(`Is Slot the Light Child Para's parent?${lightPara.parentNode === slot}`);
    </script>
</html>

Consider the styling consideration for the elements inside the Shadow DOM. Though the main document cannot style the <p> element directly, inherited properties (font-style and color in this case) from the Shadow Host (<my-element> in this case) are inherited by the Shadow tree elements. Looking at the cascade for the <p> element inside the shadow tree

enter image description here

The rule with the selector :host > p treats the <p> elements as a direct descendant of the Shadow Host (my-element). The operative word is treats. The <p> elements is not a descendant in the DOM tree. Check the output in the console due to this code in the script

console.log(`Is Shadow Host the shadow Para's parent? ${shadowPara.parentNode === shadowHost}`);

Also note that the inherited properties of fonty-style and color are considered in the cascade for <p>.

Same is the case with <slot> elements. Though the actual <slot> element is a direct descendent of the Shadow Root, the slotted elements (the <p>Light Child</p> in this case) is not a descendant of the Shadow Root. Look at the cascade for the slotted element.

enter image description here

The element picks up the specified value for color from the main document's style sheet but it also has the color:blue property in the cascade too which is coming via the style on the <slot> element. Again, the inherited property is treated as coming via the parent <slot> even though the <slot> element is not the parent of the light Para in the actual DOM.

console.log(`Is Slot the Light Child Para's parent?${lightPara.parentNode === slot}`);
  • The flattened tree, as you've rightly mentioned, is a construct used in the CSS and event engine of the browser, as a representation to model the parent-child relationship of styles on nodes but it doesn't help with understanding the DOM tree relations amongst the elements.

  • The slot assignment would need to be figured out inevitably as the elements from the Light DOM don't migrate physically into the Shadow DOM, only references to these elements are held within the Shadow DOM's <slot> elements.

  • The Flattened tree is definitely a good visualization to understand the styling part but would be hardly helpful to have it as the replacement for existing DOM constructs like HTMLSlotElement.assignedNodes() etc.

Also, the spec duly points out this fact by mentioning (emphasis mine)

Loosely, the shadow tree is treated as the shadow host’s contents instead of its normal light tree contents. However, some of its light tree children can be "pulled into" the shadow tree by assigning them to slots. This causes them to be treated as children of the slot for CSS purposes.

... not for DOM purposes.

Zoso
  • 3,273
  • 1
  • 16
  • 27