-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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
fix(a11y): focus monitor not working inside shadow dom #19135
fix(a11y): focus monitor not working inside shadow dom #19135
Conversation
In angular#18667 event delegation was implemented for the `FocusMonitor` which is based around binding a single event on the `document`. The problem is that the browser won't invoke the `focus` and `blur` handlers on the `document`, if focus is moved within the same shadow root. These changes switch to delegating the events either through the closest shadow root or the `document`. Fixes angular#19133.
|
* handlers differently from the rest of the events, because the browser won't emit events | ||
* to the document when focus moves inside of a shadow root. | ||
*/ | ||
private _rootNodeFocusListenerCount = new Map<HTMLElement|Document, number>(); |
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.
To be safe, we should scan through this in ngOnDestroy
and unlisten to any remaining entries
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.
We're looping through and calling stopMonitoring
on all the remaining elements which eventually empties this out as well.
cachedInfo!.checkChildren = checkChildren; | ||
return cachedInfo!.subject.asObservable(); | ||
const cachedInfo = this._elementInfo.get(nativeElement)!; | ||
cachedInfo.checkChildren = checkChildren; |
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 see that you didn't change this, but is this right? Won't this cause all previous calls to monitor on this element to behave according to the checkChildren
parameter on the current call?
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.
Judging by how it's written, I think that's the point. E.g. you could turn a shallow watch into a deep one by calling monitor
again with checkChildren
. We could separate it out and have 2 separate streams, but we'd have to make a cache key out of the options.
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, I'm not sure it should work that way. The main way I see it being called twice is if there's e.g. a directive and a component that need to know about focus state on the same element. They might have different requirements about whether to include the children, and we wouldn't want them to affect each other's subscriptions.
So it seems like a bug, but a pre-existing one and I haven't seen anyone complain about it yet, so I think its safe to leave this for another PR.
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
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
In #18667 event delegation was implemented for the `FocusMonitor` which is based around binding a single event on the `document`. The problem is that the browser won't invoke the `focus` and `blur` handlers on the `document`, if focus is moved within the same shadow root. These changes switch to delegating the events either through the closest shadow root or the `document`. Fixes #19133.
In angular#18667 event delegation was implemented for the `FocusMonitor` which is based around binding a single event on the `document`. The problem is that the browser won't invoke the `focus` and `blur` handlers on the `document`, if focus is moved within the same shadow root. These changes switch to delegating the events either through the closest shadow root or the `document`. Fixes angular#19133.
…ultiple times with different parameters With the current logic, if an element is being monitored with `checkChildren` set to `true` and then we start monitoring it with `false`, the initial monitor call will stop checking the children, because we take the `checkChildren` value from the most-recent call. This is something that we discussed in angular#19135 (comment), but now we have a case where it can happen: setting a tooltip on a sort header. The sort header looks for child focus, whereas the tooltip doesn't, causing the sort focus indication to break. These changes fix the issue by treating all focus as `checkChildren`, as soon as there's at least one call to `monitor` with `checkChildren` turned on. Fixes angular#19218.
…ultiple times with different parameters With the current logic, if an element is being monitored with `checkChildren` set to `true` and then we start monitoring it with `false`, the initial monitor call will stop checking the children, because we take the `checkChildren` value from the most-recent call. This is something that we discussed in angular#19135 (comment), but now we have a case where it can happen: setting a tooltip on a sort header. The sort header looks for child focus, whereas the tooltip doesn't, causing the sort focus indication to break. These changes fix the issue by treating all focus as `checkChildren`, as soon as there's at least one call to `monitor` with `checkChildren` turned on. Fixes angular#19218.
…ultiple times with different parameters (#19237) With the current logic, if an element is being monitored with `checkChildren` set to `true` and then we start monitoring it with `false`, the initial monitor call will stop checking the children, because we take the `checkChildren` value from the most-recent call. This is something that we discussed in #19135 (comment), but now we have a case where it can happen: setting a tooltip on a sort header. The sort header looks for child focus, whereas the tooltip doesn't, causing the sort focus indication to break. These changes fix the issue by treating all focus as `checkChildren`, as soon as there's at least one call to `monitor` with `checkChildren` turned on. Fixes #19218.
…ultiple times with different parameters (#19237) With the current logic, if an element is being monitored with `checkChildren` set to `true` and then we start monitoring it with `false`, the initial monitor call will stop checking the children, because we take the `checkChildren` value from the most-recent call. This is something that we discussed in #19135 (comment), but now we have a case where it can happen: setting a tooltip on a sort header. The sort header looks for child focus, whereas the tooltip doesn't, causing the sort focus indication to break. These changes fix the issue by treating all focus as `checkChildren`, as soon as there's at least one call to `monitor` with `checkChildren` turned on. Fixes #19218.
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
In #18667 event delegation was implemented for the
FocusMonitor
which is based around binding a single event on thedocument
. The problem is that the browser won't invoke thefocus
andblur
handlers on thedocument
, if focus is moved within the same shadow root. These changes switch to delegating the events either through the closest shadow root or thedocument
.Fixes #19133.
Note: I spent some time trying to reproduce this in a unit test, but I wasn't able to because the way we simulate focus programmatically isn't how things behave in the browser, and we have to use programmatic focus in order to avoid flakes on the CI.