/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #ifndef RETAINEDDISPLAYLISTBUILDER_H_ #define RETAINEDDISPLAYLISTBUILDER_H_ #include "nsDisplayList.h" #include "mozilla/Maybe.h" #include "mozilla/EnumSet.h" class nsWindowSizes; namespace mozilla { class nsDisplayItem; class nsDisplayList; /** * RetainedDisplayListData contains frame invalidation information. * Currently this is implemented as a map of frame pointers to flags. */ struct RetainedDisplayListData { enum class FrameFlag : uint8_t { Modified, HasProps, HadWillChange }; using FrameFlags = mozilla::EnumSet; RetainedDisplayListData(); /** * Adds the frame to modified frames list. */ void AddModifiedFrame(nsIFrame* aFrame); /** * Removes all the frames from this RetainedDisplayListData. */ void Clear() { mFrames.Clear(); mModifiedFrameCount = 0; } /** * Returns a mutable reference to flags set for the given |aFrame|. */ FrameFlags& Flags(nsIFrame* aFrame) { return mFrames.LookupOrInsert(aFrame); } /** * Returns flags set for the given |aFrame|, or FrameFlags::None if the frame * is not in this RetainedDisplayListData. */ FrameFlags GetFlags(nsIFrame* aFrame) const { return mFrames.Get(aFrame); } bool IsModified(nsIFrame* aFrame) const { return GetFlags(aFrame).contains(FrameFlag::Modified); } bool HasProps(nsIFrame* aFrame) const { return GetFlags(aFrame).contains(FrameFlag::HasProps); } bool HadWillChange(nsIFrame* aFrame) const { return GetFlags(aFrame).contains(FrameFlag::HadWillChange); } /** * Returns an iterator to the underlying frame storage. */ auto ConstIterator() { return mFrames.ConstIter(); } /** * Returns true if the modified frame limit has been reached. */ bool AtModifiedFrameLimit() { return mModifiedFrameCount >= mModifiedFrameLimit; } /** * Removes the given |aFrame| from this RetainedDisplayListData. */ bool Remove(nsIFrame* aFrame) { return mFrames.Remove(aFrame); } private: nsTHashMap, FrameFlags> mFrames; uint32_t mModifiedFrameCount = 0; uint32_t mModifiedFrameLimit; // initialized to a pref value in constructor }; enum class PartialUpdateResult { Failed, NoChange, Updated }; enum class PartialUpdateFailReason { NA, EmptyList, RebuildLimit, FrameType, Disabled, Content, VisibleRect, }; struct RetainedDisplayListMetrics { RetainedDisplayListMetrics() { Reset(); } void Reset() { mNewItems = 0; mRebuiltItems = 0; mRemovedItems = 0; mReusedItems = 0; mTotalItems = 0; mPartialBuildDuration = 0; mFullBuildDuration = 0; mPartialUpdateFailReason = PartialUpdateFailReason::NA; mPartialUpdateResult = PartialUpdateResult::NoChange; } void StartBuild() { mStartTime = mozilla::TimeStamp::Now(); } void EndFullBuild() { mFullBuildDuration = Elapsed(); } void EndPartialBuild(PartialUpdateResult aResult) { mPartialBuildDuration = Elapsed(); mPartialUpdateResult = aResult; } double Elapsed() { return (mozilla::TimeStamp::Now() - mStartTime).ToMilliseconds(); } const char* FailReasonString() const { switch (mPartialUpdateFailReason) { case PartialUpdateFailReason::NA: return "N/A"; case PartialUpdateFailReason::EmptyList: return "Empty list"; case PartialUpdateFailReason::RebuildLimit: return "Rebuild limit"; case PartialUpdateFailReason::FrameType: return "Frame type"; case PartialUpdateFailReason::Disabled: return "Disabled"; case PartialUpdateFailReason::Content: return "Content"; case PartialUpdateFailReason::VisibleRect: return "VisibleRect"; default: MOZ_ASSERT_UNREACHABLE("Enum value not handled!"); } } unsigned int mNewItems; unsigned int mRebuiltItems; unsigned int mRemovedItems; unsigned int mReusedItems; unsigned int mTotalItems; mozilla::TimeStamp mStartTime; double mPartialBuildDuration; double mFullBuildDuration; PartialUpdateFailReason mPartialUpdateFailReason; PartialUpdateResult mPartialUpdateResult; }; class RetainedDisplayListBuilder { public: RetainedDisplayListBuilder(nsIFrame* aReferenceFrame, nsDisplayListBuilderMode aMode, bool aBuildCaret) : mBuilder(aReferenceFrame, aMode, aBuildCaret, true), mList(&mBuilder) {} ~RetainedDisplayListBuilder() { mList.DeleteAll(&mBuilder); } nsDisplayListBuilder* Builder() { return &mBuilder; } nsDisplayList* List() { return &mList; } RetainedDisplayListMetrics* Metrics() { return &mMetrics; } RetainedDisplayListData* Data() { return &mData; } PartialUpdateResult AttemptPartialUpdate(nscolor aBackstop); /** * Clears the modified state for frames in the retained display list data. */ void ClearFramesWithProps(); void ClearRetainedData(); void ClearReuseableDisplayItems() { mBuilder.ClearReuseableDisplayItems(); } void AddSizeOfIncludingThis(nsWindowSizes&) const; NS_DECLARE_FRAME_PROPERTY_DELETABLE(Cached, RetainedDisplayListBuilder) private: void GetModifiedAndFramesWithProps(nsTArray* aOutModifiedFrames, nsTArray* aOutFramesWithProps); void IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem); /** * Invalidates the current and previous caret frame if they have changed. */ void InvalidateCaretFramesIfNeeded(); /** * A simple early exit heuristic to avoid slow partial display list rebuilds. * Returns true if a partial display list build should be attempted. */ bool ShouldBuildPartial(nsTArray& aModifiedFrames); /** * Recursively pre-processes the old display list tree before building the * new partial display lists, and serializes the old list into an array, * recording indices on items for fast lookup during merging. Builds an * initial linear DAG for the list if we don't have an existing one. Finds * items that have a different AGR from the specified one, and marks them to * also be built so that we get relative ordering correct. Passes * aKeepLinked=true internally for sub-lists that can't be changed to keep the * original list structure linked for fast re-use. */ bool PreProcessDisplayList( RetainedDisplayList* aList, nsIFrame* aAGR, PartialUpdateResult& aUpdated, nsIFrame* aAsyncAncestor, const ActiveScrolledRoot* aAsyncAncestorASR, nsIFrame* aOuterFrame = nullptr, uint32_t aCallerKey = 0, uint32_t aNestingDepth = 0, bool aKeepLinked = false); /** * Merges items from aNewList into non-invalidated items from aOldList and * stores the result in aOutList. * * aOuterItem is a pointer to an item that owns one of the lists, if * available. If both lists are populated, then both outer items must not be * invalidated, and identical, so either can be passed here. * * Returns true if changes were made, and the resulting display list (in * aOutList) is different from aOldList. */ bool MergeDisplayLists( nsDisplayList* aNewList, RetainedDisplayList* aOldList, RetainedDisplayList* aOutList, mozilla::Maybe& aOutContainerASR, nsDisplayItem* aOuterItem = nullptr); bool ComputeRebuildRegion(nsTArray& aModifiedFrames, nsRect* aOutDirty, nsIFrame** aOutModifiedAGR, nsTArray& aOutFramesWithProps); bool ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsIFrame* aStopAtFrame, nsTArray& aOutFramesWithProps, const bool aStopAtStackingContext, nsRect* aOutDirty, nsIFrame** aOutModifiedAGR); nsIFrame* RootReferenceFrame() { return mBuilder.RootReferenceFrame(); } /** * Tries to perform a simple partial display list build without display list * merging. In this mode, only the top-level stacking context items and their * contents are reused, when the frame subtree has not been modified. */ bool TrySimpleUpdate(const nsTArray& aModifiedFrames, nsTArray& aOutFramesWithProps); friend class MergeState; nsDisplayListBuilder mBuilder; RetainedDisplayList mList; WeakFrame mPreviousCaret; RetainedDisplayListMetrics mMetrics; RetainedDisplayListData mData; }; namespace RDLUtils { void AssertFrameSubtreeUnmodified(const nsIFrame* aFrame); void AssertDisplayItemUnmodified(nsDisplayItem* aItem); void AssertDisplayListUnmodified(nsDisplayList* aList); } // namespace RDLUtils } // namespace mozilla #endif // RETAINEDDISPLAYLISTBUILDER_H_