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 a regression that caused us to listen to extra events at the top #12878

Merged
merged 9 commits into from
May 22, 2018

Conversation

gaearon
Copy link
Collaborator

@gaearon gaearon commented May 22, 2018

I think this should fix the regression introduced in #12629 and that @sophiebits has patched over in #12877.

The first three commits are small refactors (I can remove them but I found the final form easier to read with these changes). The last one fixes the logic.

I'll need to work on some way to test this and verify it’s correct. Here’s the methodology with which I arrived at this solution:

  1. Take a list of all DOM event types in master

  2. For each type, record whether it exists in either of two mappings that were deleted in #12629: topLevelTypes or mediaEventTypes. Some types were new in master (pointer events) which I treat as if they were in topLevelTypes.

After doing this categorization, I ended up with four lists:

NOT top AND NOT media

  • TOP_INVALID
  • TOP_RESET
  • TOP_SUBMIT

NOT top AND media

  • TOP_ABORT
  • TOP_CAN_PLAY
  • TOP_CAN_PLAY_THROUGH
  • TOP_DURATION_CHANGE
  • TOP_EMPTIED
  • TOP_ENCRYPTED
  • TOP_ENDED
  • TOP_ERROR
  • TOP_LOADED_DATA
  • TOP_LOADED_METADATA
  • TOP_PAUSE
  • TOP_PLAY
  • TOP_PLAYING
  • TOP_PROGRESS
  • TOP_RATE_CHANGE
  • TOP_SEEKED
  • TOP_SEEKING
  • TOP_STALLED
  • TOP_SUSPEND
  • TOP_TIME_UPDATE
  • TOP_VOLUME_CHANGE
  • TOP_WAITING

top AND media

  • TOP_LOAD_START

top AND NOT media

  • TOP_ANIMATION_END
  • TOP_ANIMATION_ITERATION
  • TOP_ANIMATION_START
  • TOP_BLUR
  • TOP_CANCEL
  • TOP_CHANGE
  • TOP_CLICK
  • TOP_CLOSE
  • TOP_COMPOSITION_END
  • TOP_COMPOSITION_START
  • TOP_COMPOSITION_UPDATE
  • TOP_CONTEXT_MENU
  • TOP_COPY
  • TOP_CUT
  • TOP_DOUBLE_CLICK
  • TOP_DRAG
  • TOP_DRAG_END
  • TOP_DRAG_ENTER
  • TOP_DRAG_EXIT
  • TOP_DRAG_LEAVE
  • TOP_DRAG_OVER
  • TOP_DRAG_START
  • TOP_DROP
  • TOP_FOCUS
  • TOP_GOT_POINTER_CAPTURE
  • TOP_INPUT
  • TOP_KEY_DOWN
  • TOP_KEY_PRESS
  • TOP_KEY_UP
  • TOP_LOAD
  • TOP_LOST_POINTER_CAPTURE
  • TOP_MOUSE_DOWN
  • TOP_MOUSE_MOVE
  • TOP_MOUSE_OUT
  • TOP_MOUSE_OVER
  • TOP_MOUSE_UP
  • TOP_PASTE
  • TOP_POINTER_CANCEL
  • TOP_POINTER_DOWN
  • TOP_POINTER_ENTER
  • TOP_POINTER_LEAVE
  • TOP_POINTER_MOVE
  • TOP_POINTER_OUT
  • TOP_POINTER_OVER
  • TOP_POINTER_UP
  • TOP_SCROLL
  • TOP_SELECTION_CHANGE
  • TOP_TEXT_INPUT
  • TOP_TOGGLE
  • TOP_TOUCH_CANCEL
  • TOP_TOUCH_END
  • TOP_TOUCH_MOVE
  • TOP_TOUCH_START
  • TOP_TRANSITION_END
  • TOP_WHEEL

From these lists, it follows that:

  • “Media events should not be listened to on top level” is generally true.
  • TOP_LOAD_START is an exception to the previous rule: even though it’s a media event, we listen to it on the top level.
  • There are three additional events (TOP_INVALID, TOP_RESET, and TOP_SUBMIT) that are neither in the media nor the top whitelist. So we should exclude them too.

That’s reflected in my switch structure:

      switch (dependency) {
        // ... other special cases ...
        case TOP_INVALID:
        case TOP_SUBMIT:
        case TOP_RESET:
          // We listen to them on the target DOM elements.
          // Some of them bubble so we don't want them to fire twice.
          break;
        case TOP_LOAD_START:
          // Even though it's a media event, it also exists on <img>.
          // So we need to listen at top level, unlike other media events.
          trapBubbledEvent(dependency, mountAt);
          break;
        default:
          // By default, listen on the top level to all non-media events.
          const isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
          if (!isMediaEvent) {
            trapBubbledEvent(dependency, mountAt);
          }
          break;
      }

Note that I’m introducing an indexOf lookup there. I think it’s fine because this code only runs once per event, and indexOf is very fast on small arrays (mediaEventTypes has 23 elements). So I wouldn’t expect this to regress anything. But I can turn it into an object lookup if needed.

gaearon added 4 commits May 22, 2018 15:04
I find it a bit easier to follow than many comparison conditions.
They are being assigned below anyway. This is likely a copypasta from the FOCUS/BLUR special case (which *does* need those assignments).
Their logic is identical.
@pull-bot
Copy link

pull-bot commented May 22, 2018

ReactDOM: size: 0.0%, gzip: -0.0%

Details of bundled changes.

Comparing: ad27845...53d0d36

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js +0.1% +0.2% 618.64 KB 619.24 KB 144.25 KB 144.49 KB UMD_DEV
react-dom.production.min.js 0.0% -0.0% 93.79 KB 93.8 KB 30.36 KB 30.35 KB UMD_PROD
react-dom.development.js +0.1% +0.2% 603.01 KB 603.61 KB 140.26 KB 140.49 KB NODE_DEV
react-dom.production.min.js 0.0% -0.0% 92.29 KB 92.3 KB 29.34 KB 29.33 KB NODE_PROD
react-dom-test-utils.development.js +0.5% +0.8% 45.04 KB 45.27 KB 12.37 KB 12.47 KB UMD_DEV
react-dom-test-utils.development.js +0.6% +0.9% 39.9 KB 40.13 KB 10.94 KB 11.04 KB NODE_DEV
ReactDOM-dev.js +0.1% +0.2% 624.79 KB 625.39 KB 142.89 KB 143.13 KB FB_WWW_DEV
ReactDOM-prod.js -0.0% -0.1% 274.02 KB 273.93 KB 51.6 KB 51.57 KB FB_WWW_PROD
ReactTestUtils-dev.js +0.6% +0.9% 41.29 KB 41.52 KB 11.14 KB 11.24 KB FB_WWW_DEV

Generated by 🚫 dangerJS

@gaearon
Copy link
Collaborator Author

gaearon commented May 22, 2018

Hmm. I started looking into special cases and it looks like onLoadStart on <img> never worked at all, even in supported browsers (Firefox). Even on 15 or 0.14.

gaearon added 2 commits May 22, 2018 17:04
The case I added was wrong (just like including this event in the top level list was always wrong).

In fact it never bubbles, even for <img>. And since we don't special case it in the <img> event
attachment logic when we create it, we never supported <img onLoadStart> at all.

We could fix it. But Chrome doesn't support it either: https://bugs.chromium.org/p/chromium/issues/detail?id=458851.
Nobody asked us for it yet. And supporting it would require attaching an extra listener to every <img>.

So maybe we don't need it? Let's document the existing state of things.
formRef.current.dispatchEvent(
new Event('submit', {
// https://developer.mozilla.org/en-US/docs/Web/Events/submit
bubbles: true,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Might be nice to have a bubbles-false test for this too if we don't.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

When would it be false?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Old browsers I think. That's why we listen on the element at all.

bubbles: true,
}),
);
expect(handleSubmit).toHaveBeenCalledTimes(1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This failed before, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, it failed before your fix

// Some of them bubble so we don't want them to fire twice.
break;
default:
// By default, listen on the top level to all non-media events.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe add:

// Media events don't bubble so adding the listener wouldn't do anything.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added.

break;
default:
// By default, listen on the top level to all non-media events.
const isNonMediaEvent = mediaEventTypes.indexOf(dependency) === -1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

IMO this would be easier to read as

isMediaEvent
!== -1
if (!isMediaEvent)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Force-pushed the version without the commit that changed this

@gaearon gaearon merged commit e885791 into facebook:master May 22, 2018
@gaearon gaearon deleted the fix-evts branch May 22, 2018 18:50
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.

4 participants