Skip to content
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

Dialog's focus lock steals focus across frame boundaries #536

Closed
henningmu opened this issue Apr 6, 2020 · 10 comments
Closed

Dialog's focus lock steals focus across frame boundaries #536

henningmu opened this issue Apr 6, 2020 · 10 comments
Labels
Type: Bug Something isn't working

Comments

@henningmu
Copy link

henningmu commented Apr 6, 2020

🐛 Bug report

Current Behavior

Check the CodeSandbox example linked below. When opening the Dialog by clicking Show Dialog the button in it gets focussed (so far so good). Now use the mouse to return focus to the CodeSandbox and try to type something. As soon as you begin, the focus is returned to the button in the dialog.

The problem in our setup is a bit more complex as we're opening a Google Driver Picker from a dialog but the underlying problem seems to be the same: The focus lock on the dialog is too aggressive.

Expected Behavior

The focus lock does not interfere with frame boundaries (ie. doesn't steal focus from iframes or the like).

Reproducible example

CodeSandbox

Suggested solution(s)

At first I wanted to propose to make this configurable through a prop like disableFocusLock. However, given the extreme example of the CodeSandbox being rendered unusable maybe there is a more holistic solution to this. Maybe this is even an issue in react-focus-lock?

Happy to help implement something after a pointer in the right direction 🙌

Your environment

Software Name(s) Version
Reach Package @reach/dialog latest (0.10.0)
React 16.12.0
Browser Chrome

Edit: This might be related to #83

@chaance chaance added the Type: Enhancement General improvements or suggestions label Apr 9, 2020
@reach reach deleted a comment from enforce-template-use bot Apr 13, 2020
@chaance
Copy link
Member

chaance commented Apr 13, 2020

Interestingly this isn't an issue in Firefox 🤔 but this seems like a good use for FocusLock's whiteList prop. I'm not sure that simply exposing it directly via the DialogOverlay is the right API for us. See line 142 in Dialog.tsx: https://codesandbox.io/s/whitelist-focus-lock-for-reachdialog-8nmb1

@theKashey Have you any thoughts? I wasn't around back when Ryan implemented react-focus-lock so I'm still learning some of the nuances in its API.

@theKashey
Copy link
Contributor

theKashey commented Apr 13, 2020

I've experienced exactly this codesandbox issue during focus-lock development - while it is active the rest of application is basically 💀dead💀.

And it is a bug.

There are a few moments:

  • this is how FocusLock should work. You asked to lock the focus, so here you go.
  • if you nest iframe inside a lock - it will understand that it is still within the boundaries and work as expected. So it have to work across frame boundaries
  • when you move focus out of the page - it shall not prevent it. However there are special treatment for focus when it "goes back".

The sequence is the following:

  • focus allow body to be an active element
  • there is onBlur event on the document, which sets focusWasOutsideWindow=true
  • if body is active, and focusWasOutsideWindow is true - this means what we reactivated current tab, and focus has to be restored.
  • we are here.

If focusWasOutsideWindow would be false - no focus stealing would occur, and the problem here is that the page is still the same... and React elements receives blur as well and triggers the check.

And here is the problem - document.onBlur sets focusWasOutsideWindow synchronously, while trap.onBlur works asynchronously(Promise + setImmediate). If only the would be in the same conditions - focusWasOutsideWindow would not be set by the time onBlur handler is called.

Should be easy to fix the problem, as well as create a test for it. Thanks for calling me 👍

@chaance
Copy link
Member

chaance commented Apr 14, 2020

Even better, and thanks for the detailed explanation @theKashey!

@chaance chaance added Status: In Progress Type: Bug Something isn't working and removed Type: Enhancement General improvements or suggestions labels Apr 14, 2020
@theKashey
Copy link
Contributor

I am adding new crossFrame option, right now defaults to true, as long as this behaviour might introduce a breaking change for someone.

  • setting crossFrame=true will work "as today". Focus will try to keep focus
  • setting crossFrame=false will work "as proposed". Iframes would be treated as "pages", and let you move focus outside.

I am not sure which behaviour is actually correct - iframes are usually a part of a page, a part of your product, and the only exemptions are non-production examples like code-sandbox.

@theKashey
Copy link
Contributor

Said option was added in [email protected]

@chaance chaance added Status: Unreleased Issue addressed but not yet released and removed Status: In Progress labels Apr 17, 2020
@chaance
Copy link
Member

chaance commented Apr 17, 2020

@henningmu
Copy link
Author

Nice one @chancestrickland (and @theKashey) that fixes our problem, thank you very much 🎉 🙌

@chaance
Copy link
Member

chaance commented Apr 22, 2020

Added the unstable_lockFocusAcrossFrames prop in 0.10.1

@PaulRBerg
Copy link

@chaance, what is the difference between setting unstable_lockFocusAcrossFrames to false and dangerouslyBypassFocusLock to true?

@chaance
Copy link
Member

chaance commented Jul 14, 2021

dangerouslyBypassFocusLock completely disables the react-focus-lock, unstable_lockFocusAcrossFrames sets its crossFrame prop, re: theKashey/react-focus-lock#104

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants