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

DevTools: useModalDismissSignal bugfix #21173

Merged
merged 2 commits into from
Apr 5, 2021

Conversation

bvaughn
Copy link
Contributor

@bvaughn bvaughn commented Apr 3, 2021

Make useModalDismissSignal's manually added click/keyboard events more robust to sync flushed passive effects. (Don't let the same click event that shows a modal dialog also dismiss it.)

Fixes #21172

Make useModalDismissSignal's manually added click/keyboard events more robust to sync flushed passive effects. (Don't let the same click event that shows a modal dialog also dismiss it.)
@sizebot
Copy link

sizebot commented Apr 3, 2021

Comparing: b4f119c...8f1087e

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.min.js = 122.62 kB 122.62 kB = 39.34 kB 39.34 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 129.18 kB 129.18 kB = 41.42 kB 41.42 kB
facebook-www/ReactDOM-prod.classic.js = 405.84 kB 405.84 kB = 75.37 kB 75.37 kB
facebook-www/ReactDOM-prod.modern.js = 394.10 kB 394.10 kB = 73.51 kB 73.51 kB
facebook-www/ReactDOMForked-prod.classic.js = 405.84 kB 405.84 kB = 75.37 kB 75.37 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against 8f1087e

@gaearon
Copy link
Collaborator

gaearon commented Apr 3, 2021

Hmm. It’s not obvious to me that relying on a timestamp is the correct solution. What happens if some code in the middle just happens to spend enough CPU time? Does this affect the logic anyhow?

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Apr 3, 2021
@bvaughn
Copy link
Contributor Author

bvaughn commented Apr 4, 2021

Hmm. It’s not obvious to me that relying on a timestamp is the correct solution. What happens if some code in the middle just happens to spend enough CPU time? Does this affect the logic anyhow?

@gaearon I'm not sure I understand the scenario you're describing.

The specific issue I'm fixing here is tis:

  1. A click event causes a modal to be shown.
  2. The modal has a passive effect which adds a click handler to the document.
  3. React now flushes this passive effect synchronously.
  4. The newly added click event handler also picks up on the original click event due to bubbling.

So my fix is to check if the click event was trigger before the passive effect that added the listener. This fix should not be affected by code running in the middle. (That would just make the delta between the event and effect time larger.)

An alternative fix would be to add a small delay before attaching my event handler in the effect, but this approach seemed more obvious/direct.

@gaearon
Copy link
Collaborator

gaearon commented Apr 4, 2021

Do we expect any problems due to limited precision? Eg Firefox seems to have a privacy setting for this. https://developer.mozilla.org/en-US/docs/Web/API/Event/timeStamp

@bvaughn
Copy link
Contributor Author

bvaughn commented Apr 5, 2021

Do we expect any problems due to limited precision? Eg Firefox seems to have a privacy setting for this. developer.mozilla.org/en-US/docs/Web/API/Event/timeStamp

Even for older implementations, I don't think it seems that likely for React to render an update, commit it, and flush passive effects in < 1 ms. And because I return only if timeOfEffect > event.timeStamp, I wouldn't early return if the time between the effect running and a subsequent click event was < 1ms.

I also don't think it's very common for React DevTools to be running in older browser versions. For instance, Chrome switched to high res timestamps in version 49 (released over 5 years ago). Seems likely that people using DevTools would also be using versions of Chrome released more recently than 5 years ago.

@eps1lon

This comment has been minimized.

@bvaughn
Copy link
Contributor Author

bvaughn commented Apr 5, 2021

We didn't use event.timeStamp for the same reasons @gaearon mentioned. We just don't know how reliable event.timeStamp is in reality.

As mentioned in the comment above yours, I don't think this concern seems too likely. 😄

In Material-UI we basically just deferred "activation" of the click handler with setTimeout(() => activateClickHandler, 0). The rationale being that people are unlikely to click more often than every ~15ms and if they do they can always repeat the action (compared to not being able to open at all).

Simply delaying the event listener with setTimeout(() => activateClickHandler, 0) wouldn't be sufficient, because if another nested update (synchronously) removed the component you could end up leaking.

Something like this should be fine though:

useEffect(() => {
  function handler(event) {
    // ...
  }

  let timeoutID = setTimeout(() => {
    timeoutID = null;
    window.addEventListener("click", handler);
  }, 0);

  return () => {
    if (timeoutID !== null) {
      clearTimeout(timeoutID);
    }

    window.removeEventListener("click", handler);
  };
});

I think I slightly prefer the timeout approach in this PR though because the intent seems clearer to me.

@eps1lon
Copy link
Collaborator

eps1lon commented Apr 5, 2021

Simply delaying the event listener with setTimeout(() => activateClickHandler, 0) wouldn't be sufficient, because if another nested update (synchronously) removed the component you could end up leaking.

Yes, we're actually doing that. Just was a bit lazy with my comment.

@bvaughn
Copy link
Contributor Author

bvaughn commented Apr 5, 2021

I'm still tempted to try this fix because I prefer the clarity of it vs the timestamp and I don't think it's problematic (especially in the DevTools case) but if we're concerned about setting a highly visible precedent for this case that may be copied elsewhere, I won't choose to die on this hill.

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be good to discuss with the team what's the canonical fix for this. Like you said, it's not so much about this case but just what do we actually recommend other people do.

@bvaughn
Copy link
Contributor Author

bvaughn commented Apr 5, 2021

Sounds good. Added it to our sync topics.

@bvaughn bvaughn merged commit a817840 into facebook:master Apr 5, 2021
@bvaughn bvaughn deleted the fix-useModalDismissSignal branch April 5, 2021 15:09
@bvaughn
Copy link
Contributor Author

bvaughn commented Apr 6, 2021

Btw, @eps1lon, I took the time to write up my thoughts/preferences about this at a little more length. In case you're interested:

https://gist.github.com/bvaughn/fc1c3f27f1aab91c7378f2264f7c3aa1

acdlite pushed a commit to acdlite/react that referenced this pull request Apr 11, 2021
* DevTools: useModalDismissSignal bugfix

Make useModalDismissSignal's manually added click/keyboard events more robust to sync flushed passive effects. (Don't let the same click event that shows a modal dialog also dismiss it.)

* Replaced event.timeStamp check with setTimeout
acdlite pushed a commit to acdlite/react that referenced this pull request Apr 13, 2021
* DevTools: useModalDismissSignal bugfix

Make useModalDismissSignal's manually added click/keyboard events more robust to sync flushed passive effects. (Don't let the same click event that shows a modal dialog also dismiss it.)

* Replaced event.timeStamp check with setTimeout
acdlite pushed a commit to acdlite/react that referenced this pull request Apr 16, 2021
* DevTools: useModalDismissSignal bugfix

Make useModalDismissSignal's manually added click/keyboard events more robust to sync flushed passive effects. (Don't let the same click event that shows a modal dialog also dismiss it.)

* Replaced event.timeStamp check with setTimeout
acdlite pushed a commit to acdlite/react that referenced this pull request Apr 16, 2021
* DevTools: useModalDismissSignal bugfix

Make useModalDismissSignal's manually added click/keyboard events more robust to sync flushed passive effects. (Don't let the same click event that shows a modal dialog also dismiss it.)

* Replaced event.timeStamp check with setTimeout
acdlite pushed a commit to acdlite/react that referenced this pull request Apr 19, 2021
* DevTools: useModalDismissSignal bugfix

Make useModalDismissSignal's manually added click/keyboard events more robust to sync flushed passive effects. (Don't let the same click event that shows a modal dialog also dismiss it.)

* Replaced event.timeStamp check with setTimeout
kodiakhq bot pushed a commit to vercel/next.js that referenced this pull request Apr 23, 2021
This test is failing for the same reason as facebook/react#21173, so we'll borrow their fix, and follow along for updates.
koto pushed a commit to koto/react that referenced this pull request Jun 15, 2021
* DevTools: useModalDismissSignal bugfix

Make useModalDismissSignal's manually added click/keyboard events more robust to sync flushed passive effects. (Don't let the same click event that shows a modal dialog also dismiss it.)

* Replaced event.timeStamp check with setTimeout
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bug: DevTools settings dialog no longer opens
5 participants