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