/src/mozilla-central/layout/base/nsRefreshDriver.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | /* |
8 | | * Code to notify things that animate before a refresh, at an appropriate |
9 | | * refresh rate. (Perhaps temporary, until replaced by compositor.) |
10 | | * |
11 | | * Chrome and each tab have their own RefreshDriver, which in turn |
12 | | * hooks into one of a few global timer based on RefreshDriverTimer, |
13 | | * defined below. There are two main global timers -- one for active |
14 | | * animations, and one for inactive ones. These are implemented as |
15 | | * subclasses of RefreshDriverTimer; see below for a description of |
16 | | * their implementations. In the future, additional timer types may |
17 | | * implement things like blocking on vsync. |
18 | | */ |
19 | | |
20 | | #include "nsRefreshDriver.h" |
21 | | |
22 | | #ifdef XP_WIN |
23 | | #include <windows.h> |
24 | | // mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have |
25 | | // to manually include it |
26 | | #include <mmsystem.h> |
27 | | #include "WinUtils.h" |
28 | | #endif |
29 | | |
30 | | #include "mozilla/AnimationEventDispatcher.h" |
31 | | #include "mozilla/ArrayUtils.h" |
32 | | #include "mozilla/AutoRestore.h" |
33 | | #include "mozilla/IntegerRange.h" |
34 | | #include "mozilla/dom/FontTableURIProtocolHandler.h" |
35 | | #include "nsITimer.h" |
36 | | #include "nsLayoutUtils.h" |
37 | | #include "nsPresContext.h" |
38 | | #include "nsComponentManagerUtils.h" |
39 | | #include "mozilla/Logging.h" |
40 | | #include "nsAutoPtr.h" |
41 | | #include "nsIDocument.h" |
42 | | #include "nsIXULRuntime.h" |
43 | | #include "jsapi.h" |
44 | | #include "nsContentUtils.h" |
45 | | #include "mozilla/PendingAnimationTracker.h" |
46 | | #include "mozilla/PendingFullscreenEvent.h" |
47 | | #include "mozilla/Preferences.h" |
48 | | #include "nsViewManager.h" |
49 | | #include "GeckoProfiler.h" |
50 | | #include "nsNPAPIPluginInstance.h" |
51 | | #include "mozilla/dom/Event.h" |
52 | | #include "mozilla/dom/Performance.h" |
53 | | #include "mozilla/dom/Selection.h" |
54 | | #include "mozilla/dom/WindowBinding.h" |
55 | | #include "mozilla/RestyleManager.h" |
56 | | #include "Layers.h" |
57 | | #include "imgIContainer.h" |
58 | | #include "mozilla/dom/ScriptSettings.h" |
59 | | #include "nsDocShell.h" |
60 | | #include "nsISimpleEnumerator.h" |
61 | | #include "nsJSEnvironment.h" |
62 | | #include "mozilla/Telemetry.h" |
63 | | #include "gfxPrefs.h" |
64 | | #include "BackgroundChild.h" |
65 | | #include "mozilla/ipc/PBackgroundChild.h" |
66 | | #include "mozilla/layout/VsyncChild.h" |
67 | | #include "VsyncSource.h" |
68 | | #include "mozilla/VsyncDispatcher.h" |
69 | | #include "nsThreadUtils.h" |
70 | | #include "mozilla/Unused.h" |
71 | | #include "mozilla/TimelineConsumers.h" |
72 | | #include "nsAnimationManager.h" |
73 | | #include "nsDisplayList.h" |
74 | | #include "nsTransitionManager.h" |
75 | | |
76 | | #ifdef MOZ_XUL |
77 | | #include "nsXULPopupManager.h" |
78 | | #endif |
79 | | |
80 | | using namespace mozilla; |
81 | | using namespace mozilla::widget; |
82 | | using namespace mozilla::ipc; |
83 | | using namespace mozilla::layout; |
84 | | |
85 | | static mozilla::LazyLogModule sRefreshDriverLog("nsRefreshDriver"); |
86 | 0 | #define LOG(...) MOZ_LOG(sRefreshDriverLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) |
87 | | |
88 | 0 | #define DEFAULT_THROTTLED_FRAME_RATE 1 |
89 | 0 | #define DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS 1000 |
90 | | #define DEFAULT_NOTIFY_INTERSECTION_OBSERVERS_INTERVAL_MS 100 |
91 | | // after 10 minutes, stop firing off inactive timers |
92 | 0 | #define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600 |
93 | | |
94 | | // The number of seconds spent skipping frames because we are waiting for the compositor |
95 | | // before logging. |
96 | | #if defined(MOZ_ASAN) |
97 | | # define REFRESH_WAIT_WARNING 5 |
98 | | #elif defined(DEBUG) && !defined(MOZ_VALGRIND) |
99 | | # define REFRESH_WAIT_WARNING 5 |
100 | | #elif defined(DEBUG) && defined(MOZ_VALGRIND) |
101 | | # define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 20 : 5) |
102 | | #elif defined(MOZ_VALGRIND) |
103 | | # define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 10 : 1) |
104 | | #else |
105 | 0 | # define REFRESH_WAIT_WARNING 1 |
106 | | #endif |
107 | | |
108 | | namespace { |
109 | | // `true` if we are currently in jank-critical mode. |
110 | | // |
111 | | // In jank-critical mode, any iteration of the event loop that takes |
112 | | // more than 16ms to compute will cause an ongoing animation to miss |
113 | | // frames. |
114 | | // |
115 | | // For simplicity, the current implementation assumes that we are in |
116 | | // jank-critical mode if and only if at least one vsync driver has |
117 | | // at least one observer. |
118 | | static uint64_t sActiveVsyncTimers = 0; |
119 | | |
120 | | // The latest value of process-wide jank levels. |
121 | | // |
122 | | // For each i, sJankLevels[i] counts the number of times delivery of |
123 | | // vsync to the main thread has been delayed by at least 2^i ms. Use |
124 | | // GetJankLevels to grab a copy of this array. |
125 | | uint64_t sJankLevels[12]; |
126 | | |
127 | | // The number outstanding nsRefreshDrivers (that have been created but not |
128 | | // disconnected). When this reaches zero we will call |
129 | | // nsRefreshDriver::Shutdown. |
130 | | static uint32_t sRefreshDriverCount = 0; |
131 | | } |
132 | | |
133 | | namespace mozilla { |
134 | | |
135 | | /* |
136 | | * The base class for all global refresh driver timers. It takes care |
137 | | * of managing the list of refresh drivers attached to them and |
138 | | * provides interfaces for querying/setting the rate and actually |
139 | | * running a timer 'Tick'. Subclasses must implement StartTimer(), |
140 | | * StopTimer(), and ScheduleNextTick() -- the first two just |
141 | | * start/stop whatever timer mechanism is in use, and ScheduleNextTick |
142 | | * is called at the start of the Tick() implementation to set a time |
143 | | * for the next tick. |
144 | | */ |
145 | | class RefreshDriverTimer { |
146 | | public: |
147 | | RefreshDriverTimer() |
148 | 0 | { |
149 | 0 | } |
150 | | |
151 | | NS_INLINE_DECL_REFCOUNTING(RefreshDriverTimer) |
152 | | |
153 | | virtual void AddRefreshDriver(nsRefreshDriver* aDriver) |
154 | 0 | { |
155 | 0 | LOG("[%p] AddRefreshDriver %p", this, aDriver); |
156 | 0 |
|
157 | 0 | bool startTimer = mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty(); |
158 | 0 | if (IsRootRefreshDriver(aDriver)) { |
159 | 0 | NS_ASSERTION(!mRootRefreshDrivers.Contains(aDriver), "Adding a duplicate root refresh driver!"); |
160 | 0 | mRootRefreshDrivers.AppendElement(aDriver); |
161 | 0 | } else { |
162 | 0 | NS_ASSERTION(!mContentRefreshDrivers.Contains(aDriver), "Adding a duplicate content refresh driver!"); |
163 | 0 | mContentRefreshDrivers.AppendElement(aDriver); |
164 | 0 | } |
165 | 0 |
|
166 | 0 | if (startTimer) { |
167 | 0 | StartTimer(); |
168 | 0 | } |
169 | 0 | } |
170 | | |
171 | | void RemoveRefreshDriver(nsRefreshDriver* aDriver) |
172 | 0 | { |
173 | 0 | LOG("[%p] RemoveRefreshDriver %p", this, aDriver); |
174 | 0 |
|
175 | 0 | if (IsRootRefreshDriver(aDriver)) { |
176 | 0 | NS_ASSERTION(mRootRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the root refresh list!"); |
177 | 0 | mRootRefreshDrivers.RemoveElement(aDriver); |
178 | 0 | } else { |
179 | 0 | nsPresContext* pc = aDriver->GetPresContext(); |
180 | 0 | nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr; |
181 | 0 | // During PresContext shutdown, we can't accurately detect |
182 | 0 | // if a root refresh driver exists or not. Therefore, we have to |
183 | 0 | // search and find out which list this driver exists in. |
184 | 0 | if (!rootContext) { |
185 | 0 | if (mRootRefreshDrivers.Contains(aDriver)) { |
186 | 0 | mRootRefreshDrivers.RemoveElement(aDriver); |
187 | 0 | } else { |
188 | 0 | NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver), |
189 | 0 | "RemoveRefreshDriver without a display root for a driver that is not in the content refresh list"); |
190 | 0 | mContentRefreshDrivers.RemoveElement(aDriver); |
191 | 0 | } |
192 | 0 | } else { |
193 | 0 | NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a driver that is not in the content refresh list"); |
194 | 0 | mContentRefreshDrivers.RemoveElement(aDriver); |
195 | 0 | } |
196 | 0 | } |
197 | 0 |
|
198 | 0 | bool stopTimer = mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty(); |
199 | 0 | if (stopTimer) { |
200 | 0 | StopTimer(); |
201 | 0 | } |
202 | 0 | } |
203 | | |
204 | 0 | TimeStamp MostRecentRefresh() const { return mLastFireTime; } |
205 | | |
206 | | void SwapRefreshDrivers(RefreshDriverTimer* aNewTimer) |
207 | 0 | { |
208 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
209 | 0 |
|
210 | 0 | for (nsRefreshDriver* driver : mContentRefreshDrivers) { |
211 | 0 | aNewTimer->AddRefreshDriver(driver); |
212 | 0 | driver->mActiveTimer = aNewTimer; |
213 | 0 | } |
214 | 0 | mContentRefreshDrivers.Clear(); |
215 | 0 |
|
216 | 0 | for (nsRefreshDriver* driver : mRootRefreshDrivers) { |
217 | 0 | aNewTimer->AddRefreshDriver(driver); |
218 | 0 | driver->mActiveTimer = aNewTimer; |
219 | 0 | } |
220 | 0 | mRootRefreshDrivers.Clear(); |
221 | 0 |
|
222 | 0 | aNewTimer->mLastFireTime = mLastFireTime; |
223 | 0 |
|
224 | 0 | StopTimer(); |
225 | 0 | } |
226 | | |
227 | | virtual TimeDuration GetTimerRate() = 0; |
228 | | |
229 | | TimeStamp GetIdleDeadlineHint(TimeStamp aDefault) |
230 | 0 | { |
231 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
232 | 0 |
|
233 | 0 | TimeStamp mostRecentRefresh = MostRecentRefresh(); |
234 | 0 | TimeDuration refreshRate = GetTimerRate(); |
235 | 0 | TimeStamp idleEnd = mostRecentRefresh + refreshRate; |
236 | 0 |
|
237 | 0 | if (idleEnd + |
238 | 0 | refreshRate * nsLayoutUtils::QuiescentFramesBeforeIdlePeriod() < |
239 | 0 | TimeStamp::Now()) { |
240 | 0 | return aDefault; |
241 | 0 | } |
242 | 0 | |
243 | 0 | idleEnd = idleEnd - TimeDuration::FromMilliseconds( |
244 | 0 | nsLayoutUtils::IdlePeriodDeadlineLimit()); |
245 | 0 | return idleEnd < aDefault ? idleEnd : aDefault; |
246 | 0 | } |
247 | | |
248 | | Maybe<TimeStamp> GetNextTickHint() |
249 | 0 | { |
250 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
251 | 0 | TimeStamp nextTick = MostRecentRefresh() + GetTimerRate(); |
252 | 0 | return nextTick < TimeStamp::Now() ? Nothing() : Some(nextTick); |
253 | 0 | } |
254 | | |
255 | | protected: |
256 | | virtual ~RefreshDriverTimer() |
257 | 0 | { |
258 | 0 | MOZ_ASSERT(mContentRefreshDrivers.Length() == 0, "Should have removed all content refresh drivers from here by now!"); |
259 | 0 | MOZ_ASSERT(mRootRefreshDrivers.Length() == 0, "Should have removed all root refresh drivers from here by now!"); |
260 | 0 | } |
261 | | |
262 | | virtual void StartTimer() = 0; |
263 | | virtual void StopTimer() = 0; |
264 | | virtual void ScheduleNextTick(TimeStamp aNowTime) = 0; |
265 | | |
266 | | bool IsRootRefreshDriver(nsRefreshDriver* aDriver) |
267 | 0 | { |
268 | 0 | nsPresContext* pc = aDriver->GetPresContext(); |
269 | 0 | nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr; |
270 | 0 | if (!rootContext) { |
271 | 0 | return false; |
272 | 0 | } |
273 | 0 | |
274 | 0 | return aDriver == rootContext->RefreshDriver(); |
275 | 0 | } |
276 | | |
277 | | /* |
278 | | * Actually runs a tick, poking all the attached RefreshDrivers. |
279 | | * Grabs the "now" time via TimeStamp::Now(). |
280 | | */ |
281 | | void Tick() |
282 | 0 | { |
283 | 0 | TimeStamp now = TimeStamp::Now(); |
284 | 0 | Tick(now); |
285 | 0 | } |
286 | | |
287 | | void TickRefreshDrivers(TimeStamp aNow, nsTArray<RefPtr<nsRefreshDriver>>& aDrivers) |
288 | 0 | { |
289 | 0 | if (aDrivers.IsEmpty()) { |
290 | 0 | return; |
291 | 0 | } |
292 | 0 | |
293 | 0 | nsTArray<RefPtr<nsRefreshDriver>> drivers(aDrivers); |
294 | 0 | for (nsRefreshDriver* driver : drivers) { |
295 | 0 | // don't poke this driver if it's in test mode |
296 | 0 | if (driver->IsTestControllingRefreshesEnabled()) { |
297 | 0 | continue; |
298 | 0 | } |
299 | 0 | |
300 | 0 | TickDriver(driver, aNow); |
301 | 0 | } |
302 | 0 | } |
303 | | |
304 | | /* |
305 | | * Tick the refresh drivers based on the given timestamp. |
306 | | */ |
307 | | void Tick(TimeStamp now) |
308 | 0 | { |
309 | 0 | ScheduleNextTick(now); |
310 | 0 |
|
311 | 0 | mLastFireTime = now; |
312 | 0 |
|
313 | 0 | LOG("[%p] ticking drivers...", this); |
314 | 0 | // RD is short for RefreshDriver |
315 | 0 | AUTO_PROFILER_TRACING("Paint", "RefreshDriverTick"); |
316 | 0 |
|
317 | 0 | TickRefreshDrivers(now, mContentRefreshDrivers); |
318 | 0 | TickRefreshDrivers(now, mRootRefreshDrivers); |
319 | 0 |
|
320 | 0 | LOG("[%p] done.", this); |
321 | 0 | } |
322 | | |
323 | | static void TickDriver(nsRefreshDriver* driver, TimeStamp now) |
324 | 0 | { |
325 | 0 | driver->Tick(now); |
326 | 0 | } |
327 | | |
328 | | TimeStamp mLastFireTime; |
329 | | TimeStamp mTargetTime; |
330 | | |
331 | | nsTArray<RefPtr<nsRefreshDriver>> mContentRefreshDrivers; |
332 | | nsTArray<RefPtr<nsRefreshDriver>> mRootRefreshDrivers; |
333 | | |
334 | | // useful callback for nsITimer-based derived classes, here |
335 | | // because of c++ protected shenanigans |
336 | | static void TimerTick(nsITimer* aTimer, void* aClosure) |
337 | 0 | { |
338 | 0 | RefPtr<RefreshDriverTimer> timer = |
339 | 0 | static_cast<RefreshDriverTimer*>(aClosure); |
340 | 0 | timer->Tick(); |
341 | 0 | } |
342 | | }; |
343 | | |
344 | | /* |
345 | | * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that |
346 | | * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to |
347 | | * implement ScheduleNextTick and intelligently calculate the next time to tick, |
348 | | * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain |
349 | | * with its attempt at intelligent slack removal and such, so we don't do it. |
350 | | */ |
351 | | class SimpleTimerBasedRefreshDriverTimer : |
352 | | public RefreshDriverTimer |
353 | | { |
354 | | public: |
355 | | /* |
356 | | * aRate -- the delay, in milliseconds, requested between timer firings |
357 | | */ |
358 | | explicit SimpleTimerBasedRefreshDriverTimer(double aRate) |
359 | 0 | { |
360 | 0 | SetRate(aRate); |
361 | 0 | mTimer = NS_NewTimer(); |
362 | 0 | } |
363 | | |
364 | | virtual ~SimpleTimerBasedRefreshDriverTimer() override |
365 | 0 | { |
366 | 0 | StopTimer(); |
367 | 0 | } |
368 | | |
369 | | // will take effect at next timer tick |
370 | | virtual void SetRate(double aNewRate) |
371 | 0 | { |
372 | 0 | mRateMilliseconds = aNewRate; |
373 | 0 | mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds); |
374 | 0 | } |
375 | | |
376 | | double GetRate() const |
377 | 0 | { |
378 | 0 | return mRateMilliseconds; |
379 | 0 | } |
380 | | |
381 | | TimeDuration GetTimerRate() override |
382 | 0 | { |
383 | 0 | return mRateDuration; |
384 | 0 | } |
385 | | |
386 | | protected: |
387 | | |
388 | | void StartTimer() override |
389 | 0 | { |
390 | 0 | // pretend we just fired, and we schedule the next tick normally |
391 | 0 | mLastFireTime = TimeStamp::Now(); |
392 | 0 |
|
393 | 0 | mTargetTime = mLastFireTime + mRateDuration; |
394 | 0 |
|
395 | 0 | uint32_t delay = static_cast<uint32_t>(mRateMilliseconds); |
396 | 0 | mTimer->InitWithNamedFuncCallback( |
397 | 0 | TimerTick, |
398 | 0 | this, |
399 | 0 | delay, |
400 | 0 | nsITimer::TYPE_ONE_SHOT, |
401 | 0 | "SimpleTimerBasedRefreshDriverTimer::StartTimer"); |
402 | 0 | } |
403 | | |
404 | | void StopTimer() override |
405 | 0 | { |
406 | 0 | mTimer->Cancel(); |
407 | 0 | } |
408 | | |
409 | | double mRateMilliseconds; |
410 | | TimeDuration mRateDuration; |
411 | | RefPtr<nsITimer> mTimer; |
412 | | }; |
413 | | |
414 | | /* |
415 | | * A refresh driver that listens to vsync events and ticks the refresh driver |
416 | | * on vsync intervals. We throttle the refresh driver if we get too many |
417 | | * vsync events and wait to catch up again. |
418 | | */ |
419 | | class VsyncRefreshDriverTimer : public RefreshDriverTimer |
420 | | { |
421 | | public: |
422 | | VsyncRefreshDriverTimer() |
423 | | : mVsyncChild(nullptr) |
424 | 0 | { |
425 | 0 | MOZ_ASSERT(XRE_IsParentProcess()); |
426 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
427 | 0 | mVsyncObserver = new RefreshDriverVsyncObserver(this); |
428 | 0 | RefPtr<mozilla::gfx::VsyncSource> vsyncSource = gfxPlatform::GetPlatform()->GetHardwareVsync(); |
429 | 0 | MOZ_ALWAYS_TRUE(mVsyncDispatcher = vsyncSource->GetRefreshTimerVsyncDispatcher()); |
430 | 0 | mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver); |
431 | 0 | mVsyncRate = vsyncSource->GetGlobalDisplay().GetVsyncRate(); |
432 | 0 | } |
433 | | |
434 | | explicit VsyncRefreshDriverTimer(VsyncChild* aVsyncChild) |
435 | | : mVsyncChild(aVsyncChild) |
436 | 0 | { |
437 | 0 | MOZ_ASSERT(!XRE_IsParentProcess()); |
438 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
439 | 0 | MOZ_ASSERT(mVsyncChild); |
440 | 0 | mVsyncObserver = new RefreshDriverVsyncObserver(this); |
441 | 0 | mVsyncChild->SetVsyncObserver(mVsyncObserver); |
442 | 0 | mVsyncRate = mVsyncChild->GetVsyncRate(); |
443 | 0 | } |
444 | | |
445 | | TimeDuration GetTimerRate() override |
446 | 0 | { |
447 | 0 | if (mVsyncRate != TimeDuration::Forever()) { |
448 | 0 | return mVsyncRate; |
449 | 0 | } |
450 | 0 | |
451 | 0 | if (mVsyncChild) { |
452 | 0 | // VsyncChild::VsyncRate() is a simple getter for the cached |
453 | 0 | // hardware vsync rate. We depend on that |
454 | 0 | // VsyncChild::GetVsyncRate() being called in the constructor |
455 | 0 | // will result in a response with the actual vsync rate sooner |
456 | 0 | // or later. Until that happens VsyncChild::VsyncRate() returns |
457 | 0 | // TimeDuration::Forever() and we have to guess below. |
458 | 0 | mVsyncRate = mVsyncChild->VsyncRate(); |
459 | 0 | } |
460 | 0 |
|
461 | 0 | // If hardware queries fail / are unsupported, we have to just guess. |
462 | 0 | return mVsyncRate != TimeDuration::Forever() |
463 | 0 | ? mVsyncRate |
464 | 0 | : TimeDuration::FromMilliseconds(1000.0 / 60.0); |
465 | 0 | } |
466 | | |
467 | | private: |
468 | | // Since VsyncObservers are refCounted, but the RefreshDriverTimer are |
469 | | // explicitly shutdown. We create an inner class that has the VsyncObserver |
470 | | // and is shutdown when the RefreshDriverTimer is deleted. |
471 | | class RefreshDriverVsyncObserver final : public VsyncObserver |
472 | | { |
473 | | public: |
474 | | explicit RefreshDriverVsyncObserver(VsyncRefreshDriverTimer* aVsyncRefreshDriverTimer) |
475 | | : mVsyncRefreshDriverTimer(aVsyncRefreshDriverTimer) |
476 | | , mRefreshTickLock("RefreshTickLock") |
477 | | , mRecentVsync(TimeStamp::Now()) |
478 | | , mLastChildTick(TimeStamp::Now()) |
479 | | , mVsyncRate(TimeDuration::Forever()) |
480 | | , mProcessedVsync(true) |
481 | 0 | { |
482 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
483 | 0 | } |
484 | | |
485 | | class ParentProcessVsyncNotifier final: public Runnable, |
486 | | public nsIRunnablePriority |
487 | | { |
488 | | public: |
489 | | ParentProcessVsyncNotifier(RefreshDriverVsyncObserver* aObserver, |
490 | | TimeStamp aVsyncTimestamp) |
491 | | : Runnable("VsyncRefreshDriverTimer::RefreshDriverVsyncObserver::" |
492 | | "ParentProcessVsyncNotifier") |
493 | | , mObserver(aObserver) |
494 | | , mVsyncTimestamp(aVsyncTimestamp) |
495 | 0 | { |
496 | 0 | } |
497 | | |
498 | | NS_DECL_ISUPPORTS_INHERITED |
499 | | |
500 | | NS_IMETHOD Run() override |
501 | 0 | { |
502 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
503 | 0 | static bool sCacheInitialized = false; |
504 | 0 | static bool sHighPriorityPrefValue = false; |
505 | 0 | if (!sCacheInitialized) { |
506 | 0 | sCacheInitialized = true; |
507 | 0 | Preferences::AddBoolVarCache(&sHighPriorityPrefValue, |
508 | 0 | "vsync.parentProcess.highPriority", |
509 | 0 | mozilla::BrowserTabsRemoteAutostart()); |
510 | 0 | } |
511 | 0 | sHighPriorityEnabled = sHighPriorityPrefValue; |
512 | 0 |
|
513 | 0 | mObserver->TickRefreshDriver(mVsyncTimestamp); |
514 | 0 | return NS_OK; |
515 | 0 | } |
516 | | |
517 | | NS_IMETHOD GetPriority(uint32_t* aPriority) override |
518 | 0 | { |
519 | 0 | *aPriority = |
520 | 0 | sHighPriorityEnabled ? nsIRunnablePriority::PRIORITY_HIGH : |
521 | 0 | nsIRunnablePriority::PRIORITY_NORMAL; |
522 | 0 | return NS_OK; |
523 | 0 | } |
524 | | |
525 | | private: |
526 | 0 | ~ParentProcessVsyncNotifier() {} |
527 | | RefPtr<RefreshDriverVsyncObserver> mObserver; |
528 | | TimeStamp mVsyncTimestamp; |
529 | | static mozilla::Atomic<bool> sHighPriorityEnabled; |
530 | | }; |
531 | | |
532 | | bool NotifyVsync(TimeStamp aVsyncTimestamp) override |
533 | 0 | { |
534 | 0 | // IMPORTANT: All paths through this method MUST hold a strong ref on |
535 | 0 | // |this| for the duration of the TickRefreshDriver callback. |
536 | 0 |
|
537 | 0 | if (!NS_IsMainThread()) { |
538 | 0 | MOZ_ASSERT(XRE_IsParentProcess()); |
539 | 0 | // Compress vsync notifications such that only 1 may run at a time |
540 | 0 | // This is so that we don't flood the refresh driver with vsync messages |
541 | 0 | // if the main thread is blocked for long periods of time |
542 | 0 | { // scope lock |
543 | 0 | MonitorAutoLock lock(mRefreshTickLock); |
544 | 0 | mRecentVsync = aVsyncTimestamp; |
545 | 0 | if (!mProcessedVsync) { |
546 | 0 | return true; |
547 | 0 | } |
548 | 0 | mProcessedVsync = false; |
549 | 0 | } |
550 | 0 |
|
551 | 0 | nsCOMPtr<nsIRunnable> vsyncEvent = |
552 | 0 | new ParentProcessVsyncNotifier(this, aVsyncTimestamp); |
553 | 0 | NS_DispatchToMainThread(vsyncEvent); |
554 | 0 | } else { |
555 | 0 | mRecentVsync = aVsyncTimestamp; |
556 | 0 | if (!mBlockUntil.IsNull() && mBlockUntil > aVsyncTimestamp) { |
557 | 0 | if (mProcessedVsync) { |
558 | 0 | // Re-post vsync update as a normal priority runnable. This way |
559 | 0 | // runnables already in normal priority queue get processed. |
560 | 0 | mProcessedVsync = false; |
561 | 0 | nsCOMPtr<nsIRunnable> vsyncEvent = |
562 | 0 | NewRunnableMethod<>( |
563 | 0 | "RefreshDriverVsyncObserver::NormalPriorityNotify", |
564 | 0 | this, &RefreshDriverVsyncObserver::NormalPriorityNotify); |
565 | 0 | NS_DispatchToMainThread(vsyncEvent); |
566 | 0 | } |
567 | 0 |
|
568 | 0 | return true; |
569 | 0 | } |
570 | 0 |
|
571 | 0 | RefPtr<RefreshDriverVsyncObserver> kungFuDeathGrip(this); |
572 | 0 | TickRefreshDriver(aVsyncTimestamp); |
573 | 0 | } |
574 | 0 |
|
575 | 0 | return true; |
576 | 0 | } |
577 | | |
578 | | void Shutdown() |
579 | 0 | { |
580 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
581 | 0 | mVsyncRefreshDriverTimer = nullptr; |
582 | 0 | } |
583 | | |
584 | | void OnTimerStart() |
585 | 0 | { |
586 | 0 | if (!XRE_IsParentProcess()) { |
587 | 0 | mLastChildTick = TimeStamp::Now(); |
588 | 0 | } |
589 | 0 | } |
590 | | |
591 | | void NormalPriorityNotify() |
592 | 0 | { |
593 | 0 | if (mLastProcessedTickInChildProcess.IsNull() || |
594 | 0 | mRecentVsync > mLastProcessedTickInChildProcess) { |
595 | 0 | // mBlockUntil is for high priority vsync notifications only. |
596 | 0 | mBlockUntil = TimeStamp(); |
597 | 0 | TickRefreshDriver(mRecentVsync); |
598 | 0 | } |
599 | 0 |
|
600 | 0 | mProcessedVsync = true; |
601 | 0 | } |
602 | | |
603 | | private: |
604 | 0 | ~RefreshDriverVsyncObserver() = default; |
605 | | |
606 | | void RecordTelemetryProbes(TimeStamp aVsyncTimestamp) |
607 | 0 | { |
608 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
609 | 0 | #ifndef ANDROID /* bug 1142079 */ |
610 | 0 | if (XRE_IsParentProcess()) { |
611 | 0 | TimeDuration vsyncLatency = TimeStamp::Now() - aVsyncTimestamp; |
612 | 0 | uint32_t sample = (uint32_t)vsyncLatency.ToMilliseconds(); |
613 | 0 | Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CHROME_FRAME_DELAY_MS, |
614 | 0 | sample); |
615 | 0 | Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, |
616 | 0 | sample); |
617 | 0 | RecordJank(sample); |
618 | 0 | } else if (mVsyncRate != TimeDuration::Forever()) { |
619 | 0 | TimeDuration contentDelay = (TimeStamp::Now() - mLastChildTick) - mVsyncRate; |
620 | 0 | if (contentDelay.ToMilliseconds() < 0 ){ |
621 | 0 | // Vsyncs are noisy and some can come at a rate quicker than |
622 | 0 | // the reported hardware rate. In those cases, consider that we have 0 delay. |
623 | 0 | contentDelay = TimeDuration::FromMilliseconds(0); |
624 | 0 | } |
625 | 0 | uint32_t sample = (uint32_t)contentDelay.ToMilliseconds(); |
626 | 0 | Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CONTENT_FRAME_DELAY_MS, |
627 | 0 | sample); |
628 | 0 | Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, |
629 | 0 | sample); |
630 | 0 | RecordJank(sample); |
631 | 0 | } else { |
632 | 0 | // Request the vsync rate from the parent process. Might be a few vsyncs |
633 | 0 | // until the parent responds. |
634 | 0 | if (mVsyncRefreshDriverTimer) { |
635 | 0 | mVsyncRate = mVsyncRefreshDriverTimer->mVsyncChild->GetVsyncRate(); |
636 | 0 | } |
637 | 0 | } |
638 | 0 | #endif |
639 | 0 | } |
640 | | |
641 | | void RecordJank(uint32_t aJankMS) |
642 | 0 | { |
643 | 0 | uint32_t duration = 1 /* ms */; |
644 | 0 | for (size_t i = 0; |
645 | 0 | i < mozilla::ArrayLength(sJankLevels) && duration < aJankMS; |
646 | 0 | ++i, duration *= 2) { |
647 | 0 | sJankLevels[i]++; |
648 | 0 | } |
649 | 0 | } |
650 | | |
651 | | void TickRefreshDriver(TimeStamp aVsyncTimestamp) |
652 | 0 | { |
653 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
654 | 0 |
|
655 | 0 | RecordTelemetryProbes(aVsyncTimestamp); |
656 | 0 | if (XRE_IsParentProcess()) { |
657 | 0 | MonitorAutoLock lock(mRefreshTickLock); |
658 | 0 | aVsyncTimestamp = mRecentVsync; |
659 | 0 | mProcessedVsync = true; |
660 | 0 | } else { |
661 | 0 |
|
662 | 0 | mLastChildTick = TimeStamp::Now(); |
663 | 0 | mLastProcessedTickInChildProcess = aVsyncTimestamp; |
664 | 0 | } |
665 | 0 | MOZ_ASSERT(aVsyncTimestamp <= TimeStamp::Now()); |
666 | 0 |
|
667 | 0 | // We might have a problem that we call ~VsyncRefreshDriverTimer() before |
668 | 0 | // the scheduled TickRefreshDriver() runs. Check mVsyncRefreshDriverTimer |
669 | 0 | // before use. |
670 | 0 | if (mVsyncRefreshDriverTimer) { |
671 | 0 | RefPtr<VsyncRefreshDriverTimer> timer = mVsyncRefreshDriverTimer; |
672 | 0 | timer->RunRefreshDrivers(aVsyncTimestamp); |
673 | 0 | // Note: mVsyncRefreshDriverTimer might be null now. |
674 | 0 | } |
675 | 0 |
|
676 | 0 | if (!XRE_IsParentProcess()) { |
677 | 0 | TimeDuration tickDuration = TimeStamp::Now() - mLastChildTick; |
678 | 0 | mBlockUntil = aVsyncTimestamp + tickDuration; |
679 | 0 | } |
680 | 0 | } |
681 | | |
682 | | // VsyncRefreshDriverTimer holds this RefreshDriverVsyncObserver and it will |
683 | | // be always available before Shutdown(). We can just use the raw pointer |
684 | | // here. |
685 | | VsyncRefreshDriverTimer* mVsyncRefreshDriverTimer; |
686 | | Monitor mRefreshTickLock; |
687 | | TimeStamp mRecentVsync; |
688 | | TimeStamp mLastChildTick; |
689 | | TimeStamp mLastProcessedTickInChildProcess; |
690 | | TimeStamp mBlockUntil; |
691 | | TimeDuration mVsyncRate; |
692 | | bool mProcessedVsync; |
693 | | }; // RefreshDriverVsyncObserver |
694 | | |
695 | | ~VsyncRefreshDriverTimer() override |
696 | 0 | { |
697 | 0 | if (XRE_IsParentProcess()) { |
698 | 0 | mVsyncDispatcher->SetParentRefreshTimer(nullptr); |
699 | 0 | mVsyncDispatcher = nullptr; |
700 | 0 | } else { |
701 | 0 | // Since the PVsyncChild actors live through the life of the process, just |
702 | 0 | // send the unobserveVsync message to disable vsync event. We don't need |
703 | 0 | // to handle the cleanup stuff of this actor. PVsyncChild::ActorDestroy() |
704 | 0 | // will be called and clean up this actor. |
705 | 0 | Unused << mVsyncChild->SendUnobserve(); |
706 | 0 | mVsyncChild->SetVsyncObserver(nullptr); |
707 | 0 | mVsyncChild = nullptr; |
708 | 0 | } |
709 | 0 |
|
710 | 0 | // Detach current vsync timer from this VsyncObserver. The observer will no |
711 | 0 | // longer tick this timer. |
712 | 0 | mVsyncObserver->Shutdown(); |
713 | 0 | mVsyncObserver = nullptr; |
714 | 0 | } |
715 | | |
716 | | void StartTimer() override |
717 | 0 | { |
718 | 0 | // Protect updates to `sActiveVsyncTimers`. |
719 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
720 | 0 |
|
721 | 0 | mLastFireTime = TimeStamp::Now(); |
722 | 0 |
|
723 | 0 | if (XRE_IsParentProcess()) { |
724 | 0 | mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver); |
725 | 0 | } else { |
726 | 0 | Unused << mVsyncChild->SendObserve(); |
727 | 0 | mVsyncObserver->OnTimerStart(); |
728 | 0 | } |
729 | 0 |
|
730 | 0 | ++sActiveVsyncTimers; |
731 | 0 | } |
732 | | |
733 | | void StopTimer() override |
734 | 0 | { |
735 | 0 | // Protect updates to `sActiveVsyncTimers`. |
736 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
737 | 0 |
|
738 | 0 | if (XRE_IsParentProcess()) { |
739 | 0 | mVsyncDispatcher->SetParentRefreshTimer(nullptr); |
740 | 0 | } else { |
741 | 0 | Unused << mVsyncChild->SendUnobserve(); |
742 | 0 | } |
743 | 0 |
|
744 | 0 | MOZ_ASSERT(sActiveVsyncTimers > 0); |
745 | 0 | --sActiveVsyncTimers; |
746 | 0 | } |
747 | | |
748 | | void ScheduleNextTick(TimeStamp aNowTime) override |
749 | 0 | { |
750 | 0 | // Do nothing since we just wait for the next vsync from |
751 | 0 | // RefreshDriverVsyncObserver. |
752 | 0 | } |
753 | | |
754 | | void RunRefreshDrivers(TimeStamp aTimeStamp) |
755 | 0 | { |
756 | 0 | Tick(aTimeStamp); |
757 | 0 | } |
758 | | |
759 | | RefPtr<RefreshDriverVsyncObserver> mVsyncObserver; |
760 | | // Used for parent process. |
761 | | RefPtr<RefreshTimerVsyncDispatcher> mVsyncDispatcher; |
762 | | // Used for child process. |
763 | | // The mVsyncChild will be always available before VsncChild::ActorDestroy(). |
764 | | // After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op. |
765 | | RefPtr<VsyncChild> mVsyncChild; |
766 | | TimeDuration mVsyncRate; |
767 | | }; // VsyncRefreshDriverTimer |
768 | | |
769 | | NS_IMPL_ISUPPORTS_INHERITED(VsyncRefreshDriverTimer:: |
770 | | RefreshDriverVsyncObserver:: |
771 | | ParentProcessVsyncNotifier, |
772 | | Runnable, nsIRunnablePriority) |
773 | | |
774 | | mozilla::Atomic<bool> |
775 | | VsyncRefreshDriverTimer:: |
776 | | RefreshDriverVsyncObserver:: |
777 | | ParentProcessVsyncNotifier::sHighPriorityEnabled(false); |
778 | | |
779 | | /** |
780 | | * Since the content process takes some time to setup |
781 | | * the vsync IPC connection, this timer is used |
782 | | * during the intial startup process. |
783 | | * During initial startup, the refresh drivers |
784 | | * are ticked off this timer, and are swapped out once content |
785 | | * vsync IPC connection is established. |
786 | | */ |
787 | | class StartupRefreshDriverTimer : |
788 | | public SimpleTimerBasedRefreshDriverTimer |
789 | | { |
790 | | public: |
791 | | explicit StartupRefreshDriverTimer(double aRate) |
792 | | : SimpleTimerBasedRefreshDriverTimer(aRate) |
793 | 0 | { |
794 | 0 | } |
795 | | |
796 | | protected: |
797 | | void ScheduleNextTick(TimeStamp aNowTime) override |
798 | 0 | { |
799 | 0 | // Since this is only used for startup, it isn't super critical |
800 | 0 | // that we tick at consistent intervals. |
801 | 0 | TimeStamp newTarget = aNowTime + mRateDuration; |
802 | 0 | uint32_t delay = static_cast<uint32_t>((newTarget - aNowTime).ToMilliseconds()); |
803 | 0 | mTimer->InitWithNamedFuncCallback( |
804 | 0 | TimerTick, |
805 | 0 | this, |
806 | 0 | delay, |
807 | 0 | nsITimer::TYPE_ONE_SHOT, |
808 | 0 | "StartupRefreshDriverTimer::ScheduleNextTick"); |
809 | 0 | mTargetTime = newTarget; |
810 | 0 | } |
811 | | }; |
812 | | |
813 | | /* |
814 | | * A RefreshDriverTimer for inactive documents. When a new refresh driver is |
815 | | * added, the rate is reset to the base (normally 1s/1fps). Every time |
816 | | * it ticks, a single refresh driver is poked. Once they have all been poked, |
817 | | * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that point, |
818 | | * the timer is quiet and doesn't tick (until something is added to it again). |
819 | | * |
820 | | * When a timer is removed, there is a possibility of another timer |
821 | | * being skipped for one cycle. We could avoid this by adjusting |
822 | | * mNextDriverIndex in RemoveRefreshDriver, but there's little need to |
823 | | * add that complexity. All we want is for inactive drivers to tick |
824 | | * at some point, but we don't care too much about how often. |
825 | | */ |
826 | | class InactiveRefreshDriverTimer final : |
827 | | public SimpleTimerBasedRefreshDriverTimer |
828 | | { |
829 | | public: |
830 | | explicit InactiveRefreshDriverTimer(double aRate) |
831 | | : SimpleTimerBasedRefreshDriverTimer(aRate), |
832 | | mNextTickDuration(aRate), |
833 | | mDisableAfterMilliseconds(-1.0), |
834 | | mNextDriverIndex(0) |
835 | 0 | { |
836 | 0 | } |
837 | | |
838 | | InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds) |
839 | | : SimpleTimerBasedRefreshDriverTimer(aRate), |
840 | | mNextTickDuration(aRate), |
841 | | mDisableAfterMilliseconds(aDisableAfterMilliseconds), |
842 | | mNextDriverIndex(0) |
843 | 0 | { |
844 | 0 | } |
845 | | |
846 | | void AddRefreshDriver(nsRefreshDriver* aDriver) override |
847 | 0 | { |
848 | 0 | RefreshDriverTimer::AddRefreshDriver(aDriver); |
849 | 0 |
|
850 | 0 | LOG("[%p] inactive timer got new refresh driver %p, resetting rate", |
851 | 0 | this, aDriver); |
852 | 0 |
|
853 | 0 | // reset the timer, and start with the newly added one next time. |
854 | 0 | mNextTickDuration = mRateMilliseconds; |
855 | 0 |
|
856 | 0 | // we don't really have to start with the newly added one, but we may as well |
857 | 0 | // not tick the old ones at the fastest rate any more than we need to. |
858 | 0 | mNextDriverIndex = GetRefreshDriverCount() - 1; |
859 | 0 |
|
860 | 0 | StopTimer(); |
861 | 0 | StartTimer(); |
862 | 0 | } |
863 | | |
864 | | TimeDuration GetTimerRate() override |
865 | 0 | { |
866 | 0 | return TimeDuration::FromMilliseconds(mNextTickDuration); |
867 | 0 | } |
868 | | |
869 | | protected: |
870 | | uint32_t GetRefreshDriverCount() |
871 | 0 | { |
872 | 0 | return mContentRefreshDrivers.Length() + mRootRefreshDrivers.Length(); |
873 | 0 | } |
874 | | |
875 | | void StartTimer() override |
876 | 0 | { |
877 | 0 | mLastFireTime = TimeStamp::Now(); |
878 | 0 |
|
879 | 0 | mTargetTime = mLastFireTime + mRateDuration; |
880 | 0 |
|
881 | 0 | uint32_t delay = static_cast<uint32_t>(mRateMilliseconds); |
882 | 0 | mTimer->InitWithNamedFuncCallback(TimerTickOne, |
883 | 0 | this, |
884 | 0 | delay, |
885 | 0 | nsITimer::TYPE_ONE_SHOT, |
886 | 0 | "InactiveRefreshDriverTimer::StartTimer"); |
887 | 0 | } |
888 | | |
889 | | void StopTimer() override |
890 | 0 | { |
891 | 0 | mTimer->Cancel(); |
892 | 0 | } |
893 | | |
894 | | void ScheduleNextTick(TimeStamp aNowTime) override |
895 | 0 | { |
896 | 0 | if (mDisableAfterMilliseconds > 0.0 && |
897 | 0 | mNextTickDuration > mDisableAfterMilliseconds) |
898 | 0 | { |
899 | 0 | // We hit the time after which we should disable |
900 | 0 | // inactive window refreshes; don't schedule anything |
901 | 0 | // until we get kicked by an AddRefreshDriver call. |
902 | 0 | return; |
903 | 0 | } |
904 | 0 | |
905 | 0 | // double the next tick time if we've already gone through all of them once |
906 | 0 | if (mNextDriverIndex >= GetRefreshDriverCount()) { |
907 | 0 | mNextTickDuration *= 2.0; |
908 | 0 | mNextDriverIndex = 0; |
909 | 0 | } |
910 | 0 |
|
911 | 0 | // this doesn't need to be precise; do a simple schedule |
912 | 0 | uint32_t delay = static_cast<uint32_t>(mNextTickDuration); |
913 | 0 | mTimer->InitWithNamedFuncCallback( |
914 | 0 | TimerTickOne, |
915 | 0 | this, |
916 | 0 | delay, |
917 | 0 | nsITimer::TYPE_ONE_SHOT, |
918 | 0 | "InactiveRefreshDriverTimer::ScheduleNextTick"); |
919 | 0 |
|
920 | 0 | LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this, mNextTickDuration, |
921 | 0 | mNextDriverIndex, GetRefreshDriverCount()); |
922 | 0 | } |
923 | | |
924 | | /* Runs just one driver's tick. */ |
925 | | void TickOne() |
926 | 0 | { |
927 | 0 | TimeStamp now = TimeStamp::Now(); |
928 | 0 |
|
929 | 0 | ScheduleNextTick(now); |
930 | 0 |
|
931 | 0 | mLastFireTime = now; |
932 | 0 |
|
933 | 0 | nsTArray<RefPtr<nsRefreshDriver>> drivers(mContentRefreshDrivers); |
934 | 0 | drivers.AppendElements(mRootRefreshDrivers); |
935 | 0 | size_t index = mNextDriverIndex; |
936 | 0 |
|
937 | 0 | if (index < drivers.Length() && |
938 | 0 | !drivers[index]->IsTestControllingRefreshesEnabled()) |
939 | 0 | { |
940 | 0 | TickDriver(drivers[index], now); |
941 | 0 | } |
942 | 0 |
|
943 | 0 | mNextDriverIndex++; |
944 | 0 | } |
945 | | |
946 | | static void TimerTickOne(nsITimer* aTimer, void* aClosure) |
947 | 0 | { |
948 | 0 | RefPtr<InactiveRefreshDriverTimer> timer = |
949 | 0 | static_cast<InactiveRefreshDriverTimer*>(aClosure); |
950 | 0 | timer->TickOne(); |
951 | 0 | } |
952 | | |
953 | | double mNextTickDuration; |
954 | | double mDisableAfterMilliseconds; |
955 | | uint32_t mNextDriverIndex; |
956 | | }; |
957 | | |
958 | | } // namespace mozilla |
959 | | |
960 | | static StaticRefPtr<RefreshDriverTimer> sRegularRateTimer; |
961 | | static StaticRefPtr<InactiveRefreshDriverTimer> sThrottledRateTimer; |
962 | | |
963 | | static void |
964 | | CreateContentVsyncRefreshTimer(void*) |
965 | 0 | { |
966 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
967 | 0 | MOZ_ASSERT(!XRE_IsParentProcess()); |
968 | 0 |
|
969 | 0 | // Create the PVsync actor child for vsync-base refresh timer. |
970 | 0 | // PBackgroundChild is created synchronously. We will still use software |
971 | 0 | // timer before PVsync ready, and change to use hw timer when the connection |
972 | 0 | // is done. Please check nsRefreshDriver::PVsyncActorCreated(). |
973 | 0 |
|
974 | 0 | PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread(); |
975 | 0 | if (NS_WARN_IF(!actorChild)) { |
976 | 0 | return; |
977 | 0 | } |
978 | 0 | |
979 | 0 | layout::PVsyncChild* actor = actorChild->SendPVsyncConstructor(); |
980 | 0 | if (NS_WARN_IF(!actor)) { |
981 | 0 | return; |
982 | 0 | } |
983 | 0 | |
984 | 0 | layout::VsyncChild* child = static_cast<layout::VsyncChild*>(actor); |
985 | 0 | nsRefreshDriver::PVsyncActorCreated(child); |
986 | 0 | } |
987 | | |
988 | | static void |
989 | | CreateVsyncRefreshTimer() |
990 | 0 | { |
991 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
992 | 0 |
|
993 | 0 | PodArrayZero(sJankLevels); |
994 | 0 | // Sometimes, gfxPrefs is not initialized here. Make sure the gfxPrefs is |
995 | 0 | // ready. |
996 | 0 | gfxPrefs::GetSingleton(); |
997 | 0 |
|
998 | 0 | if (gfxPlatform::IsInLayoutAsapMode()) { |
999 | 0 | return; |
1000 | 0 | } |
1001 | 0 | |
1002 | 0 | if (XRE_IsParentProcess()) { |
1003 | 0 | // Make sure all vsync systems are ready. |
1004 | 0 | gfxPlatform::GetPlatform(); |
1005 | 0 | // In parent process, we don't need to use ipc. We can create the |
1006 | 0 | // VsyncRefreshDriverTimer directly. |
1007 | 0 | sRegularRateTimer = new VsyncRefreshDriverTimer(); |
1008 | 0 | return; |
1009 | 0 | } |
1010 | 0 | |
1011 | 0 | // If this process is not created by NUWA, just create the vsync timer here. |
1012 | 0 | CreateContentVsyncRefreshTimer(nullptr); |
1013 | 0 | } |
1014 | | |
1015 | | static uint32_t |
1016 | | GetFirstFrameDelay(imgIRequest* req) |
1017 | 0 | { |
1018 | 0 | nsCOMPtr<imgIContainer> container; |
1019 | 0 | if (NS_FAILED(req->GetImage(getter_AddRefs(container))) || !container) { |
1020 | 0 | return 0; |
1021 | 0 | } |
1022 | 0 | |
1023 | 0 | // If this image isn't animated, there isn't a first frame delay. |
1024 | 0 | int32_t delay = container->GetFirstFrameDelay(); |
1025 | 0 | if (delay < 0) |
1026 | 0 | return 0; |
1027 | 0 | |
1028 | 0 | return static_cast<uint32_t>(delay); |
1029 | 0 | } |
1030 | | |
1031 | | /* static */ void |
1032 | | nsRefreshDriver::Shutdown() |
1033 | 0 | { |
1034 | 0 | // clean up our timers |
1035 | 0 | sRegularRateTimer = nullptr; |
1036 | 0 | sThrottledRateTimer = nullptr; |
1037 | 0 | } |
1038 | | |
1039 | | /* static */ int32_t |
1040 | | nsRefreshDriver::DefaultInterval() |
1041 | 0 | { |
1042 | 0 | return NSToIntRound(1000.0 / gfxPlatform::GetDefaultFrameRate()); |
1043 | 0 | } |
1044 | | |
1045 | | // Compute the interval to use for the refresh driver timer, in milliseconds. |
1046 | | // outIsDefault indicates that rate was not explicitly set by the user |
1047 | | // so we might choose other, more appropriate rates (e.g. vsync, etc) |
1048 | | // layout.frame_rate=0 indicates "ASAP mode". |
1049 | | // In ASAP mode rendering is iterated as fast as possible (typically for stress testing). |
1050 | | // A target rate of 10k is used internally instead of special-handling 0. |
1051 | | // Backends which block on swap/present/etc should try to not block |
1052 | | // when layout.frame_rate=0 - to comply with "ASAP" as much as possible. |
1053 | | double |
1054 | | nsRefreshDriver::GetRegularTimerInterval() const |
1055 | 0 | { |
1056 | 0 | int32_t rate = Preferences::GetInt("layout.frame_rate", -1); |
1057 | 0 | if (rate < 0) { |
1058 | 0 | rate = gfxPlatform::GetDefaultFrameRate(); |
1059 | 0 | } else if (rate == 0) { |
1060 | 0 | rate = 10000; |
1061 | 0 | } |
1062 | 0 |
|
1063 | 0 | return 1000.0 / rate; |
1064 | 0 | } |
1065 | | |
1066 | | /* static */ double |
1067 | | nsRefreshDriver::GetThrottledTimerInterval() |
1068 | 0 | { |
1069 | 0 | int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1); |
1070 | 0 | if (rate <= 0) { |
1071 | 0 | rate = DEFAULT_THROTTLED_FRAME_RATE; |
1072 | 0 | } |
1073 | 0 | return 1000.0 / rate; |
1074 | 0 | } |
1075 | | |
1076 | | /* static */ mozilla::TimeDuration |
1077 | | nsRefreshDriver::GetMinRecomputeVisibilityInterval() |
1078 | 0 | { |
1079 | 0 | int32_t interval = |
1080 | 0 | Preferences::GetInt("layout.visibility.min-recompute-interval-ms", -1); |
1081 | 0 | if (interval <= 0) { |
1082 | 0 | interval = DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS; |
1083 | 0 | } |
1084 | 0 | return TimeDuration::FromMilliseconds(interval); |
1085 | 0 | } |
1086 | | |
1087 | | RefreshDriverTimer* |
1088 | | nsRefreshDriver::ChooseTimer() const |
1089 | 0 | { |
1090 | 0 | if (mThrottled) { |
1091 | 0 | if (!sThrottledRateTimer) |
1092 | 0 | sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(), |
1093 | 0 | DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0); |
1094 | 0 | return sThrottledRateTimer; |
1095 | 0 | } |
1096 | 0 |
|
1097 | 0 | if (!sRegularRateTimer) { |
1098 | 0 | double rate = GetRegularTimerInterval(); |
1099 | 0 |
|
1100 | 0 | // Try to use vsync-base refresh timer first for sRegularRateTimer. |
1101 | 0 | CreateVsyncRefreshTimer(); |
1102 | 0 |
|
1103 | 0 | if (!sRegularRateTimer) { |
1104 | 0 | sRegularRateTimer = new StartupRefreshDriverTimer(rate); |
1105 | 0 | } |
1106 | 0 | } |
1107 | 0 | return sRegularRateTimer; |
1108 | 0 | } |
1109 | | |
1110 | | nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext) |
1111 | | : mActiveTimer(nullptr), |
1112 | | mPresContext(aPresContext), |
1113 | | mRootRefresh(nullptr), |
1114 | | mNextTransactionId{0}, |
1115 | | mOutstandingTransactionId{0}, |
1116 | | mCompletedTransaction{0}, |
1117 | | mFreezeCount(0), |
1118 | | mThrottledFrameRequestInterval(TimeDuration::FromMilliseconds( |
1119 | | GetThrottledTimerInterval())), |
1120 | | mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()), |
1121 | | mThrottled(false), |
1122 | | mNeedToRecomputeVisibility(false), |
1123 | | mTestControllingRefreshes(false), |
1124 | | mViewManagerFlushIsPending(false), |
1125 | | mHasScheduleFlush(false), |
1126 | | mInRefresh(false), |
1127 | | mWaitingForTransaction(false), |
1128 | | mSkippedPaints(false), |
1129 | | mResizeSuppressed(false), |
1130 | | mNotifyDOMContentFlushed(false), |
1131 | | mWarningThreshold(REFRESH_WAIT_WARNING) |
1132 | 0 | { |
1133 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1134 | 0 | MOZ_ASSERT(mPresContext, |
1135 | 0 | "Need a pres context to tell us to call Disconnect() later " |
1136 | 0 | "and decrement sRefreshDriverCount."); |
1137 | 0 | mMostRecentRefresh = TimeStamp::Now(); |
1138 | 0 | mNextThrottledFrameRequestTick = mMostRecentRefresh; |
1139 | 0 | mNextRecomputeVisibilityTick = mMostRecentRefresh; |
1140 | 0 |
|
1141 | 0 | ++sRefreshDriverCount; |
1142 | 0 | } |
1143 | | |
1144 | | nsRefreshDriver::~nsRefreshDriver() |
1145 | 0 | { |
1146 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1147 | 0 | MOZ_ASSERT(ObserverCount() == mEarlyRunners.Length(), |
1148 | 0 | "observers, except pending selection scrolls, " |
1149 | 0 | "should have been unregistered"); |
1150 | 0 | MOZ_ASSERT(!mActiveTimer, "timer should be gone"); |
1151 | 0 | MOZ_ASSERT(!mPresContext, |
1152 | 0 | "Should have called Disconnect() and decremented " |
1153 | 0 | "sRefreshDriverCount!"); |
1154 | 0 |
|
1155 | 0 | if (mRootRefresh) { |
1156 | 0 | mRootRefresh->RemoveRefreshObserver(this, FlushType::Style); |
1157 | 0 | mRootRefresh = nullptr; |
1158 | 0 | } |
1159 | 0 | } |
1160 | | |
1161 | | // Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh |
1162 | | // for description. |
1163 | | void |
1164 | | nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) |
1165 | 0 | { |
1166 | 0 | // ensure that we're removed from our driver |
1167 | 0 | StopTimer(); |
1168 | 0 |
|
1169 | 0 | if (!mTestControllingRefreshes) { |
1170 | 0 | mMostRecentRefresh = TimeStamp::Now(); |
1171 | 0 |
|
1172 | 0 | mTestControllingRefreshes = true; |
1173 | 0 | if (mWaitingForTransaction) { |
1174 | 0 | // Disable any refresh driver throttling when entering test mode |
1175 | 0 | mWaitingForTransaction = false; |
1176 | 0 | mSkippedPaints = false; |
1177 | 0 | mWarningThreshold = REFRESH_WAIT_WARNING; |
1178 | 0 | } |
1179 | 0 | } |
1180 | 0 |
|
1181 | 0 | mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds); |
1182 | 0 |
|
1183 | 0 | mozilla::dom::AutoNoJSAPI nojsapi; |
1184 | 0 | DoTick(); |
1185 | 0 | } |
1186 | | |
1187 | | void |
1188 | | nsRefreshDriver::RestoreNormalRefresh() |
1189 | 0 | { |
1190 | 0 | mTestControllingRefreshes = false; |
1191 | 0 | EnsureTimerStarted(eAllowTimeToGoBackwards); |
1192 | 0 | mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId; |
1193 | 0 | } |
1194 | | |
1195 | | TimeStamp |
1196 | | nsRefreshDriver::MostRecentRefresh() const |
1197 | 0 | { |
1198 | 0 | // In case of stylo traversal, we have already activated the refresh driver in |
1199 | 0 | // RestyleManager::ProcessPendingRestyles(). |
1200 | 0 | if (!ServoStyleSet::IsInServoTraversal()) { |
1201 | 0 | const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted(); |
1202 | 0 | } |
1203 | 0 |
|
1204 | 0 | return mMostRecentRefresh; |
1205 | 0 | } |
1206 | | |
1207 | | bool |
1208 | | nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver, |
1209 | | FlushType aFlushType) |
1210 | 0 | { |
1211 | 0 | ObserverArray& array = ArrayFor(aFlushType); |
1212 | 0 | bool success = array.AppendElement(aObserver) != nullptr; |
1213 | 0 | EnsureTimerStarted(); |
1214 | 0 | return success; |
1215 | 0 | } |
1216 | | |
1217 | | bool |
1218 | | nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver, |
1219 | | FlushType aFlushType) |
1220 | 0 | { |
1221 | 0 | ObserverArray& array = ArrayFor(aFlushType); |
1222 | 0 | return array.RemoveElement(aObserver); |
1223 | 0 | } |
1224 | | |
1225 | | bool |
1226 | | nsRefreshDriver::AddTimerAdjustmentObserver( |
1227 | | nsATimerAdjustmentObserver *aObserver) |
1228 | 0 | { |
1229 | 0 | MOZ_ASSERT(!mTimerAdjustmentObservers.Contains(aObserver)); |
1230 | 0 |
|
1231 | 0 | return mTimerAdjustmentObservers.AppendElement(aObserver) != nullptr; |
1232 | 0 | } |
1233 | | |
1234 | | bool |
1235 | | nsRefreshDriver::RemoveTimerAdjustmentObserver( |
1236 | | nsATimerAdjustmentObserver *aObserver) |
1237 | 0 | { |
1238 | 0 | MOZ_ASSERT(mTimerAdjustmentObservers.Contains(aObserver)); |
1239 | 0 | return mTimerAdjustmentObservers.RemoveElement(aObserver); |
1240 | 0 | } |
1241 | | |
1242 | | void |
1243 | | nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent) |
1244 | 0 | { |
1245 | 0 | mScrollEvents.AppendElement(aScrollEvent); |
1246 | 0 | EnsureTimerStarted(); |
1247 | 0 | } |
1248 | | |
1249 | | void |
1250 | | nsRefreshDriver::DispatchScrollEvents() |
1251 | 0 | { |
1252 | 0 | // Scroll events are one-shot, so after running them we can drop them. |
1253 | 0 | // However, dispatching a scroll event can potentially cause more scroll |
1254 | 0 | // events to be posted, so we move the initial set into a temporary array |
1255 | 0 | // first. (Newly posted scroll events will be dispatched on the next tick.) |
1256 | 0 | ScrollEventArray events; |
1257 | 0 | events.SwapElements(mScrollEvents); |
1258 | 0 | for (auto& event : events) { |
1259 | 0 | event->Run(); |
1260 | 0 | } |
1261 | 0 | } |
1262 | | |
1263 | | void |
1264 | | nsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver) |
1265 | 0 | { |
1266 | 0 | mPostRefreshObservers.AppendElement(aObserver); |
1267 | 0 | } |
1268 | | |
1269 | | void |
1270 | | nsRefreshDriver::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver) |
1271 | 0 | { |
1272 | 0 | mPostRefreshObservers.RemoveElement(aObserver); |
1273 | 0 | } |
1274 | | |
1275 | | bool |
1276 | | nsRefreshDriver::AddImageRequest(imgIRequest* aRequest) |
1277 | 0 | { |
1278 | 0 | uint32_t delay = GetFirstFrameDelay(aRequest); |
1279 | 0 | if (delay == 0) { |
1280 | 0 | mRequests.PutEntry(aRequest); |
1281 | 0 | } else { |
1282 | 0 | ImageStartData* start = mStartTable.LookupForAdd(delay).OrInsert( |
1283 | 0 | [] () { return new ImageStartData(); }); |
1284 | 0 | start->mEntries.PutEntry(aRequest); |
1285 | 0 | } |
1286 | 0 |
|
1287 | 0 | EnsureTimerStarted(); |
1288 | 0 |
|
1289 | 0 | return true; |
1290 | 0 | } |
1291 | | |
1292 | | void |
1293 | | nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest) |
1294 | 0 | { |
1295 | 0 | // Try to remove from both places, just in case, because we can't tell |
1296 | 0 | // whether RemoveEntry() succeeds. |
1297 | 0 | mRequests.RemoveEntry(aRequest); |
1298 | 0 | uint32_t delay = GetFirstFrameDelay(aRequest); |
1299 | 0 | if (delay != 0) { |
1300 | 0 | ImageStartData* start = mStartTable.Get(delay); |
1301 | 0 | if (start) { |
1302 | 0 | start->mEntries.RemoveEntry(aRequest); |
1303 | 0 | } |
1304 | 0 | } |
1305 | 0 | } |
1306 | | |
1307 | | void |
1308 | | nsRefreshDriver::NotifyDOMContentLoaded() |
1309 | 0 | { |
1310 | 0 | // If the refresh driver is going to tick, we mark the timestamp after |
1311 | 0 | // everything is flushed in the next tick. If it isn't, mark ourselves as |
1312 | 0 | // flushed now. |
1313 | 0 | if (!HasObservers()) { |
1314 | 0 | GetPresContext()->NotifyDOMContentFlushed(); |
1315 | 0 | } else { |
1316 | 0 | mNotifyDOMContentFlushed = true; |
1317 | 0 | } |
1318 | 0 | } |
1319 | | |
1320 | | void |
1321 | | nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) |
1322 | 0 | { |
1323 | 0 | // FIXME: Bug 1346065: We should also assert the case where we have |
1324 | 0 | // STYLO_THREADS=1. |
1325 | 0 | MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal() || NS_IsMainThread(), |
1326 | 0 | "EnsureTimerStarted should be called only when we are not " |
1327 | 0 | "in servo traversal or on the main-thread"); |
1328 | 0 |
|
1329 | 0 | if (mTestControllingRefreshes) |
1330 | 0 | return; |
1331 | 0 | |
1332 | 0 | // will it already fire, and no other changes needed? |
1333 | 0 | if (mActiveTimer && !(aFlags & eForceAdjustTimer)) |
1334 | 0 | return; |
1335 | 0 | |
1336 | 0 | if (IsFrozen() || !mPresContext) { |
1337 | 0 | // If we don't want to start it now, or we've been disconnected. |
1338 | 0 | StopTimer(); |
1339 | 0 | return; |
1340 | 0 | } |
1341 | 0 | |
1342 | 0 | if (mPresContext->Document()->IsBeingUsedAsImage()) { |
1343 | 0 | // Image documents receive ticks from clients' refresh drivers. |
1344 | 0 | // XXXdholbert Exclude SVG-in-opentype fonts from this optimization, until |
1345 | 0 | // they receive refresh-driver ticks from their client docs (bug 1107252). |
1346 | 0 | nsIURI* uri = mPresContext->Document()->GetDocumentURI(); |
1347 | 0 | if (!uri || !mozilla::dom::IsFontTableURI(uri)) { |
1348 | 0 | MOZ_ASSERT(!mActiveTimer, |
1349 | 0 | "image doc refresh driver should never have its own timer"); |
1350 | 0 | return; |
1351 | 0 | } |
1352 | 0 | } |
1353 | 0 |
|
1354 | 0 | // We got here because we're either adjusting the time *or* we're |
1355 | 0 | // starting it for the first time. Add to the right timer, |
1356 | 0 | // prehaps removing it from a previously-set one. |
1357 | 0 | RefreshDriverTimer *newTimer = ChooseTimer(); |
1358 | 0 | if (newTimer != mActiveTimer) { |
1359 | 0 | if (mActiveTimer) |
1360 | 0 | mActiveTimer->RemoveRefreshDriver(this); |
1361 | 0 | mActiveTimer = newTimer; |
1362 | 0 | mActiveTimer->AddRefreshDriver(this); |
1363 | 0 | } |
1364 | 0 |
|
1365 | 0 | // When switching from an inactive timer to an active timer, the root |
1366 | 0 | // refresh driver is skipped due to being set to the content refresh |
1367 | 0 | // driver's timestamp. In case of EnsureTimerStarted is called from |
1368 | 0 | // ScheduleViewManagerFlush, we should avoid this behavior to flush |
1369 | 0 | // a paint in the same tick on the root refresh driver. |
1370 | 0 | if (aFlags & eNeverAdjustTimer) { |
1371 | 0 | return; |
1372 | 0 | } |
1373 | 0 | |
1374 | 0 | // Since the different timers are sampled at different rates, when switching |
1375 | 0 | // timers, the most recent refresh of the new timer may be *before* the |
1376 | 0 | // most recent refresh of the old timer. However, the refresh driver time |
1377 | 0 | // should not go backwards so we clamp the most recent refresh time. |
1378 | 0 | // |
1379 | 0 | // The one exception to this is when we are restoring the refresh driver |
1380 | 0 | // from test control in which case the time is expected to go backwards |
1381 | 0 | // (see bug 1043078). |
1382 | 0 | TimeStamp newMostRecentRefresh = |
1383 | 0 | aFlags & eAllowTimeToGoBackwards |
1384 | 0 | ? mActiveTimer->MostRecentRefresh() |
1385 | 0 | : std::max(mActiveTimer->MostRecentRefresh(), mMostRecentRefresh); |
1386 | 0 |
|
1387 | 0 | if (mMostRecentRefresh != newMostRecentRefresh) { |
1388 | 0 | mMostRecentRefresh = newMostRecentRefresh; |
1389 | 0 |
|
1390 | 0 | nsTObserverArray<nsATimerAdjustmentObserver*>::EndLimitedIterator |
1391 | 0 | iter(mTimerAdjustmentObservers); |
1392 | 0 | while (iter.HasMore()) { |
1393 | 0 | nsATimerAdjustmentObserver* obs = iter.GetNext(); |
1394 | 0 | obs->NotifyTimerAdjusted(mMostRecentRefresh); |
1395 | 0 | } |
1396 | 0 | } |
1397 | 0 | } |
1398 | | |
1399 | | void |
1400 | | nsRefreshDriver::StopTimer() |
1401 | 0 | { |
1402 | 0 | if (!mActiveTimer) |
1403 | 0 | return; |
1404 | 0 | |
1405 | 0 | mActiveTimer->RemoveRefreshDriver(this); |
1406 | 0 | mActiveTimer = nullptr; |
1407 | 0 | } |
1408 | | |
1409 | | uint32_t |
1410 | | nsRefreshDriver::ObserverCount() const |
1411 | 0 | { |
1412 | 0 | uint32_t sum = 0; |
1413 | 0 | for (const ObserverArray& array : mObservers) { |
1414 | 0 | sum += array.Length(); |
1415 | 0 | } |
1416 | 0 |
|
1417 | 0 | // Even while throttled, we need to process layout and style changes. Style |
1418 | 0 | // changes can trigger transitions which fire events when they complete, and |
1419 | 0 | // layout changes can affect media queries on child documents, triggering |
1420 | 0 | // style changes, etc. |
1421 | 0 | sum += mAnimationEventFlushObservers.Length(); |
1422 | 0 | sum += mResizeEventFlushObservers.Length(); |
1423 | 0 | sum += mStyleFlushObservers.Length(); |
1424 | 0 | sum += mLayoutFlushObservers.Length(); |
1425 | 0 | sum += mPendingFullscreenEvents.Length(); |
1426 | 0 | sum += mFrameRequestCallbackDocs.Length(); |
1427 | 0 | sum += mThrottledFrameRequestCallbackDocs.Length(); |
1428 | 0 | sum += mViewManagerFlushIsPending; |
1429 | 0 | sum += mEarlyRunners.Length(); |
1430 | 0 | sum += mTimerAdjustmentObservers.Length(); |
1431 | 0 | return sum; |
1432 | 0 | } |
1433 | | |
1434 | | bool |
1435 | | nsRefreshDriver::HasObservers() const |
1436 | 0 | { |
1437 | 0 | for (const ObserverArray& array : mObservers) { |
1438 | 0 | if (!array.IsEmpty()) { |
1439 | 0 | return true; |
1440 | 0 | } |
1441 | 0 | } |
1442 | 0 |
|
1443 | 0 | // We should NOT count mTimerAdjustmentObservers here since this method is |
1444 | 0 | // used to determine whether or not to stop the timer or re-start it and timer |
1445 | 0 | // adjustment observers should not influence timer starting or stopping. |
1446 | 0 | return mViewManagerFlushIsPending || |
1447 | 0 | !mStyleFlushObservers.IsEmpty() || |
1448 | 0 | !mLayoutFlushObservers.IsEmpty() || |
1449 | 0 | !mAnimationEventFlushObservers.IsEmpty() || |
1450 | 0 | !mResizeEventFlushObservers.IsEmpty() || |
1451 | 0 | !mPendingFullscreenEvents.IsEmpty() || |
1452 | 0 | !mFrameRequestCallbackDocs.IsEmpty() || |
1453 | 0 | !mThrottledFrameRequestCallbackDocs.IsEmpty() || |
1454 | 0 | !mEarlyRunners.IsEmpty(); |
1455 | 0 | } |
1456 | | |
1457 | | bool |
1458 | | nsRefreshDriver::HasImageRequests() const |
1459 | 0 | { |
1460 | 0 | for (auto iter = mStartTable.ConstIter(); !iter.Done(); iter.Next()) { |
1461 | 0 | if (!iter.UserData()->mEntries.IsEmpty()) { |
1462 | 0 | return true; |
1463 | 0 | } |
1464 | 0 | } |
1465 | 0 |
|
1466 | 0 | return !mRequests.IsEmpty(); |
1467 | 0 | } |
1468 | | |
1469 | | nsRefreshDriver::ObserverArray& |
1470 | | nsRefreshDriver::ArrayFor(FlushType aFlushType) |
1471 | 0 | { |
1472 | 0 | switch (aFlushType) { |
1473 | 0 | case FlushType::Event: |
1474 | 0 | return mObservers[0]; |
1475 | 0 | case FlushType::Style: |
1476 | 0 | return mObservers[1]; |
1477 | 0 | case FlushType::Layout: |
1478 | 0 | return mObservers[2]; |
1479 | 0 | case FlushType::Display: |
1480 | 0 | return mObservers[3]; |
1481 | 0 | default: |
1482 | 0 | MOZ_CRASH("We don't track refresh observers for this flush type"); |
1483 | 0 | } |
1484 | 0 | } |
1485 | | |
1486 | | /* |
1487 | | * nsITimerCallback implementation |
1488 | | */ |
1489 | | |
1490 | | void |
1491 | | nsRefreshDriver::DoTick() |
1492 | 0 | { |
1493 | 0 | MOZ_ASSERT(!IsFrozen(), "Why are we notified while frozen?"); |
1494 | 0 | MOZ_ASSERT(mPresContext, "Why are we notified after disconnection?"); |
1495 | 0 | MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(), |
1496 | 0 | "Shouldn't have a JSContext on the stack"); |
1497 | 0 |
|
1498 | 0 | if (mTestControllingRefreshes) { |
1499 | 0 | Tick(mMostRecentRefresh); |
1500 | 0 | } else { |
1501 | 0 | Tick(TimeStamp::Now()); |
1502 | 0 | } |
1503 | 0 | } |
1504 | | |
1505 | | struct DocumentFrameCallbacks { |
1506 | | explicit DocumentFrameCallbacks(nsIDocument* aDocument) : |
1507 | | mDocument(aDocument) |
1508 | 0 | {} |
1509 | | |
1510 | | nsCOMPtr<nsIDocument> mDocument; |
1511 | | nsIDocument::FrameRequestCallbackList mCallbacks; |
1512 | | }; |
1513 | | |
1514 | | static nsDocShell* GetDocShell(nsPresContext* aPresContext) |
1515 | 0 | { |
1516 | 0 | return static_cast<nsDocShell*>(aPresContext->GetDocShell()); |
1517 | 0 | } |
1518 | | |
1519 | | static bool |
1520 | | HasPendingAnimations(nsIPresShell* aShell) |
1521 | 0 | { |
1522 | 0 | nsIDocument* doc = aShell->GetDocument(); |
1523 | 0 | if (!doc) { |
1524 | 0 | return false; |
1525 | 0 | } |
1526 | 0 | |
1527 | 0 | PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker(); |
1528 | 0 | return tracker && tracker->HasPendingAnimations(); |
1529 | 0 | } |
1530 | | |
1531 | | /** |
1532 | | * Return a list of all the child docShells in a given root docShell that are |
1533 | | * visible and are recording markers for the profilingTimeline |
1534 | | */ |
1535 | | static void GetProfileTimelineSubDocShells(nsDocShell* aRootDocShell, |
1536 | | nsTArray<nsDocShell*>& aShells) |
1537 | 0 | { |
1538 | 0 | if (!aRootDocShell) { |
1539 | 0 | return; |
1540 | 0 | } |
1541 | 0 | |
1542 | 0 | RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); |
1543 | 0 | if (!timelines || timelines->IsEmpty()) { |
1544 | 0 | return; |
1545 | 0 | } |
1546 | 0 | |
1547 | 0 | nsCOMPtr<nsISimpleEnumerator> enumerator; |
1548 | 0 | nsresult rv = aRootDocShell->GetDocShellEnumerator( |
1549 | 0 | nsIDocShellTreeItem::typeAll, |
1550 | 0 | nsIDocShell::ENUMERATE_BACKWARDS, |
1551 | 0 | getter_AddRefs(enumerator)); |
1552 | 0 |
|
1553 | 0 | if (NS_FAILED(rv)) { |
1554 | 0 | return; |
1555 | 0 | } |
1556 | 0 | |
1557 | 0 | nsCOMPtr<nsIDocShell> curItem; |
1558 | 0 | bool hasMore = false; |
1559 | 0 | while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { |
1560 | 0 | nsCOMPtr<nsISupports> curSupports; |
1561 | 0 | enumerator->GetNext(getter_AddRefs(curSupports)); |
1562 | 0 | curItem = do_QueryInterface(curSupports); |
1563 | 0 |
|
1564 | 0 | if (!curItem || !curItem->GetRecordProfileTimelineMarkers()) { |
1565 | 0 | continue; |
1566 | 0 | } |
1567 | 0 | |
1568 | 0 | nsDocShell* shell = static_cast<nsDocShell*>(curItem.get()); |
1569 | 0 | bool isVisible = false; |
1570 | 0 | shell->GetVisibility(&isVisible); |
1571 | 0 | if (!isVisible) { |
1572 | 0 | continue; |
1573 | 0 | } |
1574 | 0 | |
1575 | 0 | aShells.AppendElement(shell); |
1576 | 0 | } |
1577 | 0 | } |
1578 | | |
1579 | | static void |
1580 | | TakeFrameRequestCallbacksFrom(nsIDocument* aDocument, |
1581 | | nsTArray<DocumentFrameCallbacks>& aTarget) |
1582 | 0 | { |
1583 | 0 | aTarget.AppendElement(aDocument); |
1584 | 0 | aDocument->TakeFrameRequestCallbacks(aTarget.LastElement().mCallbacks); |
1585 | 0 | } |
1586 | | |
1587 | | // https://fullscreen.spec.whatwg.org/#run-the-fullscreen-steps |
1588 | | void |
1589 | | nsRefreshDriver::RunFullscreenSteps() |
1590 | 0 | { |
1591 | 0 | // Swap out the current pending events |
1592 | 0 | nsTArray<UniquePtr<PendingFullscreenEvent>> |
1593 | 0 | pendings(std::move(mPendingFullscreenEvents)); |
1594 | 0 | for (UniquePtr<PendingFullscreenEvent>& event : pendings) { |
1595 | 0 | event->Dispatch(); |
1596 | 0 | } |
1597 | 0 | } |
1598 | | |
1599 | | void |
1600 | | nsRefreshDriver::UpdateIntersectionObservations() |
1601 | 0 | { |
1602 | 0 | AutoTArray<nsCOMPtr<nsIDocument>, 32> documents; |
1603 | 0 |
|
1604 | 0 | if (mPresContext->Document()->HasIntersectionObservers()) { |
1605 | 0 | documents.AppendElement(mPresContext->Document()); |
1606 | 0 | } |
1607 | 0 |
|
1608 | 0 | mPresContext->Document()->CollectDescendantDocuments( |
1609 | 0 | documents, |
1610 | 0 | [](const nsIDocument* document) -> bool { |
1611 | 0 | return document->HasIntersectionObservers(); |
1612 | 0 | }); |
1613 | 0 |
|
1614 | 0 | for (uint32_t i = 0; i < documents.Length(); ++i) { |
1615 | 0 | nsIDocument* doc = documents[i]; |
1616 | 0 | doc->UpdateIntersectionObservations(); |
1617 | 0 | doc->ScheduleIntersectionObserverNotification(); |
1618 | 0 | } |
1619 | 0 | } |
1620 | | |
1621 | | void |
1622 | | nsRefreshDriver::DispatchAnimationEvents() |
1623 | 0 | { |
1624 | 0 | if (!mPresContext) { |
1625 | 0 | return; |
1626 | 0 | } |
1627 | 0 | |
1628 | 0 | // Hold all AnimationEventDispatcher in mAnimationEventFlushObservers as |
1629 | 0 | // a RefPtr<> array since each AnimationEventDispatcher might be destroyed |
1630 | 0 | // during processing the previous dispatcher. |
1631 | 0 | AutoTArray<RefPtr<AnimationEventDispatcher>, 16> dispatchers; |
1632 | 0 | dispatchers.AppendElements(mAnimationEventFlushObservers); |
1633 | 0 | mAnimationEventFlushObservers.Clear(); |
1634 | 0 |
|
1635 | 0 | for (auto& dispatcher : dispatchers) { |
1636 | 0 | dispatcher->DispatchEvents(); |
1637 | 0 | } |
1638 | 0 | } |
1639 | | |
1640 | | void |
1641 | | nsRefreshDriver::RunFrameRequestCallbacks(TimeStamp aNowTime) |
1642 | 0 | { |
1643 | 0 | // Grab all of our frame request callbacks up front. |
1644 | 0 | nsTArray<DocumentFrameCallbacks> |
1645 | 0 | frameRequestCallbacks(mFrameRequestCallbackDocs.Length() + |
1646 | 0 | mThrottledFrameRequestCallbackDocs.Length()); |
1647 | 0 |
|
1648 | 0 | // First, grab throttled frame request callbacks. |
1649 | 0 | { |
1650 | 0 | nsTArray<nsIDocument*> docsToRemove; |
1651 | 0 |
|
1652 | 0 | // We always tick throttled frame requests if the entire refresh driver is |
1653 | 0 | // throttled, because in that situation throttled frame requests tick at the |
1654 | 0 | // same frequency as non-throttled frame requests. |
1655 | 0 | bool tickThrottledFrameRequests = mThrottled; |
1656 | 0 |
|
1657 | 0 | if (!tickThrottledFrameRequests && |
1658 | 0 | aNowTime >= mNextThrottledFrameRequestTick) { |
1659 | 0 | mNextThrottledFrameRequestTick = aNowTime + mThrottledFrameRequestInterval; |
1660 | 0 | tickThrottledFrameRequests = true; |
1661 | 0 | } |
1662 | 0 |
|
1663 | 0 | for (nsIDocument* doc : mThrottledFrameRequestCallbackDocs) { |
1664 | 0 | if (tickThrottledFrameRequests) { |
1665 | 0 | // We're ticking throttled documents, so grab this document's requests. |
1666 | 0 | // We don't bother appending to docsToRemove because we're going to |
1667 | 0 | // clear mThrottledFrameRequestCallbackDocs anyway. |
1668 | 0 | TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks); |
1669 | 0 | } else if (!doc->ShouldThrottleFrameRequests()) { |
1670 | 0 | // This document is no longer throttled, so grab its requests even |
1671 | 0 | // though we're not ticking throttled frame requests right now. If |
1672 | 0 | // this is the first unthrottled document with frame requests, we'll |
1673 | 0 | // enter high precision mode the next time the callback is scheduled. |
1674 | 0 | TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks); |
1675 | 0 | docsToRemove.AppendElement(doc); |
1676 | 0 | } |
1677 | 0 | } |
1678 | 0 |
|
1679 | 0 | // Remove all the documents we're ticking from |
1680 | 0 | // mThrottledFrameRequestCallbackDocs so they can be readded as needed. |
1681 | 0 | if (tickThrottledFrameRequests) { |
1682 | 0 | mThrottledFrameRequestCallbackDocs.Clear(); |
1683 | 0 | } else { |
1684 | 0 | // XXX(seth): We're using this approach to avoid concurrent modification |
1685 | 0 | // of mThrottledFrameRequestCallbackDocs. docsToRemove usually has either |
1686 | 0 | // zero elements or a very small number, so this should be OK in practice. |
1687 | 0 | for (nsIDocument* doc : docsToRemove) { |
1688 | 0 | mThrottledFrameRequestCallbackDocs.RemoveElement(doc); |
1689 | 0 | } |
1690 | 0 | } |
1691 | 0 | } |
1692 | 0 |
|
1693 | 0 | // Now grab unthrottled frame request callbacks. |
1694 | 0 | for (nsIDocument* doc : mFrameRequestCallbackDocs) { |
1695 | 0 | TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks); |
1696 | 0 | } |
1697 | 0 |
|
1698 | 0 | // Reset mFrameRequestCallbackDocs so they can be readded as needed. |
1699 | 0 | mFrameRequestCallbackDocs.Clear(); |
1700 | 0 |
|
1701 | 0 | if (!frameRequestCallbacks.IsEmpty()) { |
1702 | 0 | AUTO_PROFILER_TRACING("Paint", "Scripts"); |
1703 | 0 | for (const DocumentFrameCallbacks& docCallbacks : frameRequestCallbacks) { |
1704 | 0 | // XXXbz Bug 863140: GetInnerWindow can return the outer |
1705 | 0 | // window in some cases. |
1706 | 0 | nsPIDOMWindowInner* innerWindow = |
1707 | 0 | docCallbacks.mDocument->GetInnerWindow(); |
1708 | 0 | DOMHighResTimeStamp timeStamp = 0; |
1709 | 0 | if (innerWindow) { |
1710 | 0 | mozilla::dom::Performance* perf = innerWindow->GetPerformance(); |
1711 | 0 | if (perf) { |
1712 | 0 | timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime); |
1713 | 0 | } |
1714 | 0 | // else window is partially torn down already |
1715 | 0 | } |
1716 | 0 | for (auto& callback : docCallbacks.mCallbacks) { |
1717 | 0 | callback->Call(timeStamp); |
1718 | 0 | } |
1719 | 0 | } |
1720 | 0 | } |
1721 | 0 | } |
1722 | | |
1723 | | struct RunnableWithDelay |
1724 | | { |
1725 | | nsCOMPtr<nsIRunnable> mRunnable; |
1726 | | uint32_t mDelay; |
1727 | | }; |
1728 | | |
1729 | | static AutoTArray<RunnableWithDelay, 8>* sPendingIdleRunnables = nullptr; |
1730 | | |
1731 | | void |
1732 | | nsRefreshDriver::DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable, |
1733 | | uint32_t aDelay) |
1734 | 0 | { |
1735 | 0 | if (!sPendingIdleRunnables) { |
1736 | 0 | sPendingIdleRunnables = new AutoTArray<RunnableWithDelay, 8>(); |
1737 | 0 | } |
1738 | 0 |
|
1739 | 0 | RunnableWithDelay rwd = {aRunnable, aDelay}; |
1740 | 0 | sPendingIdleRunnables->AppendElement(rwd); |
1741 | 0 | } |
1742 | | |
1743 | | void |
1744 | | nsRefreshDriver::CancelIdleRunnable(nsIRunnable* aRunnable) |
1745 | 164 | { |
1746 | 164 | if (!sPendingIdleRunnables) { |
1747 | 164 | return; |
1748 | 164 | } |
1749 | 0 | |
1750 | 0 | for (uint32_t i = 0; i < sPendingIdleRunnables->Length(); ++i) { |
1751 | 0 | if ((*sPendingIdleRunnables)[i].mRunnable == aRunnable) { |
1752 | 0 | sPendingIdleRunnables->RemoveElementAt(i); |
1753 | 0 | break; |
1754 | 0 | } |
1755 | 0 | } |
1756 | 0 |
|
1757 | 0 | if (sPendingIdleRunnables->IsEmpty()) { |
1758 | 0 | delete sPendingIdleRunnables; |
1759 | 0 | sPendingIdleRunnables = nullptr; |
1760 | 0 | } |
1761 | 0 | } |
1762 | | |
1763 | | void |
1764 | | nsRefreshDriver::Tick(TimeStamp aNowTime) |
1765 | 0 | { |
1766 | 0 | MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(), |
1767 | 0 | "Shouldn't have a JSContext on the stack"); |
1768 | 0 |
|
1769 | 0 | if (nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()) { |
1770 | 0 | NS_ERROR("Refresh driver should not run during plugin call!"); |
1771 | 0 | // Try to survive this by just ignoring the refresh tick. |
1772 | 0 | return; |
1773 | 0 | } |
1774 | 0 |
|
1775 | 0 | AUTO_PROFILER_LABEL("nsRefreshDriver::Tick", LAYOUT); |
1776 | 0 |
|
1777 | 0 | // We're either frozen or we were disconnected (likely in the middle |
1778 | 0 | // of a tick iteration). Just do nothing here, since our |
1779 | 0 | // prescontext went away. |
1780 | 0 | if (IsFrozen() || !mPresContext) { |
1781 | 0 | return; |
1782 | 0 | } |
1783 | 0 | |
1784 | 0 | // We can have a race condition where the vsync timestamp |
1785 | 0 | // is before the most recent refresh due to a forced refresh. |
1786 | 0 | // The underlying assumption is that the refresh driver tick can only |
1787 | 0 | // go forward in time, not backwards. To prevent the refresh |
1788 | 0 | // driver from going back in time, just skip this tick and |
1789 | 0 | // wait until the next tick. |
1790 | 0 | if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes) { |
1791 | 0 | return; |
1792 | 0 | } |
1793 | 0 | |
1794 | 0 | if (IsWaitingForPaint(aNowTime)) { |
1795 | 0 | // We're currently suspended waiting for earlier Tick's to |
1796 | 0 | // be completed (on the Compositor). Mark that we missed the paint |
1797 | 0 | // and keep waiting. |
1798 | 0 | return; |
1799 | 0 | } |
1800 | 0 | |
1801 | 0 | TimeStamp previousRefresh = mMostRecentRefresh; |
1802 | 0 | mMostRecentRefresh = aNowTime; |
1803 | 0 |
|
1804 | 0 | if (mRootRefresh) { |
1805 | 0 | mRootRefresh->RemoveRefreshObserver(this, FlushType::Style); |
1806 | 0 | mRootRefresh = nullptr; |
1807 | 0 | } |
1808 | 0 | mSkippedPaints = false; |
1809 | 0 | mWarningThreshold = 1; |
1810 | 0 |
|
1811 | 0 | nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell(); |
1812 | 0 | if (!presShell || |
1813 | 0 | (!HasObservers() && !HasImageRequests() && mScrollEvents.IsEmpty())) { |
1814 | 0 | // Things are being destroyed, or we no longer have any observers. |
1815 | 0 | // We don't want to stop the timer when observers are initially |
1816 | 0 | // removed, because sometimes observers can be added and removed |
1817 | 0 | // often depending on what other things are going on and in that |
1818 | 0 | // situation we don't want to thrash our timer. So instead we |
1819 | 0 | // wait until we get a Notify() call when we have no observers |
1820 | 0 | // before stopping the timer. |
1821 | 0 | StopTimer(); |
1822 | 0 | return; |
1823 | 0 | } |
1824 | 0 | |
1825 | 0 | mResizeSuppressed = false; |
1826 | 0 |
|
1827 | 0 | AutoRestore<bool> restoreInRefresh(mInRefresh); |
1828 | 0 | mInRefresh = true; |
1829 | 0 |
|
1830 | 0 | AutoRestore<TimeStamp> restoreTickStart(mTickStart); |
1831 | 0 | mTickStart = TimeStamp::Now(); |
1832 | 0 |
|
1833 | 0 | gfxPlatform::GetPlatform()->SchedulePaintIfDeviceReset(); |
1834 | 0 |
|
1835 | 0 | // We want to process any pending APZ metrics ahead of their positions |
1836 | 0 | // in the queue. This will prevent us from spending precious time |
1837 | 0 | // painting a stale displayport. |
1838 | 0 | if (gfxPrefs::APZPeekMessages()) { |
1839 | 0 | nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages(); |
1840 | 0 | } |
1841 | 0 |
|
1842 | 0 | AutoTArray<nsCOMPtr<nsIRunnable>, 16> earlyRunners; |
1843 | 0 | earlyRunners.SwapElements(mEarlyRunners); |
1844 | 0 | for (auto& runner : earlyRunners) { |
1845 | 0 | runner->Run(); |
1846 | 0 | } |
1847 | 0 |
|
1848 | 0 | // Resize events should be fired before layout flushes or |
1849 | 0 | // calling animation frame callbacks. |
1850 | 0 | AutoTArray<nsIPresShell*, 16> observers; |
1851 | 0 | observers.AppendElements(mResizeEventFlushObservers); |
1852 | 0 | for (nsIPresShell* shell : Reversed(observers)) { |
1853 | 0 | if (!mPresContext || !mPresContext->GetPresShell()) { |
1854 | 0 | StopTimer(); |
1855 | 0 | return; |
1856 | 0 | } |
1857 | 0 | // Make sure to not process observers which might have been removed |
1858 | 0 | // during previous iterations. |
1859 | 0 | if (!mResizeEventFlushObservers.RemoveElement(shell)) { |
1860 | 0 | continue; |
1861 | 0 | } |
1862 | 0 | shell->FireResizeEvent(); |
1863 | 0 | } |
1864 | 0 |
|
1865 | 0 | /* |
1866 | 0 | * The timer holds a reference to |this| while calling |Notify|. |
1867 | 0 | * However, implementations of |WillRefresh| are permitted to destroy |
1868 | 0 | * the pres context, which will cause our |mPresContext| to become |
1869 | 0 | * null. If this happens, we must stop notifying observers. |
1870 | 0 | */ |
1871 | 0 | for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) { |
1872 | 0 | ObserverArray::EndLimitedIterator etor(mObservers[i]); |
1873 | 0 | while (etor.HasMore()) { |
1874 | 0 | RefPtr<nsARefreshObserver> obs = etor.GetNext(); |
1875 | 0 | obs->WillRefresh(aNowTime); |
1876 | 0 |
|
1877 | 0 | if (!mPresContext || !mPresContext->GetPresShell()) { |
1878 | 0 | StopTimer(); |
1879 | 0 | return; |
1880 | 0 | } |
1881 | 0 | } |
1882 | 0 |
|
1883 | 0 | if (i == 1) { |
1884 | 0 | // This is the FlushType::Style case. |
1885 | 0 |
|
1886 | 0 | DispatchScrollEvents(); |
1887 | 0 | DispatchAnimationEvents(); |
1888 | 0 | RunFullscreenSteps(); |
1889 | 0 | RunFrameRequestCallbacks(aNowTime); |
1890 | 0 |
|
1891 | 0 | if (mPresContext && mPresContext->GetPresShell()) { |
1892 | 0 | AutoTArray<nsIPresShell*, 16> observers; |
1893 | 0 | observers.AppendElements(mStyleFlushObservers); |
1894 | 0 | for (uint32_t j = observers.Length(); |
1895 | 0 | j && mPresContext && mPresContext->GetPresShell(); --j) { |
1896 | 0 | // Make sure to not process observers which might have been removed |
1897 | 0 | // during previous iterations. |
1898 | 0 | nsIPresShell* shell = observers[j - 1]; |
1899 | 0 | if (!mStyleFlushObservers.RemoveElement(shell)) |
1900 | 0 | continue; |
1901 | 0 | |
1902 | 0 | nsCOMPtr<nsIPresShell> shellKungFuDeathGrip(shell); |
1903 | 0 | shell->mObservingStyleFlushes = false; |
1904 | 0 | shell->FlushPendingNotifications(ChangesToFlush(FlushType::Style, false)); |
1905 | 0 | // Inform the FontFaceSet that we ticked, so that it can resolve its |
1906 | 0 | // ready promise if it needs to (though it might still be waiting on |
1907 | 0 | // a layout flush). |
1908 | 0 | shell->NotifyFontFaceSetOnRefresh(); |
1909 | 0 | mNeedToRecomputeVisibility = true; |
1910 | 0 | } |
1911 | 0 | } |
1912 | 0 | } else if (i == 2) { |
1913 | 0 | // This is the FlushType::Layout case. |
1914 | 0 | AutoTArray<nsIPresShell*, 16> observers; |
1915 | 0 | observers.AppendElements(mLayoutFlushObservers); |
1916 | 0 | for (uint32_t j = observers.Length(); |
1917 | 0 | j && mPresContext && mPresContext->GetPresShell(); --j) { |
1918 | 0 | // Make sure to not process observers which might have been removed |
1919 | 0 | // during previous iterations. |
1920 | 0 | nsIPresShell* shell = observers[j - 1]; |
1921 | 0 | if (!mLayoutFlushObservers.RemoveElement(shell)) |
1922 | 0 | continue; |
1923 | 0 | |
1924 | 0 | nsCOMPtr<nsIPresShell> shellKungFuDeathGrip(shell); |
1925 | 0 | shell->mObservingLayoutFlushes = false; |
1926 | 0 | shell->mWasLastReflowInterrupted = false; |
1927 | 0 | FlushType flushType = HasPendingAnimations(shell) |
1928 | 0 | ? FlushType::Layout |
1929 | 0 | : FlushType::InterruptibleLayout; |
1930 | 0 | shell->FlushPendingNotifications(ChangesToFlush(flushType, false)); |
1931 | 0 | // Inform the FontFaceSet that we ticked, so that it can resolve its |
1932 | 0 | // ready promise if it needs to. |
1933 | 0 | shell->NotifyFontFaceSetOnRefresh(); |
1934 | 0 | mNeedToRecomputeVisibility = true; |
1935 | 0 | } |
1936 | 0 | } |
1937 | 0 |
|
1938 | 0 | // The pres context may be destroyed during we do the flushing. |
1939 | 0 | if (!mPresContext || !mPresContext->GetPresShell()) { |
1940 | 0 | StopTimer(); |
1941 | 0 | return; |
1942 | 0 | } |
1943 | 0 | } |
1944 | 0 |
|
1945 | 0 | // Recompute approximate frame visibility if it's necessary and enough time |
1946 | 0 | // has passed since the last time we did it. |
1947 | 0 | if (mNeedToRecomputeVisibility && !mThrottled && |
1948 | 0 | aNowTime >= mNextRecomputeVisibilityTick && |
1949 | 0 | !presShell->IsPaintingSuppressed()) { |
1950 | 0 | mNextRecomputeVisibilityTick = aNowTime + mMinRecomputeVisibilityInterval; |
1951 | 0 | mNeedToRecomputeVisibility = false; |
1952 | 0 |
|
1953 | 0 | presShell->ScheduleApproximateFrameVisibilityUpdateNow(); |
1954 | 0 | } |
1955 | 0 |
|
1956 | 0 | #ifdef MOZ_XUL |
1957 | 0 | // Update any popups that may need to be moved or hidden due to their |
1958 | 0 | // anchor changing. |
1959 | 0 | if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { |
1960 | 0 | pm->UpdatePopupPositions(this); |
1961 | 0 | } |
1962 | 0 | #endif |
1963 | 0 |
|
1964 | 0 | UpdateIntersectionObservations(); |
1965 | 0 |
|
1966 | 0 | /* |
1967 | 0 | * Perform notification to imgIRequests subscribed to listen |
1968 | 0 | * for refresh events. |
1969 | 0 | */ |
1970 | 0 |
|
1971 | 0 | for (auto iter = mStartTable.Iter(); !iter.Done(); iter.Next()) { |
1972 | 0 | const uint32_t& delay = iter.Key(); |
1973 | 0 | ImageStartData* data = iter.UserData(); |
1974 | 0 |
|
1975 | 0 | if (data->mStartTime) { |
1976 | 0 | TimeStamp& start = *data->mStartTime; |
1977 | 0 | TimeDuration prev = previousRefresh - start; |
1978 | 0 | TimeDuration curr = aNowTime - start; |
1979 | 0 | uint32_t prevMultiple = uint32_t(prev.ToMilliseconds()) / delay; |
1980 | 0 |
|
1981 | 0 | // We want to trigger images' refresh if we've just crossed over a |
1982 | 0 | // multiple of the first image's start time. If so, set the animation |
1983 | 0 | // start time to the nearest multiple of the delay and move all the |
1984 | 0 | // images in this table to the main requests table. |
1985 | 0 | if (prevMultiple != uint32_t(curr.ToMilliseconds()) / delay) { |
1986 | 0 | mozilla::TimeStamp desired = |
1987 | 0 | start + TimeDuration::FromMilliseconds(prevMultiple * delay); |
1988 | 0 | BeginRefreshingImages(data->mEntries, desired); |
1989 | 0 | } |
1990 | 0 | } else { |
1991 | 0 | // This is the very first time we've drawn images with this time delay. |
1992 | 0 | // Set the animation start time to "now" and move all the images in this |
1993 | 0 | // table to the main requests table. |
1994 | 0 | mozilla::TimeStamp desired = aNowTime; |
1995 | 0 | BeginRefreshingImages(data->mEntries, desired); |
1996 | 0 | data->mStartTime.emplace(aNowTime); |
1997 | 0 | } |
1998 | 0 | } |
1999 | 0 |
|
2000 | 0 | if (mRequests.Count()) { |
2001 | 0 | // RequestRefresh may run scripts, so it's not safe to directly call it |
2002 | 0 | // while using a hashtable enumerator to enumerate mRequests in case |
2003 | 0 | // script modifies the hashtable. Instead, we build a (local) array of |
2004 | 0 | // images to refresh, and then we refresh each image in that array. |
2005 | 0 | nsCOMArray<imgIContainer> imagesToRefresh(mRequests.Count()); |
2006 | 0 |
|
2007 | 0 | for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) { |
2008 | 0 | nsISupportsHashKey* entry = iter.Get(); |
2009 | 0 | auto req = static_cast<imgIRequest*>(entry->GetKey()); |
2010 | 0 | MOZ_ASSERT(req, "Unable to retrieve the image request"); |
2011 | 0 | nsCOMPtr<imgIContainer> image; |
2012 | 0 | if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) { |
2013 | 0 | imagesToRefresh.AppendElement(image.forget()); |
2014 | 0 | } |
2015 | 0 | } |
2016 | 0 |
|
2017 | 0 | for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) { |
2018 | 0 | imagesToRefresh[i]->RequestRefresh(aNowTime); |
2019 | 0 | } |
2020 | 0 | } |
2021 | 0 |
|
2022 | 0 | bool dispatchRunnablesAfterTick = false; |
2023 | 0 | if (mViewManagerFlushIsPending) { |
2024 | 0 | RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); |
2025 | 0 |
|
2026 | 0 | nsTArray<nsDocShell*> profilingDocShells; |
2027 | 0 | GetProfileTimelineSubDocShells(GetDocShell(mPresContext), profilingDocShells); |
2028 | 0 | for (nsDocShell* docShell : profilingDocShells) { |
2029 | 0 | // For the sake of the profile timeline's simplicity, this is flagged as |
2030 | 0 | // paint even if it includes creating display lists |
2031 | 0 | MOZ_ASSERT(timelines); |
2032 | 0 | MOZ_ASSERT(timelines->HasConsumer(docShell)); |
2033 | 0 | timelines->AddMarkerForDocShell(docShell, "Paint", MarkerTracingType::START); |
2034 | 0 | } |
2035 | 0 |
|
2036 | | #ifdef MOZ_DUMP_PAINTING |
2037 | | if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { |
2038 | | printf_stderr("Starting ProcessPendingUpdates\n"); |
2039 | | } |
2040 | | #endif |
2041 | |
|
2042 | 0 | mViewManagerFlushIsPending = false; |
2043 | 0 | RefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager(); |
2044 | 0 | { |
2045 | 0 | PaintTelemetry::AutoRecordPaint record; |
2046 | 0 | vm->ProcessPendingUpdates(); |
2047 | 0 | } |
2048 | 0 |
|
2049 | | #ifdef MOZ_DUMP_PAINTING |
2050 | | if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { |
2051 | | printf_stderr("Ending ProcessPendingUpdates\n"); |
2052 | | } |
2053 | | #endif |
2054 | |
|
2055 | 0 | for (nsDocShell* docShell : profilingDocShells) { |
2056 | 0 | MOZ_ASSERT(timelines); |
2057 | 0 | MOZ_ASSERT(timelines->HasConsumer(docShell)); |
2058 | 0 | timelines->AddMarkerForDocShell(docShell, "Paint", MarkerTracingType::END); |
2059 | 0 | } |
2060 | 0 |
|
2061 | 0 | dispatchRunnablesAfterTick = true; |
2062 | 0 | mHasScheduleFlush = false; |
2063 | 0 | } |
2064 | 0 |
|
2065 | 0 | #ifndef ANDROID /* bug 1142079 */ |
2066 | 0 | mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::REFRESH_DRIVER_TICK, mTickStart); |
2067 | 0 | #endif |
2068 | 0 |
|
2069 | 0 | if (mNotifyDOMContentFlushed) { |
2070 | 0 | mNotifyDOMContentFlushed = false; |
2071 | 0 | mPresContext->NotifyDOMContentFlushed(); |
2072 | 0 | } |
2073 | 0 |
|
2074 | 0 | nsTObserverArray<nsAPostRefreshObserver*>::ForwardIterator iter(mPostRefreshObservers); |
2075 | 0 | while (iter.HasMore()) { |
2076 | 0 | nsAPostRefreshObserver* observer = iter.GetNext(); |
2077 | 0 | observer->DidRefresh(); |
2078 | 0 | } |
2079 | 0 |
|
2080 | 0 | NS_ASSERTION(mInRefresh, "Still in refresh"); |
2081 | 0 |
|
2082 | 0 | if (mPresContext->IsRoot() && XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) { |
2083 | 0 | ScheduleViewManagerFlush(); |
2084 | 0 | } |
2085 | 0 |
|
2086 | 0 | if (dispatchRunnablesAfterTick && sPendingIdleRunnables) { |
2087 | 0 | AutoTArray<RunnableWithDelay, 8>* runnables = sPendingIdleRunnables; |
2088 | 0 | sPendingIdleRunnables = nullptr; |
2089 | 0 | for (RunnableWithDelay& runnableWithDelay : *runnables) { |
2090 | 0 | NS_IdleDispatchToCurrentThread(runnableWithDelay.mRunnable.forget(), |
2091 | 0 | runnableWithDelay.mDelay); |
2092 | 0 | } |
2093 | 0 | delete runnables; |
2094 | 0 | } |
2095 | 0 | } |
2096 | | |
2097 | | void |
2098 | | nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries, |
2099 | | mozilla::TimeStamp aDesired) |
2100 | 0 | { |
2101 | 0 | for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) { |
2102 | 0 | auto req = static_cast<imgIRequest*>(iter.Get()->GetKey()); |
2103 | 0 | MOZ_ASSERT(req, "Unable to retrieve the image request"); |
2104 | 0 |
|
2105 | 0 | mRequests.PutEntry(req); |
2106 | 0 |
|
2107 | 0 | nsCOMPtr<imgIContainer> image; |
2108 | 0 | if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) { |
2109 | 0 | image->SetAnimationStartTime(aDesired); |
2110 | 0 | } |
2111 | 0 | } |
2112 | 0 | aEntries.Clear(); |
2113 | 0 | } |
2114 | | |
2115 | | void |
2116 | | nsRefreshDriver::Freeze() |
2117 | 0 | { |
2118 | 0 | StopTimer(); |
2119 | 0 | mFreezeCount++; |
2120 | 0 | } |
2121 | | |
2122 | | void |
2123 | | nsRefreshDriver::Thaw() |
2124 | 0 | { |
2125 | 0 | NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver"); |
2126 | 0 |
|
2127 | 0 | if (mFreezeCount > 0) { |
2128 | 0 | mFreezeCount--; |
2129 | 0 | } |
2130 | 0 |
|
2131 | 0 | if (mFreezeCount == 0) { |
2132 | 0 | if (HasObservers() || HasImageRequests()) { |
2133 | 0 | // FIXME: This isn't quite right, since our EnsureTimerStarted call |
2134 | 0 | // updates our mMostRecentRefresh, but the DoRefresh call won't run |
2135 | 0 | // and notify our observers until we get back to the event loop. |
2136 | 0 | // Thus MostRecentRefresh() will lie between now and the DoRefresh. |
2137 | 0 | RefPtr<nsRunnableMethod<nsRefreshDriver>> event = NewRunnableMethod( |
2138 | 0 | "nsRefreshDriver::DoRefresh", this, &nsRefreshDriver::DoRefresh); |
2139 | 0 | nsPresContext* pc = GetPresContext(); |
2140 | 0 | if (pc) { |
2141 | 0 | pc->Document()->Dispatch(TaskCategory::Other, |
2142 | 0 | event.forget()); |
2143 | 0 | EnsureTimerStarted(); |
2144 | 0 | } else { |
2145 | 0 | NS_ERROR("Thawing while document is being destroyed"); |
2146 | 0 | } |
2147 | 0 | } |
2148 | 0 | } |
2149 | 0 | } |
2150 | | |
2151 | | void |
2152 | | nsRefreshDriver::FinishedWaitingForTransaction() |
2153 | 0 | { |
2154 | 0 | mWaitingForTransaction = false; |
2155 | 0 | mSkippedPaints = false; |
2156 | 0 | mWarningThreshold = 1; |
2157 | 0 | } |
2158 | | |
2159 | | mozilla::layers::TransactionId |
2160 | | nsRefreshDriver::GetTransactionId(bool aThrottle) |
2161 | 0 | { |
2162 | 0 | mOutstandingTransactionId = mOutstandingTransactionId.Next(); |
2163 | 0 | mNextTransactionId = mNextTransactionId.Next(); |
2164 | 0 |
|
2165 | 0 | if (aThrottle && |
2166 | 0 | mOutstandingTransactionId - mCompletedTransaction >= 2 && |
2167 | 0 | !mWaitingForTransaction && |
2168 | 0 | !mTestControllingRefreshes) { |
2169 | 0 | mWaitingForTransaction = true; |
2170 | 0 | mSkippedPaints = false; |
2171 | 0 | mWarningThreshold = 1; |
2172 | 0 | } |
2173 | 0 |
|
2174 | 0 | return mNextTransactionId; |
2175 | 0 | } |
2176 | | |
2177 | | mozilla::layers::TransactionId |
2178 | | nsRefreshDriver::LastTransactionId() const |
2179 | 0 | { |
2180 | 0 | return mNextTransactionId; |
2181 | 0 | } |
2182 | | |
2183 | | void |
2184 | | nsRefreshDriver::RevokeTransactionId(mozilla::layers::TransactionId aTransactionId) |
2185 | 0 | { |
2186 | 0 | MOZ_ASSERT(aTransactionId == mNextTransactionId); |
2187 | 0 | if (mOutstandingTransactionId - mCompletedTransaction == 2 && |
2188 | 0 | mWaitingForTransaction) { |
2189 | 0 | MOZ_ASSERT(!mSkippedPaints, "How did we skip a paint when we're in the middle of one?"); |
2190 | 0 | FinishedWaitingForTransaction(); |
2191 | 0 | } |
2192 | 0 |
|
2193 | 0 | // Notify the pres context so that it can deliver MozAfterPaint for this |
2194 | 0 | // id if any caller was expecting it. |
2195 | 0 | nsPresContext* pc = GetPresContext(); |
2196 | 0 | if (pc) { |
2197 | 0 | pc->NotifyRevokingDidPaint(aTransactionId); |
2198 | 0 | } |
2199 | 0 | // Revert the outstanding transaction since we're no longer waiting on it to be |
2200 | 0 | // completed, but don't revert mNextTransactionId since we can't use the id |
2201 | 0 | // again. |
2202 | 0 | mOutstandingTransactionId = mOutstandingTransactionId.Prev(); |
2203 | 0 | } |
2204 | | |
2205 | | void |
2206 | | nsRefreshDriver::ClearPendingTransactions() |
2207 | 0 | { |
2208 | 0 | mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId; |
2209 | 0 | mWaitingForTransaction = false; |
2210 | 0 | } |
2211 | | |
2212 | | void |
2213 | | nsRefreshDriver::ResetInitialTransactionId(mozilla::layers::TransactionId aTransactionId) |
2214 | 0 | { |
2215 | 0 | mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId = aTransactionId; |
2216 | 0 | } |
2217 | | |
2218 | | mozilla::TimeStamp |
2219 | | nsRefreshDriver::GetTransactionStart() |
2220 | 0 | { |
2221 | 0 | return mTickStart; |
2222 | 0 | } |
2223 | | |
2224 | | void |
2225 | | nsRefreshDriver::NotifyTransactionCompleted(mozilla::layers::TransactionId aTransactionId) |
2226 | 0 | { |
2227 | 0 | if (aTransactionId > mCompletedTransaction) { |
2228 | 0 | if (mOutstandingTransactionId - mCompletedTransaction > 1 && |
2229 | 0 | mWaitingForTransaction) { |
2230 | 0 | mCompletedTransaction = aTransactionId; |
2231 | 0 | FinishedWaitingForTransaction(); |
2232 | 0 | } else { |
2233 | 0 | mCompletedTransaction = aTransactionId; |
2234 | 0 | } |
2235 | 0 | } |
2236 | 0 |
|
2237 | 0 | // If completed transaction id get ahead of outstanding id, reset to distance id. |
2238 | 0 | if (mCompletedTransaction > mOutstandingTransactionId) { |
2239 | 0 | mOutstandingTransactionId = mCompletedTransaction; |
2240 | 0 | } |
2241 | 0 | } |
2242 | | |
2243 | | void |
2244 | | nsRefreshDriver::WillRefresh(mozilla::TimeStamp aTime) |
2245 | 0 | { |
2246 | 0 | mRootRefresh->RemoveRefreshObserver(this, FlushType::Style); |
2247 | 0 | mRootRefresh = nullptr; |
2248 | 0 | if (mSkippedPaints) { |
2249 | 0 | DoRefresh(); |
2250 | 0 | } |
2251 | 0 | } |
2252 | | |
2253 | | bool |
2254 | | nsRefreshDriver::IsWaitingForPaint(mozilla::TimeStamp aTime) |
2255 | 0 | { |
2256 | 0 | if (mTestControllingRefreshes) { |
2257 | 0 | return false; |
2258 | 0 | } |
2259 | 0 | |
2260 | 0 | if (mWaitingForTransaction) { |
2261 | 0 | if (mSkippedPaints && aTime > (mMostRecentRefresh + TimeDuration::FromMilliseconds(mWarningThreshold * 1000))) { |
2262 | 0 | // XXX - Bug 1303369 - too many false positives. |
2263 | 0 | //gfxCriticalNote << "Refresh driver waiting for the compositor for " |
2264 | 0 | // << (aTime - mMostRecentRefresh).ToSeconds() |
2265 | 0 | // << " seconds."; |
2266 | 0 | mWarningThreshold *= 2; |
2267 | 0 | } |
2268 | 0 |
|
2269 | 0 | mSkippedPaints = true; |
2270 | 0 | return true; |
2271 | 0 | } |
2272 | 0 |
|
2273 | 0 | // Try find the 'root' refresh driver for the current window and check |
2274 | 0 | // if that is waiting for a paint. |
2275 | 0 | nsPresContext* pc = GetPresContext(); |
2276 | 0 | nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr; |
2277 | 0 | if (rootContext) { |
2278 | 0 | nsRefreshDriver *rootRefresh = rootContext->RefreshDriver(); |
2279 | 0 | if (rootRefresh && rootRefresh != this) { |
2280 | 0 | if (rootRefresh->IsWaitingForPaint(aTime)) { |
2281 | 0 | if (mRootRefresh != rootRefresh) { |
2282 | 0 | if (mRootRefresh) { |
2283 | 0 | mRootRefresh->RemoveRefreshObserver(this, FlushType::Style); |
2284 | 0 | } |
2285 | 0 | rootRefresh->AddRefreshObserver(this, FlushType::Style); |
2286 | 0 | mRootRefresh = rootRefresh; |
2287 | 0 | } |
2288 | 0 | mSkippedPaints = true; |
2289 | 0 | return true; |
2290 | 0 | } |
2291 | 0 | } |
2292 | 0 | } |
2293 | 0 | return false; |
2294 | 0 | } |
2295 | | |
2296 | | void |
2297 | | nsRefreshDriver::SetThrottled(bool aThrottled) |
2298 | 0 | { |
2299 | 0 | if (aThrottled != mThrottled) { |
2300 | 0 | mThrottled = aThrottled; |
2301 | 0 | if (mActiveTimer) { |
2302 | 0 | // We want to switch our timer type here, so just stop and |
2303 | 0 | // restart the timer. |
2304 | 0 | EnsureTimerStarted(eForceAdjustTimer); |
2305 | 0 | } |
2306 | 0 | } |
2307 | 0 | } |
2308 | | |
2309 | | /*static*/ void |
2310 | | nsRefreshDriver::PVsyncActorCreated(VsyncChild* aVsyncChild) |
2311 | 0 | { |
2312 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2313 | 0 | MOZ_ASSERT(!XRE_IsParentProcess()); |
2314 | 0 | RefPtr<RefreshDriverTimer> vsyncRefreshDriverTimer = |
2315 | 0 | new VsyncRefreshDriverTimer(aVsyncChild); |
2316 | 0 |
|
2317 | 0 | // If we are using software timer, swap current timer to |
2318 | 0 | // VsyncRefreshDriverTimer. |
2319 | 0 | if (sRegularRateTimer) { |
2320 | 0 | sRegularRateTimer->SwapRefreshDrivers(vsyncRefreshDriverTimer); |
2321 | 0 | } |
2322 | 0 | sRegularRateTimer = vsyncRefreshDriverTimer.forget(); |
2323 | 0 | } |
2324 | | |
2325 | | void |
2326 | | nsRefreshDriver::DoRefresh() |
2327 | 0 | { |
2328 | 0 | // Don't do a refresh unless we're in a state where we should be refreshing. |
2329 | 0 | if (!IsFrozen() && mPresContext && mActiveTimer) { |
2330 | 0 | DoTick(); |
2331 | 0 | } |
2332 | 0 | } |
2333 | | |
2334 | | #ifdef DEBUG |
2335 | | bool |
2336 | | nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver, |
2337 | | FlushType aFlushType) |
2338 | | { |
2339 | | ObserverArray& array = ArrayFor(aFlushType); |
2340 | | return array.Contains(aObserver); |
2341 | | } |
2342 | | #endif |
2343 | | |
2344 | | void |
2345 | | nsRefreshDriver::ScheduleViewManagerFlush() |
2346 | 0 | { |
2347 | 0 | NS_ASSERTION(mPresContext->IsRoot(), |
2348 | 0 | "Should only schedule view manager flush on root prescontexts"); |
2349 | 0 | mViewManagerFlushIsPending = true; |
2350 | 0 | mHasScheduleFlush = true; |
2351 | 0 | EnsureTimerStarted(eNeverAdjustTimer); |
2352 | 0 | } |
2353 | | |
2354 | | void |
2355 | | nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument) |
2356 | 0 | { |
2357 | 0 | NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) == |
2358 | 0 | mFrameRequestCallbackDocs.NoIndex && |
2359 | 0 | mThrottledFrameRequestCallbackDocs.IndexOf(aDocument) == |
2360 | 0 | mThrottledFrameRequestCallbackDocs.NoIndex, |
2361 | 0 | "Don't schedule the same document multiple times"); |
2362 | 0 | if (aDocument->ShouldThrottleFrameRequests()) { |
2363 | 0 | mThrottledFrameRequestCallbackDocs.AppendElement(aDocument); |
2364 | 0 | } else { |
2365 | 0 | mFrameRequestCallbackDocs.AppendElement(aDocument); |
2366 | 0 | } |
2367 | 0 |
|
2368 | 0 | // make sure that the timer is running |
2369 | 0 | EnsureTimerStarted(); |
2370 | 0 | } |
2371 | | |
2372 | | void |
2373 | | nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument) |
2374 | 0 | { |
2375 | 0 | mFrameRequestCallbackDocs.RemoveElement(aDocument); |
2376 | 0 | mThrottledFrameRequestCallbackDocs.RemoveElement(aDocument); |
2377 | 0 | // No need to worry about restarting our timer in slack mode if it's already |
2378 | 0 | // running; that will happen automatically when it fires. |
2379 | 0 | } |
2380 | | |
2381 | | void |
2382 | | nsRefreshDriver::ScheduleFullscreenEvent( |
2383 | | UniquePtr<PendingFullscreenEvent> aEvent) |
2384 | 0 | { |
2385 | 0 | mPendingFullscreenEvents.AppendElement(std::move(aEvent)); |
2386 | 0 | // make sure that the timer is running |
2387 | 0 | EnsureTimerStarted(); |
2388 | 0 | } |
2389 | | |
2390 | | void |
2391 | | nsRefreshDriver::CancelPendingFullscreenEvents(nsIDocument* aDocument) |
2392 | 0 | { |
2393 | 0 | for (auto i : Reversed(IntegerRange(mPendingFullscreenEvents.Length()))) { |
2394 | 0 | if (mPendingFullscreenEvents[i]->Document() == aDocument) { |
2395 | 0 | mPendingFullscreenEvents.RemoveElementAt(i); |
2396 | 0 | } |
2397 | 0 | } |
2398 | 0 | } |
2399 | | |
2400 | | void |
2401 | | nsRefreshDriver::CancelPendingAnimationEvents(AnimationEventDispatcher* aDispatcher) |
2402 | 0 | { |
2403 | 0 | MOZ_ASSERT(aDispatcher); |
2404 | 0 | aDispatcher->ClearEventQueue(); |
2405 | 0 | mAnimationEventFlushObservers.RemoveElement(aDispatcher); |
2406 | 0 | } |
2407 | | |
2408 | | /* static */ TimeStamp |
2409 | | nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault) |
2410 | 83 | { |
2411 | 83 | MOZ_ASSERT(NS_IsMainThread()); |
2412 | 83 | MOZ_ASSERT(!aDefault.IsNull()); |
2413 | 83 | |
2414 | 83 | if (!sRegularRateTimer) { |
2415 | 83 | return aDefault; |
2416 | 83 | } |
2417 | 0 | |
2418 | 0 | // For computing idleness of refresh drivers we only care about |
2419 | 0 | // sRegularRateTimer, since we consider refresh drivers attached to |
2420 | 0 | // sThrottledRateTimer to be inactive. This implies that tasks |
2421 | 0 | // resulting from a tick on the sRegularRateTimer counts as being |
2422 | 0 | // busy but tasks resulting from a tick on sThrottledRateTimer |
2423 | 0 | // counts as being idle. |
2424 | 0 | return sRegularRateTimer->GetIdleDeadlineHint(aDefault); |
2425 | 0 | } |
2426 | | |
2427 | | /* static */ Maybe<TimeStamp> |
2428 | | nsRefreshDriver::GetNextTickHint() |
2429 | 0 | { |
2430 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2431 | 0 |
|
2432 | 0 | if (!sRegularRateTimer) { |
2433 | 0 | return Nothing(); |
2434 | 0 | } |
2435 | 0 | return sRegularRateTimer->GetNextTickHint(); |
2436 | 0 | } |
2437 | | |
2438 | | void |
2439 | | nsRefreshDriver::Disconnect() |
2440 | 0 | { |
2441 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2442 | 0 |
|
2443 | 0 | StopTimer(); |
2444 | 0 |
|
2445 | 0 | if (mPresContext) { |
2446 | 0 | mPresContext = nullptr; |
2447 | 0 | if (--sRefreshDriverCount == 0) { |
2448 | 0 | Shutdown(); |
2449 | 0 | } |
2450 | 0 | } |
2451 | 0 | } |
2452 | | |
2453 | | /* static */ bool |
2454 | | nsRefreshDriver::IsJankCritical() |
2455 | 0 | { |
2456 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2457 | 0 | return sActiveVsyncTimers > 0; |
2458 | 0 | } |
2459 | | |
2460 | | /* static */ bool |
2461 | 0 | nsRefreshDriver::GetJankLevels(Vector<uint64_t>& aJank) { |
2462 | 0 | aJank.clear(); |
2463 | 0 | return aJank.append(sJankLevels, ArrayLength(sJankLevels)); |
2464 | 0 | } |
2465 | | |
2466 | | #undef LOG |