Bug 1610630 [wpt PR 21320] - Add meta:timeout=long to FileSystemBaseHandle-postMessag...
[gecko.git] / widget / SystemTimeConverter.h
blobb38f6acee0505b983a45538e310726536dfec349
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
35 mLastBackwardsSkewCheck(Time(0)),
36 kTimeRange(std::numeric_limits<Time>::max()),
37 kTimeHalfRange(kTimeRange / 2),
38 kBackwardsSkewCheckInterval(Time(2000)) {
39 static_assert(!IsSigned<Time>::value, "Expected Time to be unsigned");
42 template <typename CurrentTimeGetter>
43 mozilla::TimeStamp GetTimeStampFromSystemTime(
44 Time aTime, CurrentTimeGetter& aCurrentTimeGetter) {
45 TimeStamp roughlyNow = TimeStamp::Now();
47 // If the reference time is not set, use the current time value to fill
48 // it in.
49 if (mReferenceTimeStamp.IsNull()) {
50 // This sometimes happens when ::GetMessageTime returns 0 for the first
51 // message on Windows.
52 if (!aTime) return roughlyNow;
53 UpdateReferenceTime(aTime, aCurrentTimeGetter);
56 // Check for skew between the source of Time values and TimeStamp values.
57 // We do this by comparing two durations (both in ms):
59 // i. The duration from the reference time to the passed-in time.
60 // (timeDelta in the diagram below)
61 // ii. The duration from the reference timestamp to the current time
62 // based on TimeStamp::Now.
63 // (timeStampDelta in the diagram below)
65 // Normally, we'd expect (ii) to be slightly larger than (i) to account
66 // for the time taken between generating the event and processing it.
68 // If (ii) - (i) is negative then the source of Time values is getting
69 // "ahead" of TimeStamp. We call this "forwards" skew below.
71 // For the reverse case, if (ii) - (i) is positive (and greater than some
72 // tolerance factor), then we may have "backwards" skew. This is often
73 // the case when we have a backlog of events and by the time we process
74 // them, the time given by the system is comparatively "old".
76 // We call the absolute difference between (i) and (ii), "deltaFromNow".
78 // Graphically:
80 // mReferenceTime aTime
81 // Time scale: ........+.......................*........
82 // |--------timeDelta------|
84 // mReferenceTimeStamp roughlyNow
85 // TimeStamp scale: ........+...........................*....
86 // |------timeStampDelta-------|
88 // |---|
89 // deltaFromNow
91 Time deltaFromNow;
92 bool newer = IsTimeNewerThanTimestamp(aTime, roughlyNow, &deltaFromNow);
94 // Tolerance when detecting clock skew.
95 static const Time kTolerance = 30;
97 // Check for forwards skew
98 if (newer) {
99 // Make aTime correspond to roughlyNow
100 UpdateReferenceTime(aTime, roughlyNow);
102 // We didn't have backwards skew so don't bother checking for
103 // backwards skew again for a little while.
104 mLastBackwardsSkewCheck = aTime;
106 return roughlyNow;
109 if (deltaFromNow <= kTolerance) {
110 // If the time between event times and TimeStamp values is within
111 // the tolerance then assume we don't have clock skew so we can
112 // avoid checking for backwards skew for a while.
113 mLastBackwardsSkewCheck = aTime;
114 } else if (aTime - mLastBackwardsSkewCheck > kBackwardsSkewCheckInterval) {
115 aCurrentTimeGetter.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow);
116 mLastBackwardsSkewCheck = aTime;
119 // Finally, calculate the timestamp
120 return roughlyNow - TimeDuration::FromMilliseconds(deltaFromNow);
123 void CompensateForBackwardsSkew(Time aReferenceTime,
124 const TimeStamp& aLowerBound) {
125 // Check if we actually have backwards skew. Backwards skew looks like
126 // the following:
128 // mReferenceTime
129 // Time: ..+...a...b...c..........................
131 // mReferenceTimeStamp
132 // TimeStamp: ..+.....a.....b.....c....................
134 // Converted
135 // time: ......a'..b'..c'.........................
137 // What we need to do is bring mReferenceTime "forwards".
139 // Suppose when we get (c), we detect possible backwards skew and trigger
140 // an async request for the current time (which is passed in here as
141 // aReferenceTime).
143 // We end up with something like the following:
145 // mReferenceTime aReferenceTime
146 // Time: ..+...a...b...c...v......................
148 // mReferenceTimeStamp
149 // TimeStamp: ..+.....a.....b.....c..........x.........
150 // ^ ^
151 // aLowerBound TimeStamp::Now()
153 // If the duration (aLowerBound - mReferenceTimeStamp) is greater than
154 // (aReferenceTime - mReferenceTime) then we know we have backwards skew.
156 // If that's not the case, then we probably just got caught behind
157 // temporarily.
158 Time delta;
159 if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, &delta)) {
160 return;
163 // We have backwards skew; the equivalent TimeStamp for aReferenceTime lies
164 // somewhere between aLowerBound (which was the TimeStamp when we triggered
165 // the async request for the current time) and TimeStamp::Now().
167 // If aReferenceTime was waiting in the event queue for a long time, the
168 // equivalent TimeStamp might be much closer to aLowerBound than
169 // TimeStamp::Now() so for now we just set it to aLowerBound. That's
170 // guaranteed to be at least somewhat of an improvement.
171 UpdateReferenceTime(aReferenceTime, aLowerBound);
174 private:
175 template <typename CurrentTimeGetter>
176 void UpdateReferenceTime(Time aReferenceTime,
177 const CurrentTimeGetter& aCurrentTimeGetter) {
178 Time currentTime = aCurrentTimeGetter.GetCurrentTime();
179 TimeStamp currentTimeStamp = TimeStamp::Now();
180 Time timeSinceReference = currentTime - aReferenceTime;
181 TimeStamp referenceTimeStamp =
182 currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference);
183 UpdateReferenceTime(aReferenceTime, referenceTimeStamp);
186 void UpdateReferenceTime(Time aReferenceTime,
187 const TimeStamp& aReferenceTimeStamp) {
188 mReferenceTime = aReferenceTime;
189 mReferenceTimeStamp = aReferenceTimeStamp;
192 bool IsTimeNewerThanTimestamp(Time aTime, TimeStamp aTimeStamp,
193 Time* aDelta) {
194 Time timeDelta = aTime - mReferenceTime;
196 // Cast the result to signed 64-bit integer first since that should be
197 // enough to hold the range of values returned by ToMilliseconds() and
198 // the result of converting from double to an integer-type when the value
199 // is outside the integer range is undefined.
200 // Then we do an implicit cast to Time (typically an unsigned 32-bit
201 // integer) which wraps times outside that range.
202 Time timeStampDelta = static_cast<int64_t>(
203 (aTimeStamp - mReferenceTimeStamp).ToMilliseconds());
205 Time timeToTimeStamp = timeStampDelta - timeDelta;
206 bool isNewer = false;
207 if (timeToTimeStamp == 0) {
208 *aDelta = 0;
209 } else if (timeToTimeStamp < kTimeHalfRange) {
210 *aDelta = timeToTimeStamp;
211 } else {
212 isNewer = true;
213 *aDelta = timeDelta - timeStampDelta;
216 return isNewer;
219 Time mReferenceTime;
220 TimeStamp mReferenceTimeStamp;
221 Time mLastBackwardsSkewCheck;
223 const Time kTimeRange;
224 const Time kTimeHalfRange;
225 const Time kBackwardsSkewCheckInterval;
228 } // namespace mozilla
230 #endif /* SystemTimeConverter_h */