-
Notifications
You must be signed in to change notification settings - Fork 376
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
Method for detecting finally-distributed nodes. #611
Comments
You can use |
@hayatoito Not with closed trees. In a tree of node trees where all the trees are mode open, it is possible to implement
Basically, if the slot is assigned, then obviously nodes aren't finally distributed there. Only a final distribution point returns anything. This doesn't work with closed trees because An official method for this would be nice because we wouldn't need to make a hack when dealing with the closed trees. |
Basically, we will not provide any APIs which can tell us information about a structure of a closed tree from outside. |
@hayatoito If a method like
This is no more revealing than Having a way to get finally-distributed nodes is important for libraries like A-Frame if you wish for such libraries to promote ShadowDOM by being ShadowDOM-compatible. Libraries like that -- which render to WebGL or somewhere outside of DOM rendering -- need to be able to determine final-distribution of nodes in order to know how to render things. If the platform doesn't have such a feature, then library authors (who are willing to take the time) will have to spend time coming up with a hack in order to achieve this with closed trees. Libraries will contain uglier code. On the other hand, it would be nice for a library author to simply call something like Plus, as you know, ShadowDOM is not a security feature. It can be hacked to achieve this, but it's ugly that it has to be done. I believe that for most cases -- for libraries that rely on DOM rendering -- this isn't an issue. But for libraries that need to know final-distribution (i.e. know flat the flat tree), closed trees are a temporary road block. Having something like Here is a list of libraries that (I believe) would benefit from such a feature if they want to be ShadowDOM-compatible:
|
That is exactly what I thought as an example of leaking information; a closed shadow tree has a slot. Suppose |
True, but I've been thinking about how to achieve this for a while now, at first with slottedCallback idea. I think If my library owns custom elements inside of each tree in a tree of shadow trees, then I can access slots in each tree, and on |
The most important point is this: library authors who write libraries that that expose custom elements for defining scene graphs that render in custom ways (f.e. to WebGL), and who wish for their libraries to be ShadowDOM-compatible, are going to find a way to do this the hard way or (if browser APIs allow) the easy way. I'm simply suggesting that having the easy way would be really convenient. In the meantime, I'm going to do it the complicated (and as far as I know not pretty) way. |
"Not leaking an internal structure" is a design goal for closed shadow trees from the beginning. If this design goal does not meet requirements, you can always use open shadow trees. |
My library does not use ShadowDOM at the moment, and has no plans to use ShadowDOM. My library aims to be ShadowDOM-compatible. It is not I who is making a design decision on whether to use "open" or "closed" shadow trees. The end user of my library (or of any of the libraries mentioned above) can take any of those libraries and decide to use open or closed trees to distribute the elements as the end-user sees fit. I can not possibly know if the end user will use open or closed trees, but any of the above libraries should work regardless of what the end user chooses, just like with native elements. It would be great if you could solve the following challenge to experience what I mean: Make three custom elements with the given attributes that will be used to render to a 2D canvas context:
Then write the following markup which should (approximately) render a circle at position <x-scene>
<x-transform x="20" y="50">
<x-transform x="60" y="30">
<x-circle radius="10">
</x-circle>
</x-transform>
</x-transform>
</x-scene> Once that is working, then write this markup: <x-scene>
<x-transform x="20" y="50">
<x-circle radius="10">
</x-circle>
</x-transform>
</x-scene> and then this JavaScript: var t = document.querySelector('x-transform')
var r = t.attachShadow({mode:'closed'})
r.innerHTML = `
<x-transform x="60" y="30">
<slot></slot>
</x-transform>
` The results should be the same, but the second example will distribute the circle into the second transform. That shadow root example is simple though. It is easy to get that working just with Now, imagine we have two layers of shadow roots. Write the following markup, <x-scene>
<x-circle radius="10">
</x-circle>
</x-scene> and the following javascript: var s = document.querySelector('x-scene')
var r = s.attachShadow({mode:'closed'})
r.innerHTML = `
<x-transform x="20" y="50">
<slot></slot>
</x-transform>
`
var t = r.querySelector('x-transform')
r = t.attachShadow({mode:'closed'})
r.innerHTML = `
<x-transform x="60" y="30">
<slot></slot>
</x-transform> Now, the The What is your solution to that, so that the |
You can tell users the limitation of your library; only open shadow roots are fully supported. If you have any further question, I think stackoverflow is a good place to discuss that. |
Of course, I know I can just do that. I could also just tell users "we don't support ShadowDOM, don't use ShadowDOM with this library", just like A-Frame stated. Limitations are not ideal. People don't like limitations. But that's not what I'm aiming for. I'm aiming for "This library is fully compatible with the latest awesome DOM standards like ShadowDOM, etc." and let them use ShadowDOM the way that they see fit and the way that it is publicly documented to work. I want to promote ShadowDOM by fully supporting it in my library, which I am currently going to do by hacking a solution. I'm not saying that what I want to do is not possible; it is possible, but it is not clean. |
Not everyone always reads every piece of documentation. If I support only open shadow trees when browsers give an API for creating both open and closed trees, someone is going to try using the library with closed trees and it is going to cause headaches for the end user, and possibly headaches for the library author when the end user starts asking questions that wouldn't need to be asked if the library just worked with open or closed trees. |
When I tried A-Frame as an end user, I was disappointed that I could not write Custom Element components with ShadowRoots. It means I can't create A-Frame components the way that I want to as an end user. These sorts of limitations are what any custom-element library author should aim not to have. I don't see what possible harm can come from knowing that "some child tree has a slot". It would be helpful if you can elaborate on that. |
(not sure if you read emails first: I fixed typo in the last message, I meant "disappointed that I could not write") |
@HAYATO, check this out. Custom Elements in my library observe _handleDistributedChildren(slot) {
const diff = this._getDistributedChildDifference(slot)
for (const addedNode of diff.added) {
// We do this because if the given slot is assigned to another
// slot, then this logic will run again for the next slot on
// that next slot's slotchange, so we remove the distributed
// node from the previous shadowParent and add it to the next
// one. If we don't do this, then the distributed node will
// exist in multiple shadowChildren lists when there is a
// a chain of assigned slots. For more info, see
// https://github.com/w3c/webcomponents/issues/611
if (addedNode._shadowParent)
addedNode._shadowParent._shadowChildren.delete(addedNode)
addedNode._shadowParent = this
this._shadowChildren.add(addedNode)
}
for (const removedNode of diff.removed) {
removedNode._shadowParent = null
this._shadowChildren.delete(removedNode)
}
} where _getDistributedChildDifference(slot) {
let previousNodes
if (this._slotElementsAssignedNodes.has(slot))
previousNodes = this._slotElementsAssignedNodes.get(slot)
else
previousNodes = []
const newNodes = slot.assignedNodes({flatten: true})
// save the newNodes to be used as the previousNodes for next time.
this._slotElementsAssignedNodes.set(slot, newNodes)
const diff = {
removed: [],
}
for (let i=0, l=previousNodes.length; i<l; i+=1) {
let oldNode = previousNodes[i]
let newIndex = newNodes.indexOf(oldNode)
// if it exists in the previousNodes but not the newNodes, then
// the node was removed.
if (!(newIndex >= 0)) {
diff.removed.push(oldNode)
}
// otherwise the node wasn't added or removed.
else {
newNodes.splice(i, 1)
}
}
// Remaining nodes in newNodes must have been added.
diff.added = newNodes
return diff
} When a chain of assigned slots exists (i.e. slot1 assigned to slot2, slot2 assigned to slot3, etc), then the that code will run multiple times, once per Because there's not a way to get finally-distributed nodes, I have to guard against the fact that a previous slot in the chain might have added the distributed node to If there were a _handleDistributedChildren(slot) {
const diff = this._getDistributedChildDifference(slot)
for (const addedNode of diff.added) {
addedNode._shadowParent = this
this._shadowChildren.add(addedNode)
}
for (const removedNode of diff.removed) {
removedNode._shadowParent = null
this._shadowChildren.delete(removedNode)
}
} furthermore, this logic wouldn't have to fire once per slot if there was an event that only fired on finally-distributed children. Right now, if there is a chain of 4 slots across 4 shadow tree, then this logic will fire four times from shallowest to deepest slot. TLDR: I've gotten it to work, but it's ugly due to the fact that there's no possible way to know if the nodes returned from from Is that order ot |
I think changing the behavior of @travisleithead @annevk : any opinions here? |
I think that is a breaking change since this feature was already shipped in Blink. |
I highly doubt that anyone is relying on this particular behavior. If this turns out be a breaking change, then we can add a new option. |
Yeah, we should use a new option, if we want the new behavior. However, this new behavior still reveals the existence of slot elements in a closed shadow tree, indirectly, I think. |
I agree a new option may be better than breaking the current behavior. I was thinking about it, and there are three possible cases that might be nice to satisfy, two of which are already supported, when elements pass through a slot chain:
|
This is true. What are the specific downsides of this? |
Given you can already detect whether an element is assigned to a slot or not by checking a child element's bounding rect, etc... beyond the most simple case (e.g. shadow tree just contains a single default slot), I don't think there is any concern here. |
That seems rather complicated and indirect. What happens when 3D transforms are applied? Have an example? |
I'm suggesting that author do this. I'm saying that there is no concern to add this feature because one could already detect whether an element is assigned to some slot or not with a pretty decent accuracy. |
Can you provide a code sample of this? If it is not 100% accurate, then it isn't viable. |
I think you're misunderstanding what I stated. Please go re-read what I wrote before making a further reply. All I'm saying is that we should add this new option because there is no new information leak. |
@rniwa You said
I'm just asking if you have an example (or link) on how to do that. I would love to know! |
I know how to do it (heuristically) but I don’t want to derail the discussion here. |
@rniwa If you email me that I would be super grateful. |
@rniwa I think a new feature like that would be better discussed in a focused issue. I tend to agree with @hayatoito that it's better not to violate the theoretical boundaries. I'm closing this issue as I don't think it'll lead us anywhere fruitful at this point. |
Also, this is possible now anyways, Shadow DOM isn't a security feature. I don't think this argument provides something that is more useful than the one proposed here. |
It's an encapsulation feature and preserving that encapsulation is good. |
How does the HTML engine render the visuals? Does it traverse the composed tree to do that rendering? |
You mean layout or rendering engine? If so, yes. |
Then this is proof that we need to do this in userland too, if we want to implement custom rendering. (for example, WebGL rendering). I can currently do it, it's just not as easy as it could be. |
(continuing from #288 (comment))
Currently,
slot.assignedNodes({flatten: true})
doesn't tell the full story; it tells which nodes are possibly distributed at the given point (the slot) in a treeofnode trees, but it doesn't take into consideration the fact that nodes might be distributed even deeper into the tree of node trees if the context slot is assigned to a deeper slot; i.e. it does tell us if the returned nodes are actually distributed to that point.It'd be nice to have a method
distributedNodes()
that answers the question: which nodes are distributed here, i.e. which nodes render relative to this point.What about an
Element.prototype.distributedNodes
orHTMLSlotElement.prototype.distributedNodes
property that returns children that are distributed to the context, and returns an empty array otherwise?Which of the following would make more sense?
slot.distributedNodes()
which returns elements that are finally distributed and render relative tothe slot's parent. (i.e. the slot is not assigned anywhere else, so it is the furthest point where nodes are finally distributed).el.distributedNodes()
which returns nodes that render relative toel
(distributed via a child slot element ofel
).The text was updated successfully, but these errors were encountered: