whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
8.18k stars 2.71k forks source link

Element reflection and shadow roots #5401

Open alice opened 4 years ago

alice commented 4 years ago

What is the relationship between Element type IDL attributes and Shadow DOM encapsulation?

(Previous general discussion, and concepts and terminology)

For example, suppose an author wants to set an aria-activedescendant-associated element, descendant, on an element host.

host.ariaActiveDescendantElement = descendant;

To what extent, for what reasons, and at what cost, should the user agent prevent the author from setting this relationship if descendant is, say, closed-shadow-hidden[1] from host?

Why prevent referring to closed-shadow-hidden elements?

In general, shadow DOM is intended to hide implementation details in order to prevent authors depending on those details, and thus causing things to break if those implementation details change.

From https://gist.github.com/alice/174ae481dacdae9c934e3ecb2f752ccb:

  • May cause problems if scripts "accidentally" walk into deeper shadow content via known properties e.g.

    // lightEl.ariaActiveDescendantElement was set by the component to be 
    // an element within the component's shadow DOM
    lightEl.ariaActiveDescendantElement.textContent = "new text";
    
    // ** Now it is possible to access the entire shadow tree for the component! **
    lightEl.ariaActiveDescendantElement
           .parent
           .appendChild(document.createTextNode("🆕"));
    • (Note that extension scripts often already have access to Shadow DOM in any case; the concern here would be page scripts.)

This roughly corresponds to "type 1 encapsulation", as described by @othermaciej and @annevk: "Encapsulation against accidental exposure — DOM nodes from the shadow tree are not leaked via pre-existing generic APIs — for example, events flowing out of a shadow tree don't expose shadow nodes as the event target."

The caveat is that in this case, an author would have to have explicitly set the ariaLabelledByElement property to be a closed-shadow-hidden element; that is, the author would have to already have access to those nodes, rather than the nodes being leaked with no action from the author.

However, the author did not deliberately provide access to every element inside shadow DOM.

  • Provides a surface for developers to "hack" their way into depending on implementation details of components, if components expose elements within shadow DOM, e.g.
     // In this method, component sets `for` on lightDOMElement 
     // to an <input> inside Shadow DOM.
     // The author uses this to get access to elements inside Shadow DOM,
     // creating an implicit dependency on Shadow DOM internals.
     component.setLabel(lightDOMLabelElement);
     lightDOMLabelElement.for.style.backgroundColor = "papayawhip";
     lightDOMLabelElement.for = null;

This corresponds to "type 2 encapsulation" in the same framework: "Encapsulation against deliberate access — no API is provided which lets code outside the component poke at the shadow DOM. Only internals that the component chooses to expose are exposed."

The same caveat from above applies.

  • May be cited as a precedent for other APIs which may also be construed as weakening Shadow DOM encapsulation.

This might be called the "rule of inference" problem: the potential to open the door to other APIs which allow access to closed-shadow-hidden elements.

Possible solutions and trade-offs

0. Do nothing/authoring advice

Advise authors against setting closed-shadow-hidden elements as attr-associated elements for elements in light DOM, but do nothing to prevent them.

Optional (0.1): allow (but do not require) using opaque references in place of element references. This allows authors to preserve their own encapsulation in the case where they wish to make a semantic connection between two elements where the connection would otherwise leak implementation details. (Obviously, this requires setting up the spec and implementation machinery for opaque references.)

1. Allow access but prevent tree walking

Allow setting references to closed-shadow-hidden elements, but prevent using them to access the rest of the shadow tree.

This might look something like: at get time, check whether the attr-associated element is closed-shadow-hidden relative to the host element, and if so, do not return it (i.e. return null instead).

It might make sense to return something like an opaque reference instead, to avoid confusion as to whether a reference has been set.

lightEl.ariaActiveDescendantElement = shadowHiddenElement;
assertEquals(lightEl.ariaActiveDescendantElement, 
             document.getOpaqueReference(shadowHiddenElement));

Variations:

2. Check on setting

Fail silently by setting the reference to null if the given value is not a descendant of any of the host element's shadow-including ancestors. (This is what is in the current spec PR.)

lightEl.ariaActiveDescendantElement = shadowHiddenElement;
assertEquals(lightEl.ariaActiveDescendantElement, null);

This does not prevent authors from removing elements from the light DOM and re-adding them into shadow DOM, however.

lightEl.ariaActiveDescendantElement = lightSibling;
lightEl.shadowRoot.appendChild(lightSibling)
assertEquals(lightEl.ariaActiveDescendantElement, lightSibling);

3. Check on getting

This is similar to (1), but also affects the computed attr-associated element, impacting accessibility APIs etc.

That being the case, this runs into issues since some of those APIs need timely updates when attr-associated elements change (for example, setting aria-activedescendant is equivalent to a focus change for some API consumers) so if an element were to be re-parented causing the association to be effectively lost, there would not be a timely update since there is no "get" in that case.

4. Check on setting and during the "adopt node" algorithm

In addition to preventing attr-associations being created where the attr-associated element is closed-shadow-hidden relative to the host node, add steps to the "adopt a node" algorithm to check that for each attr-association an element is participating in (either a host or as an attr-associated element), the attr-associated element isn't currently closed-shadow-hidden relative to the host.

This potentially involves a significant run-time cost, as checking whether one element is closed shadow-hidden relative to another can involve an ancestor walk.

5. Check on setting, and disallow references to elements which are not connected

This would effectively prevent references being created to closed-shadow-hidden elements, but at the cost of not being able to create associations to elements which are not yet inserted into the DOM, which was one of the requirements described by @zcorpan at TPAC.


[1] The same logic probably applies to open shadow roots, in general, but there doesn't seem to be a named concept for "open shadow hidden".

annevk commented 4 years ago

Well, appendChild() calls the internal adopt algorithm. If you're thinking of changes to adopt, that's likely where you'd make them, no?

alice commented 4 years ago

@annevk I'm not sure I follow. Could you elaborate?

annevk commented 4 years ago

It sounded like you were considering making changes to adoptNode(). Typically any such changes would be made in https://dom.spec.whatwg.org/#concept-node-adopt, which also impacts tree mutation operations.

alice commented 4 years ago

Thanks for the pointer.

It seems like this would be an extra step in the "If document is not oldDocument" branch.

I take the point that we could add a check for the shadow scope (is there a better term than this? I realise it's not spec language) at that point as well, but I'm still not convinced of the value of doing that as compared to the potential runtime cost of recursively checking the relative shadow scope every time a node is inserted in the document.

I would rather caution authors against breaking their own encapsulation, and give them an option to safely refer into deeper shadow scopes without leaking implementation details.

annevk commented 4 years ago

Do you mean shadow tree? https://dom.spec.whatwg.org/#shadow-trees has all the relevant terminology.

alice commented 4 years ago

Seems likely, thanks.

Any thoughts on my other comments?

annevk commented 4 years ago

At a high level, I don't think we should increase the number of differences between adoptNode() and appendChild(). And I think as rniwa and I argued before, leaking through expandos is very different from a platform API leaking. (Having said that, I'm having a bit of trouble interpreting what you wrote due to the non-standard terminology.)

alice commented 4 years ago

Could you clarify what part you're having trouble with, and I can try and re-state it?

alice commented 4 years ago

Regarding the difference with expandos - granted, but the comparison is with authors having the ability to create leaks, as opposed to leaks arising from normal usage like the case with event paths.

alice commented 4 years ago

I re-worked the issue description to present the alternatives I can see, and some sense of the trade-offs involved. Hope that helps.

othermaciej commented 4 years ago

This issue enumerates solutions without directly explaining what the problem is. Why does code without access to the Shadow DOM need to see or manipulate an ariaActiveDescendantElement that points to an element inside the Shadow DOM? Which solutions make sense would depend on the problem to be solved.

Maybe it's meant to be obvious why authors would want to do such a thing but it isn't obvious to me. I would expect libraries that use Shadow DOM for encapsulation to manage ariaActiveDescendant themselves. Or maybe it's just that there needs to be some defined behavio for this caser, without it mattering much what it is. Or perhaps this issue is assuming context that I don't have. (Reading it only because I was @-referenced).

alice commented 4 years ago

@othermaciej Apologies for summoning you, and thank you for reading!

Why does code without access to the Shadow DOM need to see or manipulate an ariaActiveDescendantElement that points to an element inside the Shadow DOM?

It's more about defining what should happen, as you suggest later in your comment. The "typical" case is more likely not to involve crossing shadow roots, but unless we explicitly define what should happen in that case, we end up with the situation described in option 0. (And maybe that's fine, if we don't expect authors to actually end up doing this.)

I would expect libraries that use Shadow DOM for encapsulation to manage ariaActiveDescendant themselves.

I assume you mean via the ElementInternals object? Agreed, that would be the more likely situation.

bkardell commented 4 years ago

[Snip] I would expect libraries that use Shadow DOM for encapsulation to manage ariaActiveDescendant themselves. Or maybe it's just that there needs to be some defined behavio for this caser, without it mattering much what it is.

You would expect them to manage all of these things themselves, yes.

Also, it seems like generally a bad idea to expose any reference (opaque seems fine and kind of consistent with some other parts of the platform), yes.

But that doesn't mean someone won't design something like that. I think is the idea - the same way they could with any other node reference on a closed shadow root today. But if you do that it's kind of on you, you violated your own stated desire for encapsulation, and we don't explicitly prevent it.

So the question is, I think, what to do with when someone in the Shadow connects some ref to something in the light Dom or used a leaked ref to a node as a reference for one of these. Something defined, presumably, but what? I think option 0 or .1 make sense from this perspective.

alice commented 4 years ago

This is a really critical feature for accessibility of Web Components, and this is the last remaining issue to sort out before we have a design we can actually implement and ship. How can we get this discussion moving somewhere productive?

annevk commented 4 years ago

It seems the opaque reference discussion in #4925 got quite far. Any reason that isn't being fleshed out more? Did it stall on something?

alice commented 4 years ago

The conversation went in a different direction and we didn't pick it up again.

Would that be your preference?

annevk commented 4 years ago

I'm not sure I have a strong sense of the right solution here. All seem to have drawbacks of some kind. The other solution that seemed somewhat interesting to me is to continue with ID-based references, but making it easier to mint them.

alice commented 4 years ago

Do you have a specific proposal for an ID-based API? Note that an ID-based API wouldn't allow crossing Shadow boundaries in any direction, including to "lighter" DOM.

annevk commented 4 years ago

Very briefly, I was thinking something like elementA.idRef(elementB, { scope: "into-the-light" }) which gives you id-ref="into-the-light #uuid-or-some-such or some such on elementA and id="uuid-or-some-such" on elementB (or it reuses the existing id="" attribute on elementB, if any). Depending on the exact choices you'd make with such a design there would also be shortcomings of course, but all the lifetime issues go away.

alice commented 4 years ago

Thanks for that.

Might you be able to spell out the specific issues you see with the other options, as well?

annevk commented 4 years ago

0) Violates encapsulation. 1) Unclear, seems interesting and I think the opaque references are worth flushing out. 2) Violates encapsulation. Also brittle. 3) Not sure. 4) If the runtime cost is real I suspect this wouldn't be acceptable to people. 5) This seems very surprising for web developers.

alice commented 4 years ago

We disagree on what "violates encapsulation" means. Can you possibly spell out the consequences of allowing authors to break their own encapsulation?

annevk commented 4 years ago

A single developer might know what they are doing, but when multiple developers and libraries are involved, encapsulation is good to have. Making private state globally accessible isn't a good idea.

alice commented 4 years ago

I agree that making private state globally accessible isn't a good idea, but even option 0 doesn't do that without a developer explicitly giving access. Do you have a scenario in mind under which that would happen?

alice commented 4 years ago

To answer my own question: @hober came up with a plausible (but improbable) scenario.

Alice sets up an ariaLabelledByElement relationship between inputA and labelB.

Bob's library, "Boomerang", moves labelB into a closed Shadow Root.

Carol's extension, "Label Embiggener", walks from inputA to labelB, and adds a style element adjacent to labelB to apply a custom style to the label.

alice commented 4 years ago

I was discussing Declarative Shadow DOM with @justinfagnani and we noted that programmatic associations are, like Shadow DOM, not serializable.

We also noted that IDREFs were designed with the assumption that an ID would be globally unique and globally applicable, and Shadow Roots broke that assumption, and thus any features which depend on it.

One "simple" option might be to re-introduce a concept of globally unique IDs, as a separate property which may be used in an IDREF lookup, but not used with document.getElementById().

This would have similar properties to a hypothetical OpaqueReference, but would be explicitly string based, and based on an actual content attribute.

Here's a sketch of what that might look like:

<custom-combobox>
  #shadow-root
  |  <input>
  |  <slot></slot>
  <custom-optionlist>
    <x-option id="opt1">Option 1</x-option>
    <x-option id="opt2">Option 2</x-option>
    <x-option id='opt3'>Option 3</x-option>
 </custom-optionlist>
</custom-combobox>
const input = comboBox.shadowRoot.querySelector("input");
const optionList = comboBox.querySelector("custom-optionlist");
input.activeDescendant = optionList.firstChild.globalID;

with the resulting HTML:

<custom-combobox>
  #shadow-root
  |  <input aria-activedescendant="bei8ishu">
  |  <slot></slot>
  <custom-optionlist>
    <x-option id="opt1" globalid="bei8ishu">Option 1</x-option>
    <x-option id="opt2">Option 2</x-option>
    <x-option id='opt3'>Option 3</x-option>
 </custom-optionlist>
</custom-combobox>

The globalID accessor would generate a globally unique ID if a value had not already been assigned to the globalid content attribute.

An element's ID would need to be defined in terms of its local ID (via the id attribute)) and its global ID (via the globalid attribute), with some advice about which takes precedence. @justinfagnani suggested that the local ID should take precedence, based on the same logic as CSS precedence.

Similar to id, if an authoring error had occurred such that globalid was not actually globally unique on the page, an IDREF referring to that string would get a reference to the first element with a matching globalid attribute.

This does entail having attributes "sprout" on the related element, but at least allows referring in any direction across shadow roots without risking leaking internals.

(Edit) One potential downside is that by causing unique ID attributes to "sprout" on light DOM nodes, the existence of Shadow DOM may be inferred.

@annevk this is vaguely close to what you were proposing - any thoughts?

annevk commented 4 years ago

Why can we not keep using the id attribute? It seems the problematic aspect is on the side making the reference, not the side being referenced. (See also https://w3ctag.github.io/design-principles/#attributes-like-data with regards to the getter.)

justinfagnani commented 4 years ago

I think the reason is that the side making the reference needs to know whether it should look in the local scope (id) or the global scope (globalid). I don't think it could just ignore scope boundaries and still look up by id. Alice and I discussed whether there could be a sigil that marks an id as unscoped, either on the referencing side or referenced side, but given that ids can contain any character, that doesn't seem feasible.

Regarding getters, we could have a getGlobalId() method instead that could induce the attribute, or creating the attribute could be left up to the caller with the option of using the value of getGlobalId(). I believe this is what Internet Explorer did with the uniqueId property. It didn't set an attribute, but you could use it to populate `id.

alice commented 4 years ago

id is not intended to be globally unique (any more), so it would require some extra indirection to describe the scope of where to perform the id lookup. With globalid, the scope is explicitly global.

I could easily imagine wanting an element to have a local id for the purposes of logic within the Custom Element (including CSS), and a globalid for references across shadow boundaries.

annevk commented 4 years ago

Why would you want both id and globalid if id were made to work for the latter purpose? (FWIW, it's not clear to me we redefined ID and IDs are at least not allowed to have whitespace in them and some syntaxes make use of that.)

Also, even if we somehow could not use whitespace, it seems you could encode such information in the referencing attribute name too.

alice commented 4 years ago

So your proposal is to add the indirection I referred to? i.e. some type of syntax to explain hopping out of shadow roots as necessary?

I take it moving into shadow roots would be impossible in this scenario?

annevk commented 4 years ago

Yeah, see https://github.com/whatwg/html/issues/5401#issuecomment-623851897.

atanassov commented 4 years ago

@annevk if I understand correctly, you're advocating for basic relationship between elements to be expressed as an Element-to-id relation vs Element-to-Element. If this is the case, does your proposed syntax in https://github.com/whatwg/html/issues/5401#issuecomment-623851897 assume that both elements are known in advance? ... or not?

hober commented 4 years ago

1. Allow access but prevent tree walking

Allow setting references to closed-shadow-hidden elements, but prevent using them to access the rest of the shadow tree.

This might look something like: at get time, check whether the attr-associated element is closed-shadow-hidden relative to the host element, and if so, do not return it (i.e. return null instead).

This seems to me like the simplest solution that preserves encapsulation. I think I'd rather we not block this on coming up with a globalid feature.

annevk commented 4 years ago

@atanassov I'm not sure I understand the question.

atanassov commented 4 years ago

@atanassov I'm not sure I understand the question.

Ah, my bad for omitting some of the background in my question!

Reading through a related PR discussion https://github.com/whatwg/html/pull/3917#issuecomment-529013132 and considering that id-refs express element relationship through the notion of id belonging to the first matched element (element-to-id relationship) as opposed to a more direct element reference (element-to-element relationship).

In the micro syntax you suggested, id-refs are minted by:

which gives you id-ref="into-the-light #uuid-or-some-such or some such on elementA and id="uuid-or-some-such" on elementB (or it reuses the existing id="" attribute on elementB, if any).

That lead me to believe that in the absence of id attribute on elementB, a uuid-or-some-such is minted for elementB and is used as the id-ref. Would that make the relationship more like element-to-element because of the id-ref uniqueness?

annevk commented 4 years ago

I see, I should probably have left the "syntax sugar" out. That the user agent assists in generating IDs is only a convenience to make linking easier. A web developer could mint them themselves and it would work the same way.

alice commented 4 years ago

Summary of what I think was the consensus from recent AOM discussions:

https://gist.github.com/alice/5b755f7f4487fa614d9c70cdf82259fa

@rniwa does this match your recollection?

rniwa commented 4 years ago

Summary of what I think was the consensus from recent AOM discussions:

https://gist.github.com/alice/5b755f7f4487fa614d9c70cdf82259fa

@rniwa does this match your recollection?

Thanks for the summary! I'm not sure which part of that is something of which we've reached a consensus but it does seem to capture the latest proposal / discussions we've had.

JanMiksovsky commented 4 years ago

We discussed this at yesterday's web components meeting.

In general, the group was keen to avoid putting component authors in a situation where they would need to have a component leak a reference to an inner shadow element in pursuit of accessibility.

To ground the discussion, we looked at a concrete use case of a combo box component with an inner list component whose shadow contains the list items of interest to the combo box.

<combo-box>
  #shadow-root
    <input aria-activedescendant=(how to point to an option inside option-list???)> 
    <option-list>
      #shadow-root
        <option id="option1">1</option>
        <option id="option2">2</option>
        <option id="option3">3</option>
    </option-list>
</combo-box>

We could ask the option-list component to expose a reference to one of the options hiding in its shadow — option-list would expose a activeItem property, say — so that combo-box can hand that element reference to the input. But this violates encapsulation. Even if option-list is willing to have its shadow open, it still feels odd that the option-list author must expose this public API to return the active item. Worse, the public API is proprietary; a different list component might expose a different API for the same purpose, making it challenging for the combo-box author to try different list components.

We could address these problems by introducing a layer of indirection through delegation: 1) the input indicates that it wants to get its activedescendant from the option-list component and 2) the option-list indicates that, if it is being used as an activedescendant, it wants to delegate that responsibility to an inner element. Step #1 could be accomplished via existing aria- attributes or element references. Step #2 would be accomplished through a TBD mechanism, likely elementInternals.

<combo-box>
  #shadow-root
    <input aria-activedescendant="optionList"> 
    <option-list id="optionList">
        .elementInternals.elementToUseAsActiveDescendant = option1   // set programmatically
      #shadow-root
        <option id="option1">1</option>
        <option id="option2">2</option>
        <option id="option3">3</option>
    </option-list>
</combo-box>

The goal of this approach would be to let option-list participate in the input's accessibility as an intermediary, without having to expose a reference to an element in its shadow. Additionally, the API which option-list supports would be a standard one, so the author of combo-box could swap in a different list component and get the same results.

Another use case we discussed was having a component host serve as a label for another element — but the host would then delegate its label responsibility to an inner shadow element which is not publicly disclosed.

There was general interest in this approach. @jcsteh, @shleewhite, and I expressed willingness to pull together some more use cases, then work towards a concrete proposal. If that gains traction, we could obviate the need to deal with references to elements in closed shadow trees.

dbatiste commented 4 years ago

I am also willing to contribute to concrete use-cases - this is an issue we've run into many times over the recent years.

While the aria-activedescendant case is denoted as the classic case above, it's actually one that we don't run into that often. Labelling via aria-labelledby where either one or both the labelling element or element being labelled is within a different DOM context is probably the most common case we run into.

Another very common case we run into is using aria-describedby to describe an element by a tooltip component, again, where either element may be in a different DOM context.

A more complex use-case that I described in the F2F (in a more simplified way) relates to the use of aria-controls and aria-labelledby for wiring up tabs and tab panels, where the tabs may be rendered within a tab-list component's shadow DOM, with the panels defined in the light DOM. This case is somewhat different from the others, in that it relates many tabs to many panels across a single shadow boundary.

While I prefer a solution that preserves encapsulation, as a component author I am putting our users first, which unfortunately means doing whatever is necessary to make the component accessible, encapsulation aside. In some cases, this may mean looking for work-arounds that may compromise the component API.

So, I am happy to share more details or contribute further to that discussion. Feel free to include me. 😄

dot-miniscule commented 4 years ago

Thanks for that summary, Jan!

I'd like to clarify the behaviour around moving explicitly set element references around the document/scope. "Valid scope" for two elements, A→B, is currently understood to mean:

  1. Both A and B are in the same document.
  2. B is in a "lighter" DOM (B is a descendant of a shadow including ancestor of A)
  3. Neither A nor B is in a document (this allows for authors to set and check these relationships before inserting either element into the DOM).

"Invalid scope" for two elements A→B:

  1. B is not in the document.
  2. B is in a "darker" shadow root.

The group seemed in favour of keeping these relationships alive even as elements move between scopes, as long as at the time of getting, the relationship is valid.

(apologies for the contrived/weird example).

<div id="container">
<input id="inputElement"></input> 
<option id="opt1">Option 1</option>

 #shadow-root (open)
  |  <slot></slot>  
</div>
// Same document/scope
inputElement.ariaActiveDescendantElement = option1;
console.log(inputElement.ariaActiveDescendantElement);           // logs <Option1>
console.log(inputElement.getAttribute("aria-activedescendant");  // logs "option1"

// Referred to element gets adopted into deeper shadow root.
// The relationship still exists, but is not observable/gettable.
shadowRoot.appendChild(option1);
console.log(inputElement.ariaActiveDescendantElement);           // logs null

// Content attribute is synchronised on setting only
console.log(inputElement.getAttribute("aria-activedescendant");  // logs "option1"

// Re-adopt the referred to element back into light DOM/the same scope.
// The relationship does not need to be re-established.
optionList.appendChild(option1);
console.log(inputElement.ariaActiveDescendantElement);           // logs <Option1>
console.log(inputElement.getAttribute("aria-activedescendant");  // logs "option1"
</script>

The explicitly set element association still exists when the referred to element passes through an "invalid" state, it is just not allowed to be the attr-associated element (the relationship exists, but is not observable).

The same rules would apply for if the referred to element is not in the same document. If it has been explicitly set via element.attrElement, when B moves out of the document, the relationship persists, it just may not be observable, and hence querying either the IDL attr will return null.

At the moment the spec only synchronises the content attribute with the IDL attribute when the relationship is set, so when the relationship passes through an invalid state it would actually return the id "option1" but that's somewhat inconsistent with the behaviour Jan outlined in this comment on #4925.

rniwa commented 4 years ago

Thanks for that summary, Jan!

I'd like to clarify the behaviour around moving explicitly set element references around the document/scope. "Valid scope" for two elements, A→B, is currently understood to mean:

  1. Both A and B are in the same document.
  2. B is in a "lighter" DOM (B is a descendant of a shadow including ancestor of A)
  3. Neither A nor B is in a document (this allows for authors to set and check these relationships before inserting either element into the DOM).

"Invalid scope" for two elements A→B:

  1. B is not in the document.
  2. B is in a "darker" shadow root.

The group seemed in favour of keeping these relationships alive even as elements move between scopes, as long as at the time of getting, the relationship is valid.

That is not my understanding of the discussion. I'm pretty certain we were in favor of keeping the relationship when B is not in a document (1) but wanted to severe the relationship when B moves into an inner / deeper shadow root (2) the same way we don't want to keep the relationship when B is inserted into another document.

alice commented 4 years ago

@rniwa Do you recall, or can you give our own reasoning behind these decisions?

(Edit: Meant to type "your" above, oops)

rniwa commented 4 years ago

@rniwa Do you recall, or can you give our own reasoning behind these decisions?

We want to keep the relationship because of the use case that we want to be able to move the “related” node from one place in document to another. This would not be possible if the relationship was severed immediately at the time of a referenced or referencee getting disconnected from a document.

In the case when a node is inserted into a shadow tree or another document, we should be clearing the relationship since in the case of shadow trees we want to preserve encapsulation and in the case of documents, we want to avoid leaks.

alice commented 4 years ago

in the case of shadow trees we want to preserve encapsulation

Can you spell out how allowing the explicitly set attr-element to persist (but not be observed) fails to preserve encapsulation?

and in the case of documents, we want to avoid leaks.

Again, can you spell out what the leak situation would be?

rniwa commented 4 years ago

in the case of shadow trees we want to preserve encapsulation

Can you spell out how allowing the explicitly set attr-element to persist (but not be observed) fails to preserve encapsulation?

Not making it observable would solve encapsulation problem but Mozilla raised the concern that any use case that involves such a relationship is better served by a delegation mechanism akin to delegatesFocus to which we (Apple) agreed.

and in the case of documents, we want to avoid leaks.

Again, can you spell out what the leak situation would be?

The leak would be the entire document of the node being referenced despite of the fact AT will not be able to respect such a relationship (cross document) anyway.

alice commented 4 years ago

Can you spell out how allowing the explicitly set attr-element to persist (but not be observed) fails to preserve encapsulation?

Not making it observable would solve encapsulation problem but Mozilla raised the concern that any use case that involves such a relationship is better served by a delegation mechanism akin to delegatesFocus to which we (Apple) agreed.

I think we're all on board with trying to find an alternative solution to allow exposing a reference to an element in deeper shadow DOM.

I'm not following how that implies that the explicitly set attr element has to drop off, though.

(Edit:) ... since the neither the JS getter nor the computed attr-associated element will make it observable until the element moves into lighter Shadow DOM.

The leak would be the entire document of the node being referenced despite of the fact AT will not be able to respect such a relationship (cross document) anyway.

I'm sorry, I'm still not following - if the relationship is not valid, the getter won't return the element (per Meredith's comment). Is this a memory leak issue, or an encapsulation leak issue?

rniwa commented 4 years ago

Can you spell out how allowing the explicitly set attr-element to persist (but not be observed) fails to preserve encapsulation?

Not making it observable would solve encapsulation problem but Mozilla raised the concern that any use case that involves such a relationship is better served by a delegation mechanism akin to delegatesFocus to which we (Apple) agreed.

I think we're all on board with trying to find an alternative solution to allow exposing a reference to an element in deeper shadow DOM.

No, Mozilla was making an argument that even in the case of referencing to an outer tree, a better approach is to use a delegation mechanism. I think that's largely true. The CSS shadow parts has an explicit part forwarding mechanism to address the use cases like that instead of replying on scripts to establish the relationship.

I'm not following how that implies that the explicitly set attr element has to drop off, though.

It's largely because developers in the room preferred to have the observed behavior of the element reflections match that of what assistive technology sees.

The leak would be the entire document of the node being referenced despite of the fact AT will not be able to respect such a relationship (cross document) anyway.

I'm sorry, I'm still not following - if the relationship is not valid, the getter won't return the element (per Meredith's comment). Is this a memory leak issue, or an encapsulation leak issue?

Cross-document referencing is a memory leak issue, not an encapsulation issue.

alice commented 4 years ago

Mozilla was making an argument that even in the case of referencing to an outer tree, a better approach is to use a delegation mechanism. I think that's largely true. The CSS shadow parts has an explicit part forwarding mechanism to address the use cases like that instead of replying on scripts to establish the relationship.

Hm, that's the first I've heard of this argument. I didn't see this captured anywhere. So this would rule out referring to lighter Shadow DOM as well?

It's largely because developers in the room preferred to have the observed behavior of the element reflections match that of what assistive technology sees.

Right, and that would be the computed attr-associated element, not the explicitly set attr-element (which is a piece of data used to compute the computed attr-associated element).

Cross-document referencing is a memory leak issue, not an encapsulation issue.

Can you expand on this?