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

Avoid "Member not found exception" in IE10 #7343

Merged
merged 1 commit into from
Jul 31, 2016

Conversation

nhunzaker
Copy link
Contributor

In <= IE10, custom change events raise "Member not found" when accessing 'cancelBubble' . To circumvent this, the SyntheticEvent class now checks for "typeof event.cancelBubble !== 'unknown'". This eliminates this exception and maintains the expected bubbling functionality.

I'd never heard of typeof x === 'unknown' before, but it seems to be a goofy JScript thing. I discovered it in the jQuery issue tracker:

https://bugs.jquery.com/ticket/10004

I've confirmed that this preserves bubbling functionality from IE9-11, Safari, Chrome, and Firefox.

This technically is not a problem on master, but it fixes the issue for 15-stable and I'm sort of curious what other events have this same issue.

@nhunzaker
Copy link
Contributor Author

Looks like this fails because eslint won't allow typeof x === 'unknown' :-/

~/react/src/renderers/dom/client/syntheticEvents/SyntheticEvent.js
  138:46  error  Invalid typeof comparison value  valid-typeof

Any suggestions?

@ghost ghost added the CLA Signed label Jul 23, 2016
@syranide
Copy link
Contributor

typeof x === 'undefined' you mean?

@nhunzaker
Copy link
Contributor Author

nhunzaker commented Jul 25, 2016

I do mean typeof x === 'unknown'. This seems to be a browser quirk with IE:

https://bytes.com/topic/javascript/answers/515951-typeof-operator-returns-unknown-instead-undefined

I tried to get around this by doing "cancelBubble" in event, but that explodes in IE9.

@syranide
Copy link
Contributor

What the... perhaps eslint is ok with it if you assign "unknown" to a constant and compare with that instead? If the warning can't be disabled (make an issue for eslint perhaps?).

@syranide
Copy link
Contributor

I'm seeing conflicting information on this from every source (what and which browsers are affected) and I haven't been able to find any code to actually reproduce it. Do you have it?

@nhunzaker
Copy link
Contributor Author

It's definitely bizarre. This issue was filed in #7320. I realize that I never linked it up.

The following gist will reproduce the problem:

https://gist.github.com/nhunzaker/79d48674f543a3bf0977667c2513e9ac

IE9

Likewise, entering text in the "Stops propagation" here will raise the same exception:

screen shot 2016-07-25 at 7 40 28 am

IE10

Entering text in the "Stops propagation" input will raise a "Member not found" exception:

screen shot 2016-07-25 at 7 37 06 am

IE 11, Edge 13, Edge 14

Gets it right:

screen shot 2016-07-25 at 7 42 16 am


I haven't figured out a way to reproduce this outside of React. As you've mentioned, the documentation is fuzzy at best. I think there might be something going on in LinkedValueUtils.

@nhunzaker
Copy link
Contributor Author

Okay. Here we go. For whatever reason in IE9-10, a ton of properties are not found in the event backing the SyntheticEvent dispatching change:

screen shot 2016-07-25 at 8 14 00 am

I wish I could provide a spec on how IE treats these events. When I create a custom event:

>> var evt = document.createEvent("Event");
evt.initEvent("change", true, false);
console.log(evt.cancelBubble); 
 false 

Seems fine. I totally get the aversion to committing a hack like typeof x === 'unknown' until the problem is better understood. I'm going to keep digging in

@syranide
Copy link
Contributor

From some experimentation it seems its the MSEventObj base class that has this problematic behavior. So event.nativeEvent instanceof MSEventObj actually seems to identify these events correctly, if there are false positives or vice versa I don't really know (but I doubt it).

Also, FYI for anyone trying to understand this. This is because we're listening to the propertychange event, which I guess is special for some reason.

Seems fine. I totally get the aversion to committing a hack like typeof x === 'unknown' until the problem is better understood. I'm going to keep digging in

IMHO, I don't really see an issue, it works and it's precise. If eslint is the only issue then working around it seems fine to me at least, either by comparing to a constant (if that works) or putting in a separate file and disabling eslint in that file.

On another note (and I may be totally off here), but I'm curious why we even stop propagation of native events at all, they're all document listeners so unless I'm mistaken nothing good comes from cancelling them in the first place? We might still want to change that in the future, so it makes sense to fix it... it just struck me as odd.

@mxstbr
Copy link
Contributor

mxstbr commented Jul 25, 2016

or putting in a separate file and disabling eslint in that file.

Chiming in, no need for a separate file, this should work:

// eslint-disable-next-line valid-typeof
if (typeof x === 'unknown') {
  /* … */
}

(if valid-typeof is the correct rulename)

@ghost ghost added the CLA Signed label Jul 25, 2016
@nhunzaker
Copy link
Contributor Author

@mxstbr Beat me to it! Done.

@alexzherdev
Copy link
Contributor

For completeness:
if (typeof x === 'unknown') { // eslint-disable-line valid-typeof

@nhunzaker
Copy link
Contributor Author

On another note (and I may be totally off here), but I'm curious why we even stop propagation of native events at all

Did a quick test, and my initial impression is that we don't need to invoke them directly at all. It seems like the only reason to do it would be so that the native event reference indicates that stopPropagation has been applied.

@ghost ghost added the CLA Signed label Jul 25, 2016
} else {
} else if (typeof event.cancelBubble !== 'unknown') { // eslint-disable-line valid-typeof
// <= IE10 throws a "Member not found" error if the cancelBubble
// attribute is accessed from an onChange event. A typeof check of "unknown"
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps clarify that this is because of "propertychange" and not because of actual change events.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it:

      // The ChangeEventPlugin registers a "propertychange" event for
      // IE. This event does not support bubbling or cancelling, and
      // any references to cancelBubble throw "Member not found".  A
      // typeof check of "unknown" circumvents this issue (and is also
      // IE specific).

@ghost ghost added the CLA Signed label Jul 25, 2016
'change' custom events raise "Member not found" in <= IE10. To
circumvent this, the SyntheticEvent class now checks for "typeof
event.cancelBubble !== 'unknown'". This eliminates this exception and
maintains the expected bubbling functionality.

Addresses facebook#7320.
@ghost ghost added the CLA Signed label Jul 25, 2016
@nhunzaker
Copy link
Contributor Author

Alright. I think I'm set!

@nhunzaker
Copy link
Contributor Author

Anything else to follow up on here?

@@ -135,9 +135,15 @@ Object.assign(SyntheticEvent.prototype, {

if (event.stopPropagation) {
event.stopPropagation();
} else {
} else if (typeof event.cancelBubble !== 'unknown') { // eslint-disable-line valid-typeof
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm assuming this fails the lint line length rule?

Copy link
Contributor Author

@nhunzaker nhunzaker Jul 29, 2016

Choose a reason for hiding this comment

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

Technically no, though it's against the React style guide all the same.

ESLint seems to struggle with else if. The only other valid option I can manage is:

if (event.stopPropagation) {
  event.stopPropagation();

  /* eslint-disable valid-typeof */
} else if (typeof event.cancelBubble !== 'unknown') {
  /* eslint-enable valid-typeof */
}

Or we could flip the if to place typeof event.cancelBubble before the event.stopPropagation check.

I know this is annoying and silly. Thank you for your patience on this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm care-face on this (not my decision). If it's a problem just leave it as is and they'll complain if need be.

@syranide
Copy link
Contributor

syranide commented Jul 29, 2016

I fail to see how this can be harmful (EDIT: The PR that is). The only thing that strikes me as odd I guess is that this doesn't really solve the problem, it just prevents an error right? So technically, you can stop propagation of onchange with this, but in IE<=10 it will not actually do it?

It might make sense to throw a warning or something as well perhaps?

@nhunzaker
Copy link
Contributor Author

It works as expected. The bubbling is handled by the synthetic event system. Best I can tell, the whole reason for the stopPropagation logic is for interaction with code that might be listening to to the DOM nodes themselves (from a lifecycle hook or something).

Given the following code:

function onChange (event) {
  event.stopPropagation()
}

ReactDOM.render((
  <div onChange={ () => alert('Noooo!') }>
    <input defaultValue="hey" onChange={ onChange } />
  </div>
),document.getElementById('container'))

It behaves as one would expect. The onChange callback on the parent is never fired. If you remove event.stopPropagation() in the onChange callback, you'll see the alert. This is consistent from IE9 to Edge, Chrome, Safari, and Firefox.

@syranide
Copy link
Contributor

syranide commented Jul 29, 2016

The bubbling is handled by the synthetic event system.

Oh right right, that still happens. Yeah, then I have nothing to object to :)

👍

@syranide
Copy link
Contributor

syranide commented Jul 29, 2016

cc @zpao @spicyj any objections?

@ghost ghost added the CLA Signed label Jul 29, 2016
@sophiebits sophiebits added this to the 15-next milestone Jul 31, 2016
@sophiebits sophiebits merged commit 2823dfc into facebook:master Jul 31, 2016
@sophiebits
Copy link
Collaborator

Okay, thanks!

@nhunzaker
Copy link
Contributor Author

Hizzah!

@gmp
Copy link

gmp commented Aug 2, 2016

I believe this only fixes half the problem. This same logic will also need to be added to preventDefault as well, correct? I'm running into this problem now and adding similar logic to the preventDefault handler as well fixes the issue.

@nhunzaker
Copy link
Contributor Author

@g-palmer Cool. I can send out a PR with this change, unless you would like to.

gmp pushed a commit to gmp/react that referenced this pull request Aug 3, 2016
Explanation and similar change as facebook#7343

Addresses facebook#7320
@gmp
Copy link

gmp commented Aug 3, 2016

@nhunzaker If you don't mind, I'd like to go ahead and make the change real quick

@nhunzaker
Copy link
Contributor Author

I don't mind. Go for it!

gmp pushed a commit to gmp/react that referenced this pull request Aug 3, 2016
Explanation, discussion, and similar change as facebook#7343

Addresses facebook#7320
gmp added a commit to gmp/react that referenced this pull request Aug 12, 2016
Explanation, discussion, and similar change as facebook#7343

Addresses facebook#7320
gmp added a commit to gmp/react that referenced this pull request Aug 12, 2016
Explanation, discussion, and similar change as facebook#7343

Addresses facebook#7320
@zpao zpao modified the milestones: 15.3.1, 15-next Aug 12, 2016
zpao pushed a commit that referenced this pull request Aug 12, 2016
'change' custom events raise "Member not found" in <= IE10. To
circumvent this, the SyntheticEvent class now checks for "typeof
event.cancelBubble !== 'unknown'". This eliminates this exception and
maintains the expected bubbling functionality.

Addresses #7320.
(cherry picked from commit 2823dfc)
aweary pushed a commit that referenced this pull request Aug 19, 2016
Explanation, discussion, and similar change as #7343

Addresses #7320
zpao pushed a commit that referenced this pull request Sep 15, 2016
Explanation, discussion, and similar change as #7343

Addresses #7320
(cherry picked from commit a874196)
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