michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Code to notify things that animate before a refresh, at an appropriate michael@0: * refresh rate. (Perhaps temporary, until replaced by compositor.) michael@0: */ michael@0: michael@0: #ifndef nsRefreshDriver_h_ michael@0: #define nsRefreshDriver_h_ michael@0: michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "mozFlushType.h" michael@0: #include "nsTObserverArray.h" michael@0: #include "nsTArray.h" michael@0: #include "nsTHashtable.h" michael@0: #include "nsClassHashtable.h" michael@0: #include "nsHashKeys.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/Maybe.h" michael@0: michael@0: class nsPresContext; michael@0: class nsIPresShell; michael@0: class nsIDocument; michael@0: class imgIRequest; michael@0: class nsIRunnable; michael@0: michael@0: namespace mozilla { michael@0: class RefreshDriverTimer; michael@0: } michael@0: michael@0: /** michael@0: * An abstract base class to be implemented by callers wanting to be michael@0: * notified at refresh times. When nothing needs to be painted, callers michael@0: * may not be notified. michael@0: */ michael@0: class nsARefreshObserver { michael@0: public: michael@0: // AddRef and Release signatures that match nsISupports. Implementors michael@0: // must implement reference counting, and those that do implement michael@0: // nsISupports will already have methods with the correct signature. michael@0: // michael@0: // The refresh driver does NOT hold references to refresh observers michael@0: // except while it is notifying them. michael@0: NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; michael@0: NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; michael@0: michael@0: virtual void WillRefresh(mozilla::TimeStamp aTime) = 0; michael@0: }; michael@0: michael@0: /** michael@0: * An abstract base class to be implemented by callers wanting to be notified michael@0: * that a refresh has occurred. Callers must ensure an observer is removed michael@0: * before it is destroyed. michael@0: */ michael@0: class nsAPostRefreshObserver { michael@0: public: michael@0: virtual void DidRefresh() = 0; michael@0: }; michael@0: michael@0: class nsRefreshDriver MOZ_FINAL : public nsISupports { michael@0: public: michael@0: nsRefreshDriver(nsPresContext *aPresContext); michael@0: ~nsRefreshDriver(); michael@0: michael@0: static void InitializeStatics(); michael@0: static void Shutdown(); michael@0: michael@0: // nsISupports implementation michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: /** michael@0: * Methods for testing, exposed via nsIDOMWindowUtils. See michael@0: * nsIDOMWindowUtils.advanceTimeAndRefresh for description. michael@0: */ michael@0: void AdvanceTimeAndRefresh(int64_t aMilliseconds); michael@0: void RestoreNormalRefresh(); michael@0: void DoTick(); michael@0: bool IsTestControllingRefreshesEnabled() const michael@0: { michael@0: return mTestControllingRefreshes; michael@0: } michael@0: michael@0: /** michael@0: * Return the time of the most recent refresh. This is intended to be michael@0: * used by callers who want to start an animation now and want to know michael@0: * what time to consider the start of the animation. (This helps michael@0: * ensure that multiple animations started during the same event off michael@0: * the main event loop have the same start time.) michael@0: */ michael@0: mozilla::TimeStamp MostRecentRefresh() const; michael@0: /** michael@0: * Same thing, but in microseconds since the epoch. michael@0: */ michael@0: int64_t MostRecentRefreshEpochTime() const; michael@0: michael@0: /** michael@0: * Add / remove refresh observers. Returns whether the operation michael@0: * succeeded. michael@0: * michael@0: * The flush type affects: michael@0: * + the order in which the observers are notified (lowest flush michael@0: * type to highest, in order registered) michael@0: * + (in the future) which observers are suppressed when the display michael@0: * doesn't require current position data or isn't currently michael@0: * painting, and, correspondingly, which get notified when there michael@0: * is a flush during such suppression michael@0: * and it must be either Flush_Style, Flush_Layout, or Flush_Display. michael@0: * michael@0: * The refresh driver does NOT own a reference to these observers; michael@0: * they must remove themselves before they are destroyed. michael@0: * michael@0: * The observer will be called even if there is no other activity. michael@0: */ michael@0: bool AddRefreshObserver(nsARefreshObserver *aObserver, michael@0: mozFlushType aFlushType); michael@0: bool RemoveRefreshObserver(nsARefreshObserver *aObserver, michael@0: mozFlushType aFlushType); michael@0: michael@0: /** michael@0: * Add an observer that will be called after each refresh. The caller michael@0: * must remove the observer before it is deleted. This does not trigger michael@0: * refresh driver ticks. michael@0: */ michael@0: void AddPostRefreshObserver(nsAPostRefreshObserver *aObserver); michael@0: void RemovePostRefreshObserver(nsAPostRefreshObserver *aObserver); michael@0: michael@0: /** michael@0: * Add/Remove imgIRequest versions of observers. michael@0: * michael@0: * These are used for hooking into the refresh driver for michael@0: * controlling animated images. michael@0: * michael@0: * @note The refresh driver owns a reference to these listeners. michael@0: * michael@0: * @note Technically, imgIRequest objects are not nsARefreshObservers, but michael@0: * for controlling animated image repaint events, we subscribe the michael@0: * imgIRequests to the nsRefreshDriver for notification of paint events. michael@0: * michael@0: * @returns whether the operation succeeded, or void in the case of removal. michael@0: */ michael@0: bool AddImageRequest(imgIRequest* aRequest); michael@0: void RemoveImageRequest(imgIRequest* aRequest); michael@0: michael@0: /** michael@0: * Add / remove presshells that we should flush style and layout on michael@0: */ michael@0: bool AddStyleFlushObserver(nsIPresShell* aShell) { michael@0: NS_ASSERTION(!mStyleFlushObservers.Contains(aShell), michael@0: "Double-adding style flush observer"); michael@0: bool appended = mStyleFlushObservers.AppendElement(aShell) != nullptr; michael@0: EnsureTimerStarted(false); michael@0: return appended; michael@0: } michael@0: void RemoveStyleFlushObserver(nsIPresShell* aShell) { michael@0: mStyleFlushObservers.RemoveElement(aShell); michael@0: } michael@0: bool AddLayoutFlushObserver(nsIPresShell* aShell) { michael@0: NS_ASSERTION(!IsLayoutFlushObserver(aShell), michael@0: "Double-adding layout flush observer"); michael@0: bool appended = mLayoutFlushObservers.AppendElement(aShell) != nullptr; michael@0: EnsureTimerStarted(false); michael@0: return appended; michael@0: } michael@0: void RemoveLayoutFlushObserver(nsIPresShell* aShell) { michael@0: mLayoutFlushObservers.RemoveElement(aShell); michael@0: } michael@0: bool IsLayoutFlushObserver(nsIPresShell* aShell) { michael@0: return mLayoutFlushObservers.Contains(aShell); michael@0: } michael@0: bool AddPresShellToInvalidateIfHidden(nsIPresShell* aShell) { michael@0: NS_ASSERTION(!mPresShellsToInvalidateIfHidden.Contains(aShell), michael@0: "Double-adding style flush observer"); michael@0: bool appended = mPresShellsToInvalidateIfHidden.AppendElement(aShell) != nullptr; michael@0: EnsureTimerStarted(false); michael@0: return appended; michael@0: } michael@0: void RemovePresShellToInvalidateIfHidden(nsIPresShell* aShell) { michael@0: mPresShellsToInvalidateIfHidden.RemoveElement(aShell); michael@0: } michael@0: michael@0: /** michael@0: * Remember whether our presshell's view manager needs a flush michael@0: */ michael@0: void ScheduleViewManagerFlush(); michael@0: void RevokeViewManagerFlush() { michael@0: mViewManagerFlushIsPending = false; michael@0: } michael@0: bool ViewManagerFlushIsPending() { michael@0: return mViewManagerFlushIsPending; michael@0: } michael@0: michael@0: /** michael@0: * Add a document for which we have nsIFrameRequestCallbacks michael@0: */ michael@0: void ScheduleFrameRequestCallbacks(nsIDocument* aDocument); michael@0: michael@0: /** michael@0: * Remove a document for which we have nsIFrameRequestCallbacks michael@0: */ michael@0: void RevokeFrameRequestCallbacks(nsIDocument* aDocument); michael@0: michael@0: /** michael@0: * Tell the refresh driver that it is done driving refreshes and michael@0: * should stop its timer and forget about its pres context. This may michael@0: * be called from within a refresh. michael@0: */ michael@0: void Disconnect() { michael@0: StopTimer(); michael@0: mPresContext = nullptr; michael@0: } michael@0: michael@0: bool IsFrozen() { return mFreezeCount > 0; } michael@0: michael@0: /** michael@0: * Freeze the refresh driver. It should stop delivering future michael@0: * refreshes until thawed. Note that the number of calls to Freeze() must michael@0: * match the number of calls to Thaw() in order for the refresh driver to michael@0: * be un-frozen. michael@0: */ michael@0: void Freeze(); michael@0: michael@0: /** michael@0: * Thaw the refresh driver. If the number of calls to Freeze() matches the michael@0: * number of calls to this function, the refresh driver should start michael@0: * delivering refreshes again. michael@0: */ michael@0: void Thaw(); michael@0: michael@0: /** michael@0: * Throttle or unthrottle the refresh driver. This is done if the michael@0: * corresponding presshell is hidden or shown. michael@0: */ michael@0: void SetThrottled(bool aThrottled); michael@0: michael@0: /** michael@0: * Return the prescontext we were initialized with michael@0: */ michael@0: nsPresContext* PresContext() const { return mPresContext; } michael@0: michael@0: #ifdef DEBUG michael@0: /** michael@0: * Check whether the given observer is an observer for the given flush type michael@0: */ michael@0: bool IsRefreshObserver(nsARefreshObserver *aObserver, michael@0: mozFlushType aFlushType); michael@0: #endif michael@0: michael@0: /** michael@0: * Default interval the refresh driver uses, in ms. michael@0: */ michael@0: static int32_t DefaultInterval(); michael@0: michael@0: bool IsInRefresh() { return mInRefresh; } michael@0: michael@0: private: michael@0: typedef nsTObserverArray ObserverArray; michael@0: typedef nsTHashtable RequestTable; michael@0: struct ImageStartData { michael@0: ImageStartData() michael@0: { michael@0: } michael@0: michael@0: mozilla::Maybe mStartTime; michael@0: RequestTable mEntries; michael@0: }; michael@0: typedef nsClassHashtable ImageStartTable; michael@0: michael@0: void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime); michael@0: michael@0: void EnsureTimerStarted(bool aAdjustingTimer); michael@0: void StopTimer(); michael@0: michael@0: uint32_t ObserverCount() const; michael@0: uint32_t ImageRequestCount() const; michael@0: static PLDHashOperator ImageRequestEnumerator(nsISupportsHashKey* aEntry, michael@0: void* aUserArg); michael@0: static PLDHashOperator StartTableRequestCounter(const uint32_t& aKey, michael@0: ImageStartData* aEntry, michael@0: void* aUserArg); michael@0: static PLDHashOperator StartTableRefresh(const uint32_t& aKey, michael@0: ImageStartData* aEntry, michael@0: void* aUserArg); michael@0: static PLDHashOperator BeginRefreshingImages(nsISupportsHashKey* aEntry, michael@0: void* aUserArg); michael@0: ObserverArray& ArrayFor(mozFlushType aFlushType); michael@0: // Trigger a refresh immediately, if haven't been disconnected or frozen. michael@0: void DoRefresh(); michael@0: michael@0: double GetRefreshTimerInterval() const; michael@0: double GetRegularTimerInterval(bool *outIsDefault = nullptr) const; michael@0: double GetThrottledTimerInterval() const; michael@0: michael@0: bool HaveFrameRequestCallbacks() const { michael@0: return mFrameRequestCallbackDocs.Length() != 0; michael@0: } michael@0: michael@0: mozilla::RefreshDriverTimer* ChooseTimer() const; michael@0: mozilla::RefreshDriverTimer *mActiveTimer; michael@0: michael@0: nsPresContext *mPresContext; // weak; pres context passed in constructor michael@0: // and unset in Disconnect michael@0: michael@0: uint32_t mFreezeCount; michael@0: bool mThrottled; michael@0: bool mTestControllingRefreshes; michael@0: bool mViewManagerFlushIsPending; michael@0: bool mRequestedHighPrecision; michael@0: bool mInRefresh; michael@0: michael@0: int64_t mMostRecentRefreshEpochTime; michael@0: mozilla::TimeStamp mMostRecentRefresh; michael@0: michael@0: // separate arrays for each flush type we support michael@0: ObserverArray mObservers[3]; michael@0: RequestTable mRequests; michael@0: ImageStartTable mStartTable; michael@0: michael@0: nsAutoTArray mStyleFlushObservers; michael@0: nsAutoTArray mLayoutFlushObservers; michael@0: nsAutoTArray mPresShellsToInvalidateIfHidden; michael@0: // nsTArray on purpose, because we want to be able to swap. michael@0: nsTArray mFrameRequestCallbackDocs; michael@0: nsTArray mPostRefreshObservers; michael@0: michael@0: // Helper struct for processing image requests michael@0: struct ImageRequestParameters { michael@0: mozilla::TimeStamp mCurrent; michael@0: mozilla::TimeStamp mPrevious; michael@0: RequestTable* mRequests; michael@0: mozilla::TimeStamp mDesired; michael@0: }; michael@0: michael@0: friend class mozilla::RefreshDriverTimer; michael@0: michael@0: // turn on or turn off high precision based on various factors michael@0: void ConfigureHighPrecision(); michael@0: void SetHighPrecisionTimersEnabled(bool aEnable); michael@0: }; michael@0: michael@0: #endif /* !defined(nsRefreshDriver_h_) */