Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 */
#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<FrameFlag, uint8_t>;
* Adds the frame to modified frames list.
void AddModifiedFrame(nsIFrame* aFrame);
* Removes all the frames from this RetainedDisplayListData.
void 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); }
nsTHashMap<nsPtrHashKey<nsIFrame>, 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 {
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";
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 {
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;
void GetModifiedAndFramesWithProps(nsTArray<nsIFrame*>* aOutModifiedFrames,
nsTArray<nsIFrame*>* 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<nsIFrame*>& 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<const mozilla::ActiveScrolledRoot*>& aOutContainerASR,
nsDisplayItem* aOuterItem = nullptr);
bool ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedFrames,
nsRect* aOutDirty, nsIFrame** aOutModifiedAGR,
nsTArray<nsIFrame*>& aOutFramesWithProps);
bool ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder* aBuilder,
nsIFrame* aStopAtFrame,
nsTArray<nsIFrame*>& aOutFramesWithProps,
const bool aStopAtStackingContext, nsRect* aOutDirty,
nsIFrame** aOutModifiedAGR);
nsIFrame* RootReferenceFrame() { return mBuilder.RootReferenceFrame(); }
const nsIFrame* RootReferenceFrame() const {
return mBuilder.RootReferenceFrame();
nsRect RootOverflowRect() const;
* 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<nsIFrame*>& aModifiedFrames,
nsTArray<nsIFrame*>& 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