Bug 1376625 - Updating meta data for newly added tests r=ato
[gecko.git] / widget / SystemTimeConverter.h
blob5eb201fc86d3f2202768708280c100814c9d08fd
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 "mozilla/TimeStamp.h"
11 #include "mozilla/TypeTraits.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>
29 class SystemTimeConverter {
30 public:
31 SystemTimeConverter()
32 : mReferenceTime(Time(0))
33 , mReferenceTimeStamp() // Initializes to the null timestamp
34 , mLastBackwardsSkewCheck(Time(0))
35 , kTimeRange(std::numeric_limits<Time>::max())
36 , kTimeHalfRange(kTimeRange / 2)
37 , kBackwardsSkewCheckInterval(Time(2000))
39 static_assert(!IsSigned<Time>::value, "Expected Time to be unsigned");
42 template <typename CurrentTimeGetter>
43 mozilla::TimeStamp
44 GetTimeStampFromSystemTime(Time aTime,
45 CurrentTimeGetter& aCurrentTimeGetter) {
46 // If the reference time is not set, use the current time value to fill
47 // it in.
48 if (mReferenceTimeStamp.IsNull()) {
49 UpdateReferenceTime(aTime, aCurrentTimeGetter);
51 TimeStamp roughlyNow = TimeStamp::Now();
53 // Check for skew between the source of Time values and TimeStamp values.
54 // We do this by comparing two durations (both in ms):
56 // i. The duration from the reference time to the passed-in time.
57 // (timeDelta in the diagram below)
58 // ii. The duration from the reference timestamp to the current time
59 // based on TimeStamp::Now.
60 // (timeStampDelta in the diagram below)
62 // Normally, we'd expect (ii) to be slightly larger than (i) to account
63 // for the time taken between generating the event and processing it.
65 // If (ii) - (i) is negative then the source of Time values is getting
66 // "ahead" of TimeStamp. We call this "forwards" skew below.
68 // For the reverse case, if (ii) - (i) is positive (and greater than some
69 // tolerance factor), then we may have "backwards" skew. This is often
70 // the case when we have a backlog of events and by the time we process
71 // them, the time given by the system is comparatively "old".
73 // We call the absolute difference between (i) and (ii), "deltaFromNow".
75 // Graphically:
77 // mReferenceTime aTime
78 // Time scale: ........+.......................*........
79 // |--------timeDelta------|
81 // mReferenceTimeStamp roughlyNow
82 // TimeStamp scale: ........+...........................*....
83 // |------timeStampDelta-------|
85 // |---|
86 // deltaFromNow
88 Time deltaFromNow;
89 bool newer = IsTimeNewerThanTimestamp(aTime, roughlyNow, &deltaFromNow);
91 // Tolerance when detecting clock skew.
92 static const Time kTolerance = 30;
94 // Check for forwards skew
95 if (newer) {
96 // Make aTime correspond to roughlyNow
97 UpdateReferenceTime(aTime, roughlyNow);
99 // We didn't have backwards skew so don't bother checking for
100 // backwards skew again for a little while.
101 mLastBackwardsSkewCheck = aTime;
103 return roughlyNow;
106 if (deltaFromNow <= kTolerance) {
107 // If the time between event times and TimeStamp values is within
108 // the tolerance then assume we don't have clock skew so we can
109 // avoid checking for backwards skew for a while.
110 mLastBackwardsSkewCheck = aTime;
111 } else if (aTime - mLastBackwardsSkewCheck > kBackwardsSkewCheckInterval) {
112 aCurrentTimeGetter.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow);
113 mLastBackwardsSkewCheck = aTime;
116 // Finally, calculate the timestamp
117 return roughlyNow - TimeDuration::FromMilliseconds(deltaFromNow);
120 void
121 CompensateForBackwardsSkew(Time aReferenceTime,
122 const TimeStamp &aLowerBound) {
123 // Check if we actually have backwards skew. Backwards skew looks like
124 // the following:
126 // mReferenceTime
127 // Time: ..+...a...b...c..........................
129 // mReferenceTimeStamp
130 // TimeStamp: ..+.....a.....b.....c....................
132 // Converted
133 // time: ......a'..b'..c'.........................
135 // What we need to do is bring mReferenceTime "forwards".
137 // Suppose when we get (c), we detect possible backwards skew and trigger
138 // an async request for the current time (which is passed in here as
139 // aReferenceTime).
141 // We end up with something like the following:
143 // mReferenceTime aReferenceTime
144 // Time: ..+...a...b...c...v......................
146 // mReferenceTimeStamp
147 // TimeStamp: ..+.....a.....b.....c..........x.........
148 // ^ ^
149 // aLowerBound TimeStamp::Now()
151 // If the duration (aLowerBound - mReferenceTimeStamp) is greater than
152 // (aReferenceTime - mReferenceTime) then we know we have backwards skew.
154 // If that's not the case, then we probably just got caught behind
155 // temporarily.
156 Time delta;
157 if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, &delta)) {
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
175 UpdateReferenceTime(Time aReferenceTime,
176 const CurrentTimeGetter& aCurrentTimeGetter) {
177 Time currentTime = aCurrentTimeGetter.GetCurrentTime();
178 TimeStamp currentTimeStamp = TimeStamp::Now();
179 Time timeSinceReference = currentTime - aReferenceTime;
180 TimeStamp referenceTimeStamp =
181 currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference);
182 UpdateReferenceTime(aReferenceTime, referenceTimeStamp);
185 void
186 UpdateReferenceTime(Time aReferenceTime,
187 const TimeStamp& aReferenceTimeStamp) {
188 mReferenceTime = aReferenceTime;
189 mReferenceTimeStamp = aReferenceTimeStamp;
192 bool
193 IsTimeNewerThanTimestamp(Time aTime, TimeStamp aTimeStamp, Time* aDelta)
195 Time timeDelta = aTime - mReferenceTime;
197 // Cast the result to signed 64-bit integer first since that should be
198 // enough to hold the range of values returned by ToMilliseconds() and
199 // the result of converting from double to an integer-type when the value
200 // is outside the integer range is undefined.
201 // Then we do an implicit cast to Time (typically an unsigned 32-bit
202 // integer) which wraps times outside that range.
203 Time timeStampDelta =
204 static_cast<int64_t>((aTimeStamp - mReferenceTimeStamp).ToMilliseconds());
206 Time timeToTimeStamp = timeStampDelta - timeDelta;
207 bool isNewer = false;
208 if (timeToTimeStamp == 0) {
209 *aDelta = 0;
210 } else if (timeToTimeStamp < kTimeHalfRange) {
211 *aDelta = timeToTimeStamp;
212 } else {
213 isNewer = true;
214 *aDelta = timeDelta - timeStampDelta;
217 return isNewer;
220 Time mReferenceTime;
221 TimeStamp mReferenceTimeStamp;
222 Time mLastBackwardsSkewCheck;
224 const Time kTimeRange;
225 const Time kTimeHalfRange;
226 const Time kBackwardsSkewCheckInterval;
229 } // namespace mozilla
231 #endif /* SystemTimeConverter_h */