-
Notifications
You must be signed in to change notification settings - Fork 673
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
[cssom-view] elementFromPoint, elementsFromPoint, and caretPositionFromPoint should not return an element inside a shadow tree #556
Comments
Okay, there's another issue with the current definition either in CSS OM or shadow DOM specification. When the result of hit testing results in a But none of this is clear because either specification really says anything about in which tree the node is found (e.g. composed tree versus flat tree; probably the latter) and when & where the retargeting happens. |
Another edge case to consider is when the hit testing results in a |
I'm going to implement the proposed behavior in https://bugs.webkit.org/show_bug.cgi?id=162882 because whatever Chrome/Blink currently implements is awfully broken. |
https://bugs.webkit.org/show_bug.cgi?id=162882 Reviewed by Chris Dumez. Source/WebCore: Add elementFromPoint to ShadowRoot's prototype as specified at: https://www.w3.org/TR/shadow-dom/#extensions-to-the-documentorshadowroot-mixin with changes proposed at w3c/csswg-drafts#556 Added TreeScope::retargetToScope which implements This patch also factors DocumentOrShadowRoot.idl out of Document and ShadowRoot interfaces to better match the latest DOM specification: https://dom.spec.whatwg.org/#mixin-documentorshadowroot Test: fast/shadow-dom/Document-prototype-elementFromPoint.html * CMakeLists.txt: * DerivedSources.make: * WebCore.xcodeproj/project.pbxproj: * dom/Document.cpp: (WebCore::Document::nodeFromPoint): Moved to TreeScope. (WebCore::Document::elementFromPoint): Moved to TreeScope. * dom/Document.h: * dom/Document.idl: Moved elementFromPoint and activeElement to DocumentOrShadowRoot.idl. * dom/DocumentOrShadowRoot.idl: Added. * dom/EventPath.cpp: (WebCore::RelatedNodeRetargeter::checkConsistency): Use newly added TreeScope::retargetToScope. * dom/ShadowRoot.idl: Moved activeElement to DocumentOrShadowRoot.idl. * dom/TreeScope.cpp: (WebCore::TreeScope::retargetToScope): Added. Implements https://dom.spec.whatwg.org/#retarget efficiently. Instead of checking whether A (node) is a shadow-including inclusive ancestor of B (this scope) at each parent, find the lowest ancestor which contains both A and B, and return the self-inclusive ancestor of B in that tree. To find the lowest common ancestor in O(n), traverse all ancestors of A and B separately and do a top-down traversal. The last tree scope in which A's ancestor and B's ancestor match is the lowest common ancestor. (WebCore::TreeScope::nodeFromPoint): Moved from Document. (WebCore::TreeScope::elementFromPoint): Moved from Document. Use retargetToScope and parentInComposedTree instead of parentNode and ancestorInThisScope to match the semantics proposed in w3c/csswg-drafts#556 * dom/TreeScope.h: LayoutTests: Add a W3C style testharness.js test for elementFromPoint on ShadowRoot. * fast/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint-expected.txt: Added. * fast/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html: Added. git-svn-id: http://svn.webkit.org/repository/webkit/trunk@206795 268f45cc-cd09-0410-ab3c-d52691b4dbfc
caretPositionFromPoint is even weirder. It’s not clear to me what should happen to an offset when the node is retargeted. |
Hmm. It is not good that we would not have interoperable implementations. @yosinch FYI. He should know better than me about how Blink implemented these APIs. |
So given WICG/webcomponents#735 (comment) it appears that chromium and gecko are moving towards what WebKit implemented. Is that right @rakina @smaug---- ? It seems the spec needs to cover at least these things, in addition to the above:
Where is flat tree defined? |
flat tree is defined here: https://drafts.csswg.org/css-scoping/#flattening |
I'm not quite sure, at least based on test results. Like Gecko returns null for text under shadowroot, since that is what is most logical (host is after all above ShadowRoot). (random note, it is weird to have wpt for methods which behavior is mostly undefined.) |
Blink's behavior is moving towards WebKit's implementation. A patch landed about a month ago but it's not reflected on wpt.fyi yet I guess (it seems wpt.fyi is still using Chrome 63?) |
And what is that behavior? Need to get the spec changed before making random changes to implementations. |
For elementFromPoint, we retarget the hit test result againts context object (document/shadow root the elementFromPoint method is called on). For elementsFromPoint, we retarget the hit test result againts context object, one-by-one and only include elements once in the list (no duplicates). More details on cases with text nodes and slots etc, it is as the test at https://cs.chromium.org/chromium/src/third_party/WebKit/LayoutTests/external/wpt/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html?rcl=9f75a2842405ed0f378d8a4e60d6de2568704880&l=43 says, and I guess this WPT should be removed as long as the spec is not clear yet? |
My guess is that Blink's HitTest never returns an element which doesn't have LayoutBox. From this perspective, the test looks correct to me. |
What's its parent element? The nearest ancestor that's an element in the flat tree? Or something else? (Sorry, I'm just not sure which spec concept the LayoutTree is approximating.) |
Let me clarify my intention:
I don't know any good terminology to express this. :( |
@tabatkins LayoutTree is almost certainly the box tree. It's not immediately clear to me however why hit testing wouldn't immediately go from the box in the box tree to the associated node in the flat tree/node tree, and then from there you find the nearest ancestor element. It seems a little weird to have a special element hit testing primitive that finds a box corresponding to an element. At least I hadn't heard of that concept before. |
Properly, hit-testing applies to fragments; they're what results from layout. (Boxes don't have positions or sizes, they're the result of applying box-construction only.) (explanation) The relevant question is exactly how the traversal is done. Here's one method:
@hayatoito Is this what we do? If not, what differs? |
@tabatkins - for non-shadow-allowing contexts, that may return a |
No, it can't, because display:contents elements can't create boxes, so they can't create fragments, so they won't get selected by step 3. |
@tabatkins - but they can be/contain shadow DOM, that does contain boxes and fragments and so on...
And |
@hayatoito Oh yeah, duh, you're right, they can just be merged with that change. So:
@rniwa Taking your example, and assuming the If the |
I don't think that matches the author expectation. If a hit testing was done on a text assigned to a slot, then I want to know that the content inside a slot is picked, not on a shadow host. Whether an element generates a CSS box or not doesn't seem like an important distinction from the developer ergonomics standpoint of view. With the proposed behavior, the author who wants to detect whether a given point is inside a slot or not, would have to check whether a node / element returned by these functions are assigned to some slot. It's even worse. If it was a descendent of another element with |
I see, however, your concern is also applied to the following case, right? <div id=a>
<div id=b style="display: contents">
foo
</div>
<div id=c style="display: contents">
bar
</div>
</div> In this case, we can't tell whether the point is on I think what we want here is |
Right. So the deal is, we're excluding Here's an example of why this makes sense: if you call (For example, see http://software.hixie.ch/utilities/js/live-dom-viewer/saved/6290.) If we don't return such an element in this situation, we shouldn't return it from elementFromPoint(), just because it happens to be the parent node of some clicked text; elementFromPoint() should always return the first element of the array returned by elementsFromPoint(). So yeah, insofar as capturing a useful notion of what text was clicked is important (and I agree it is), what you want is nodeFromPoint(), so we can return the text node, rather than trying to do some tricky nonsense with elementFromPoint() to return an element that is literally not at the specified point. |
UA can certainly tell which text node given all major UAs support text selection via mouse drag. Here's what I'm suggesting. Instead of look for the first element in CSS box tree, find the node and the return its parent in the composed tree if the node is not an element (obviously after considering which node should be visible to which tree). |
By "we", Hayato was referring to the user of the API; they wouldn't be able to tell which text was at the provided point (given the suggested behavior), since they'd just get the container element.
That doesn't seem to address my points:
Defining the algorithm as suggested, where it finds the first element generating a box generating a fragment containing the specified point, gives us good, consistent answers to the first two bullet points. Adding a |
Rough consensus at TPAC F2F: Add an option to include 'display: contents', implement what @hayatoito and @tabatkins proposed, and add |
So the "option to include The display:contents seeker would be something like:
Then |
We still need a proper algorithm here, especially for the cases where the first non-display:contents element is inside some other shadow tree. In that case elementFromPoint needs to find the first ancestor which is accessible to the ShadowRoot on which the method is called. |
Ugh, I rewrote the algorithm several times and ended up accidentally killing the "search for a shadow-exposed node" step. Assume that happens between steps 3 and 4. |
Regarding duplicated content, it's a bit trickier than that. Right now all engines can return a node twice if you absolutely position some generated content. For example, the following HTML: <!doctype html>
<style>
html, body { margin: 0; }
div {
width: 100px;
height: 100px;
background: red;
position: relative;
}
span {
display: inline-block;
width: 100px;
height: 100px;
background: green;
}
div::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
background: rgba(0, 0, 0, .2);
}
</style>
<div><span></span></div>
<script>
console.log(document.elementsFromPoint(50, 50));
</script> Will log the |
As far as I can see, CSSOM View doesn't say anything about generated content. So I guess that needs to be clarified, too. Also, as an author I'd expect that it is somehow indicated that the first element is actually the pseudo-element and not the Sebastian |
Since this is not directly related to the issue topic (elementFromPoint etc behavior w/ Shadow DOM), I suggest this to be discussed in another issue. |
@rakina As @smaug---- mentioned in #556 (comment), we still need a well defined algorithm to handle |
@rniwa What else do you think is missing from #556 (comment)? For the shadow handling part, we can do something like the retarget algorithm, but working with flat tree instead. So while the node's root is a shadow root and node is not a flat-tree descendant of the context object, we set the node as its shadow host. (I don't know where flat-tree is defined in the specs though, cc @hayatoito ) |
Right, something like the retarget algorithm over flat tree is what we need. What we're missing is the precise definition / algorithm of what that is. #556 (comment) is insufficient because we don't really retarget the first ancestor in the flat tree for example. It would be great if you or someone else can come up with a precise algorithm for this. Unfortunately, I'm caught up in other urgent matters at the moment so won't have a time to do it myself. FYI, CSS Scoping Module Level 1 defines flat tree. |
These functions continue to behave differently in Blink vs WebKit. It's a major interoperability concern for shadow DOM at this point. |
It looks like there is a WPT here: https://wpt.fyi/results/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html @rniwa does it reflect the behavior you desire? Are there any other relevant WPTs you are aware of? |
No, that test expects Chrome's behavior. This is what we want: https://github.com/WebKit/WebKit/blob/main/LayoutTests/fast/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html |
CSS Fragmentation doesn't define "fragment tree." @tabatkins, is there an equivalent concept? |
@rniwa @rakina - I notice that the DocumentOrShadowRoot-prototype-elementFromPoint.html has not been updated since this discussion. Should the test be updated to reflect what is written in the WebKit repo, or is there still an agreement to reach here? I'm happy to copy over the WebKit implementation of the tests, if that reflects what we want going forward, just let me know, and I can do that work. |
I recently found myself needing to write an engine-independent While I'm naive to the internal implementation details in each engine, I found that Gecko's
Whether or not elements in open shadow DOMs should be included in higher-level It'd be nice however to address difference no. 2, i.e. the inconsistency of a shadow root host element being included in the |
elementFromPoint
andelementsFromPoint
should not return an element inside a shadow tree. Instead, it should look for the highest shadow host of the element and return that instead so that it doesn't leak an element in shadow trees.See the shadow DOM specification.
More precisely, once these methods are added on
DocumentOrShadowRoot
interface, then we need to retarget the element we found against the context object.The text was updated successfully, but these errors were encountered: