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

Exception in IE 9 due to unsupported High Resolution Time API / getNow race-condition #9632

Closed
felixbuenemann opened this issue Mar 6, 2019 · 6 comments

Comments

@felixbuenemann
Copy link
Contributor

Version

2.6.8

Reproduction link

https://jsfiddle.net/re08ag7z/

Steps to reproduce

When Vue.js is used in IE 9 (or IE 10/11 in IE 9 Emulation Mode), it sometimes crashes with an exception:

[Vue warn]: Error in nextTick: "TypeError: Object doesn't support property or method 'now'"

This seems to be due to the usage of performance.now() which is not supported in IE 9, see:

https://caniuse.com/#feat=high-resolution-time

What is expected?

It should not crash, since IE 9 is a supported browser for Vue 2.x.

What is actually happening?

It crashes sometimes and sometimes it works.


I think the problem is a race-condition in the detection code of getNow:

var getNow = Date.now;
if (inBrowser && getNow() > document.createEvent('Event').timeStamp) {
  getNow = function () { return performance.now(); };
}

It looks like if document.createEvent('Event').timeStamp returns a unix timestamp, the result of this check is unpredictable, and is sometimes true and sometimes false.

Running the following in the IE 11 console when in IE 9 emulation mode sometimes returns true and sometimes false:

Date.now() > document.createEvent('Event').timeStamp

This check can also fail in IE 11 in IE 10 emulation mode, so it probably affects IE 10 as well.

A possible workaround would be to check for something like getNow() > document.createEvent('Event').timeStamp 1000, though that still seems a bit brittle.

In my testing in a Windows 10 VM, the difference for Date.now() - document.createEvent('Event').timeStamp was usually /- 2 ms, but sometimes I also got larger offsets, like Date.now() around 22 ms larger than timeStamp.

This problem could also be influenced by clock correction, when NTP or the VM host corrects the guest time between calls.

Generally comparisons of realtime clocks in computers is error prone and only monotonic timers should be trusted, which is probably one of the reasons why performance.now() was created…

@felixbuenemann
Copy link
Contributor Author

felixbuenemann commented Mar 6, 2019

It looks like this bug also affects IE 11.

In IE 10 and 11 the result of the bug will be different, because they might choose performance.now() when they should not, but it will not cause an exception, because both IE 10 and 11 support the API. However their event timestamps use UNIX epoch.

You can open the linked JS fiddle in IE 11, choose read-only mode and then repeatedly click the "Result" tab and you'll see the result cycle randomly between eg. 1, 0, -1.

@felixbuenemann
Copy link
Contributor Author

I think the following would work in all cases without relying on offsets:

var getNow = Date.now;
if (inBrowser && window.performance && performance.now
  && document.createEvent('Event').timeStamp <= performance.now()) {
  getNow = function () { return performance.now(); };
}

@felixbuenemann
Copy link
Contributor Author

Fix proposed in #9647.

@felixbuenemann
Copy link
Contributor Author

@yyx990803 Please re-open this issue, your fix in da77d6a is not sufficient.

It sidesteps the bug in IE 9, but does not fix it for IE 10 and IE 11.

Please see #9647 for a proper fix.

The problem with your fix is that on IE 10/11 document.createEvent('Event').timeStamp returns a unix timestamp and this is compared to Date.now(). Due to clock skew this comparison can fail:

  • You call Date.now()
  • Clock gets corrected backwards
  • You call document.createEvent('Event').timeStamp

Now the timestamp from Date.now() is bigger than the one from document.createEvent('Event').timeStamp and the detection fails.

This is why the comparison needs to happen with a monotonic timer like performance.now(), because it can never decrease.

@felixbuenemann
Copy link
Contributor Author

felixbuenemann commented Mar 10, 2019

I have submitted a rebased version of PR #9647 in #9667, which switches the comparison to use performance.now() (the part that wasn't merged).

@felixbuenemann
Copy link
Contributor Author

Btw. here is a screenshot from IE 11 running under Windows 10 Pro in VMware Fusion on macOS Mojave that shows how the comparison with Date.now() can fail:

https://www.dropbox.com/s/tx4zw0u3bqhfxnl/Screenshot%202019-03-10%2004.33.26.png?raw=1

yyx990803 added a commit that referenced this issue Mar 19, 2019
fix #9729

This reverts #9667, but also fixes the original issue #9632 by skipping
the check in IE altogether (since all IE use low-res event timestamps).
kiku-jw pushed a commit to kiku-jw/vue that referenced this issue Jun 18, 2019
kiku-jw pushed a commit to kiku-jw/vue that referenced this issue Jun 18, 2019
fix vuejs#9729

This reverts vuejs#9667, but also fixes the original issue vuejs#9632 by skipping
the check in IE altogether (since all IE use low-res event timestamps).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants