Bug 1873591 - Use SimpleTest.promiseWaitForCondition to tell :active state change...
[gecko.git] / widget / SystemTimeConverter.h
blobaa2a760487e380191f0c19838ebbec0571008139
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #ifndef SystemTimeConverter_h
7 #define SystemTimeConverter_h
9 #include <limits>
10 #include <type_traits>
11 #include "mozilla/TimeStamp.h"
13 namespace mozilla {
15 // Utility class that converts time values represented as an unsigned integral
16 // number of milliseconds from one time source (e.g. a native event time) to
17 // corresponding mozilla::TimeStamp objects.
19 // This class handles wrapping of integer values and skew between the time
20 // source and mozilla::TimeStamp values.
22 // It does this by using an historical reference time recorded in both time
23 // scales (i.e. both as a numerical time value and as a TimeStamp).
25 // For performance reasons, this class is careful to minimize calls to the
26 // native "current time" function (e.g. gdk_x11_server_get_time) since this can
27 // be slow.
28 template <typename Time, typename TimeStampNowProvider = TimeStamp>
29 class SystemTimeConverter {
30 public:
31 SystemTimeConverter()
32 : mReferenceTime(Time(0)),
33 mLastBackwardsSkewCheck(Time(0)),
34 kTimeRange(std::numeric_limits<Time>::max()),
35 kTimeHalfRange(kTimeRange / 2),
36 kBackwardsSkewCheckInterval(Time(2000)) {
37 static_assert(!std::is_signed_v<Time>, "Expected Time to be unsigned");
40 template <typename CurrentTimeGetter>
41 mozilla::TimeStamp GetTimeStampFromSystemTime(
42 Time aTime, CurrentTimeGetter& aCurrentTimeGetter) {
43 TimeStamp roughlyNow = TimeStampNowProvider::Now();
45 // If the reference time is not set, use the current time value to fill
46 // it in.
47 if (mReferenceTimeStamp.IsNull()) {
48 // This sometimes happens when ::GetMessageTime returns 0 for the first
49 // message on Windows.
50 if (!aTime) return roughlyNow;
51 UpdateReferenceTime(aTime, aCurrentTimeGetter);
54 // Check for skew between the source of Time values and TimeStamp values.
55 // We do this by comparing two durations (both in ms):
57 // i. The duration from the reference time to the passed-in time.
58 // (timeDelta in the diagram below)
59 // ii. The duration from the reference timestamp to the current time
60 // based on TimeStamp::Now.
61 // (timeStampDelta in the diagram below)
63 // Normally, we'd expect (ii) to be slightly larger than (i) to account
64 // for the time taken between generating the event and processing it.
66 // If (ii) - (i) is negative then the source of Time values is getting
67 // "ahead" of TimeStamp. We call this "forwards" skew below.
69 // For the reverse case, if (ii) - (i) is positive (and greater than some
70 // tolerance factor), then we may have "backwards" skew. This is often
71 // the case when we have a backlog of events and by the time we process
72 // them, the time given by the system is comparatively "old".
74 // The IsNewerThanTimestamp function computes the equivalent of |aTime| in
75 // the TimeStamp scale and returns that in |timeAsTimeStamp|.
77 // Graphically:
79 // mReferenceTime aTime
80 // Time scale: ........+.......................*........
81 // |--------timeDelta------|
83 // mReferenceTimeStamp roughlyNow
84 // TimeStamp scale: ........+...........................*....
85 // |------timeStampDelta-------|
87 // |---|
88 // roughlyNow-timeAsTimeStamp
90 TimeStamp timeAsTimeStamp;
91 bool newer = IsTimeNewerThanTimestamp(aTime, roughlyNow, &timeAsTimeStamp);
93 // Tolerance when detecting clock skew.
94 static const TimeDuration kTolerance = TimeDuration::FromMilliseconds(30.0);
96 // Check for forwards skew
97 if (newer) {
98 // Make aTime correspond to roughlyNow
99 UpdateReferenceTime(aTime, roughlyNow);
101 // We didn't have backwards skew so don't bother checking for
102 // backwards skew again for a little while.
103 mLastBackwardsSkewCheck = aTime;
105 return roughlyNow;
108 if (roughlyNow - timeAsTimeStamp <= kTolerance) {
109 // If the time between event times and TimeStamp values is within
110 // the tolerance then assume we don't have clock skew so we can
111 // avoid checking for backwards skew for a while.
112 mLastBackwardsSkewCheck = aTime;
113 } else if (aTime - mLastBackwardsSkewCheck > kBackwardsSkewCheckInterval) {
114 aCurrentTimeGetter.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow);
115 mLastBackwardsSkewCheck = aTime;
118 // Finally, calculate the timestamp
119 return timeAsTimeStamp;
122 void CompensateForBackwardsSkew(Time aReferenceTime,
123 const TimeStamp& aLowerBound) {
124 // Check if we actually have backwards skew. Backwards skew looks like
125 // the following:
127 // mReferenceTime
128 // Time: ..+...a...b...c..........................
130 // mReferenceTimeStamp
131 // TimeStamp: ..+.....a.....b.....c....................
133 // Converted
134 // time: ......a'..b'..c'.........................
136 // What we need to do is bring mReferenceTime "forwards".
138 // Suppose when we get (c), we detect possible backwards skew and trigger
139 // an async request for the current time (which is passed in here as
140 // aReferenceTime).
142 // We end up with something like the following:
144 // mReferenceTime aReferenceTime
145 // Time: ..+...a...b...c...v......................
147 // mReferenceTimeStamp
148 // TimeStamp: ..+.....a.....b.....c..........x.........
149 // ^ ^
150 // aLowerBound TimeStamp::Now()
152 // If the duration (aLowerBound - mReferenceTimeStamp) is greater than
153 // (aReferenceTime - mReferenceTime) then we know we have backwards skew.
155 // If that's not the case, then we probably just got caught behind
156 // temporarily.
157 if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, nullptr)) {
158 return;
161 // We have backwards skew; the equivalent TimeStamp for aReferenceTime lies
162 // somewhere between aLowerBound (which was the TimeStamp when we triggered
163 // the async request for the current time) and TimeStamp::Now().
165 // If aReferenceTime was waiting in the event queue for a long time, the
166 // equivalent TimeStamp might be much closer to aLowerBound than
167 // TimeStamp::Now() so for now we just set it to aLowerBound. That's
168 // guaranteed to be at least somewhat of an improvement.
169 UpdateReferenceTime(aReferenceTime, aLowerBound);
172 private:
173 template <typename CurrentTimeGetter>
174 void UpdateReferenceTime(Time aReferenceTime,
175 const CurrentTimeGetter& aCurrentTimeGetter) {
176 Time currentTime = aCurrentTimeGetter.GetCurrentTime();
177 TimeStamp currentTimeStamp = TimeStampNowProvider::Now();
178 Time timeSinceReference = currentTime - aReferenceTime;
179 TimeStamp referenceTimeStamp =
180 currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference);
181 UpdateReferenceTime(aReferenceTime, referenceTimeStamp);
184 void UpdateReferenceTime(Time aReferenceTime,
185 const TimeStamp& aReferenceTimeStamp) {
186 mReferenceTime = aReferenceTime;
187 mReferenceTimeStamp = aReferenceTimeStamp;
190 bool IsTimeNewerThanTimestamp(Time aTime, TimeStamp aTimeStamp,
191 TimeStamp* aTimeAsTimeStamp) {
192 Time timeDelta = aTime - mReferenceTime;
194 // Cast the result to signed 64-bit integer first since that should be
195 // enough to hold the range of values returned by ToMilliseconds() and
196 // the result of converting from double to an integer-type when the value
197 // is outside the integer range is undefined.
198 // Then we do an implicit cast to Time (typically an unsigned 32-bit
199 // integer) which wraps times outside that range.
200 TimeDuration timeStampDelta = (aTimeStamp - mReferenceTimeStamp);
201 int64_t wholeMillis = static_cast<int64_t>(timeStampDelta.ToMilliseconds());
202 Time wrappedTimeStampDelta = wholeMillis; // truncate to unsigned
204 Time timeToTimeStamp = wrappedTimeStampDelta - timeDelta;
205 bool isNewer = false;
206 if (timeToTimeStamp == 0) {
207 // wholeMillis needs no adjustment
208 } else if (timeToTimeStamp < kTimeHalfRange) {
209 wholeMillis -= timeToTimeStamp;
210 } else {
211 isNewer = true;
212 wholeMillis += (-timeToTimeStamp);
214 if (aTimeAsTimeStamp) {
215 *aTimeAsTimeStamp =
216 mReferenceTimeStamp + TimeDuration::FromMilliseconds(wholeMillis);
219 return isNewer;
222 Time mReferenceTime;
223 TimeStamp mReferenceTimeStamp;
224 Time mLastBackwardsSkewCheck;
226 const Time kTimeRange;
227 const Time kTimeHalfRange;
228 const Time kBackwardsSkewCheckInterval;
231 } // namespace mozilla
233 #endif /* SystemTimeConverter_h */