-
Notifications
You must be signed in to change notification settings - Fork 47.2k
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
Add infrastructure for passive/non-passive event support for future API exploration #15036
Conversation
…vent API experimentation
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 is great 🎉 I can't wait to see how we gonna use the new implementation.
The code path for legacy listeners looks pretty much unchanged expect that we now require WeakMap
instead of using the expando.
Do we know which browsers we want to support in the new implementation? Based on that we might need a few feature tests.
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.
Awesome to see this work happening, 👍
As per the request of @gaearon, I've run a bunch of performance tests on this PR vs master. I slightly optimized the approach and now I see a slight performance gain in Chrome and Safari. No noticeable differences in Edge and Firefox. I'd really appreciate it if we could get this PR landed soon. |
Where did you land on feature checking event listener options objects? Adding a feature detection like the MDN recommendation seems fine and with minimal impact. Also if passive events won't be publicly exposed for a while maybe it could be kicked down the road a bit. Does this need additional testing? I'd be happy to give it a run across browsers. |
@nhunzaker I added that support just now. It would be awesome if you could do some manual tests for it too :) |
I've updated the PR. I've removed the confusing |
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.
Nice! The enum helped to clear up my last questions as well.
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 lgtm. I don't have context around some of it, but the changes look fine. bugging @trueadm for a walkthrough today.
props = rawProps; | ||
break; | ||
case 'input': | ||
ReactDOMInputInitWrapperState(domElement, rawProps); | ||
props = ReactDOMInputGetHostProps(domElement, rawProps); | ||
trapBubbledEvent(TOP_INVALID, domElement); | ||
trapBubbledEvent(TOP_INVALID, domElement, true); |
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.
Per our discussion, it seems like these code paths will never "want" to get two listeners attached/invoked. So instead of branching deeply and passing an argument through, let's fork the innermost implementation and call the fork from the new code when we want to. That will also likely let us DCE more based on a feature flag.
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'm still reviewing the implementation details but can we wrap some of the forked branches in a featureFlag before landing? A small increase is expected but this is increasing a little more than I'd expect for a flag that's turned off. Presumably it'll keep growing as you add more requirements/features so good to make sure the boundaries are clear from the beginning.
@gaearon @sebmarkbage I've re-written the implementation based on feedback you both gave me. It now uses bitwise flags to express the intention of the event system. Furthermore, everything should almost now get DCE due to |
Nice, this actually makes ReactDOM 0.1% smaller :P |
): void { | ||
EventListenerWWW.listen(element, eventType, listener); | ||
EventListenerWWW.listen(element, eventType, listener, passive); |
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 doesn't seem right. The internal EventListener module doesn't accept a 4th argument (and Event.listen accepts a forth argument which is a priority and won't work with this). What's your plan here?
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’ll update the www version
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.
Seems fine to me. Just make sure it works with www before you land so we can sync.
// supported too. This way the responder event plugins know, | ||
// and can provide polyfills if needed. | ||
if (passive) { | ||
if (passiveBrowserEventsSupported) { |
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 seems like the wrong check to me. Shouldn't this case be inverted? This will mark it as active and PASSIVE_NOT_SUPPORTED only if passive events are supported.
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.
Well spotted!
export const RESPONDER_EVENT_SYSTEM = 1 << 1; | ||
export const IS_PASSIVE = 1 << 2; | ||
export const IS_ACTIVE = 1 << 3; | ||
export const PASSIVE_NOT_SUPPORTED = 1 << 4; |
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.
why we don't use literal number(and add comment) here just like maxSigned31BitInt
?
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.
These are used to do bitwise operations on, as there can be a combination of the above being 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.
I think the question was why 1 << 4
is used over 16
. 1 << 4
makes the intent clearer. maxSigned31BitInt
"solves" this by adding a comment. I don't think it matters what pattern is 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.
yea, just like @eps1lon said
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 pattern is clear, as we may add more in the future, which is simply 1 << 5
, 1 << 6
etc. We use this approach throughout the React codebase and we've verified that Google Closure Compiler simplifies the constant value.
Summary
This PR makes a bunch of changes to the underlying implementation for how ReactDOM listens to events on the DOM. Notably, it opens up the possibility of exploring the proper handling of passive/non-passive event listeners. As to not break existing event listeners, they now fall under "legacy", where they work 100% as they do now without any changes in control flow. Also some old code has been tidied up and Flow annotations have been added where they were previously missing.
Why do we need this?
For future event API exploration we need to properly hook into controlling how event listeners are registered with respect to if they are passive or non-passive. The way ReactDOM deals with events now is to not specify if an event is passive or non-passive. By doing so, we let the browser determine the behaviour behind the scenes. Common cases like when
touchmove
is registered by React to thedocument
mean browsers actually make the eventspassive
behind the scenes. This is the ideal case, but in the future we also need ways of controlling this behaviour for future event API work.Validate this works
This PR is a part of a larger part of work which has implementations and tests that validate that the passive/non-passive logic works. In the interests of keeping this PR slim, I've intentionally omitted that code. In further follow up PRs, I'll add that logic to validate the behaviour of this PR. I know this isn't the most ideal case, but it was the best approach I could come up with.
Polyfills
In the future, if we decide to ship an API that makes use of passive events, support for
addEventListener
and specifically the third argument onaddEventListener
where the argument is an object needs to supported. There is a polyfill we could advise people to use or we could try and check for this in DEV and warn. I'm not fully sure on the path we should take yet - maybe it's something we should consider in the future once we're ready to ship said features.