Skip to content

Conversation

steveluscher
Copy link
Contributor

@steveluscher steveluscher commented Dec 12, 2019

The event that we store in lastNativeEvent has a reference to its related DOM nodes which has a reference to some FiberNode instances. When a component unmounts we don't want to retain any of that.

This PR stores the lastNativeEvents that we've seen in a WeakSet so as not to cause them to be retained. Where WeakSet is not supported, we use a shim that does what the old thing used to do (and leaks like it too).

Here's a snapshot of one of these leaks in the Chrome Memory profiler. tk is FiberNode and tl is lastNativeEvent.

Screen Shot 2019-12-11 at 5 54 58 PM

This aims to address #17580. Credit for the idea to use WeakSet goes to @bgirard!

@codesandbox-ci
Copy link

codesandbox-ci bot commented Dec 12, 2019

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit e26761e:

Sandbox Source
agitated-nobel-jg253 Configuration

@sizebot
Copy link

sizebot commented Dec 12, 2019

No significant bundle size changes to report.

Size changes (stable)

Generated by 🚫 dangerJS against e26761e

@sizebot
Copy link

sizebot commented Dec 12, 2019

No significant bundle size changes to report.

Size changes (experimental)

Generated by 🚫 dangerJS against e26761e

return [leave];
}
lastNativeEvent = nativeEvent;
if (seenNativeEvents instanceof Set) {
seenNativeEvents.clear();
Copy link
Contributor Author

@steveluscher steveluscher Dec 12, 2019

Choose a reason for hiding this comment

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

This replicates the ‘overwrite’ behavior of lastNativeEvent = nativeEvent for clients that don't support WeakSet.

lastNativeEvent = nativeEvent;
if (seenNativeEvents instanceof Set) {
seenNativeEvents.clear();
}
Copy link
Contributor

@bgirard bgirard Dec 12, 2019

Choose a reason for hiding this comment

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

Maybe:

seenNativeEvents = typeof WeakSet === 'function' ? new WeakSet() : new Set();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When WeakSet is supported then is there any harm to letting the set ‘grow’ infinitely? Something's presence in the set will be contingent on it being held on to somewhere else in your app, right?

When WeakSet is not supported, I wonder if it's more efficient to call clear() rather than to construct a new Set.

Copy link
Contributor

Choose a reason for hiding this comment

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

There's a small one. If you're leaking the thing you're holding a weak reference to, or retaining them on purpose, then this set is growing. If we only care about the last thing it's best to only track the last thing. It's unfortunate you can't simply clear a WeakSet.

…nging to long-ago unmounted DOM nodes. Addresses #17580.
@steveluscher steveluscher requested a review from trueadm December 12, 2019 02:25
if (nativeEvent === lastNativeEvent) {
lastNativeEvent = null;
if (seenNativeEvents.has(nativeEvent)) {
if (seenNativeEvents instanceof Set) {
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 get away with not performing the instanceof check each time? We should know at the point seenNativeEvents is defined whether or not we're working with a Set or WeakSet

@sebmarkbage
Copy link
Collaborator

In generally we use expandos instead of weaksets as they've had better perf characteristics and doesn't require fallbacks implementations. E.g. event._reactId = i++; lastSeenID = event._reactId;.

However, in this case, we really shouldn't need to mess with the native events. It's just poorly structured code.

One solution would be to just pass a boolean or eventSystemFlags here that signifies that this is the first processed ancestor tree.

runExtractedPluginEventsInBatch(

@trueadm
Copy link
Contributor

trueadm commented Dec 12, 2019

@sebmarkbage That's exactly what I did in my follow up PR! I noticed you added the flags to the EventPlugins, which made this fix slightly more obvious.

Follow up PR: #17585. Thank you @steveluscher for taking the time to investigate this!

@steveluscher
Copy link
Contributor Author

Yay! Thanks @trueadm!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants