[web:a11y] fix traversal and hit-test orders#32712
Conversation
| _accessibilityPlaceholder, | ||
| _sceneHostElement!, | ||
|
|
||
| // The semantic host goes last because hit-test order-wise we want it to |
There was a problem hiding this comment.
here and everywhere, we may refer to different entity based on the reader's perspective.
| } | ||
|
|
||
| // At this point we're guaranteed to have had a non-empty previous child list. | ||
| final List<SemanticsObject> previousChildrenInRenderOrder = _currentChildrenInRenderOrder!; |
There was a problem hiding this comment.
Is goal for the rest of this function is too detach old children and attach new children? It feels quite complex as is? the intersectionIndicesNew can probably be removed since it is not used
There was a problem hiding this comment.
maybe just insert old list to a set to store old list, and loop through the new list and see if it is in the set. If it is not, attach the element. If it is, remove from the set. At the end just detach remaining element it the set.
There was a problem hiding this comment.
The most complex part (starting with the "Both non-empty case" comment) tries to deal with the child order. When DOM nodes move unnecessarily ATs lose focus. So what this function does is compute the longest chain of existing nodes that did not move relative to each other. It keeps those nodes stationary, and moves/adds/removes the nodes around them. This covers most scenarios (scrolls in either direction, insertions, deletions, drag'n'drop, and more). I don't think this happens in native accessibility APIs because they can track moving nodes so long as their IDs are stable.
I am going to leave a comment explaining this. Would that help?
There was a problem hiding this comment.
Good catch about intersectionIndicesNew! It's unnecessary. Removed.
There was a problem hiding this comment.
ah so the attach and detach order matter. SGTM then
chunhtai
left a comment
There was a problem hiding this comment.
I nitpicked some doc string styling, since they are not introduced in this pr, I will leave decision to you whether you want to fix them. Overall this looks good. just one concern about missing test for glassPaneElementHostNode element order
| @@ -99,7 +99,7 @@ class FlutterViewEmbedder { | |||
| html.Element? _sceneElement; | |||
|
|
|||
| /// This is state persistent across hot restarts that indicates what | |||
There was a problem hiding this comment.
nit: doc string should start with a brief sentence, or convert this to //
There was a problem hiding this comment.
Doc comments under engine are not public (the dart:_engine library is private). We only use /// to get dartdoc linking (// will lose the links). We do not aim for public dartdoc level of quality. But thanks for setting a high standard!
| } | ||
|
|
||
| /// We don't want to unnecessarily move DOM nodes around. If a DOM node is | ||
| /// Don't unnecessarily move DOM nodes around. If a DOM node is |
| @@ -361,7 +369,7 @@ class FlutterViewEmbedder { | |||
| } | |||
|
|
|||
| /// The framework specifies semantics in physical pixels, but CSS uses | |||
| // DOM nodes created for semantics objects are positioned absolutely using | ||
| // transforms. | ||
| element.style.position = 'absolute'; | ||
| element.setAttribute('id', 'flt-semantic-node-$id'); |
There was a problem hiding this comment.
Probably not a big deal: Is this test only? if so, it can be put into assert
There was a problem hiding this comment.
Yeah, accessibility node count tends to be small enough for this extra info to not matter in terms of performance. We also aggressively reuse accessibility nodes, so this constructor is only called once for the entire lifetime of a node with an ID. It is useful for debugging production apps, so I'd like to keep it. If this becomes a performance problem, it's easy enough to remove. It's not used for anything.
| } | ||
|
|
||
| // At this point we're guaranteed to have had a non-empty previous child list. | ||
| final List<SemanticsObject> previousChildrenInRenderOrder = _currentChildrenInRenderOrder!; |
There was a problem hiding this comment.
ah so the attach and detach order matter. SGTM then
| childrenInHitTestOrder: Int32List.fromList(<int>[1]), | ||
| childrenInTraversalOrder: Int32List.fromList(<int>[1]), | ||
| ); | ||
| updateNode( |
There was a problem hiding this comment.
Maybe add a comment about why this additional update is needed
There was a problem hiding this comment.
It was just a bug in the previous code. It pretended to have a child, but didn't send child info. I added more asserts in the SemanticsOwner, which uncovered the bug. I don't think a comment is necessary because this is now enforced everywhere (I'd have to write many comments like that, everywhere there are child nodes), and the assertion failure should make it obvious what's wrong if someone mistakenly removes it.
| // elements transparent. This way, if a platform view appears among other | ||
| // interactive Flutter widgets, as long as those widgets do not intersect | ||
| // with the platform view, the platform view will be reachable. | ||
| semanticsHostElement, |
There was a problem hiding this comment.
Is there a test coverage for this?
There was a problem hiding this comment.
In the past I could find a way to write a test that would emulate browser's hit test. But I just found this: https://developer.mozilla.org/en-US/docs/web/api/document/elementsfrompoint. I'm going to try.
There was a problem hiding this comment.
elementsFromPoint worked! I added a test. Also added CanvasKit mode to that test (since platform views are composited differently in CanvasKit), and while doing that discovered that offset was ignored in addPlatformView, so fixed that along the way.
ditman
left a comment
There was a problem hiding this comment.
This change is only ~20% scary, but the code makes sense, and the tests are logical. LGTM!
| semanticsHostElement, | ||
| ]); | ||
|
|
||
| // When debugging semantics, make the scene semi-transparent so that the |
There was a problem hiding this comment.
Now that the accessibility tree renders on top of the scene host, this is probably not required anymore :P
There was a problem hiding this comment.
Good point. Strictly speaking, no. But reducing the transparency of the scene makes the semantics overlay more visible, making it easier to work with. I'll keep the transparency, but I will update the comment.
56b84e9 to
23ba1f8
Compare
* [web:a11y] implement traversal and hit-test orders * remove unused intersectionIndicesNew * canvaskit: fix platform view offset and scene host initialization * remove "we" in a bunch of comments
* [web:a11y] implement traversal and hit-test orders * remove unused intersectionIndicesNew * canvaskit: fix platform view offset and scene host initialization * remove "we" in a bunch of comments
…es (#32867) * [web] do not allocate canvases just for text (#30804) * [web:a11y] fix traversal and hit-test orders (#32712) * [web:a11y] implement traversal and hit-test orders * remove unused intersectionIndicesNew * canvaskit: fix platform view offset and scene host initialization * remove "we" in a bunch of comments Co-authored-by: Yegor <yjbanov@google.com>
Fix traversal and hit test orders for web.
Problems with the previous approach:
pointer-events: none.z-indexCSS property to implement child hit test order.aria-hidden: trueon the<flt-scene-host>element, but because that property applies to the whole subtree and platform views are composited under it, platform views were hidden from accessibility too, even though they still received regular pointer and keyboard events.<flt-scene-host>, but hide<flt-picture>elements instead. We are only interested in hiding DOM used to render pixels for widgets, so hiding just the pictures is sufficient.Fixes flutter/flutter#100157