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

Issue with "performance.now()": Chrome, Firefox, and Safari are not spec compliant on certain platforms #4713

Closed
jonathanmayer opened this issue May 4, 2021 · 4 comments
Labels
Content:WebAPI Web API docs effort: small This task is a small effort. help wanted If you know something about this topic, we would love your help!

Comments

@jonathanmayer
Copy link

jonathanmayer commented May 4, 2021

MDN URL: https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

I've edited this issue a number of times, because monotonic timing in browsers is a deep rabbit hole of standards, implementations, operating system clock APIs, and clock hardware (see w3c/hr-time#115). The material below attempts to accurately and concisely document current browser behavior.

What information was incorrect, unhelpful, or incomplete?

There are several components of the documentation that are outdated or incomplete.

High Resolution Time vs. High Resolution Time Level 2

The semantics of performance.now() changed from the original High Resolution Time spec to the Level 2 spec. In the original spec, performance.now() was relative to performance.timing.navigationStart from the Navigation Timing spec. In the Level 2 spec, performance.now() is relative to performance.timeOrigin, which is defined in the spec.

There are two key differences between these reference times.

  1. The triggering conditions for navigationStart and timeOrigin are a little different. navigationStart timestamps a document fetch or an unload prompt (if any), while timeOrigin timestamps creation of the browsing context (if no prior document), an unload prompt (if any), or the start of navigation (as defined by the HTML spec, which is a few steps before fetch).
  2. navigationStart is set with the system clock, which might not be monotonic and might not be consistent across navigation events. timeOrigin, by contrast, is set with a shared monotonic clock that is defined to be monotonic, consistent across globals, and initially synced to the system clock (e.g., on browser startup). These properties means that comparing timestamps across webpages with the original spec (performance.timing.navigationStart + performance.now()) has clock change risks, while comparing timestamps across webpages with the Level 2 spec (performance.timeOrigin + performance.now()) doesn't have clock change risks.
Ticking During Sleep

The original High Resolution Time spec does not address whether performance.now() should tick during sleep. There was consensus for the Level 2 spec that performance.now() should tick during sleep (see w3c/hr-time#65), but the text of the spec is somewhat ambiguous about sleep behavior and whether that's a normative requirement (see w3c/hr-time#115).

Here's a summary table of how browsers appear to currently handle sleep, where ✅ means performance.now() keeps ticking and 🚫 means it doesn't.

Chrome/Chromium (bug) Firefox (bug) Safari/WebKit (bug)
Windows N/A
macOS 🚫 🚫 🚫
Linux 🚫 🚫 🚫 (except maybe WebKitGTK?)
Android 🚫 🚫 N/A
iOS N/A (WebKit wrapper) N/A (WebKit wrapper) 🚫
performance.timeOrigin in Chrome and Firefox

There is currently a bug in Chrome where performance.timeOrigin is set for each global with the system clock rather than a shared monotonic clock. As a result, comparing High Resolution Time Level 2 timestamps across webpages in Chrome has clock change risks.

There is also currently a bug in Firefox where performance.timeOrigin is set with a monotonic clock that is synced to the system clock once per process, rather than with a shared monotonic clock that is synced to the system clock once on browser startup. The result is the same: comparing High Resolution Time Level 2 timestamps across webpages in Firefox has clock change risks.

Clock Drift on macOS

The current macOS implementation of performance.now() in Chrome/Chromium, Firefox, and Safari/WebKit uses a monotonic clock that does not allow for even small, monotonic adjustments (e.g., NTP oscillator corrections). The result can be clock drift between the system clock and High Resolution Time monotonic clocks. Chrome has a bug open on this issue.

Specific section or headline?

This content probably belongs in the general description of performance.now() because of the significant consequences. Components might also belong on the performance.timeOrigin and DOMHighResTimeStamp pages.

What did you expect to see?

  • The description is for High Resolution Time Level 2, but the example is for the original spec (referencing performance.timing.navigationStart). The page should probably include a description for both versions of the spec, examples for both versions of the spec, and an explanation of how the two versions differ, since the changes are subtle and Safari/WebKit hasn't implemented Level 2 yet.
  • A description of the browser- and platform-specific inconsistencies in sleep behavior, and what that could mean for use cases.
  • A mention of the Chrome and Firefox performance.timeOrigin bugs, and what those could mean for use cases.
  • A mention of the High Resolution Time clock drift behavior on macOS, and what that could mean for use cases.
  • An explicit indication that, while Safari/WebKit does support performance.now(), it has implemented the original High Resolution Time semantics and not the High Resolution Time Level 2 semantics.
  • Further explanation of the High Resolution Time Level 2 shared monotonic clock and per-document monotonic clock concepts would be valuable.

Did you test this? If so, how?

I've confirmed the following behaviors with both testing and code review:

  • Ticking During Sleep
    • Chrome: Windows, macOS, Linux
    • Firefox: Windows, macOS, Linux
    • Safari: macOS
  • Time Origin Set with the System Clock
    • Chrome: macOS and Linux
    • Firefox: macOS and Linux
  • Clock Drift on macOS
    • Chrome: macOS
    • Firefox: macOS
    • Safari: macOS
MDN Content page report details
jonathanmayer added a commit to mozilla-rally/web-science that referenced this issue May 4, 2021
@chrisdavidmills chrisdavidmills added Content:WebAPI Web API docs needs triage Triage needed by staff and/or partners. Automatically applied when an issue is opened. labels May 5, 2021
@jonathanmayer jonathanmayer changed the title Issue with "performance.now()": Chrome is not spec compliant Issue with "performance.now()": Chrome and Firefox are not spec compliant May 6, 2021
@jonathanmayer jonathanmayer changed the title Issue with "performance.now()": Chrome and Firefox are not spec compliant Issue with "performance.now()": Chrome, Firefox, and Safari are not spec compliant on certain platforms May 6, 2021
@sideshowbarker sideshowbarker added the help wanted If you know something about this topic, we would love your help! label Jun 14, 2021
asakusuma added a commit to asakusuma/spaniel-1 that referenced this issue Jun 17, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues.

This change also starts running the tests with and without native intersection observer. Previously we were not running tests with native intersection observer.

w3c/hr-time#115
mdn/content#4713
asakusuma added a commit to asakusuma/spaniel-1 that referenced this issue Jun 18, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues. The native intersection observer uses DOMHighResTimeStamp, so we should use that type wherever possible in calculations.

This change also removes the native-* files, which are superseded by USE_NATIVE_IO and never part of the public API.

Thanks to @xg-wang for pointing out some potential issues affecting one clock and not the other, which could in turn cause asymmetrical bugs:
w3c/hr-time#115
mdn/content#4713
asakusuma added a commit to asakusuma/spaniel-1 that referenced this issue Jul 29, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues. The native intersection observer uses DOMHighResTimeStamp, so we should use that type wherever possible in calculations.

This change also removes the native-* files, which are superseded by USE_NATIVE_IO and never part of the public API.

Thanks to @xg-wang for pointing out some potential issues affecting one clock and not the other, which could in turn cause asymmetrical bugs:
w3c/hr-time#115
mdn/content#4713
asakusuma added a commit to asakusuma/spaniel-1 that referenced this issue Aug 16, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues. The native intersection observer uses DOMHighResTimeStamp, so we should use that type wherever possible in calculations.

This change also removes the native-* files, which are superseded by USE_NATIVE_IO and never part of the public API.

Thanks to @xg-wang for pointing out some potential issues affecting one clock and not the other, which could in turn cause asymmetrical bugs:
w3c/hr-time#115
mdn/content#4713
asakusuma added a commit to asakusuma/spaniel-1 that referenced this issue Aug 16, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues. The native intersection observer uses DOMHighResTimeStamp, so we should use that type wherever possible in calculations.

This change also removes the native-* files, which are superseded by USE_NATIVE_IO and never part of the public API.

Thanks to @xg-wang for pointing out some potential issues affecting one clock and not the other, which could in turn cause asymmetrical bugs:
w3c/hr-time#115
mdn/content#4713
asakusuma added a commit to asakusuma/spaniel-1 that referenced this issue Aug 17, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues. The native intersection observer uses DOMHighResTimeStamp, so we should use that type wherever possible in calculations.

This change also removes the native-* files, which are superseded by USE_NATIVE_IO and never part of the public API.

Thanks to @xg-wang for pointing out some potential issues affecting one clock and not the other, which could in turn cause asymmetrical bugs:
w3c/hr-time#115
mdn/content#4713
asakusuma added a commit to asakusuma/spaniel-1 that referenced this issue Aug 18, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues. The native intersection observer uses DOMHighResTimeStamp, so we should use that type wherever possible in calculations.

This change also removes the native-* files, which are superseded by USE_NATIVE_IO and never part of the public API.

Thanks to @xg-wang for pointing out some potential issues affecting one clock and not the other, which could in turn cause asymmetrical bugs:
w3c/hr-time#115
mdn/content#4713
asakusuma added a commit to asakusuma/spaniel-1 that referenced this issue Aug 18, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues. The native intersection observer uses DOMHighResTimeStamp, so we should use that type wherever possible in calculations.

This change also removes the native-* files, which are superseded by USE_NATIVE_IO and never part of the public API.

Thanks to @xg-wang for pointing out some potential issues affecting one clock and not the other, which could in turn cause asymmetrical bugs:
w3c/hr-time#115
mdn/content#4713
asakusuma added a commit to asakusuma/spaniel-1 that referenced this issue Aug 18, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues. The native intersection observer uses DOMHighResTimeStamp, so we should use that type wherever possible in calculations.

This change also removes the native-* files, which are superseded by USE_NATIVE_IO and never part of the public API.

Thanks to @xg-wang for pointing out some potential issues affecting one clock and not the other, which could in turn cause asymmetrical bugs:
w3c/hr-time#115
mdn/content#4713
asakusuma added a commit to asakusuma/spaniel-1 that referenced this issue Aug 18, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues. The native intersection observer uses DOMHighResTimeStamp, so we should use that type wherever possible in calculations.

This change also removes the native-* files, which are superseded by USE_NATIVE_IO and never part of the public API.

Thanks to @xg-wang for pointing out some potential issues affecting one clock and not the other, which could in turn cause asymmetrical bugs:
w3c/hr-time#115
mdn/content#4713
asakusuma added a commit to linkedin/spaniel that referenced this issue Aug 18, 2021
Previously, we were combining Date.now() and DOMHighResTimeStamp values to calculate duration. These two value types use different clocks, so we should avoid combining the two value types where possible, since using two different clocks leaves us vulnerable to asymetrical issues. The native intersection observer uses DOMHighResTimeStamp, so we should use that type wherever possible in calculations.

This change also removes the native-* files, which are superseded by USE_NATIVE_IO and never part of the public API.

Thanks to @xg-wang for pointing out some potential issues affecting one clock and not the other, which could in turn cause asymmetrical bugs:
w3c/hr-time#115
mdn/content#4713
dbarnespaychex added a commit to paychex/core that referenced this issue Sep 17, 2021
Based on mdn/content#4713, change Tracker so
it uses performance.timeOrigin + performance.now() for absolute time
where available, falling back to navigationStart + performance.now()
where available, or Date#getTime where necessary. Browsers may still
have bugs in how their clocks behave while frozen but this code is the
best approach possible at the moment.
@sideshowbarker sideshowbarker added needs triage Triage needed by staff and/or partners. Automatically applied when an issue is opened. effort: small This task is a small effort. and removed needs triage Triage needed by staff and/or partners. Automatically applied when an issue is opened. labels Jan 4, 2022
@teoli2003 teoli2003 reopened this May 29, 2022
@github-actions github-actions bot added the needs triage Triage needed by staff and/or partners. Automatically applied when an issue is opened. label May 29, 2022
@sideshowbarker sideshowbarker removed the needs triage Triage needed by staff and/or partners. Automatically applied when an issue is opened. label May 30, 2022
@Elchi3
Copy link
Member

Elchi3 commented Jan 2, 2023

Thanks for the detailed write up, I will look into updating the page.

Note to self: Another thing to mention on the performance.now page: coarsening based on cross-origin isolation status per https://chromestatus.com/feature/6497206758539264

@Elchi3
Copy link
Member

Elchi3 commented Mar 6, 2023

I've opened #25107. A while ago I also improved the timeOrigin page in #23063.

@jonathanmayer would you mind taking a look and let me know if these updates are sufficient enough? Would be happy to work with you on more updates if needed.

@Elchi3
Copy link
Member

Elchi3 commented Apr 18, 2023

I've now also added a guide page at https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/High_precision_timing

Would appreciate a review on that one, too, @jonathanmayer.

(This issue will probably be closed if there is no further response)

@Josh-Cena
Copy link
Member

I'm going to close this assuming it's fixed :)

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 1, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Content:WebAPI Web API docs effort: small This task is a small effort. help wanted If you know something about this topic, we would love your help!
Projects
None yet
Development

No branches or pull requests

6 participants