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

Fix wheel/touch browser locking in IE and Safari #9333

Closed
wants to merge 17 commits into from

Conversation

nhunzaker
Copy link
Contributor

@nhunzaker nhunzaker commented Apr 5, 2017

In order for Firefox, Edge, and Safari to optimize scroll rendering, scroll events must be attached locally.

The React synthetic event system isn't really built to handle this, attaching events locally causes a dispatch to the React Synthetic event system, which is problematic for nested scroll event handlers. To work around that, this PR adds a deduping mechanism that track what native events have already dispatched, bailing early if they have already been processed

Testing

I have forked @nolanlawson's wheel jank example and built it with this branch's version of React. You can view the difference by doing the following:

  1. Visit http://nh-react-scroll-fix.surge.sh in IE Edge or Safari
  2. Using a wheel, or two-finger scroll, scroll outer container of the page for ~10 seconds.
  3. Your scrolling should never be interrupted (though in Safari you will see the scroll bar lock up, the page still scrolls)

Related issues: #1254 (comment)

@@ -151,6 +152,39 @@ var topEventMapping = {
topWheel: 'wheel',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This object is the exact same as EventConstants, sans the values. Has there been any thought on consolidating them into EventConstants?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, please send a follow-up to consolidate these! Would be nice to have 'Browser' or 'DOM' in the name too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@@ -948,6 +833,7 @@ ReactDOMComponent.Mixin = {
DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey);
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm going to kill some of this whitespace noise.

@aweary aweary self-assigned this Apr 5, 2017
@aweary aweary requested a review from sebmarkbage April 5, 2017 01:18
@nhunzaker nhunzaker force-pushed the nh-scroll-fix branch 3 times, most recently from d26fc06 to e61ddae Compare April 6, 2017 02:15
@nhunzaker
Copy link
Contributor Author

Talked through how to deal with invalid nesting with @spicyj. I'm sure there's some extra work here, but I think it's in a good spot to review.

Copy link
Contributor

@sophiebits sophiebits left a comment

Choose a reason for hiding this comment

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

I think this is good overall. A couple inlines.

Are we able to add touch events too? They can block scrolling on touch devices. We should be careful around the behavior for touch events on a node that gets removed though. Currently I think you lose move/end events if a node is removed while touching. We might even want to change this but should be conscious of it. Maybe best in a separate PR.

It used to be that we needed to remove listeners when a node is unmounted (a fix ages ago was in c62c2c5). I believe this is no longer necessary since we no longer track nodes by hierarchical IDs but I'm mentioning it for completeness and posterity.

@@ -151,6 +152,39 @@ var topEventMapping = {
topWheel: 'wheel',
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, please send a follow-up to consolidate these! Would be nice to have 'Browser' or 'DOM' in the name too.

@@ -224,7 +258,7 @@ var ReactBrowserEventEmitter = Object.assign({}, ReactEventEmitterMixin, {
* @param {string} registrationName Name of listener (e.g. `onClick`).
* @param {object} contentDocumentHandle Document which owns the container
*/
listenTo: function(registrationName, contentDocumentHandle) {
listenTo: function(registrationName, contentDocumentHandle, node) {
var mountAt = contentDocumentHandle;
var isListening = getListeningForDocument(mountAt);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a 'Doc' somewhere in this name for clarity?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep!

topTouchMove: true,
topTouchStart: true,
topTouchCancel: true,
topTouchEnd: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@spicyj I've added touch cancel and touch end to the list here ☝️. They'll get attached locally now.

@nhunzaker nhunzaker force-pushed the nh-scroll-fix branch 2 times, most recently from 8911065 to a655b5b Compare April 7, 2017 12:31
@nhunzaker
Copy link
Contributor Author

Having some trouble npm linking the new flat bundled builds to the test case, but I'll get an update on http://nh-react-scroll-fix.surge.sh when possible.

@gaearon
Copy link
Collaborator

gaearon commented Apr 18, 2017

I tried to fix the conflict with master, let's see if CI passes.
What's the next actionable item here?

@nhunzaker
Copy link
Contributor Author

I'll work on getting CI to pass later today. @spicyj I've pushed up some stuff to address your feedback, and I have a PR nearly done which consolidates the event names. It just needs to be rebased with master.

Beyond that, I wonder if it is a good idea to add some DOM fixtures for media events.

@gaearon
Copy link
Collaborator

gaearon commented Apr 21, 2017

@spicyj Could you take over rebasing this PR so that @nhunzaker can continue work on #7311 and not worry about this one?

nhunzaker added a commit to nhunzaker/react that referenced this pull request Apr 24, 2017
This is a follow up to
facebook#9333. This commit removes some
duplication of event names and renames the EventConstants module to
BrowserEventConstants.
nhunzaker added a commit to nhunzaker/react that referenced this pull request Apr 24, 2017
This is a follow up to
facebook#9333. This commit removes some
duplication of event names and renames the EventConstants module to
BrowserEventConstants.
nhunzaker added a commit to nhunzaker/react that referenced this pull request Apr 24, 2017
This is a follow up to
facebook#9333. This commit removes some
duplication of event names and renames the EventConstants module to
BrowserEventConstants.
nhunzaker added a commit to nhunzaker/react that referenced this pull request Apr 24, 2017
This is a follow up to
facebook#9333. This commit removes some
duplication of event names and renames the EventConstants module to
BrowserEventConstants.
nhunzaker added a commit to nhunzaker/react that referenced this pull request Apr 25, 2017
This is a follow up to
facebook#9333. This commit removes some
duplication of event names and renames the EventConstants module to
BrowserEventConstants.
@nhunzaker
Copy link
Contributor Author

Upstreamed this with my other PR that consolidated the event list.

@nhunzaker
Copy link
Contributor Author

@philipp-spiess Ah good deal. I can just make them attach with bubbling. We only need capture for scroll/wheel.

Scroll events need to be attached locally in order to receive
rendering optimizations in Firefox, Edge, and Safari.

Unfortunately, scroll events can be nested. When attached locally,
events bubble up through both the DOM and React event systems.

This commit adds a module that tracks what events have
dispatched. When possible, it uses a WeakSet to test membership. When
the event is fully dispatched, it should be released with GC.

For browsers that do not support WeakSets, this module modifies the
event with a flag. This needs to be tested in IE.
Prior work included an event listener on the root container that would
release events from tracking. This is not necessary. Instead, we can
track the last currentTarget to see if the dispatch should be counted.

This appears to work for both bubbling and capture. Assuming the
following markup:

```html
<div id="top" onScroll={...}>
  <div id="middle" onScroll={...}>
    <div id="bottom" onScroll={...} />
  </div>
</div>
```

Assume all levels have a local scroll listener. If we dispatch from
the bottom:

1. The first target is the "top", because we assign it as
  captured. Capture works in reverse.
2. "middle" is skipped because the event is tracked to "top"
3. "bottom" is skipped because the event is trakced to "top"

This means that only one synthetic event dispatch occurs once.
@nhunzaker
Copy link
Contributor Author

@philipp-spiess moved touch events to bubbling and I added a test suite that verifies the bubble order on the react end (it's the same between capture and bubble, but using capture would definitely break existing expectations with events outside of react)

e7884fc

Are these valuable tests? It was useful for me to work through them, but I'm not sure if they give us any extra coverage.

@philipp-spiess
Copy link
Contributor

Are these valuable tests? It was useful for me to work through them, but I'm not sure if they give us any extra coverage.

I think it makes sense. If we plan on handling touch events separately, we should at least make sure they still behave similarly.

@nhunzaker
Copy link
Contributor Author

Supporting bubbling on touch added an edge case where re-dispatching the same event below the last dispatch ignores the second dispatch incorrectly. I've pushed up that failing case.

I'm about out of ideas, but inspiration might strike.

@stale
Copy link

stale bot commented Jan 10, 2020

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Jan 10, 2020
@stale
Copy link

stale bot commented Jan 17, 2020

Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you!

@stale stale bot closed this Jan 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Resolution: Stale Automatically closed due to inactivity
Projects
None yet
Development

Successfully merging this pull request may close these issues.