Bug 1420298 'layout.display-list.retain.verify' to debug retained-dl - r=mattwoodrow
Setting the 'layout.display-list.retain.verify' gfxPrefs implies
'layout.display-list.build-twice', and then compares the retained-built tree
to the non-retained one, and outputs differences&trees to the terminal.
MozReview-Commit-ID: 3dnyIUTbtH8
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -645,16 +645,17 @@ private:
DECL_GFX_PREF(Live, "layout.css.scroll-behavior.spring-constant", ScrollBehaviorSpringConstant, float, 250.0f);
DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-max-velocity", ScrollSnapPredictionMaxVelocity, int32_t, 2000);
DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-sensitivity", ScrollSnapPredictionSensitivity, float, 0.750f);
DECL_GFX_PREF(Live, "layout.css.scroll-snap.proximity-threshold", ScrollSnapProximityThreshold, int32_t, 200);
DECL_GFX_PREF(Live, "layout.css.touch_action.enabled", TouchActionEnabled, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.build-twice", LayoutDisplayListBuildTwice, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.retain", LayoutRetainDisplayList, bool, true);
+ DECL_GFX_PREF(Live, "layout.display-list.retain.verify", LayoutVerifyRetainDisplayList, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.rebuild-frame-limit", LayoutRebuildFrameLimit, uint32_t, 500);
DECL_GFX_PREF(Live, "layout.display-list.dump", LayoutDumpDisplayList, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.dump-content", LayoutDumpDisplayListContent, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.dump-parent", LayoutDumpDisplayListParent, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.show-rebuild-area", LayoutDisplayListShowArea, bool, false);
DECL_GFX_PREF(Live, "layout.event-regions.enabled", LayoutEventRegionsEnabledDoNotUseDirectly, bool, false);
DECL_GFX_PREF(Once, "layout.frame_rate", LayoutFrameRate, int32_t, -1);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -123,16 +123,17 @@
#include "mozilla/StyleSetHandleInlines.h"
#include "RegionBuilder.h"
#include "SVGViewportElement.h"
#include "DisplayItemClip.h"
#include "mozilla/layers/StackingContextHelper.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "prenv.h"
#include "RetainedDisplayListBuilder.h"
+#include "DisplayListChecker.h"
#include "TextDrawTarget.h"
#include "nsDeckFrame.h"
#include "nsIEffectiveTLDService.h" // for IsInStyloBlocklist
#include "mozilla/StylePrefs.h"
#ifdef MOZ_XUL
#include "nsXULPopupManager.h"
#endif
@@ -3840,26 +3841,36 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(&builder, id);
builder.SetVisibleRect(visibleRect);
builder.SetIsBuilding(true);
builder.SetAncestorHasApzAwareEventHandler(
builder.IsBuildingLayerEventRegions() &&
nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell));
+ DisplayListChecker beforeMergeChecker;
+ DisplayListChecker afterMergeChecker;
+
// Attempt to do a partial build and merge into the existing list.
// This calls BuildDisplayListForStacking context on a subset of the
// viewport.
bool merged = false;
if (useRetainedBuilder) {
+ if (gfxPrefs::LayoutVerifyRetainDisplayList()) {
+ beforeMergeChecker.Set(&list, "BM");
+ }
merged = retainedBuilder->AttemptPartialUpdate(aBackstop);
+ if (merged && beforeMergeChecker) {
+ afterMergeChecker.Set(&list, "AM");
+ }
}
- if (merged && gfxPrefs::LayoutDisplayListBuildTwice()) {
+ if (merged &&
+ (gfxPrefs::LayoutDisplayListBuildTwice() || afterMergeChecker)) {
merged = false;
if (gfxPrefs::LayersDrawFPS()) {
if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) {
pt->dl2Ms() = (TimeStamp::Now() - dlStart).ToMilliseconds();
}
}
}
@@ -3870,16 +3881,32 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
list.DeleteAll(&builder);
builder.EnterPresShell(aFrame);
builder.SetDirtyRect(visibleRect);
builder.ClearWindowDraggingRegion();
aFrame->BuildDisplayListForStackingContext(&builder, &list);
AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion, aBackstop);
builder.LeavePresShell(aFrame, &list);
+
+ if (afterMergeChecker) {
+ DisplayListChecker nonRetainedChecker(&list, "NR");
+ std::stringstream ss;
+ ss << "**** Differences between retained-after-merged (AM) and "
+ << "non-retained (NR) display lists:";
+ if (!nonRetainedChecker.CompareList(afterMergeChecker, ss)) {
+ ss << "\n\n*** non-retained display items:";
+ nonRetainedChecker.Dump(ss);
+ ss << "\n\n*** before-merge retained display items:";
+ beforeMergeChecker.Dump(ss);
+ ss << "\n\n*** after-merge retained display items:";
+ afterMergeChecker.Dump(ss);
+ fprintf(stderr, "%s\n\n", ss.str().c_str());
+ }
+ }
}
}
builder.SetIsBuilding(false);
builder.IncrementPresShellPaintCount(presShell);
if (gfxPrefs::LayersDrawFPS()) {
if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
new file mode 100644
--- /dev/null
+++ b/layout/painting/DisplayListChecker.cpp
@@ -0,0 +1,371 @@
+/* -*- 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/. */
+
+#include "DisplayListChecker.h"
+
+#include "nsDisplayList.h"
+
+namespace mozilla {
+
+class DisplayItemBlueprint;
+
+// Stack node used during tree visits, to store the path to a display item.
+struct DisplayItemBlueprintStack
+{
+ const DisplayItemBlueprintStack* mPrevious;
+ const DisplayItemBlueprint* mItem;
+ // Output stack to aSs, with format "name#index > ... > name#index".
+ // Returns true if anything was output, false if empty.
+ bool Output(std::stringstream& aSs) const;
+};
+
+// Object representing a list of display items (either the top of the tree, or
+// an item's children), with just enough information to compare with another
+// tree and output useful debugging information.
+class DisplayListBlueprint
+{
+public:
+ DisplayListBlueprint(nsDisplayList* aList, const char* aName)
+ : DisplayListBlueprint(aList, 0, aName)
+ {
+ }
+
+ DisplayListBlueprint(nsDisplayList* aList,
+ const char* aName,
+ unsigned& aIndex)
+ {
+ processChildren(aList, aName, aIndex);
+ }
+
+ // Find a display item with the given frame and per-frame key.
+ // Returns empty string if not found.
+ std::string Find(const nsIFrame* aFrame, uint32_t aPerFrameKey) const
+ {
+ const DisplayItemBlueprintStack stack{ nullptr, nullptr };
+ return Find(aFrame, aPerFrameKey, stack);
+ }
+
+ std::string Find(const nsIFrame* aFrame,
+ uint32_t aPerFrameKey,
+ const DisplayItemBlueprintStack& aStack) const;
+
+ // Compare this list with another one, output differences between the two
+ // into aDiff.
+ // Differences include: Display items from one tree for which a corresponding
+ // item (same frame and per-frame key) cannot be found under corresponding
+ // parent items.
+ // Returns true if trees are similar, false if different.
+ bool CompareList(const DisplayListBlueprint& aOther,
+ std::stringstream& aDiff) const
+ {
+ const DisplayItemBlueprintStack stack{ nullptr, nullptr };
+ const bool ab = CompareList(*this, aOther, aOther, aDiff, stack, stack);
+ const bool ba =
+ aOther.CompareList(aOther, *this, *this, aDiff, stack, stack);
+ return ab && ba;
+ }
+
+ bool CompareList(const DisplayListBlueprint& aRoot,
+ const DisplayListBlueprint& aOther,
+ const DisplayListBlueprint& aOtherRoot,
+ std::stringstream& aDiff,
+ const DisplayItemBlueprintStack& aStack,
+ const DisplayItemBlueprintStack& aStackOther) const;
+
+ // Output this tree to aSs.
+ void Dump(std::stringstream& aSs) const { Dump(aSs, 0); }
+
+ void Dump(std::stringstream& aSs, unsigned aDepth) const;
+
+private:
+ // Only used by first constructor, to call the 2nd constructor with an index
+ // variable on the stack.
+ DisplayListBlueprint(nsDisplayList* aList, unsigned aIndex, const char* aName)
+ : DisplayListBlueprint(aList, aName, aIndex)
+ {
+ }
+
+ void processChildren(nsDisplayList* aList,
+ const char* aName,
+ unsigned& aIndex);
+
+ std::vector<DisplayItemBlueprint> mItems;
+};
+
+// Object representing one display item, with just enough information to
+// compare with another item and output useful debugging information.
+class DisplayItemBlueprint
+{
+public:
+ DisplayItemBlueprint(nsDisplayItem& aItem,
+ const char* aName,
+ unsigned& aIndex)
+ : mListName(aName)
+ , mIndex(++aIndex)
+ , mIndexString(WriteIndex(aName, aIndex))
+ , mIndexStringFW(WriteIndexFW(aName, aIndex))
+ , mDisplayItemPointer(WriteDisplayItemPointer(aItem))
+ , mDescription(WriteDescription(aName, aIndex, aItem))
+ , mFrame(aItem.HasDeletedFrame() ? nullptr : aItem.Frame())
+ , mPerFrameKey(aItem.GetPerFrameKey())
+ , mChildren(aItem.GetChildren(), aName, aIndex)
+ {
+ }
+
+ // Compare this item with another one, based on frame and per-frame key.
+ // Not recursive! I.e., children are not examined.
+ bool CompareItem(const DisplayItemBlueprint& aOther,
+ std::stringstream& aDiff) const
+ {
+ return mFrame == aOther.mFrame && mPerFrameKey == aOther.mPerFrameKey;
+ }
+
+ void Dump(std::stringstream& aSs, unsigned aDepth) const;
+
+ const char* mListName;
+ const unsigned mIndex;
+ const std::string mIndexString;
+ const std::string mIndexStringFW;
+ const std::string mDisplayItemPointer;
+ const std::string mDescription;
+
+ // For pointer comparison only, do not dereference!
+ const nsIFrame* const mFrame;
+ const uint32_t mPerFrameKey;
+
+ const DisplayListBlueprint mChildren;
+
+private:
+ static std::string WriteIndex(const char* aName, unsigned aIndex)
+ {
+ return nsPrintfCString("%s#%u", aName, aIndex).get();
+ }
+
+ static std::string WriteIndexFW(const char* aName, unsigned aIndex)
+ {
+ return nsPrintfCString("%s#%4u", aName, aIndex).get();
+ }
+
+ static std::string WriteDisplayItemPointer(nsDisplayItem& aItem)
+ {
+ return nsPrintfCString("0x%p", &aItem).get();
+ }
+
+ static std::string WriteDescription(const char* aName,
+ unsigned aIndex,
+ nsDisplayItem& aItem)
+ {
+ if (aItem.HasDeletedFrame()) {
+ return nsPrintfCString(
+ "%s %s#%u 0x%p f=0x0", aItem.Name(), aName, aIndex, &aItem)
+ .get();
+ }
+
+ const nsIFrame* f = aItem.Frame();
+ nsAutoString contentData;
+#ifdef DEBUG_FRAME_DUMP
+ f->GetFrameName(contentData);
+#endif
+ nsIContent* content = f->GetContent();
+ if (content) {
+ nsString tmp;
+ if (content->GetID()) {
+ content->GetID()->ToString(tmp);
+ contentData.AppendLiteral(" id:");
+ contentData.Append(tmp);
+ }
+ const nsAttrValue* classes =
+ content->IsElement() ? content->AsElement()->GetClasses() : nullptr;
+ if (classes) {
+ classes->ToString(tmp);
+ contentData.AppendLiteral(" class:");
+ contentData.Append(tmp);
+ }
+ }
+ return nsPrintfCString("%s %s#%u p=0x%p f=0x%p(%s) key=%" PRIu32,
+ aItem.Name(),
+ aName,
+ aIndex,
+ &aItem,
+ f,
+ NS_ConvertUTF16toUTF8(contentData).get(),
+ aItem.GetPerFrameKey())
+ .get();
+ }
+};
+
+void
+DisplayListBlueprint::processChildren(nsDisplayList* aList,
+ const char* aName,
+ unsigned& aIndex)
+{
+ if (!aList) {
+ return;
+ }
+ const uint32_t n = aList->Count();
+ if (n == 0) {
+ return;
+ }
+ mItems.reserve(n);
+ for (nsDisplayItem* item = aList->GetBottom(); item;
+ item = item->GetAbove()) {
+ mItems.emplace_back(*item, aName, aIndex);
+ }
+ MOZ_ASSERT(mItems.size() == n);
+}
+
+bool
+DisplayItemBlueprintStack::Output(std::stringstream& aSs) const
+{
+ const bool output = mPrevious ? mPrevious->Output(aSs) : false;
+ if (mItem) {
+ if (output) {
+ aSs << " > ";
+ }
+ aSs << mItem->mIndexString;
+ return true;
+ }
+ return output;
+}
+
+std::string
+DisplayListBlueprint::Find(const nsIFrame* aFrame,
+ uint32_t aPerFrameKey,
+ const DisplayItemBlueprintStack& aStack) const
+{
+ for (const DisplayItemBlueprint& item : mItems) {
+ if (item.mFrame == aFrame && item.mPerFrameKey == aPerFrameKey) {
+ std::stringstream ss;
+ if (aStack.Output(ss)) {
+ ss << " > ";
+ }
+ ss << item.mDescription;
+ return ss.str();
+ }
+ const DisplayItemBlueprintStack stack = { &aStack, &item };
+ std::string s = item.mChildren.Find(aFrame, aPerFrameKey, stack);
+ if (!s.empty()) {
+ return s;
+ }
+ }
+ return "";
+}
+
+bool
+DisplayListBlueprint::CompareList(
+ const DisplayListBlueprint& aRoot,
+ const DisplayListBlueprint& aOther,
+ const DisplayListBlueprint& aOtherRoot,
+ std::stringstream& aDiff,
+ const DisplayItemBlueprintStack& aStack,
+ const DisplayItemBlueprintStack& aStackOther) const
+{
+ bool same = true;
+ for (const DisplayItemBlueprint& itemBefore : mItems) {
+ bool found = false;
+ for (const DisplayItemBlueprint& itemAfter : aOther.mItems) {
+ if (itemBefore.CompareItem(itemAfter, aDiff)) {
+ found = true;
+
+ const DisplayItemBlueprintStack stack = { &aStack, &itemBefore };
+ const DisplayItemBlueprintStack stackOther = { &aStackOther,
+ &itemAfter };
+ if (!itemBefore.mChildren.CompareList(aRoot,
+ itemAfter.mChildren,
+ aOtherRoot,
+ aDiff,
+ stack,
+ stackOther)) {
+ same = false;
+ }
+ break;
+ }
+ }
+ if (!found) {
+ same = false;
+ aDiff << "\n";
+ if (aStack.Output(aDiff)) {
+ aDiff << " > ";
+ }
+ aDiff << itemBefore.mDescription;
+ aDiff << "\n * Cannot find corresponding item under ";
+ if (!aStackOther.Output(aDiff)) {
+ if (!aOtherRoot.mItems.empty()) {
+ aDiff << aOtherRoot.mItems[0].mListName;
+ } else {
+ aDiff << "other root";
+ }
+ }
+ std::string elsewhere =
+ aOtherRoot.Find(itemBefore.mFrame, itemBefore.mPerFrameKey);
+ if (!elsewhere.empty()) {
+ aDiff << "\n * But found: " << elsewhere;
+ }
+ }
+ }
+ return same;
+}
+
+void
+DisplayListBlueprint::Dump(std::stringstream& aSs, unsigned aDepth) const
+{
+ for (const DisplayItemBlueprint& item : mItems) {
+ item.Dump(aSs, aDepth);
+ }
+}
+
+void
+DisplayItemBlueprint::Dump(std::stringstream& aSs, unsigned aDepth) const
+{
+ aSs << "\n" << mIndexStringFW << " ";
+ for (unsigned i = 0; i < aDepth; ++i) {
+ aSs << " ";
+ }
+ aSs << mDescription;
+ mChildren.Dump(aSs, aDepth + 1);
+}
+
+DisplayListChecker::DisplayListChecker()
+ : mBlueprint(nullptr)
+{
+}
+
+DisplayListChecker::DisplayListChecker(nsDisplayList* aList, const char* aName)
+ : mBlueprint(MakeUnique<DisplayListBlueprint>(aList, aName))
+{
+}
+
+DisplayListChecker::~DisplayListChecker() = default;
+
+void
+DisplayListChecker::Set(nsDisplayList* aList, const char* aName)
+{
+ mBlueprint = MakeUnique<DisplayListBlueprint>(aList, aName);
+}
+
+// Compare this list with another one, output differences between the two
+// into aDiff.
+// Differences include: Display items from one tree for which a corresponding
+// item (same frame and per-frame key) cannot be found under corresponding
+// parent items.
+// Returns true if trees are similar, false if different.
+bool
+DisplayListChecker::CompareList(const DisplayListChecker& aOther,
+ std::stringstream& aDiff) const
+{
+ MOZ_ASSERT(mBlueprint);
+ MOZ_ASSERT(aOther.mBlueprint);
+ return mBlueprint->CompareList(*aOther.mBlueprint, aDiff);
+}
+
+void
+DisplayListChecker::Dump(std::stringstream& aSs) const
+{
+ MOZ_ASSERT(mBlueprint);
+ mBlueprint->Dump(aSs);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/painting/DisplayListChecker.h
@@ -0,0 +1,49 @@
+/* -*- 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 DisplayListChecker_h__
+#define DisplayListChecker_h__
+
+#include <sstream>
+#include <mozilla/UniquePtr.h>
+
+class nsDisplayList;
+
+namespace mozilla {
+
+class DisplayListBlueprint;
+
+class DisplayListChecker
+{
+public:
+ DisplayListChecker();
+ DisplayListChecker(nsDisplayList* aList, const char* aName);
+
+ ~DisplayListChecker();
+
+ void Set(nsDisplayList* aList, const char* aName);
+
+ explicit operator bool() const { return mBlueprint.get(); }
+
+ // Compare this list with another one, output differences between the two
+ // into aDiff.
+ // Differences include: Display items from one tree for which a corresponding
+ // item (same frame and per-frame key) cannot be found under corresponding
+ // parent items.
+ // Returns true if trees are similar, false if different.
+ bool CompareList(const DisplayListChecker& aOther,
+ std::stringstream& aDiff) const;
+
+ // Output this tree to aSs.
+ void Dump(std::stringstream& aSs) const;
+
+private:
+ UniquePtr<DisplayListBlueprint> mBlueprint;
+};
+
+} // namespace mozilla
+
+#endif // DisplayListChecker_h__
--- a/layout/painting/moz.build
+++ b/layout/painting/moz.build
@@ -30,16 +30,17 @@ EXPORTS.mozilla += [
]
UNIFIED_SOURCES += [
'ActiveLayerTracker.cpp',
'DashedCornerFinder.cpp',
'DisplayItemClip.cpp',
'DisplayItemClipChain.cpp',
'DisplayItemScrollClip.cpp',
+ 'DisplayListChecker.cpp',
'DisplayListClipState.cpp',
'DottedCornerFinder.cpp',
'FrameLayerBuilder.cpp',
'MaskLayerImageCache.cpp',
'nsCSSRendering.cpp',
'nsCSSRenderingBorders.cpp',
'nsCSSRenderingGradients.cpp',
'nsDisplayList.cpp',