-
Notifications
You must be signed in to change notification settings - Fork 6k
[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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: avoid we
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here and everywhere, we may refer to different entity based on the reader's perspective.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| } | ||
|
|
||
| // 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch about intersectionIndicesNew! It's unnecessary. Removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah so the attach and detach order matter. SGTM then
chunhtai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
| html.Element? get sceneElement => _sceneElement; | ||
| html.Element? _sceneElement; | ||
|
|
||
| /// This is state persistent across hot restarts that indicates what |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: doc string should start with a brief sentence, or convert this to //
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: same as above
| } | ||
| } | ||
|
|
||
| /// The framework specifies semantics in physical pixels, but CSS uses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: same as above
| // 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not a big deal: Is this test only? if so, it can be put into assert
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sg
| } | ||
|
|
||
| // 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a comment about why this additional update is needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sg
| // 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a test coverage for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
chunhtai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
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 <[email protected]>
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