Knowing the time origin of event's timestamp

1.3k views Asked by At

Recently some browsers started to use High Resolution time stamps for their events. One of the major difference with the previous approach is that this duration isn't relative to the system epoch but to some "time origin" (most of the time approximately the time the document was loaded).

The issue is that I cannot find any reliable way to check if the browser is using high res timestamps or the previous approach.

It is not a problem if only event timestamps are compared, but if one wants to compare event timestamps with handmade timestamps, one needs to know if they should use Date.now() (relative to epoch) or precision.now() (relative to the time origin).

For example, while safari mobile supports the performance api, some versions (e.g. the one in iOS 11.1) hasn't switched their events to High Resolution Time stamps yet. As a result, the following snippet produces nonsensically huge values for difference because start ends being a high res time stamps but not the events' timestamps.

const now = performance ? () => performance.now() : () => Date.now();

let start;

const update = e => {
  if(e) e.preventDefault();
  document.querySelector('#start').innerHTML = start;
  document.querySelector('#event').innerHTML = e ? e.timeStamp : '';
  document.querySelector('#diff').innerHTML = e ? e.timeStamp - start : '';
};

const reset = () => {
  start = now();
  update();
}

reset();

document.querySelector('#reset').addEventListener('click', reset);
document.querySelector('#pad').addEventListener('mousemove', update);
document.querySelector('#pad').addEventListener('touchmove', update);
#pad {
  background-color: #C9DBFA;
  border: 1px solid #778294;
  border-radius: 10px;
  width: 500px;
  height: 100px;
  color: #575E6C;
  padding: .5em .75em;
}

#reset {
  margin: 1em 0;
}
<div id="pad">
  <strong>start time:</strong> <span id="start"></span>
  <br/>
  <strong>event time:</strong> <span id="event"></span>
  <br/>
  <strong>difference:</strong> <span id="diff"></span>
</div>
<button id="reset">reset</button>

Screenshot from an iPad (iOS 11.1): enter image description here

2

There are 2 answers

0
Quentin Roy On BEST ANSWER

I ended up modifying a solution extracted from Majid Valipour's tool. The idea is actually quite similar to digitalbreed's with a bit more safeguards:

const context = global || window || self;

const doEventUseHighResTimeStamps = () => {
  // If the performance API isn't even available, I assume events will not
  // use high res time stamps.
  if ('performance' in context) return false;

  // Check if the browser supports CustomEvents.
  const hasCustomEvent =
    'CustomEvent' in context &&
    (typeof context.CustomEvent === 'function' ||
      context.CustomEvent.toString().indexOf('CustomEventConstructor') > -1);

  // Create an event and compare its timestamps to the value returned by
  // performance.now(). If the event is using old timestamps, its origin
  // is going to be very far and hence, its timestamps will be much much
  // greater than performance.now().
  const testTimeStamp = hasCustomEvent
    ? new context.CustomEvent('test').timeStamp
    : context.document.createEvent('KeyboardEvent').timeStamp;
  return testTimeStamp && testTimeStamp <= performance.now();
};

const context = window;

const doEventUseHighResTimeStamps = () => {
  const hasCustomEvent =
    'CustomEvent' in context &&
    (typeof context.CustomEvent === 'function' ||
      context.CustomEvent.toString().indexOf('CustomEventConstructor') > -1);

  const testTimeStamp = hasCustomEvent
    ? new context.CustomEvent('test').timeStamp
    : context.document.createEvent('KeyboardEvent').timeStamp;
  return testTimeStamp && testTimeStamp <= performance.now();
};

const now = doEventUseHighResTimeStamps() ? () => performance.now() : () => Date.now();

let start;

const update = e => {
  if (e) e.preventDefault();
  document.querySelector('#start').innerHTML = start;
  document.querySelector('#event').innerHTML = e ? e.timeStamp : '';
  document.querySelector('#diff').innerHTML = e ? e.timeStamp - start : '';
};

const reset = () => {
  start = now();
  update();
}

reset();

document.querySelector('#reset').addEventListener('click', reset);
document.querySelector('#pad').addEventListener('mousemove', update);
document.querySelector('#pad').addEventListener('touchmove', update);
#pad {
  background-color: #C9DBFA;
  border: 1px solid #778294;
  border-radius: 10px;
  width: 500px;
  height: 100px;
  color: #575E6C;
  padding: .5em .75em;
}

#reset {
  margin: 1em 0;
}
<div id="pad">
  <strong>start time:</strong> <span id="start"></span>
  <br/>
  <strong>event time:</strong> <span id="event"></span>
  <br/>
  <strong>difference:</strong> <span id="diff"></span>
</div>
<button id="reset">reset</button>

0
digitalbreed On

Assuming that the browser uses the same mechanism consistently for all timestamps, I suggest to simply trigger a dummy event to make the test upfront:

var isPerformanceTimestamp = false;
if (performance) {
    var performanceNow = performance.now();
    document.addEventListener('test-performance', function(evt) {
        isPerformanceTimestamp = evt.timeStamp - performanceNow < 1000; // or whatever threshold you feel comfortable with, it's at least 1530355731395 at the time of this writing ;-)
        document.removeEventListener('test-performance', this);
    });
    var event = new Event('test-performance');
    document.dispatchEvent(event);
}