Files
palemoon27/layout/base/nsRefreshDriver.cpp
T
roytam1 fffeb135bb import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1040668 part 2 - Parse and compute text emphasis properties. r=dbaron (0fb79d4709)
- Bug 1040668 part 3 - Add helper function gfxTextRun::GetAdvanceForGlyph. r=jfkthame (f1cf02f5ff)
- Bug 1216427 - Tests for backspacing over a character with variation selector, and over Regional Indicator flag symbols. r=emk (18957bfe77)
- Bug 1216427 - part 1 - Ensure a character+VS sequence or a ligated Regional-Indicator flag symbol is deleted as a single unit when backspacing. r=emk (2776ff8f4a)
- Bug 1216427 - part 2 - Ensure mouse selection does not split up a Regional Indicator flag symbol. r=emk (58eb82e6a1)
- Bug 1040668 part 4 - Add helper function for ensuring a glyph is a complex glyph. r=jfkthame (ba17f7d0c4)
- Bug 1040668 part 5 - Avoid unnecessary allocation inside EnsureComplexGlyph helper function. r=jfkthame (4968a7c68e)
- Bug 1040668 part 6 - Add some specifier on gfxTextRun and gfxShapedWord so that compilers are able to reason out certain optimizations. r=jfkthame (69ca3eb959)
- Bug 1040668 part 7 - Add NO_EMPHASIS_MARK flag in CompressedGlyph. r=jfkthame (34e9d8a6a6)
- Bug 1227001 part 1 - Remove SetupBreakSinksFlags from BuildTextRunsScanner. r=jfkthame (520b1ba111)
- Bug 1227001 part 2 - Remove no longer used mExistingTextRun from BreaSink. r=jfkthame (201782a78c)
- Bug 1227001 part 3 - Remove no longer used mChangedBreaks from BreakSink. r=jfkthame (e754e2b13f)
- Bug 1040668 part 8 - Setup text emphasis for text run. r=jfkthame (376377180e)
- Bug 1040668 part 9 - Compute overflow from text-shadow after text decorations. r=dbaron (05c9bd08c2)
- Bug 1040668 part 10 - Implement emphasis mark rendering. r=jfkthame (4d270afca8)
- Bug 1040668 part 11 - Move line leadings adjusting code into a separate function in nsLineLayout. r=dholbert (fd4dd20ca5)
- Bug 1040668 part 12 - Add line leadings for emphasis marks if necessary. r=dholbert (138add5ff7)
- Bug 1040668 part 13 - Move first part of nsStyleFont::GetLanguage to nsPresContext::GetContentLanguage. r=dbaron (1e872d2a58)
- Bug 1040668 part 14 - Add helper function nsStyleUtil::MatchesLanguagePrefix for doing simple language matching. r=dbaron (9322a02369)
- Bug 1040668 part 15 - Make the default value of text-emphasis-position aware of the language. r=dbaron (6587c628da)
- Bug 1040668 part 16 - Add reftests for text-emphasis. r=dbaron (9940d65182)
- Bug 1040668 followup - Use monospace for text-emphasis reftests. rs=dbaron on a CLOSED TREE (fb7598c3ea)
- Bug 1040668 followup 2 - Disable failing reftests of text-emphasis on Windows XP. (7c3f24ac22)
- Bug 1040668 followup 3 - Wrap lang attribute mapping code in NS_STYLE_INHERIT_BIT test. r=dbaron (640e3b7b8f)
- Bug 1219145 - nsRefreshDriver::IsJankCritical(). r=hiro To refine its alerts, Performance Stats API needs to be able to know whether a long-running operation is actually causing user-visible jank in the current process. This patch introduces a trivial API that lets clients ask the refresh driver whether any kind of animation is ongoing. (7c0868d7c4)
- more missing XP theme stuff (637af0c6a0)
- Bug 1210261. Tick root refresh driver last. r=mattwoodrow (dae1a325fa)
- Bug 1221674 Part 2: Correct for negative content delay values. r=avih (9b9811c41c)
- Bug 1211334 - Check if presshell is still available after dispatching transition events; r=mats (8a044a462c)
- Bug 1211599 - Only allow whitelisted histograms to have > 100 buckets. r=nfroyd (d467e84130)
- Bug 1219733 - Allow a 'bug_numbers' field in Histograms.json entries. r=vladan (8e63a713ec)
- Bug 1222044 - Only allow lists of alert_emails r=vladan (1d5fcb009d)
- Bug 1168263 - Add a flags parameter to GetResultingTransformMatrix instead of using bools. r=roc (ec5224f9af)
- Bug 1168263 - Remove TransformRectOut since it's unused. r=roc (d20a79ae0e)
- Bug 1215406 - Part 1: Remove NS_STYLE_ANIMATION_DIRECTION_XXX and NS_STYLE_ANIMATION_FILL_MODE_XXX. r=heycam (4bd7f8116b)
- Bug 1215406 - Part 2: Change the types of direction and fillmode in StyleAnimation. r=heycam (e06323c81b)
- Bug 1215406 - Part 3: Change the types of direction and fillmode in AnimationTiming. r=heycam (e5454d3ea5)
- Bug 1215406 - Part 4: Add KeyframeEffectOptions. r=smaug Add KeyframeEffectOptions in KeyframeEffect.webidl (0ce3372fd1)
- Bug 1215406 - Part 5: Implement KeyframeEffectOptions in KeyframeEffectReadOnly constructor. r=birtles (a472e9ac5f)
- tch 2 - Use an enum class for NS_STYLE_BOX_SIZING_*. r=heycam (4897b7b8ff)
- Bug 1218195, mark MutationObserver as observing in all the nested DOM mutations, r=bz (48a4aa8a91)
- Bug 1172870 - Part 3 - Fix openWindow mochitest to work on e10s (574cc6fa4d)
- Bug 1223265 - Fix -Wunreachable-code and -Wimplicit-fallthrough warnings in dom/bindings and dom/ipc. r=khuey (9eab632140)
- Bug 1189195 - Fix PContentPermissionRequest shutdown () r=fabrice (6bc1a681f5)
- Bug 1210508 - Handle null OriginAttributes from JS-implemented nsILoadContext. r=me (2f71edb9d7)
- Bug 1224596 part 1. Add a version of WorkerMainThreadRunnable::Dispatch that takes an ErrorResult to report failure to dispatch on. r=khuey (e60a0fb115)
- Bug 1224596 part 2. Switch Navigator to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (d9298a4763)
- Bug 1224596 part 3. Make some WebSocket(Impl) methods whose return value is totally ignored void. r=khuey (1d04b52d44)
- Bug 1224596 part 4. Switch WebSocket to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (aa06e6417b)
- Bug 1224596 part 5. Switch nsPerformance to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (0303b7de09)
- Bug 1224596 part 6. Switch BroadcastChannel to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (feacc21f63)
- Bug 1224636. Fix some code in ImageBitmap that rejects a promise with an ErrorResult, then keeps trying to use that ErrorResult. r=kaku (89b769fe07)
- Bug 1224596 part 7. Switch ImageBitmap to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (e21b0a4a0a)
- Bug 1183954, Don't leak nsStructuredCloneContainer (use of 'auto' is error prone), r=leak (1a9b30f2ca)
- Bug 1114554 - Patch 9 - Fixed crash in b2g-desktop tests. r=nsm (6b12c6d121)
- Bug 1189090 - Rework the nsISupports implementation in the ScopeCheckingGetCallback and its subclasses; r=nsm (6bea544597)
- Bug 1187018 - Ensure feature is nulled out if it does not get added. r=khuey (9d7439498a)
- Bug 1224596 part 8. Switch Notification to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (03f0f6877f)
- Bug 1196079 - Always try to release Notification via normal WorkerRunnable first. r=wchen (c093253d6f)
- Bug 1203324 - disable notifications on serviceworkers. r=ehsan,wchen (aa39310dc1)
- Bug 1199901 - GetOrigin() fails cleanly instead of asserting principal. r=wchen (148c634a4f)
- Bug 1199901 - Clear mObserver when WorkerNotificationObserver is destroyed. r=wchen (6d5cd99183)
- Bug 1199901 - Bustage fix due to rebase. a=bustage (344bd62af5)
- Bug 1225470 Report a message to the console when a service worker waitUntil() is rejected. r=baku (610da2eec6)
- Bug 1217909 P1 Report service worker exceptions to controlled documents. r=catalinb (f8bd4677d5)
- Bug 1216566 - Fix a bug in nsIServiceWorkerManager.getAllRegistrations;r=catalinb (ed3e14ab43)
- Bug 1219205 - ServiceWorkerInfo should be an XPCOM object;r=catalinb (677a6f1ffd)
- Bug 1217909 P2 Track registering documents as weak reference so SWM can report errors to them. r=catalinb (4233dc3edc)
- Bug 1217909 P3 Refactor service worker register()/update() to reject only with SecurityErr or TypeErr. r=catalinb (c6891a7fae)
- Bug 1220740 - nsIServiceWorkerRegistrationInfo should emit an event when its scriptSpec property changes;r=amarchesini (5772bb5914)
- Bug 1207727 - Add WPT tests for service worker update algorithm. r=bkelly (a4812571a1)
- Bug 1217367 - Service workers update algorithm optimization. r=bkelly (e377debad1)
- Bug 1226479. Change ErrorResult::ThrowTypeError/ThrowRangeError to take string references, not pointers. r=mccr8 (0804899666)
- Bug 1224659 - Worker DataStore code should not use ErrorResult cross threads, r=bz (aef03b0fdd)
- Bug 1224596 part 9. Switch DataStore to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (1eb1c427f2)
- Bug 1224596 part 10. Switch DataStoreCursor to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (114af8021b)
- Bug 1224596 part 11. Switch WorkerNavigator to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (b2347a0c49)
- Bug 1224596 part 12. Switch ServiceWorkerRegistration to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (d1ff076836)
- Bug 1224596 part 13. Switch gfxUtils to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (a523997588)
- Bug 1224007 part 1. Rename ThrowMethodFailed to MaybeSetPendingException and make it an ErrorResult instance method. r=peterv (bdf0891f54)
- Bug 1224007 part 2. Make the various ErrorResult::Report* methods private, so consumers all go through MaybeSetPendingException and rename them to more clearly indicate what they're actually doing. r=peterv (48f483c153)
- Bug 1203151 - Allow disabling of screen wakelocks for video elements. r=baku (6bd9ff6916)
- Bug 1224007 part 4. Fix some cases in which ErrorResult instances are destroyed without doing anything useful with exceptions on them. r=peterv (e328785e5d)
- Bug 1224007 part 5. Get rid of ErrorResult::StealJSException. r=peterv (db19cfb31e)
- Bug 1224007 part 3. Push down WouldReportJSException into MaybeSetPendingException, since anyone calling the latter will propagate the JS exception as needed. r=peterv (1d3b7b415d)
- Bug 1224007 part 6. Change MaybeSetPendingException to set the ErrorResult state to "not failed", just like SuppressException and StealNSResult already do, and assert in the destructor that the ErrorResult is not Failed(). (a028838e8d)
- Bug 1213815 - Update URLSearchParams and URLUtils in webidl files, r=bz (28fb8f7de5)
- Bug 1213815 - dom/webidl/HTMLHyperlinkElementUtils and URL don't need to throw exceptions as we did before, r=bz (b13dc3bcb9)
- Bug 1224596 part 14. Switch URL to using the new WorkerMainThreadRunnable::Dispatch signature. r=baku (232677e50e)
- fix updating the backport (9e958da5ca)
- Bug 1224596 part 16. Switch Fetch to using the new WorkerMainThreadRunnable::Dispatch signature. r=bkelly (939e338f22)
- Bug 1224596 part 17. Remove the old WorkerMainThreadRunnable::Dispatch signature. r=khuey (5a70429ec8)
- Bug 1143575. Don't report negative frame delays. r=cpearce (7d8bc0f753)
- Bug 1187371 - Get rid of dom.broadcastChannel.enabled pref, r=bz (9335b7ae90)
- Bug 1196514 - remove dom.messagechannel.enabled pref, r=smaug (31e06119b4)
- Bug 1166356 - Properly detect double-caching in nsXULPrototypeCache; r=ehsan (29df9ffb2d)
- Bug 1168916 - Get rid of redundant pref callback in nsXULPrototypeCache; r=janv (9f37fff405)
- Bug 1139099: Dispatch DOMDocElementInserted to match the document-element-inserted observer notification. r=mrbkap (6565e4b924)
- Bug 1187068 - Tell the cycle collector about nsContentSink::mCSSLoader. r=heycam (4ae23eb26c)
- Bug 1172189 - Fix overflow in nsXULContentSink.cpp. r=ehsan (cc6330f5de)
- Bug 1126010 - XULContentSinkImpl::mParser should be an nsRefPtr. r=smaug (d6bb567692)
- Bug 1147946, part 7 - Remove trailing whitespace from nsXULContentSink.cpp. r=baku (cdcadbfeeb)
- Bug 1147946, part 1 - Tuck elses in nsXULContentSink.cpp. r=baku (26fd806676)
- Bug 1147946, part 2 - Move body of check inside prior if in XULContentSinkImpl::OpenScript(). r=baku (b509455bdb)
- Bug 1147946, part 3 - Eliminate unused case for non-JS scripting languages in XULContentSinkImpl::OpenScript(). r=baku (4136933cc2)
- Bug 1147946, part 4 - Use an early return in XULContentSinkImpl::OpenScript(). r=baku (c3e293474b)
- Bug 1147946, part 5 - Don't use the generic nsIProgrammingLanguage enum in XULContentSinkImpl::OpenScript(). r=baku (89a124e23f)
- Bug 1147946, part 6 - Remove some useless null checks on infallible new in XULContentSinkImpl. r=baku (1ac57e8c3a)
- Bug 1221351 P1 ServiceWorkerContainer and ServiceWorkerRegistration should not crash for null window owner. r=catalinb (1a72748632)
- Bug 1212867 - Node.isEqualNode() should ignore internal subsets; r=bzBug (99b166ffee)
- Bug 492933 - getElementsByTagName should match on localName not tagName, r=smaug (d0c6ceabf1)
- Bug 912470 part 1 - Implement Encoding Standard-compliant big5 decoder. r=emk. (c680b0ae9b)
- Bug 1170932: Test handling of unmapped characters in unicode-to-codepage encoders (ca36bcbd35)
- fix style (95a90bfe3a)
- Bug 1170794 - patch 2 - Improve the length check of the input in nsUnicode*::GetMaxLength, r=dveditz (aa864d656f)
- Bug 1170794 followup: Add 'override' annotations to Convert() & Reset() methods in intl/uconv. rs=ehsan (bb3e6e492e)
- Bug 1176462 - Remove nsTableDecoderSupport. r=smontagu (f4a86c44b3)
- Bug 1169248 - Fix GBK/GB18030 encoders. r=smontagu (ed946e1ee1)
- Bug 1155539 - Remove obsolete encoding decoder telemetry probes. r=emk. (44e15bfb40)
- Bug 912470 part 2 - Implement Encoding Standard-compliant big5 encoder. r=emk. (5cca2dc4a0)
- Bug 912470 addendum - Pass override static analysis. r=emk. (c163bffeb4)
- Bug 1170932: Improve error handling for the gbk encoder, r=emk (30e95b34a5)
- Bug 1202366 - Implement the encoder error mode "HTML" for nsFormSubmission without nsISaveAsCharset. r=NOT_emk. (ebc8b542dd)
- update manifest (6cc19172cc)
- Bug 1197309 - remove PR_snprintf calls in intl/; r=froydnj (79fcdfa845)
- Bug 1214619 - Remove nsISaveAsCharset as much as possible without breaking extensions in popular use. r=emk. (89b71b3d87)
- Bug 1214857. Store the document-is-HTML state directly in nsContentList instead of refetching from the node being matched. r=smaug (4c4fbf469e)
- Bug 1221351 P2 Add a web-platform-test to check for crash when calling .register() on closed window. r=catalinb (eeb30c1bba)
- Bug 1221351 P3 Fix test name in register-closed-window.https.html. a=testonly (5bfa840044)
- Bug 1224436: Remove enumerator usage in ServiceWorkerManager.cpp. r=njn (b8cb094d3c)
- Bug 1223716. Make HTMLCollection check for the element being HTML before checking for its name inside its named getter. r=bkelly (977e0bff5a)
- Bug 1180737 - Add update-test.py and update test to latest version. r=bkelly. (59faa36d5c)
- Bug 1217909 P4 Extend wpt tests to verify update() promise values for different script failures. r=catalinb (a2f7352a3a)
- Bug 1217909 P5 Add wpt test case for fetch event handlers that throw. r=catalinb (56a77f611c)
- Bug 1217909 P6 Fix wpt registration.https.html to expect TypeError for script evaluation errors. r=catalinb (3de8a45688)
- Bug 1217909 P7 Fix mochitest to expect TypeError when serviceWorker.register() rejects. r=catalinb (6e8841c41e)
- Bug 1217909 P8 Track navigation interceptions per scope in ServiceWorkerManager. r=catalinb (6705ba8337)
- Bug 1217909 P9 Report exceptions to windows performing an intercepted navigation. r=catalinb (52f9fece14)
- Bug 1217909 P10 Remove stale nsTArray when the last registering document for a scope is removed. r=catalinb (b739bcc3b2)
- Bug 1217909 P11 Only report errors to documents that are active and not in the bfcache. r=catalinb (5ffd633af2)
- Bug 1223378 Tighten service worker register() principal checks. r=baku (478785f2cc)
- Bug 1189685 - Part 1: Ensure that the state of all ServiceWorker instances is up to date when dispatching statechange events; r=bkelly (2bd9b78c58)
- Bug 1189685 - Part 2: Make synced-state.https.html pass; r=bkelly (029f942d8c)
- Bug 1220740 - nsIServiceWorkerRegistrationInfo should emit an event when its worker properties change;r=amarchesini (8243a3debc)
- Bug 1186856 ServiceWorker .register() should always stop current registration from uninstalling. r=jdm (ec7d6e0e7c)
- Bug 1224941 Don't crash during ServiceWorker life cycle event dispatch if window is gone. r=baku (a3f45af3e4)
- Bug 1180754 - Get serviceworkerobject-scripturl test passing. r=bkelly (c7979bef47)
- Bug 1201498 - Service worker update should compare scriptURL to worker URL without fragment, r=bkelly (914f630528)
- Bug 594505 - Remove obsolete comment since this bug has now been fixed. r=me DONTBUILD (44f3a15b91)
- Bug 1221840. Support repeating images in 1 axis. r=seth (449ea3e97e)
- const-var (5433688051)
- Bug 1574573 - Disambiguate a use of Handle in XPCShellEnvironment.cpp r=Ehsan (15b44177d1)
- clean up warnings (6e64313d0c)
2023-02-14 09:56:52 +08:00

2135 lines
66 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Code to notify things that animate before a refresh, at an appropriate
* refresh rate. (Perhaps temporary, until replaced by compositor.)
*
* Chrome and each tab have their own RefreshDriver, which in turn
* hooks into one of a few global timer based on RefreshDriverTimer,
* defined below. There are two main global timers -- one for active
* animations, and one for inactive ones. These are implemented as
* subclasses of RefreshDriverTimer; see below for a description of
* their implementations. In the future, additional timer types may
* implement things like blocking on vsync.
*/
#ifdef XP_WIN
#include <windows.h>
// mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have
// to manually include it
#include <mmsystem.h>
#include "WinUtils.h"
#endif
#include "mozilla/ArrayUtils.h"
#include "mozilla/AutoRestore.h"
#include "nsHostObjectProtocolHandler.h"
#include "nsRefreshDriver.h"
#include "nsITimer.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsComponentManagerUtils.h"
#include "mozilla/Logging.h"
#include "nsAutoPtr.h"
#include "nsIDocument.h"
#include "jsapi.h"
#include "nsContentUtils.h"
#include "mozilla/PendingAnimationTracker.h"
#include "mozilla/Preferences.h"
#include "nsViewManager.h"
#include "GeckoProfiler.h"
#include "nsNPAPIPluginInstance.h"
#include "nsPerformance.h"
#include "mozilla/dom/WindowBinding.h"
#include "RestyleManager.h"
#include "Layers.h"
#include "imgIContainer.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsDocShell.h"
#include "nsISimpleEnumerator.h"
#include "nsJSEnvironment.h"
#include "mozilla/Telemetry.h"
#include "gfxPrefs.h"
#include "BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "nsIIPCBackgroundChildCreateCallback.h"
#include "mozilla/layout/VsyncChild.h"
#include "VsyncSource.h"
#include "mozilla/VsyncDispatcher.h"
#include "nsThreadUtils.h"
#include "mozilla/unused.h"
#include "mozilla/TimelineConsumers.h"
#include "nsAnimationManager.h"
#ifdef MOZ_NUWA_PROCESS
#include "ipc/Nuwa.h"
#endif
using namespace mozilla;
using namespace mozilla::widget;
using namespace mozilla::ipc;
using namespace mozilla::layout;
static mozilla::LazyLogModule sRefreshDriverLog("nsRefreshDriver");
#define LOG(...) MOZ_LOG(sRefreshDriverLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
#define DEFAULT_THROTTLED_FRAME_RATE 1
#define DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS 1000
// after 10 minutes, stop firing off inactive timers
#define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600
namespace {
// `true` if we are currently in jank-critical mode.
//
// In jank-critical mode, any iteration of the event loop that takes
// more than 16ms to compute will cause an ongoing animation to miss
// frames.
//
// For simplicity, the current implementation assumes that we are in
// jank-critical mode if and only if at least one vsync driver has
// at least one observer.
static uint64_t sActiveVsyncTimers = 0;
}
namespace mozilla {
/*
* The base class for all global refresh driver timers. It takes care
* of managing the list of refresh drivers attached to them and
* provides interfaces for querying/setting the rate and actually
* running a timer 'Tick'. Subclasses must implement StartTimer(),
* StopTimer(), and ScheduleNextTick() -- the first two just
* start/stop whatever timer mechanism is in use, and ScheduleNextTick
* is called at the start of the Tick() implementation to set a time
* for the next tick.
*/
class RefreshDriverTimer {
public:
RefreshDriverTimer()
: mLastFireEpoch(0)
{
}
virtual ~RefreshDriverTimer()
{
MOZ_ASSERT(mContentRefreshDrivers.Length() == 0, "Should have removed all content refresh drivers from here by now!");
MOZ_ASSERT(mRootRefreshDrivers.Length() == 0, "Should have removed all root refresh drivers from here by now!");
}
virtual void AddRefreshDriver(nsRefreshDriver* aDriver)
{
LOG("[%p] AddRefreshDriver %p", this, aDriver);
bool startTimer = mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
if (IsRootRefreshDriver(aDriver)) {
NS_ASSERTION(!mRootRefreshDrivers.Contains(aDriver), "Adding a duplicate root refresh driver!");
mRootRefreshDrivers.AppendElement(aDriver);
} else {
NS_ASSERTION(!mContentRefreshDrivers.Contains(aDriver), "Adding a duplicate content refresh driver!");
mContentRefreshDrivers.AppendElement(aDriver);
}
if (startTimer) {
StartTimer();
}
}
virtual void RemoveRefreshDriver(nsRefreshDriver* aDriver)
{
LOG("[%p] RemoveRefreshDriver %p", this, aDriver);
if (IsRootRefreshDriver(aDriver)) {
NS_ASSERTION(mRootRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the root refresh list!");
mRootRefreshDrivers.RemoveElement(aDriver);
} else {
nsPresContext* rootContext = aDriver->PresContext()->GetRootPresContext();
// During PresContext shutdown, we can't accurately detect
// if a root refresh driver exists or not. Therefore, we have to
// search and find out which list this driver exists in.
if (!rootContext) {
if (mRootRefreshDrivers.Contains(aDriver)) {
mRootRefreshDrivers.RemoveElement(aDriver);
} else {
NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),
"RemoveRefreshDriver without a display root for a driver that is not in the content refresh list");
mContentRefreshDrivers.RemoveElement(aDriver);
}
} else {
NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a driver that is not in the content refresh list");
mContentRefreshDrivers.RemoveElement(aDriver);
}
}
bool stopTimer = mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
if (stopTimer) {
StopTimer();
}
}
TimeStamp MostRecentRefresh() const { return mLastFireTime; }
int64_t MostRecentRefreshEpochTime() const { return mLastFireEpoch; }
void SwapRefreshDrivers(RefreshDriverTimer* aNewTimer)
{
MOZ_ASSERT(NS_IsMainThread());
for (nsRefreshDriver* driver : mContentRefreshDrivers) {
aNewTimer->AddRefreshDriver(driver);
driver->mActiveTimer = aNewTimer;
}
mContentRefreshDrivers.Clear();
for (nsRefreshDriver* driver : mRootRefreshDrivers) {
aNewTimer->AddRefreshDriver(driver);
driver->mActiveTimer = aNewTimer;
}
mRootRefreshDrivers.Clear();
aNewTimer->mLastFireEpoch = mLastFireEpoch;
aNewTimer->mLastFireTime = mLastFireTime;
}
protected:
virtual void StartTimer() = 0;
virtual void StopTimer() = 0;
virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
bool IsRootRefreshDriver(nsRefreshDriver* aDriver)
{
nsPresContext* rootContext = aDriver->PresContext()->GetRootPresContext();
if (!rootContext) {
return false;
}
return aDriver == rootContext->RefreshDriver();
}
/*
* Actually runs a tick, poking all the attached RefreshDrivers.
* Grabs the "now" time via JS_Now and TimeStamp::Now().
*/
void Tick()
{
int64_t jsnow = JS_Now();
TimeStamp now = TimeStamp::Now();
Tick(jsnow, now);
}
void TickRefreshDrivers(int64_t aJsNow, TimeStamp aNow, nsTArray<RefPtr<nsRefreshDriver>>& aDrivers)
{
if (aDrivers.IsEmpty()) {
return;
}
nsTArray<RefPtr<nsRefreshDriver> > drivers(aDrivers);
for (nsRefreshDriver* driver : drivers) {
// don't poke this driver if it's in test mode
if (driver->IsTestControllingRefreshesEnabled()) {
continue;
}
TickDriver(driver, aJsNow, aNow);
}
}
/*
* Tick the refresh drivers based on the given timestamp.
*/
void Tick(int64_t jsnow, TimeStamp now)
{
ScheduleNextTick(now);
mLastFireEpoch = jsnow;
mLastFireTime = now;
LOG("[%p] ticking drivers...", this);
// RD is short for RefreshDriver
profiler_tracing("Paint", "RD", TRACING_INTERVAL_START);
TickRefreshDrivers(jsnow, now, mContentRefreshDrivers);
TickRefreshDrivers(jsnow, now, mRootRefreshDrivers);
profiler_tracing("Paint", "RD", TRACING_INTERVAL_END);
LOG("[%p] done.", this);
}
static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now)
{
LOG(">> TickDriver: %p (jsnow: %lld)", driver, jsnow);
driver->Tick(jsnow, now);
}
int64_t mLastFireEpoch;
TimeStamp mLastFireTime;
TimeStamp mTargetTime;
nsTArray<RefPtr<nsRefreshDriver> > mContentRefreshDrivers;
nsTArray<RefPtr<nsRefreshDriver> > mRootRefreshDrivers;
// useful callback for nsITimer-based derived classes, here
// bacause of c++ protected shenanigans
static void TimerTick(nsITimer* aTimer, void* aClosure)
{
RefreshDriverTimer *timer = static_cast<RefreshDriverTimer*>(aClosure);
timer->Tick();
}
};
/*
* A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that
* this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to
* implement ScheduleNextTick and intelligently calculate the next time to tick,
* and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain
* with its attempt at intelligent slack removal and such, so we don't do it.
*/
class SimpleTimerBasedRefreshDriverTimer :
public RefreshDriverTimer
{
public:
/*
* aRate -- the delay, in milliseconds, requested between timer firings
*/
explicit SimpleTimerBasedRefreshDriverTimer(double aRate)
{
SetRate(aRate);
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
}
virtual ~SimpleTimerBasedRefreshDriverTimer()
{
StopTimer();
}
// will take effect at next timer tick
virtual void SetRate(double aNewRate)
{
mRateMilliseconds = aNewRate;
mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds);
}
double GetRate() const
{
return mRateMilliseconds;
}
protected:
virtual void StartTimer()
{
// pretend we just fired, and we schedule the next tick normally
mLastFireEpoch = JS_Now();
mLastFireTime = TimeStamp::Now();
mTargetTime = mLastFireTime + mRateDuration;
uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT);
}
virtual void StopTimer()
{
mTimer->Cancel();
}
double mRateMilliseconds;
TimeDuration mRateDuration;
RefPtr<nsITimer> mTimer;
};
/*
* A refresh driver that listens to vsync events and ticks the refresh driver
* on vsync intervals. We throttle the refresh driver if we get too many
* vsync events and wait to catch up again.
*/
class VsyncRefreshDriverTimer : public RefreshDriverTimer
{
public:
VsyncRefreshDriverTimer()
: mVsyncChild(nullptr)
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
mVsyncObserver = new RefreshDriverVsyncObserver(this);
RefPtr<mozilla::gfx::VsyncSource> vsyncSource = gfxPlatform::GetPlatform()->GetHardwareVsync();
MOZ_ALWAYS_TRUE(mVsyncDispatcher = vsyncSource->GetRefreshTimerVsyncDispatcher());
mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver);
}
explicit VsyncRefreshDriverTimer(VsyncChild* aVsyncChild)
: mVsyncChild(aVsyncChild)
{
MOZ_ASSERT(!XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mVsyncChild);
mVsyncObserver = new RefreshDriverVsyncObserver(this);
mVsyncChild->SetVsyncObserver(mVsyncObserver);
}
private:
// Since VsyncObservers are refCounted, but the RefreshDriverTimer are
// explicitly shutdown. We create an inner class that has the VsyncObserver
// and is shutdown when the RefreshDriverTimer is deleted. The alternative is
// to (a) make all RefreshDriverTimer RefCounted or (b) use different
// VsyncObserver types.
class RefreshDriverVsyncObserver final : public VsyncObserver
{
public:
explicit RefreshDriverVsyncObserver(VsyncRefreshDriverTimer* aVsyncRefreshDriverTimer)
: mVsyncRefreshDriverTimer(aVsyncRefreshDriverTimer)
, mRefreshTickLock("RefreshTickLock")
, mRecentVsync(TimeStamp::Now())
, mLastChildTick(TimeStamp::Now())
, mVsyncRate(TimeDuration::Forever())
, mProcessedVsync(true)
{
MOZ_ASSERT(NS_IsMainThread());
}
virtual bool NotifyVsync(TimeStamp aVsyncTimestamp) override
{
// IMPORTANT: All paths through this method MUST hold a strong ref on
// |this| for the duration of the TickRefreshDriver callback.
if (!NS_IsMainThread()) {
MOZ_ASSERT(XRE_IsParentProcess());
// Compress vsync notifications such that only 1 may run at a time
// This is so that we don't flood the refresh driver with vsync messages
// if the main thread is blocked for long periods of time
{ // scope lock
MonitorAutoLock lock(mRefreshTickLock);
mRecentVsync = aVsyncTimestamp;
if (!mProcessedVsync) {
return true;
}
mProcessedVsync = false;
}
nsCOMPtr<nsIRunnable> vsyncEvent =
NS_NewRunnableMethodWithArg<TimeStamp>(this,
&RefreshDriverVsyncObserver::TickRefreshDriver,
aVsyncTimestamp);
NS_DispatchToMainThread(vsyncEvent);
} else {
RefPtr<RefreshDriverVsyncObserver> kungFuDeathGrip(this);
TickRefreshDriver(aVsyncTimestamp);
}
return true;
}
void Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
mVsyncRefreshDriverTimer = nullptr;
}
void OnTimerStart()
{
if (!XRE_IsParentProcess()) {
mLastChildTick = TimeStamp::Now();
}
}
private:
virtual ~RefreshDriverVsyncObserver() {}
void RecordTelemetryProbes(TimeStamp aVsyncTimestamp)
{
MOZ_ASSERT(NS_IsMainThread());
#ifndef ANDROID /* bug 1142079 */
if (XRE_IsParentProcess()) {
TimeDuration vsyncLatency = TimeStamp::Now() - aVsyncTimestamp;
Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CHROME_FRAME_DELAY_MS,
vsyncLatency.ToMilliseconds());
} else if (mVsyncRate != TimeDuration::Forever()) {
TimeDuration contentDelay = (TimeStamp::Now() - mLastChildTick) - mVsyncRate;
if (contentDelay.ToMilliseconds() < 0 ){
// Vsyncs are noisy and some can come at a rate quicker than
// the reported hardware rate. In those cases, consider that we have 0 delay.
contentDelay = TimeDuration::FromMilliseconds(0);
}
Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CONTENT_FRAME_DELAY_MS,
contentDelay.ToMilliseconds());
} else {
// Request the vsync rate from the parent process. Might be a few vsyncs
// until the parent responds.
mVsyncRate = mVsyncRefreshDriverTimer->mVsyncChild->GetVsyncRate();
}
#endif
}
void TickRefreshDriver(TimeStamp aVsyncTimestamp)
{
MOZ_ASSERT(NS_IsMainThread());
RecordTelemetryProbes(aVsyncTimestamp);
if (XRE_IsParentProcess()) {
MonitorAutoLock lock(mRefreshTickLock);
aVsyncTimestamp = mRecentVsync;
mProcessedVsync = true;
} else {
mLastChildTick = TimeStamp::Now();
}
MOZ_ASSERT(aVsyncTimestamp <= TimeStamp::Now());
// We might have a problem that we call ~VsyncRefreshDriverTimer() before
// the scheduled TickRefreshDriver() runs. Check mVsyncRefreshDriverTimer
// before use.
if (mVsyncRefreshDriverTimer) {
mVsyncRefreshDriverTimer->RunRefreshDrivers(aVsyncTimestamp);
}
}
// VsyncRefreshDriverTimer holds this RefreshDriverVsyncObserver and it will
// be always available before Shutdown(). We can just use the raw pointer
// here.
VsyncRefreshDriverTimer* mVsyncRefreshDriverTimer;
Monitor mRefreshTickLock;
TimeStamp mRecentVsync;
TimeStamp mLastChildTick;
TimeDuration mVsyncRate;
bool mProcessedVsync;
}; // RefreshDriverVsyncObserver
virtual ~VsyncRefreshDriverTimer()
{
if (XRE_IsParentProcess()) {
mVsyncDispatcher->SetParentRefreshTimer(nullptr);
mVsyncDispatcher = nullptr;
} else {
// Since the PVsyncChild actors live through the life of the process, just
// send the unobserveVsync message to disable vsync event. We don't need
// to handle the cleanup stuff of this actor. PVsyncChild::ActorDestroy()
// will be called and clean up this actor.
Unused << mVsyncChild->SendUnobserve();
mVsyncChild->SetVsyncObserver(nullptr);
mVsyncChild = nullptr;
}
// Detach current vsync timer from this VsyncObserver. The observer will no
// longer tick this timer.
mVsyncObserver->Shutdown();
mVsyncObserver = nullptr;
}
virtual void StartTimer() override
{
// Protect updates to `sActiveVsyncTimers`.
MOZ_ASSERT(NS_IsMainThread());
mLastFireEpoch = JS_Now();
mLastFireTime = TimeStamp::Now();
if (XRE_IsParentProcess()) {
mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver);
} else {
Unused << mVsyncChild->SendObserve();
mVsyncObserver->OnTimerStart();
}
++sActiveVsyncTimers;
}
virtual void StopTimer() override
{
// Protect updates to `sActiveVsyncTimers`.
MOZ_ASSERT(NS_IsMainThread());
if (XRE_IsParentProcess()) {
mVsyncDispatcher->SetParentRefreshTimer(nullptr);
} else {
Unused << mVsyncChild->SendUnobserve();
}
MOZ_ASSERT(sActiveVsyncTimers > 0);
--sActiveVsyncTimers;
}
virtual void ScheduleNextTick(TimeStamp aNowTime) override
{
// Do nothing since we just wait for the next vsync from
// RefreshDriverVsyncObserver.
}
void RunRefreshDrivers(TimeStamp aTimeStamp)
{
int64_t jsnow = JS_Now();
TimeDuration diff = TimeStamp::Now() - aTimeStamp;
int64_t vsyncJsNow = jsnow - diff.ToMicroseconds();
Tick(vsyncJsNow, aTimeStamp);
}
RefPtr<RefreshDriverVsyncObserver> mVsyncObserver;
// Used for parent process.
RefPtr<RefreshTimerVsyncDispatcher> mVsyncDispatcher;
// Used for child process.
// The mVsyncChild will be always available before VsncChild::ActorDestroy().
// After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op.
RefPtr<VsyncChild> mVsyncChild;
}; // VsyncRefreshDriverTimer
/**
* Since the content process takes some time to setup
* the vsync IPC connection, this timer is used
* during the intial startup process.
* During initial startup, the refresh drivers
* are ticked off this timer, and are swapped out once content
* vsync IPC connection is established.
*/
class StartupRefreshDriverTimer :
public SimpleTimerBasedRefreshDriverTimer
{
public:
explicit StartupRefreshDriverTimer(double aRate)
: SimpleTimerBasedRefreshDriverTimer(aRate)
{
}
protected:
virtual void ScheduleNextTick(TimeStamp aNowTime)
{
// Since this is only used for startup, it isn't super critical
// that we tick at consistent intervals.
TimeStamp newTarget = aNowTime + mRateDuration;
uint32_t delay = static_cast<uint32_t>((newTarget - aNowTime).ToMilliseconds());
mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT);
mTargetTime = newTarget;
}
};
/*
* A RefreshDriverTimer for inactive documents. When a new refresh driver is
* added, the rate is reset to the base (normally 1s/1fps). Every time
* it ticks, a single refresh driver is poked. Once they have all been poked,
* the duration between ticks doubles, up to mDisableAfterMilliseconds. At that point,
* the timer is quiet and doesn't tick (until something is added to it again).
*
* When a timer is removed, there is a possibility of another timer
* being skipped for one cycle. We could avoid this by adjusting
* mNextDriverIndex in RemoveRefreshDriver, but there's little need to
* add that complexity. All we want is for inactive drivers to tick
* at some point, but we don't care too much about how often.
*/
class InactiveRefreshDriverTimer final :
public SimpleTimerBasedRefreshDriverTimer
{
public:
explicit InactiveRefreshDriverTimer(double aRate)
: SimpleTimerBasedRefreshDriverTimer(aRate),
mNextTickDuration(aRate),
mDisableAfterMilliseconds(-1.0),
mNextDriverIndex(0)
{
}
InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds)
: SimpleTimerBasedRefreshDriverTimer(aRate),
mNextTickDuration(aRate),
mDisableAfterMilliseconds(aDisableAfterMilliseconds),
mNextDriverIndex(0)
{
}
virtual void AddRefreshDriver(nsRefreshDriver* aDriver)
{
RefreshDriverTimer::AddRefreshDriver(aDriver);
LOG("[%p] inactive timer got new refresh driver %p, resetting rate",
this, aDriver);
// reset the timer, and start with the newly added one next time.
mNextTickDuration = mRateMilliseconds;
// we don't really have to start with the newly added one, but we may as well
// not tick the old ones at the fastest rate any more than we need to.
mNextDriverIndex = GetRefreshDriverCount() - 1;
StopTimer();
StartTimer();
}
protected:
uint32_t GetRefreshDriverCount()
{
return mContentRefreshDrivers.Length() + mRootRefreshDrivers.Length();
}
virtual void StartTimer()
{
mLastFireEpoch = JS_Now();
mLastFireTime = TimeStamp::Now();
mTargetTime = mLastFireTime + mRateDuration;
uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT);
}
virtual void StopTimer()
{
mTimer->Cancel();
}
virtual void ScheduleNextTick(TimeStamp aNowTime)
{
if (mDisableAfterMilliseconds > 0.0 &&
mNextTickDuration > mDisableAfterMilliseconds)
{
// We hit the time after which we should disable
// inactive window refreshes; don't schedule anything
// until we get kicked by an AddRefreshDriver call.
return;
}
// double the next tick time if we've already gone through all of them once
if (mNextDriverIndex >= GetRefreshDriverCount()) {
mNextTickDuration *= 2.0;
mNextDriverIndex = 0;
}
// this doesn't need to be precise; do a simple schedule
uint32_t delay = static_cast<uint32_t>(mNextTickDuration);
mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT);
LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this, mNextTickDuration,
mNextDriverIndex, GetRefreshDriverCount());
}
/* Runs just one driver's tick. */
void TickOne()
{
int64_t jsnow = JS_Now();
TimeStamp now = TimeStamp::Now();
ScheduleNextTick(now);
mLastFireEpoch = jsnow;
mLastFireTime = now;
nsTArray<RefPtr<nsRefreshDriver> > drivers(mContentRefreshDrivers);
drivers.AppendElements(mRootRefreshDrivers);
if (mNextDriverIndex < drivers.Length() &&
!drivers[mNextDriverIndex]->IsTestControllingRefreshesEnabled())
{
TickDriver(drivers[mNextDriverIndex], jsnow, now);
}
mNextDriverIndex++;
}
static void TimerTickOne(nsITimer* aTimer, void* aClosure)
{
InactiveRefreshDriverTimer *timer = static_cast<InactiveRefreshDriverTimer*>(aClosure);
timer->TickOne();
}
double mNextTickDuration;
double mDisableAfterMilliseconds;
uint32_t mNextDriverIndex;
};
// The PBackground protocol connection callback. It will be called when
// PBackground is ready. Then we create the PVsync sub-protocol for our
// vsync-base RefreshTimer.
class VsyncChildCreateCallback final : public nsIIPCBackgroundChildCreateCallback
{
NS_DECL_ISUPPORTS
public:
VsyncChildCreateCallback()
{
MOZ_ASSERT(NS_IsMainThread());
}
static void CreateVsyncActor(PBackgroundChild* aPBackgroundChild)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPBackgroundChild);
layout::PVsyncChild* actor = aPBackgroundChild->SendPVsyncConstructor();
layout::VsyncChild* child = static_cast<layout::VsyncChild*>(actor);
nsRefreshDriver::PVsyncActorCreated(child);
}
private:
virtual ~VsyncChildCreateCallback() {}
virtual void ActorCreated(PBackgroundChild* aPBackgroundChild) override
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPBackgroundChild);
CreateVsyncActor(aPBackgroundChild);
}
virtual void ActorFailed() override
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_CRASH("Failed To Create VsyncChild Actor");
}
}; // VsyncChildCreateCallback
NS_IMPL_ISUPPORTS(VsyncChildCreateCallback, nsIIPCBackgroundChildCreateCallback)
} // namespace mozilla
static RefreshDriverTimer* sRegularRateTimer;
static InactiveRefreshDriverTimer* sThrottledRateTimer;
#ifdef XP_WIN
static int32_t sHighPrecisionTimerRequests = 0;
// a bare pointer to avoid introducing a static constructor
static nsITimer *sDisableHighPrecisionTimersTimer = nullptr;
#endif
static void
CreateContentVsyncRefreshTimer(void*)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!XRE_IsParentProcess());
// Create the PVsync actor child for vsync-base refresh timer.
// PBackgroundChild is created asynchronously. If PBackgroundChild is still
// unavailable, setup VsyncChildCreateCallback callback to handle the async
// connect. We will still use software timer before PVsync ready, and change
// to use hw timer when the connection is done. Please check
// VsyncChildCreateCallback::CreateVsyncActor() and
// nsRefreshDriver::PVsyncActorCreated().
PBackgroundChild* backgroundChild = BackgroundChild::GetForCurrentThread();
if (backgroundChild) {
// If we already have PBackgroundChild, create the
// child VsyncRefreshDriverTimer here.
VsyncChildCreateCallback::CreateVsyncActor(backgroundChild);
return;
}
// Setup VsyncChildCreateCallback callback
RefPtr<nsIIPCBackgroundChildCreateCallback> callback = new VsyncChildCreateCallback();
if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread(callback))) {
MOZ_CRASH("PVsync actor create failed!");
}
}
static void
CreateVsyncRefreshTimer()
{
MOZ_ASSERT(NS_IsMainThread());
// Sometimes, gfxPrefs is not initialized here. Make sure the gfxPrefs is
// ready.
gfxPrefs::GetSingleton();
if (gfxPlatform::IsInLayoutAsapMode()) {
return;
}
if (XRE_IsParentProcess()) {
// Make sure all vsync systems are ready.
gfxPlatform::GetPlatform();
// In parent process, we don't need to use ipc. We can create the
// VsyncRefreshDriverTimer directly.
sRegularRateTimer = new VsyncRefreshDriverTimer();
return;
}
#ifdef MOZ_NUWA_PROCESS
// NUWA process will just use software timer. Use NuwaAddFinalConstructor()
// to register a callback to create the vsync-base refresh timer after a
// process is created.
if (IsNuwaProcess()) {
NuwaAddFinalConstructor(&CreateContentVsyncRefreshTimer, nullptr);
return;
}
#endif
// If this process is not created by NUWA, just create the vsync timer here.
CreateContentVsyncRefreshTimer(nullptr);
}
static uint32_t
GetFirstFrameDelay(imgIRequest* req)
{
nsCOMPtr<imgIContainer> container;
if (NS_FAILED(req->GetImage(getter_AddRefs(container))) || !container) {
return 0;
}
// If this image isn't animated, there isn't a first frame delay.
int32_t delay = container->GetFirstFrameDelay();
if (delay < 0)
return 0;
return static_cast<uint32_t>(delay);
}
/* static */ void
nsRefreshDriver::InitializeStatics()
{
}
/* static */ void
nsRefreshDriver::Shutdown()
{
// clean up our timers
delete sRegularRateTimer;
delete sThrottledRateTimer;
sRegularRateTimer = nullptr;
sThrottledRateTimer = nullptr;
#ifdef XP_WIN
if (sDisableHighPrecisionTimersTimer) {
sDisableHighPrecisionTimersTimer->Cancel();
NS_RELEASE(sDisableHighPrecisionTimersTimer);
timeEndPeriod(1);
} else if (sHighPrecisionTimerRequests) {
timeEndPeriod(1);
}
#endif
}
/* static */ int32_t
nsRefreshDriver::DefaultInterval()
{
return NSToIntRound(1000.0 / gfxPlatform::GetDefaultFrameRate());
}
// Compute the interval to use for the refresh driver timer, in milliseconds.
// outIsDefault indicates that rate was not explicitly set by the user
// so we might choose other, more appropriate rates (e.g. vsync, etc)
// layout.frame_rate=0 indicates "ASAP mode".
// In ASAP mode rendering is iterated as fast as possible (typically for stress testing).
// A target rate of 10k is used internally instead of special-handling 0.
// Backends which block on swap/present/etc should try to not block
// when layout.frame_rate=0 - to comply with "ASAP" as much as possible.
double
nsRefreshDriver::GetRegularTimerInterval(bool *outIsDefault) const
{
int32_t rate = Preferences::GetInt("layout.frame_rate", -1);
if (rate < 0) {
rate = gfxPlatform::GetDefaultFrameRate();
if (outIsDefault) {
*outIsDefault = true;
}
} else {
if (outIsDefault) {
*outIsDefault = false;
}
}
if (rate == 0) {
rate = 10000;
}
return 1000.0 / rate;
}
/* static */ double
nsRefreshDriver::GetThrottledTimerInterval()
{
int32_t rate = Preferences::GetInt("layout.throttled_frame_rate", -1);
if (rate <= 0) {
rate = DEFAULT_THROTTLED_FRAME_RATE;
}
return 1000.0 / rate;
}
/* static */ mozilla::TimeDuration
nsRefreshDriver::GetMinRecomputeVisibilityInterval()
{
int32_t interval =
Preferences::GetInt("layout.visibility.min-recompute-interval-ms", -1);
if (interval <= 0) {
interval = DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS;
}
return TimeDuration::FromMilliseconds(interval);
}
double
nsRefreshDriver::GetRefreshTimerInterval() const
{
return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval();
}
RefreshDriverTimer*
nsRefreshDriver::ChooseTimer() const
{
if (mThrottled) {
if (!sThrottledRateTimer)
sThrottledRateTimer = new InactiveRefreshDriverTimer(GetThrottledTimerInterval(),
DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0);
return sThrottledRateTimer;
}
if (!sRegularRateTimer) {
bool isDefault = true;
double rate = GetRegularTimerInterval(&isDefault);
// Try to use vsync-base refresh timer first for sRegularRateTimer.
CreateVsyncRefreshTimer();
if (!sRegularRateTimer) {
sRegularRateTimer = new StartupRefreshDriverTimer(rate);
}
}
return sRegularRateTimer;
}
nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
: mActiveTimer(nullptr),
mReflowCause(nullptr),
mStyleCause(nullptr),
mPresContext(aPresContext),
mRootRefresh(nullptr),
mPendingTransaction(0),
mCompletedTransaction(0),
mFreezeCount(0),
mThrottledFrameRequestInterval(TimeDuration::FromMilliseconds(
GetThrottledTimerInterval())),
mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()),
mThrottled(false),
mNeedToRecomputeVisibility(false),
mTestControllingRefreshes(false),
mViewManagerFlushIsPending(false),
mRequestedHighPrecision(false),
mInRefresh(false),
mWaitingForTransaction(false),
mSkippedPaints(false)
{
mMostRecentRefreshEpochTime = JS_Now();
mMostRecentRefresh = TimeStamp::Now();
mMostRecentTick = mMostRecentRefresh;
mNextThrottledFrameRequestTick = mMostRecentTick;
mNextRecomputeVisibilityTick = mMostRecentTick;
}
nsRefreshDriver::~nsRefreshDriver()
{
MOZ_ASSERT(ObserverCount() == 0,
"observers should have unregistered");
MOZ_ASSERT(!mActiveTimer, "timer should be gone");
if (mRootRefresh) {
mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
mRootRefresh = nullptr;
}
for (nsIPresShell* shell : mPresShellsToInvalidateIfHidden) {
shell->InvalidatePresShellIfHidden();
}
mPresShellsToInvalidateIfHidden.Clear();
profiler_free_backtrace(mStyleCause);
profiler_free_backtrace(mReflowCause);
}
// Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh
// for description.
void
nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds)
{
// ensure that we're removed from our driver
StopTimer();
if (!mTestControllingRefreshes) {
mMostRecentRefreshEpochTime = JS_Now();
mMostRecentRefresh = TimeStamp::Now();
mTestControllingRefreshes = true;
if (mWaitingForTransaction) {
// Disable any refresh driver throttling when entering test mode
mWaitingForTransaction = false;
mSkippedPaints = false;
}
}
mMostRecentRefreshEpochTime += aMilliseconds * 1000;
mMostRecentRefresh += TimeDuration::FromMilliseconds((double) aMilliseconds);
mozilla::dom::AutoNoJSAPI nojsapi;
DoTick();
}
void
nsRefreshDriver::RestoreNormalRefresh()
{
mTestControllingRefreshes = false;
EnsureTimerStarted(eAllowTimeToGoBackwards);
mCompletedTransaction = mPendingTransaction;
}
TimeStamp
nsRefreshDriver::MostRecentRefresh() const
{
const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
return mMostRecentRefresh;
}
int64_t
nsRefreshDriver::MostRecentRefreshEpochTime() const
{
const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
return mMostRecentRefreshEpochTime;
}
bool
nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver,
mozFlushType aFlushType)
{
ObserverArray& array = ArrayFor(aFlushType);
bool success = array.AppendElement(aObserver) != nullptr;
EnsureTimerStarted();
return success;
}
bool
nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
mozFlushType aFlushType)
{
ObserverArray& array = ArrayFor(aFlushType);
return array.RemoveElement(aObserver);
}
void
nsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver)
{
mPostRefreshObservers.AppendElement(aObserver);
}
void
nsRefreshDriver::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver)
{
mPostRefreshObservers.RemoveElement(aObserver);
}
bool
nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
{
uint32_t delay = GetFirstFrameDelay(aRequest);
if (delay == 0) {
if (!mRequests.PutEntry(aRequest)) {
return false;
}
} else {
ImageStartData* start = mStartTable.Get(delay);
if (!start) {
start = new ImageStartData();
mStartTable.Put(delay, start);
}
start->mEntries.PutEntry(aRequest);
}
EnsureTimerStarted();
return true;
}
void
nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest)
{
// Try to remove from both places, just in case, because we can't tell
// whether RemoveEntry() succeeds.
mRequests.RemoveEntry(aRequest);
uint32_t delay = GetFirstFrameDelay(aRequest);
if (delay != 0) {
ImageStartData* start = mStartTable.Get(delay);
if (start) {
start->mEntries.RemoveEntry(aRequest);
}
}
}
void
nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags)
{
if (mTestControllingRefreshes)
return;
// will it already fire, and no other changes needed?
if (mActiveTimer && !(aFlags & eAdjustingTimer))
return;
if (IsFrozen() || !mPresContext) {
// If we don't want to start it now, or we've been disconnected.
StopTimer();
return;
}
if (mPresContext->Document()->IsBeingUsedAsImage()) {
// Image documents receive ticks from clients' refresh drivers.
// XXXdholbert Exclude SVG-in-opentype fonts from this optimization, until
// they receive refresh-driver ticks from their client docs (bug 1107252).
nsIURI* uri = mPresContext->Document()->GetDocumentURI();
if (!uri || !IsFontTableURI(uri)) {
MOZ_ASSERT(!mActiveTimer,
"image doc refresh driver should never have its own timer");
return;
}
}
// We got here because we're either adjusting the time *or* we're
// starting it for the first time. Add to the right timer,
// prehaps removing it from a previously-set one.
RefreshDriverTimer *newTimer = ChooseTimer();
if (newTimer != mActiveTimer) {
if (mActiveTimer)
mActiveTimer->RemoveRefreshDriver(this);
mActiveTimer = newTimer;
mActiveTimer->AddRefreshDriver(this);
}
// Since the different timers are sampled at different rates, when switching
// timers, the most recent refresh of the new timer may be *before* the
// most recent refresh of the old timer. However, the refresh driver time
// should not go backwards so we clamp the most recent refresh time.
//
// The one exception to this is when we are restoring the refresh driver
// from test control in which case the time is expected to go backwards
// (see bug 1043078).
mMostRecentRefresh =
aFlags & eAllowTimeToGoBackwards
? mActiveTimer->MostRecentRefresh()
: std::max(mActiveTimer->MostRecentRefresh(), mMostRecentRefresh);
mMostRecentRefreshEpochTime =
aFlags & eAllowTimeToGoBackwards
? mActiveTimer->MostRecentRefreshEpochTime()
: std::max(mActiveTimer->MostRecentRefreshEpochTime(),
mMostRecentRefreshEpochTime);
}
void
nsRefreshDriver::StopTimer()
{
if (!mActiveTimer)
return;
mActiveTimer->RemoveRefreshDriver(this);
mActiveTimer = nullptr;
if (mRequestedHighPrecision) {
SetHighPrecisionTimersEnabled(false);
}
}
#ifdef XP_WIN
static void
DisableHighPrecisionTimersCallback(nsITimer *aTimer, void *aClosure)
{
timeEndPeriod(1);
NS_RELEASE(sDisableHighPrecisionTimersTimer);
}
#endif
void
nsRefreshDriver::ConfigureHighPrecision()
{
bool haveUnthrottledFrameRequestCallbacks =
mFrameRequestCallbackDocs.Length() > 0;
// if the only change that's needed is that we need high precision,
// then just set that
if (!mThrottled && !mRequestedHighPrecision &&
haveUnthrottledFrameRequestCallbacks) {
SetHighPrecisionTimersEnabled(true);
} else if (mRequestedHighPrecision && !haveUnthrottledFrameRequestCallbacks) {
SetHighPrecisionTimersEnabled(false);
}
}
void
nsRefreshDriver::SetHighPrecisionTimersEnabled(bool aEnable)
{
LOG("[%p] SetHighPrecisionTimersEnabled (%s)", this, aEnable ? "true" : "false");
if (aEnable) {
NS_ASSERTION(!mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(true) called when already requested!");
#ifdef XP_WIN
if (++sHighPrecisionTimerRequests == 1) {
// If we had a timer scheduled to disable it, that means that it's already
// enabled; just cancel the timer. Otherwise, really enable it.
if (sDisableHighPrecisionTimersTimer) {
sDisableHighPrecisionTimersTimer->Cancel();
NS_RELEASE(sDisableHighPrecisionTimersTimer);
} else {
timeBeginPeriod(1);
}
}
#endif
mRequestedHighPrecision = true;
} else {
NS_ASSERTION(mRequestedHighPrecision, "SetHighPrecisionTimersEnabled(false) called when not requested!");
#ifdef XP_WIN
if (--sHighPrecisionTimerRequests == 0) {
// Don't jerk us around between high precision and low precision
// timers; instead, only allow leaving high precision timers
// after 90 seconds. This is arbitrary, but hopefully good
// enough.
NS_ASSERTION(!sDisableHighPrecisionTimersTimer, "We shouldn't have an outstanding disable-high-precision timer !");
nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
if (timer) {
timer.forget(&sDisableHighPrecisionTimersTimer);
sDisableHighPrecisionTimersTimer->InitWithFuncCallback(DisableHighPrecisionTimersCallback,
nullptr,
90 * 1000,
nsITimer::TYPE_ONE_SHOT);
} else {
// might happen if we're shutting down XPCOM; just drop the time period down
// immediately
timeEndPeriod(1);
}
}
#endif
mRequestedHighPrecision = false;
}
}
uint32_t
nsRefreshDriver::ObserverCount() const
{
uint32_t sum = 0;
for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
sum += mObservers[i].Length();
}
// Even while throttled, we need to process layout and style changes. Style
// changes can trigger transitions which fire events when they complete, and
// layout changes can affect media queries on child documents, triggering
// style changes, etc.
sum += mStyleFlushObservers.Length();
sum += mLayoutFlushObservers.Length();
sum += mFrameRequestCallbackDocs.Length();
sum += mThrottledFrameRequestCallbackDocs.Length();
sum += mViewManagerFlushIsPending;
return sum;
}
uint32_t
nsRefreshDriver::ImageRequestCount() const
{
uint32_t count = 0;
for (auto iter = mStartTable.ConstIter(); !iter.Done(); iter.Next()) {
count += iter.UserData()->mEntries.Count();
}
return count + mRequests.Count();
}
nsRefreshDriver::ObserverArray&
nsRefreshDriver::ArrayFor(mozFlushType aFlushType)
{
switch (aFlushType) {
case Flush_Style:
return mObservers[0];
case Flush_Layout:
return mObservers[1];
case Flush_Display:
return mObservers[2];
default:
MOZ_ASSERT(false, "bad flush type");
return *static_cast<ObserverArray*>(nullptr);
}
}
/*
* nsITimerCallback implementation
*/
void
nsRefreshDriver::DoTick()
{
NS_PRECONDITION(!IsFrozen(), "Why are we notified while frozen?");
NS_PRECONDITION(mPresContext, "Why are we notified after disconnection?");
NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
"Shouldn't have a JSContext on the stack");
if (mTestControllingRefreshes) {
Tick(mMostRecentRefreshEpochTime, mMostRecentRefresh);
} else {
Tick(JS_Now(), TimeStamp::Now());
}
}
struct DocumentFrameCallbacks {
explicit DocumentFrameCallbacks(nsIDocument* aDocument) :
mDocument(aDocument)
{}
nsCOMPtr<nsIDocument> mDocument;
nsIDocument::FrameRequestCallbackList mCallbacks;
};
static nsDocShell* GetDocShell(nsPresContext* aPresContext)
{
return static_cast<nsDocShell*>(aPresContext->GetDocShell());
}
static bool
HasPendingAnimations(nsIPresShell* aShell)
{
nsIDocument* doc = aShell->GetDocument();
if (!doc) {
return false;
}
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
return tracker && tracker->HasPendingAnimations();
}
/**
* Return a list of all the child docShells in a given root docShell that are
* visible and are recording markers for the profilingTimeline
*/
static void GetProfileTimelineSubDocShells(nsDocShell* aRootDocShell,
nsTArray<nsDocShell*>& aShells)
{
if (!aRootDocShell) {
return;
}
RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
if (!timelines || timelines->IsEmpty()) {
return;
}
nsCOMPtr<nsISimpleEnumerator> enumerator;
nsresult rv = aRootDocShell->GetDocShellEnumerator(
nsIDocShellTreeItem::typeAll,
nsIDocShell::ENUMERATE_BACKWARDS,
getter_AddRefs(enumerator));
if (NS_FAILED(rv)) {
return;
}
nsCOMPtr<nsIDocShell> curItem;
bool hasMore = false;
while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> curSupports;
enumerator->GetNext(getter_AddRefs(curSupports));
curItem = do_QueryInterface(curSupports);
if (!curItem || !curItem->GetRecordProfileTimelineMarkers()) {
continue;
}
nsDocShell* shell = static_cast<nsDocShell*>(curItem.get());
bool isVisible = false;
shell->GetVisibility(&isVisible);
if (!isVisible) {
continue;
}
aShells.AppendElement(shell);
};
}
static void
TakeFrameRequestCallbacksFrom(nsIDocument* aDocument,
nsTArray<DocumentFrameCallbacks>& aTarget)
{
aTarget.AppendElement(aDocument);
aDocument->TakeFrameRequestCallbacks(aTarget.LastElement().mCallbacks);
}
static bool
DispatchAnimationEventsOnSubDocuments(nsIDocument* aDocument,
void* aRefreshDriver)
{
nsIPresShell* shell = aDocument->GetShell();
if (!shell) {
return true;
}
RefPtr<nsPresContext> context = shell->GetPresContext();
if (!context || context->RefreshDriver() != aRefreshDriver) {
return true;
}
nsCOMPtr<nsIDocument> kungFuDeathGrip(aDocument);
context->TransitionManager()->SortEvents();
context->AnimationManager()->SortEvents();
// Dispatch transition events first since transitions conceptually sit
// below animations in terms of compositing order.
context->TransitionManager()->DispatchEvents();
// Check that the presshell has not been destroyed
if (context->GetPresShell()) {
context->AnimationManager()->DispatchEvents();
}
aDocument->EnumerateSubDocuments(DispatchAnimationEventsOnSubDocuments,
aRefreshDriver);
return true;
}
void
nsRefreshDriver::DispatchAnimationEvents()
{
if (!mPresContext) {
return;
}
DispatchAnimationEventsOnSubDocuments(mPresContext->Document(), this);
}
void
nsRefreshDriver::RunFrameRequestCallbacks(TimeStamp aNowTime)
{
// Grab all of our frame request callbacks up front.
nsTArray<DocumentFrameCallbacks>
frameRequestCallbacks(mFrameRequestCallbackDocs.Length() +
mThrottledFrameRequestCallbackDocs.Length());
// First, grab throttled frame request callbacks.
{
nsTArray<nsIDocument*> docsToRemove;
// We always tick throttled frame requests if the entire refresh driver is
// throttled, because in that situation throttled frame requests tick at the
// same frequency as non-throttled frame requests.
bool tickThrottledFrameRequests = mThrottled;
if (!tickThrottledFrameRequests &&
aNowTime >= mNextThrottledFrameRequestTick) {
mNextThrottledFrameRequestTick = aNowTime + mThrottledFrameRequestInterval;
tickThrottledFrameRequests = true;
}
for (nsIDocument* doc : mThrottledFrameRequestCallbackDocs) {
if (tickThrottledFrameRequests) {
// We're ticking throttled documents, so grab this document's requests.
// We don't bother appending to docsToRemove because we're going to
// clear mThrottledFrameRequestCallbackDocs anyway.
TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
} else if (!doc->ShouldThrottleFrameRequests()) {
// This document is no longer throttled, so grab its requests even
// though we're not ticking throttled frame requests right now. If
// this is the first unthrottled document with frame requests, we'll
// enter high precision mode the next time the callback is scheduled.
TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
docsToRemove.AppendElement(doc);
}
}
// Remove all the documents we're ticking from
// mThrottledFrameRequestCallbackDocs so they can be readded as needed.
if (tickThrottledFrameRequests) {
mThrottledFrameRequestCallbackDocs.Clear();
} else {
// XXX(seth): We're using this approach to avoid concurrent modification
// of mThrottledFrameRequestCallbackDocs. docsToRemove usually has either
// zero elements or a very small number, so this should be OK in practice.
for (nsIDocument* doc : docsToRemove) {
mThrottledFrameRequestCallbackDocs.RemoveElement(doc);
}
}
}
// Now grab unthrottled frame request callbacks.
for (nsIDocument* doc : mFrameRequestCallbackDocs) {
TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
}
// Reset mFrameRequestCallbackDocs so they can be readded as needed.
mFrameRequestCallbackDocs.Clear();
if (!frameRequestCallbacks.IsEmpty()) {
profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_START);
for (const DocumentFrameCallbacks& docCallbacks : frameRequestCallbacks) {
// XXXbz Bug 863140: GetInnerWindow can return the outer
// window in some cases.
nsPIDOMWindow* innerWindow = docCallbacks.mDocument->GetInnerWindow();
DOMHighResTimeStamp timeStamp = 0;
if (innerWindow && innerWindow->IsInnerWindow()) {
nsPerformance* perf = innerWindow->GetPerformance();
if (perf) {
timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime);
}
// else window is partially torn down already
}
for (auto& callback : docCallbacks.mCallbacks) {
ErrorResult ignored;
callback->Call(timeStamp, ignored);
ignored.SuppressException();
}
}
profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_END);
}
}
void
nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
{
NS_PRECONDITION(!nsContentUtils::GetCurrentJSContext(),
"Shouldn't have a JSContext on the stack");
if (nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()) {
NS_ERROR("Refresh driver should not run during plugin call!");
// Try to survive this by just ignoring the refresh tick.
return;
}
PROFILER_LABEL("nsRefreshDriver", "Tick",
js::ProfileEntry::Category::GRAPHICS);
// We're either frozen or we were disconnected (likely in the middle
// of a tick iteration). Just do nothing here, since our
// prescontext went away.
if (IsFrozen() || !mPresContext) {
return;
}
// We can have a race condition where the vsync timestamp
// is before the most recent refresh due to a forced refresh.
// The underlying assumption is that the refresh driver tick can only
// go forward in time, not backwards. To prevent the refresh
// driver from going back in time, just skip this tick and
// wait until the next tick.
if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes) {
return;
}
TimeStamp previousRefresh = mMostRecentRefresh;
mMostRecentRefresh = aNowTime;
mMostRecentRefreshEpochTime = aNowEpoch;
if (IsWaitingForPaint(aNowTime)) {
// We're currently suspended waiting for earlier Tick's to
// be completed (on the Compositor). Mark that we missed the paint
// and keep waiting.
return;
}
mMostRecentTick = aNowTime;
if (mRootRefresh) {
mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
mRootRefresh = nullptr;
}
mSkippedPaints = false;
nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) {
// Things are being destroyed, or we no longer have any observers.
// We don't want to stop the timer when observers are initially
// removed, because sometimes observers can be added and removed
// often depending on what other things are going on and in that
// situation we don't want to thrash our timer. So instead we
// wait until we get a Notify() call when we have no observers
// before stopping the timer.
StopTimer();
return;
}
AutoRestore<bool> restoreInRefresh(mInRefresh);
mInRefresh = true;
AutoRestore<TimeStamp> restoreTickStart(mTickStart);
mTickStart = TimeStamp::Now();
/*
* The timer holds a reference to |this| while calling |Notify|.
* However, implementations of |WillRefresh| are permitted to destroy
* the pres context, which will cause our |mPresContext| to become
* null. If this happens, we must stop notifying observers.
*/
for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
ObserverArray::EndLimitedIterator etor(mObservers[i]);
while (etor.HasMore()) {
RefPtr<nsARefreshObserver> obs = etor.GetNext();
obs->WillRefresh(aNowTime);
if (!mPresContext || !mPresContext->GetPresShell()) {
StopTimer();
return;
}
}
if (i == 0) {
// This is the Flush_Style case.
DispatchAnimationEvents();
RunFrameRequestCallbacks(aNowTime);
if (mPresContext && mPresContext->GetPresShell()) {
bool tracingStyleFlush = false;
nsAutoTArray<nsIPresShell*, 16> observers;
observers.AppendElements(mStyleFlushObservers);
for (uint32_t j = observers.Length();
j && mPresContext && mPresContext->GetPresShell(); --j) {
// Make sure to not process observers which might have been removed
// during previous iterations.
nsIPresShell* shell = observers[j - 1];
if (!mStyleFlushObservers.Contains(shell))
continue;
if (!tracingStyleFlush) {
tracingStyleFlush = true;
profiler_tracing("Paint", "Styles", mStyleCause, TRACING_INTERVAL_START);
mStyleCause = nullptr;
}
NS_ADDREF(shell);
mStyleFlushObservers.RemoveElement(shell);
shell->GetPresContext()->RestyleManager()->mObservingRefreshDriver = false;
shell->FlushPendingNotifications(ChangesToFlush(Flush_Style, false));
// Inform the FontFaceSet that we ticked, so that it can resolve its
// ready promise if it needs to (though it might still be waiting on
// a layout flush).
nsPresContext* presContext = shell->GetPresContext();
if (presContext) {
presContext->NotifyFontFaceSetOnRefresh();
}
NS_RELEASE(shell);
mNeedToRecomputeVisibility = true;
}
if (tracingStyleFlush) {
profiler_tracing("Paint", "Styles", TRACING_INTERVAL_END);
}
if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) {
mPresContext->TickLastStyleUpdateForAllAnimations();
}
}
} else if (i == 1) {
// This is the Flush_Layout case.
bool tracingLayoutFlush = false;
nsAutoTArray<nsIPresShell*, 16> observers;
observers.AppendElements(mLayoutFlushObservers);
for (uint32_t j = observers.Length();
j && mPresContext && mPresContext->GetPresShell(); --j) {
// Make sure to not process observers which might have been removed
// during previous iterations.
nsIPresShell* shell = observers[j - 1];
if (!mLayoutFlushObservers.Contains(shell))
continue;
if (!tracingLayoutFlush) {
tracingLayoutFlush = true;
profiler_tracing("Paint", "Reflow", mReflowCause, TRACING_INTERVAL_START);
mReflowCause = nullptr;
}
NS_ADDREF(shell);
mLayoutFlushObservers.RemoveElement(shell);
shell->mReflowScheduled = false;
shell->mSuppressInterruptibleReflows = false;
mozFlushType flushType = HasPendingAnimations(shell)
? Flush_Layout
: Flush_InterruptibleLayout;
shell->FlushPendingNotifications(ChangesToFlush(flushType, false));
// Inform the FontFaceSet that we ticked, so that it can resolve its
// ready promise if it needs to.
nsPresContext* presContext = shell->GetPresContext();
if (presContext) {
presContext->NotifyFontFaceSetOnRefresh();
}
NS_RELEASE(shell);
mNeedToRecomputeVisibility = true;
}
if (tracingLayoutFlush) {
profiler_tracing("Paint", "Reflow", TRACING_INTERVAL_END);
}
}
// The pres context may be destroyed during we do the flushing.
if (!mPresContext || !mPresContext->GetPresShell()) {
StopTimer();
return;
}
}
// Recompute image visibility if it's necessary and enough time has passed
// since the last time we did it.
if (mNeedToRecomputeVisibility && !mThrottled &&
aNowTime >= mNextRecomputeVisibilityTick &&
!presShell->IsPaintingSuppressed()) {
mNextRecomputeVisibilityTick = aNowTime + mMinRecomputeVisibilityInterval;
mNeedToRecomputeVisibility = false;
presShell->ScheduleImageVisibilityUpdate();
}
/*
* Perform notification to imgIRequests subscribed to listen
* for refresh events.
*/
for (auto iter = mStartTable.Iter(); !iter.Done(); iter.Next()) {
const uint32_t& delay = iter.Key();
ImageStartData* data = iter.UserData();
if (data->mStartTime) {
TimeStamp& start = *data->mStartTime;
TimeDuration prev = previousRefresh - start;
TimeDuration curr = aNowTime - start;
uint32_t prevMultiple = uint32_t(prev.ToMilliseconds()) / delay;
// We want to trigger images' refresh if we've just crossed over a
// multiple of the first image's start time. If so, set the animation
// start time to the nearest multiple of the delay and move all the
// images in this table to the main requests table.
if (prevMultiple != uint32_t(curr.ToMilliseconds()) / delay) {
mozilla::TimeStamp desired =
start + TimeDuration::FromMilliseconds(prevMultiple * delay);
BeginRefreshingImages(data->mEntries, desired);
}
} else {
// This is the very first time we've drawn images with this time delay.
// Set the animation start time to "now" and move all the images in this
// table to the main requests table.
mozilla::TimeStamp desired = aNowTime;
BeginRefreshingImages(data->mEntries, desired);
data->mStartTime.emplace(aNowTime);
}
}
if (mRequests.Count()) {
// RequestRefresh may run scripts, so it's not safe to directly call it
// while using a hashtable enumerator to enumerate mRequests in case
// script modifies the hashtable. Instead, we build a (local) array of
// images to refresh, and then we refresh each image in that array.
nsCOMArray<imgIContainer> imagesToRefresh(mRequests.Count());
for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) {
nsISupportsHashKey* entry = iter.Get();
auto req = static_cast<imgIRequest*>(entry->GetKey());
MOZ_ASSERT(req, "Unable to retrieve the image request");
nsCOMPtr<imgIContainer> image;
if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
imagesToRefresh.AppendElement(image);
}
}
for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) {
imagesToRefresh[i]->RequestRefresh(aNowTime);
}
}
for (nsIPresShell* shell : mPresShellsToInvalidateIfHidden) {
shell->InvalidatePresShellIfHidden();
}
mPresShellsToInvalidateIfHidden.Clear();
if (mViewManagerFlushIsPending) {
RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
nsTArray<nsDocShell*> profilingDocShells;
GetProfileTimelineSubDocShells(GetDocShell(mPresContext), profilingDocShells);
for (nsDocShell* docShell : profilingDocShells) {
// For the sake of the profile timeline's simplicity, this is flagged as
// paint even if it includes creating display lists
MOZ_ASSERT(timelines);
MOZ_ASSERT(timelines->HasConsumer(docShell));
timelines->AddMarkerForDocShell(docShell, "Paint", MarkerTracingType::START);
}
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr("Starting ProcessPendingUpdates\n");
}
#endif
mViewManagerFlushIsPending = false;
RefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
vm->ProcessPendingUpdates();
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr("Ending ProcessPendingUpdates\n");
}
#endif
for (nsDocShell* docShell : profilingDocShells) {
MOZ_ASSERT(timelines);
MOZ_ASSERT(timelines->HasConsumer(docShell));
timelines->AddMarkerForDocShell(docShell, "Paint", MarkerTracingType::END);
}
if (nsContentUtils::XPConnect()) {
nsContentUtils::XPConnect()->NotifyDidPaint();
nsJSContext::NotifyDidPaint();
}
}
#ifndef ANDROID /* bug 1142079 */
mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::REFRESH_DRIVER_TICK, mTickStart);
#endif
nsTObserverArray<nsAPostRefreshObserver*>::ForwardIterator iter(mPostRefreshObservers);
while (iter.HasMore()) {
nsAPostRefreshObserver* observer = iter.GetNext();
observer->DidRefresh();
}
// Check if we should exit high precision timer mode.
ConfigureHighPrecision();
NS_ASSERTION(mInRefresh, "Still in refresh");
}
void
nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
mozilla::TimeStamp aDesired)
{
for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) {
auto req = static_cast<imgIRequest*>(iter.Get()->GetKey());
MOZ_ASSERT(req, "Unable to retrieve the image request");
mRequests.PutEntry(req);
nsCOMPtr<imgIContainer> image;
if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
image->SetAnimationStartTime(aDesired);
}
}
aEntries.Clear();
}
void
nsRefreshDriver::Freeze()
{
StopTimer();
mFreezeCount++;
}
void
nsRefreshDriver::Thaw()
{
NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver");
if (mFreezeCount > 0) {
mFreezeCount--;
}
if (mFreezeCount == 0) {
if (ObserverCount() || ImageRequestCount()) {
// FIXME: This isn't quite right, since our EnsureTimerStarted call
// updates our mMostRecentRefresh, but the DoRefresh call won't run
// and notify our observers until we get back to the event loop.
// Thus MostRecentRefresh() will lie between now and the DoRefresh.
NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsRefreshDriver::DoRefresh));
EnsureTimerStarted();
}
}
}
void
nsRefreshDriver::FinishedWaitingForTransaction()
{
mWaitingForTransaction = false;
if (mSkippedPaints &&
!IsInRefresh() &&
(ObserverCount() || ImageRequestCount())) {
profiler_tracing("Paint", "RD", TRACING_INTERVAL_START);
DoRefresh();
profiler_tracing("Paint", "RD", TRACING_INTERVAL_END);
}
mSkippedPaints = false;
}
uint64_t
nsRefreshDriver::GetTransactionId()
{
++mPendingTransaction;
if (mPendingTransaction >= mCompletedTransaction + 2 &&
!mWaitingForTransaction &&
!mTestControllingRefreshes) {
mWaitingForTransaction = true;
mSkippedPaints = false;
}
return mPendingTransaction;
}
void
nsRefreshDriver::RevokeTransactionId(uint64_t aTransactionId)
{
MOZ_ASSERT(aTransactionId == mPendingTransaction);
if (mPendingTransaction == mCompletedTransaction + 2 &&
mWaitingForTransaction) {
MOZ_ASSERT(!mSkippedPaints, "How did we skip a paint when we're in the middle of one?");
FinishedWaitingForTransaction();
}
mPendingTransaction--;
}
mozilla::TimeStamp
nsRefreshDriver::GetTransactionStart()
{
return mTickStart;
}
void
nsRefreshDriver::NotifyTransactionCompleted(uint64_t aTransactionId)
{
if (aTransactionId > mCompletedTransaction) {
if (mPendingTransaction > mCompletedTransaction + 1 &&
mWaitingForTransaction) {
mCompletedTransaction = aTransactionId;
FinishedWaitingForTransaction();
} else {
mCompletedTransaction = aTransactionId;
}
}
}
void
nsRefreshDriver::WillRefresh(mozilla::TimeStamp aTime)
{
mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
mRootRefresh = nullptr;
if (mSkippedPaints) {
DoRefresh();
}
}
bool
nsRefreshDriver::IsWaitingForPaint(mozilla::TimeStamp aTime)
{
if (mTestControllingRefreshes) {
return false;
}
// If we've skipped too many ticks then it's possible
// that something went wrong and we're waiting on
// a notification that will never arrive.
if (aTime > (mMostRecentTick + TimeDuration::FromMilliseconds(200))) {
mSkippedPaints = false;
mWaitingForTransaction = false;
if (mRootRefresh) {
mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
}
return false;
}
if (mWaitingForTransaction) {
mSkippedPaints = true;
return true;
}
// Try find the 'root' refresh driver for the current window and check
// if that is waiting for a paint.
nsPresContext *rootContext = PresContext()->GetRootPresContext();
if (rootContext) {
nsRefreshDriver *rootRefresh = rootContext->RefreshDriver();
if (rootRefresh && rootRefresh != this) {
if (rootRefresh->IsWaitingForPaint(aTime)) {
if (mRootRefresh != rootRefresh) {
if (mRootRefresh) {
mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
}
rootRefresh->AddRefreshObserver(this, Flush_Style);
mRootRefresh = rootRefresh;
}
mSkippedPaints = true;
return true;
}
}
}
return false;
}
void
nsRefreshDriver::SetThrottled(bool aThrottled)
{
if (aThrottled != mThrottled) {
mThrottled = aThrottled;
if (mActiveTimer) {
// We want to switch our timer type here, so just stop and
// restart the timer.
EnsureTimerStarted(eAdjustingTimer);
}
}
}
/*static*/ void
nsRefreshDriver::PVsyncActorCreated(VsyncChild* aVsyncChild)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!XRE_IsParentProcess());
VsyncRefreshDriverTimer* vsyncRefreshDriverTimer =
new VsyncRefreshDriverTimer(aVsyncChild);
// If we are using software timer, swap current timer to
// VsyncRefreshDriverTimer.
if (sRegularRateTimer) {
sRegularRateTimer->SwapRefreshDrivers(vsyncRefreshDriverTimer);
delete sRegularRateTimer;
}
sRegularRateTimer = vsyncRefreshDriverTimer;
}
void
nsRefreshDriver::DoRefresh()
{
// Don't do a refresh unless we're in a state where we should be refreshing.
if (!IsFrozen() && mPresContext && mActiveTimer) {
DoTick();
}
}
#ifdef DEBUG
bool
nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver,
mozFlushType aFlushType)
{
ObserverArray& array = ArrayFor(aFlushType);
return array.Contains(aObserver);
}
#endif
void
nsRefreshDriver::ScheduleViewManagerFlush()
{
NS_ASSERTION(mPresContext->IsRoot(),
"Should only schedule view manager flush on root prescontexts");
mViewManagerFlushIsPending = true;
EnsureTimerStarted();
}
void
nsRefreshDriver::ScheduleFrameRequestCallbacks(nsIDocument* aDocument)
{
NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) ==
mFrameRequestCallbackDocs.NoIndex &&
mThrottledFrameRequestCallbackDocs.IndexOf(aDocument) ==
mThrottledFrameRequestCallbackDocs.NoIndex,
"Don't schedule the same document multiple times");
if (aDocument->ShouldThrottleFrameRequests()) {
mThrottledFrameRequestCallbackDocs.AppendElement(aDocument);
} else {
mFrameRequestCallbackDocs.AppendElement(aDocument);
}
// make sure that the timer is running
ConfigureHighPrecision();
EnsureTimerStarted();
}
void
nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument)
{
mFrameRequestCallbackDocs.RemoveElement(aDocument);
mThrottledFrameRequestCallbackDocs.RemoveElement(aDocument);
ConfigureHighPrecision();
// No need to worry about restarting our timer in slack mode if it's already
// running; that will happen automatically when it fires.
}
/* static */ bool
nsRefreshDriver::IsJankCritical()
{
MOZ_ASSERT(NS_IsMainThread());
return sActiveVsyncTimers > 0;
}
#undef LOG