1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "DisplayListChecker.h"
8
9 #include "gfxPrefs.h"
10 #include "nsDisplayList.h"
11
12 namespace mozilla {
13
14 class DisplayItemBlueprint;
15
16 // Stack node used during tree visits, to store the path to a display item.
17 struct DisplayItemBlueprintStack {
18 const DisplayItemBlueprintStack* mPrevious;
19 const DisplayItemBlueprint* mItem;
20 // Output stack to aSs, with format "name#index > ... > name#index".
21 // Returns true if anything was output, false if empty.
22 bool Output(std::stringstream& aSs) const;
23 };
24
25 // Object representing a list of display items (either the top of the tree, or
26 // an item's children), with just enough information to compare with another
27 // tree and output useful debugging information.
28 class DisplayListBlueprint {
29 public:
DisplayListBlueprint(nsDisplayList * aList,const char * aName)30 DisplayListBlueprint(nsDisplayList* aList, const char* aName)
31 : DisplayListBlueprint(aList, 0, aName) {}
32
DisplayListBlueprint(nsDisplayList * aList,const char * aName,unsigned & aIndex)33 DisplayListBlueprint(nsDisplayList* aList, const char* aName,
34 unsigned& aIndex) {
35 processChildren(aList, aName, aIndex);
36 }
37
38 // Find a display item with the given frame and per-frame key.
39 // Returns empty string if not found.
Find(const nsIFrame * aFrame,uint32_t aPerFrameKey) const40 std::string Find(const nsIFrame* aFrame, uint32_t aPerFrameKey) const {
41 const DisplayItemBlueprintStack stack{nullptr, nullptr};
42 return Find(aFrame, aPerFrameKey, stack);
43 }
44
45 std::string Find(const nsIFrame* aFrame, uint32_t aPerFrameKey,
46 const DisplayItemBlueprintStack& aStack) const;
47
48 // Compare this list with another one, output differences between the two
49 // into aDiff.
50 // Differences include: Display items from one tree for which a corresponding
51 // item (same frame and per-frame key) cannot be found under corresponding
52 // parent items.
53 // Returns true if trees are similar, false if different.
CompareList(const DisplayListBlueprint & aOther,std::stringstream & aDiff) const54 bool CompareList(const DisplayListBlueprint& aOther,
55 std::stringstream& aDiff) const {
56 const DisplayItemBlueprintStack stack{nullptr, nullptr};
57 const bool ab = CompareList(*this, aOther, aOther, aDiff, stack, stack);
58 const bool ba =
59 aOther.CompareList(aOther, *this, *this, aDiff, stack, stack);
60 return ab && ba;
61 }
62
63 bool CompareList(const DisplayListBlueprint& aRoot,
64 const DisplayListBlueprint& aOther,
65 const DisplayListBlueprint& aOtherRoot,
66 std::stringstream& aDiff,
67 const DisplayItemBlueprintStack& aStack,
68 const DisplayItemBlueprintStack& aStackOther) const;
69
70 // Output this tree to aSs.
Dump(std::stringstream & aSs) const71 void Dump(std::stringstream& aSs) const { Dump(aSs, 0); }
72
73 void Dump(std::stringstream& aSs, unsigned aDepth) const;
74
75 private:
76 // Only used by first constructor, to call the 2nd constructor with an index
77 // variable on the stack.
DisplayListBlueprint(nsDisplayList * aList,unsigned aIndex,const char * aName)78 DisplayListBlueprint(nsDisplayList* aList, unsigned aIndex, const char* aName)
79 : DisplayListBlueprint(aList, aName, aIndex) {}
80
81 void processChildren(nsDisplayList* aList, const char* aName,
82 unsigned& aIndex);
83
84 std::vector<DisplayItemBlueprint> mItems;
85 const bool mVerifyOrder = gfxPrefs::LayoutVerifyRetainDisplayListOrder();
86 };
87
88 // Object representing one display item, with just enough information to
89 // compare with another item and output useful debugging information.
90 class DisplayItemBlueprint {
91 public:
DisplayItemBlueprint(nsDisplayItem & aItem,const char * aName,unsigned & aIndex)92 DisplayItemBlueprint(nsDisplayItem& aItem, const char* aName,
93 unsigned& aIndex)
94 : mListName(aName),
95 mIndex(++aIndex),
96 mIndexString(WriteIndex(aName, aIndex)),
97 mIndexStringFW(WriteIndexFW(aName, aIndex)),
98 mDisplayItemPointer(WriteDisplayItemPointer(aItem)),
99 mDescription(WriteDescription(aName, aIndex, aItem)),
100 mFrame(aItem.HasDeletedFrame() ? nullptr : aItem.Frame()),
101 mPerFrameKey(aItem.GetPerFrameKey()),
102 mChildren(aItem.GetChildren(), aName, aIndex) {}
103
104 // Compare this item with another one, based on frame and per-frame key.
105 // Not recursive! I.e., children are not examined.
CompareItem(const DisplayItemBlueprint & aOther,std::stringstream & aDiff) const106 bool CompareItem(const DisplayItemBlueprint& aOther,
107 std::stringstream& aDiff) const {
108 return mFrame == aOther.mFrame && mPerFrameKey == aOther.mPerFrameKey;
109 }
110
111 void Dump(std::stringstream& aSs, unsigned aDepth) const;
112
113 const char* mListName;
114 const unsigned mIndex;
115 const std::string mIndexString;
116 const std::string mIndexStringFW;
117 const std::string mDisplayItemPointer;
118 const std::string mDescription;
119
120 // For pointer comparison only, do not dereference!
121 const nsIFrame* const mFrame;
122 const uint32_t mPerFrameKey;
123
124 const DisplayListBlueprint mChildren;
125
126 private:
WriteIndex(const char * aName,unsigned aIndex)127 static std::string WriteIndex(const char* aName, unsigned aIndex) {
128 return nsPrintfCString("%s#%u", aName, aIndex).get();
129 }
130
WriteIndexFW(const char * aName,unsigned aIndex)131 static std::string WriteIndexFW(const char* aName, unsigned aIndex) {
132 return nsPrintfCString("%s#%4u", aName, aIndex).get();
133 }
134
WriteDisplayItemPointer(nsDisplayItem & aItem)135 static std::string WriteDisplayItemPointer(nsDisplayItem& aItem) {
136 return nsPrintfCString("0x%p", &aItem).get();
137 }
138
WriteDescription(const char * aName,unsigned aIndex,nsDisplayItem & aItem)139 static std::string WriteDescription(const char* aName, unsigned aIndex,
140 nsDisplayItem& aItem) {
141 if (aItem.HasDeletedFrame()) {
142 return nsPrintfCString("%s %s#%u 0x%p f=0x0", aItem.Name(), aName, aIndex,
143 &aItem)
144 .get();
145 }
146
147 const nsIFrame* f = aItem.Frame();
148 nsAutoString contentData;
149 #ifdef DEBUG_FRAME_DUMP
150 f->GetFrameName(contentData);
151 #endif
152 nsIContent* content = f->GetContent();
153 if (content) {
154 nsString tmp;
155 if (content->GetID()) {
156 content->GetID()->ToString(tmp);
157 contentData.AppendLiteral(" id:");
158 contentData.Append(tmp);
159 }
160 const nsAttrValue* classes =
161 content->IsElement() ? content->AsElement()->GetClasses() : nullptr;
162 if (classes) {
163 classes->ToString(tmp);
164 contentData.AppendLiteral(" class:");
165 contentData.Append(tmp);
166 }
167 }
168 return nsPrintfCString("%s %s#%u p=0x%p f=0x%p(%s) key=%" PRIu32,
169 aItem.Name(), aName, aIndex, &aItem, f,
170 NS_ConvertUTF16toUTF8(contentData).get(),
171 aItem.GetPerFrameKey())
172 .get();
173 }
174 };
175
processChildren(nsDisplayList * aList,const char * aName,unsigned & aIndex)176 void DisplayListBlueprint::processChildren(nsDisplayList* aList,
177 const char* aName,
178 unsigned& aIndex) {
179 if (!aList) {
180 return;
181 }
182 const uint32_t n = aList->Count();
183 if (n == 0) {
184 return;
185 }
186 mItems.reserve(n);
187 for (nsDisplayItem* item = aList->GetBottom(); item;
188 item = item->GetAbove()) {
189 mItems.emplace_back(*item, aName, aIndex);
190 }
191 MOZ_ASSERT(mItems.size() == n);
192 }
193
Output(std::stringstream & aSs) const194 bool DisplayItemBlueprintStack::Output(std::stringstream& aSs) const {
195 const bool output = mPrevious ? mPrevious->Output(aSs) : false;
196 if (mItem) {
197 if (output) {
198 aSs << " > ";
199 }
200 aSs << mItem->mIndexString;
201 return true;
202 }
203 return output;
204 }
205
Find(const nsIFrame * aFrame,uint32_t aPerFrameKey,const DisplayItemBlueprintStack & aStack) const206 std::string DisplayListBlueprint::Find(
207 const nsIFrame* aFrame, uint32_t aPerFrameKey,
208 const DisplayItemBlueprintStack& aStack) const {
209 for (const DisplayItemBlueprint& item : mItems) {
210 if (item.mFrame == aFrame && item.mPerFrameKey == aPerFrameKey) {
211 std::stringstream ss;
212 if (aStack.Output(ss)) {
213 ss << " > ";
214 }
215 ss << item.mDescription;
216 return ss.str();
217 }
218 const DisplayItemBlueprintStack stack = {&aStack, &item};
219 std::string s = item.mChildren.Find(aFrame, aPerFrameKey, stack);
220 if (!s.empty()) {
221 return s;
222 }
223 }
224 return "";
225 }
226
CompareList(const DisplayListBlueprint & aRoot,const DisplayListBlueprint & aOther,const DisplayListBlueprint & aOtherRoot,std::stringstream & aDiff,const DisplayItemBlueprintStack & aStack,const DisplayItemBlueprintStack & aStackOther) const227 bool DisplayListBlueprint::CompareList(
228 const DisplayListBlueprint& aRoot, const DisplayListBlueprint& aOther,
229 const DisplayListBlueprint& aOtherRoot, std::stringstream& aDiff,
230 const DisplayItemBlueprintStack& aStack,
231 const DisplayItemBlueprintStack& aStackOther) const {
232 bool same = true;
233 unsigned previousFoundIndex = 0;
234 const DisplayItemBlueprint* previousFoundItemBefore = nullptr;
235 const DisplayItemBlueprint* previousFoundItemAfter = nullptr;
236 for (const DisplayItemBlueprint& itemBefore : mItems) {
237 bool found = false;
238 unsigned foundIndex = 0;
239 for (const DisplayItemBlueprint& itemAfter : aOther.mItems) {
240 if (itemBefore.CompareItem(itemAfter, aDiff)) {
241 found = true;
242
243 if (mVerifyOrder) {
244 if (foundIndex < previousFoundIndex) {
245 same = false;
246 aDiff << "\n";
247 if (aStack.Output(aDiff)) {
248 aDiff << " > ";
249 }
250 aDiff << itemBefore.mDescription;
251 aDiff << "\n * Corresponding item in unexpected order: ";
252 if (aStackOther.Output(aDiff)) {
253 aDiff << " > ";
254 }
255 aDiff << itemAfter.mDescription;
256 aDiff << "\n * Was expected after: ";
257 if (aStackOther.Output(aDiff)) {
258 aDiff << " > ";
259 }
260 MOZ_ASSERT(previousFoundItemAfter);
261 aDiff << previousFoundItemAfter->mDescription;
262 aDiff << "\n which corresponds to: ";
263 if (aStack.Output(aDiff)) {
264 aDiff << " > ";
265 }
266 MOZ_ASSERT(previousFoundItemBefore);
267 aDiff << previousFoundItemBefore->mDescription;
268 }
269 previousFoundIndex = foundIndex;
270 previousFoundItemBefore = &itemBefore;
271 previousFoundItemAfter = &itemAfter;
272 }
273
274 const DisplayItemBlueprintStack stack = {&aStack, &itemBefore};
275 const DisplayItemBlueprintStack stackOther = {&aStackOther, &itemAfter};
276 if (!itemBefore.mChildren.CompareList(aRoot, itemAfter.mChildren,
277 aOtherRoot, aDiff, stack,
278 stackOther)) {
279 same = false;
280 }
281 break;
282 }
283 ++foundIndex;
284 }
285 if (!found) {
286 same = false;
287 aDiff << "\n";
288 if (aStack.Output(aDiff)) {
289 aDiff << " > ";
290 }
291 aDiff << itemBefore.mDescription;
292 aDiff << "\n * Cannot find corresponding item under ";
293 if (!aStackOther.Output(aDiff)) {
294 if (!aOtherRoot.mItems.empty()) {
295 aDiff << aOtherRoot.mItems[0].mListName;
296 } else {
297 aDiff << "other root";
298 }
299 }
300 std::string elsewhere =
301 aOtherRoot.Find(itemBefore.mFrame, itemBefore.mPerFrameKey);
302 if (!elsewhere.empty()) {
303 aDiff << "\n * But found: " << elsewhere;
304 }
305 }
306 }
307 return same;
308 }
309
Dump(std::stringstream & aSs,unsigned aDepth) const310 void DisplayListBlueprint::Dump(std::stringstream& aSs, unsigned aDepth) const {
311 for (const DisplayItemBlueprint& item : mItems) {
312 item.Dump(aSs, aDepth);
313 }
314 }
315
Dump(std::stringstream & aSs,unsigned aDepth) const316 void DisplayItemBlueprint::Dump(std::stringstream& aSs, unsigned aDepth) const {
317 aSs << "\n" << mIndexStringFW << " ";
318 for (unsigned i = 0; i < aDepth; ++i) {
319 aSs << " ";
320 }
321 aSs << mDescription;
322 mChildren.Dump(aSs, aDepth + 1);
323 }
324
DisplayListChecker()325 DisplayListChecker::DisplayListChecker() : mBlueprint(nullptr) {}
326
DisplayListChecker(nsDisplayList * aList,const char * aName)327 DisplayListChecker::DisplayListChecker(nsDisplayList* aList, const char* aName)
328 : mBlueprint(MakeUnique<DisplayListBlueprint>(aList, aName)) {}
329
330 DisplayListChecker::~DisplayListChecker() = default;
331
Set(nsDisplayList * aList,const char * aName)332 void DisplayListChecker::Set(nsDisplayList* aList, const char* aName) {
333 mBlueprint = MakeUnique<DisplayListBlueprint>(aList, aName);
334 }
335
336 // Compare this list with another one, output differences between the two
337 // into aDiff.
338 // Differences include: Display items from one tree for which a corresponding
339 // item (same frame and per-frame key) cannot be found under corresponding
340 // parent items.
341 // Returns true if trees are similar, false if different.
CompareList(const DisplayListChecker & aOther,std::stringstream & aDiff) const342 bool DisplayListChecker::CompareList(const DisplayListChecker& aOther,
343 std::stringstream& aDiff) const {
344 MOZ_ASSERT(mBlueprint);
345 MOZ_ASSERT(aOther.mBlueprint);
346 return mBlueprint->CompareList(*aOther.mBlueprint, aDiff);
347 }
348
Dump(std::stringstream & aSs) const349 void DisplayListChecker::Dump(std::stringstream& aSs) const {
350 MOZ_ASSERT(mBlueprint);
351 mBlueprint->Dump(aSs);
352 }
353
354 } // namespace mozilla
355