/src/mozilla-central/layout/painting/DisplayListChecker.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | { |
19 | | const DisplayItemBlueprintStack* mPrevious; |
20 | | const DisplayItemBlueprint* mItem; |
21 | | // Output stack to aSs, with format "name#index > ... > name#index". |
22 | | // Returns true if anything was output, false if empty. |
23 | | bool Output(std::stringstream& aSs) const; |
24 | | }; |
25 | | |
26 | | // Object representing a list of display items (either the top of the tree, or |
27 | | // an item's children), with just enough information to compare with another |
28 | | // tree and output useful debugging information. |
29 | | class DisplayListBlueprint |
30 | | { |
31 | | public: |
32 | | DisplayListBlueprint(nsDisplayList* aList, const char* aName) |
33 | | : DisplayListBlueprint(aList, 0, aName) |
34 | 0 | { |
35 | 0 | } |
36 | | |
37 | | DisplayListBlueprint(nsDisplayList* aList, |
38 | | const char* aName, |
39 | | unsigned& aIndex) |
40 | 0 | { |
41 | 0 | processChildren(aList, aName, aIndex); |
42 | 0 | } |
43 | | |
44 | | // Find a display item with the given frame and per-frame key. |
45 | | // Returns empty string if not found. |
46 | | std::string Find(const nsIFrame* aFrame, uint32_t aPerFrameKey) const |
47 | 0 | { |
48 | 0 | const DisplayItemBlueprintStack stack{ nullptr, nullptr }; |
49 | 0 | return Find(aFrame, aPerFrameKey, stack); |
50 | 0 | } |
51 | | |
52 | | std::string Find(const nsIFrame* aFrame, |
53 | | uint32_t aPerFrameKey, |
54 | | const DisplayItemBlueprintStack& aStack) const; |
55 | | |
56 | | // Compare this list with another one, output differences between the two |
57 | | // into aDiff. |
58 | | // Differences include: Display items from one tree for which a corresponding |
59 | | // item (same frame and per-frame key) cannot be found under corresponding |
60 | | // parent items. |
61 | | // Returns true if trees are similar, false if different. |
62 | | bool CompareList(const DisplayListBlueprint& aOther, |
63 | | std::stringstream& aDiff) const |
64 | 0 | { |
65 | 0 | const DisplayItemBlueprintStack stack{ nullptr, nullptr }; |
66 | 0 | const bool ab = CompareList(*this, aOther, aOther, aDiff, stack, stack); |
67 | 0 | const bool ba = |
68 | 0 | aOther.CompareList(aOther, *this, *this, aDiff, stack, stack); |
69 | 0 | return ab && ba; |
70 | 0 | } |
71 | | |
72 | | bool CompareList(const DisplayListBlueprint& aRoot, |
73 | | const DisplayListBlueprint& aOther, |
74 | | const DisplayListBlueprint& aOtherRoot, |
75 | | std::stringstream& aDiff, |
76 | | const DisplayItemBlueprintStack& aStack, |
77 | | const DisplayItemBlueprintStack& aStackOther) const; |
78 | | |
79 | | // Output this tree to aSs. |
80 | 0 | void Dump(std::stringstream& aSs) const { Dump(aSs, 0); } |
81 | | |
82 | | void Dump(std::stringstream& aSs, unsigned aDepth) const; |
83 | | |
84 | | private: |
85 | | // Only used by first constructor, to call the 2nd constructor with an index |
86 | | // variable on the stack. |
87 | | DisplayListBlueprint(nsDisplayList* aList, unsigned aIndex, const char* aName) |
88 | | : DisplayListBlueprint(aList, aName, aIndex) |
89 | 0 | { |
90 | 0 | } |
91 | | |
92 | | void processChildren(nsDisplayList* aList, |
93 | | const char* aName, |
94 | | unsigned& aIndex); |
95 | | |
96 | | std::vector<DisplayItemBlueprint> mItems; |
97 | | const bool mVerifyOrder = gfxPrefs::LayoutVerifyRetainDisplayListOrder(); |
98 | | }; |
99 | | |
100 | | // Object representing one display item, with just enough information to |
101 | | // compare with another item and output useful debugging information. |
102 | | class DisplayItemBlueprint |
103 | | { |
104 | | public: |
105 | | DisplayItemBlueprint(nsDisplayItem& aItem, |
106 | | const char* aName, |
107 | | unsigned& aIndex) |
108 | | : mListName(aName) |
109 | | , mIndex(++aIndex) |
110 | | , mIndexString(WriteIndex(aName, aIndex)) |
111 | | , mIndexStringFW(WriteIndexFW(aName, aIndex)) |
112 | | , mDisplayItemPointer(WriteDisplayItemPointer(aItem)) |
113 | | , mDescription(WriteDescription(aName, aIndex, aItem)) |
114 | | , mFrame(aItem.HasDeletedFrame() ? nullptr : aItem.Frame()) |
115 | | , mPerFrameKey(aItem.GetPerFrameKey()) |
116 | | , mChildren(aItem.GetChildren(), aName, aIndex) |
117 | 0 | { |
118 | 0 | } |
119 | | |
120 | | // Compare this item with another one, based on frame and per-frame key. |
121 | | // Not recursive! I.e., children are not examined. |
122 | | bool CompareItem(const DisplayItemBlueprint& aOther, |
123 | | std::stringstream& aDiff) const |
124 | 0 | { |
125 | 0 | return mFrame == aOther.mFrame && mPerFrameKey == aOther.mPerFrameKey; |
126 | 0 | } |
127 | | |
128 | | void Dump(std::stringstream& aSs, unsigned aDepth) const; |
129 | | |
130 | | const char* mListName; |
131 | | const unsigned mIndex; |
132 | | const std::string mIndexString; |
133 | | const std::string mIndexStringFW; |
134 | | const std::string mDisplayItemPointer; |
135 | | const std::string mDescription; |
136 | | |
137 | | // For pointer comparison only, do not dereference! |
138 | | const nsIFrame* const mFrame; |
139 | | const uint32_t mPerFrameKey; |
140 | | |
141 | | const DisplayListBlueprint mChildren; |
142 | | |
143 | | private: |
144 | | static std::string WriteIndex(const char* aName, unsigned aIndex) |
145 | 0 | { |
146 | 0 | return nsPrintfCString("%s#%u", aName, aIndex).get(); |
147 | 0 | } |
148 | | |
149 | | static std::string WriteIndexFW(const char* aName, unsigned aIndex) |
150 | 0 | { |
151 | 0 | return nsPrintfCString("%s#%4u", aName, aIndex).get(); |
152 | 0 | } |
153 | | |
154 | | static std::string WriteDisplayItemPointer(nsDisplayItem& aItem) |
155 | 0 | { |
156 | 0 | return nsPrintfCString("0x%p", &aItem).get(); |
157 | 0 | } |
158 | | |
159 | | static std::string WriteDescription(const char* aName, |
160 | | unsigned aIndex, |
161 | | nsDisplayItem& aItem) |
162 | 0 | { |
163 | 0 | if (aItem.HasDeletedFrame()) { |
164 | 0 | return nsPrintfCString( |
165 | 0 | "%s %s#%u 0x%p f=0x0", aItem.Name(), aName, aIndex, &aItem) |
166 | 0 | .get(); |
167 | 0 | } |
168 | 0 | |
169 | 0 | const nsIFrame* f = aItem.Frame(); |
170 | 0 | nsAutoString contentData; |
171 | | #ifdef DEBUG_FRAME_DUMP |
172 | | f->GetFrameName(contentData); |
173 | | #endif |
174 | | nsIContent* content = f->GetContent(); |
175 | 0 | if (content) { |
176 | 0 | nsString tmp; |
177 | 0 | if (content->GetID()) { |
178 | 0 | content->GetID()->ToString(tmp); |
179 | 0 | contentData.AppendLiteral(" id:"); |
180 | 0 | contentData.Append(tmp); |
181 | 0 | } |
182 | 0 | const nsAttrValue* classes = |
183 | 0 | content->IsElement() ? content->AsElement()->GetClasses() : nullptr; |
184 | 0 | if (classes) { |
185 | 0 | classes->ToString(tmp); |
186 | 0 | contentData.AppendLiteral(" class:"); |
187 | 0 | contentData.Append(tmp); |
188 | 0 | } |
189 | 0 | } |
190 | 0 | return nsPrintfCString("%s %s#%u p=0x%p f=0x%p(%s) key=%" PRIu32, |
191 | 0 | aItem.Name(), |
192 | 0 | aName, |
193 | 0 | aIndex, |
194 | 0 | &aItem, |
195 | 0 | f, |
196 | 0 | NS_ConvertUTF16toUTF8(contentData).get(), |
197 | 0 | aItem.GetPerFrameKey()) |
198 | 0 | .get(); |
199 | 0 | } |
200 | | }; |
201 | | |
202 | | void |
203 | | DisplayListBlueprint::processChildren(nsDisplayList* aList, |
204 | | const char* aName, |
205 | | unsigned& aIndex) |
206 | 0 | { |
207 | 0 | if (!aList) { |
208 | 0 | return; |
209 | 0 | } |
210 | 0 | const uint32_t n = aList->Count(); |
211 | 0 | if (n == 0) { |
212 | 0 | return; |
213 | 0 | } |
214 | 0 | mItems.reserve(n); |
215 | 0 | for (nsDisplayItem* item = aList->GetBottom(); item; |
216 | 0 | item = item->GetAbove()) { |
217 | 0 | mItems.emplace_back(*item, aName, aIndex); |
218 | 0 | } |
219 | 0 | MOZ_ASSERT(mItems.size() == n); |
220 | 0 | } |
221 | | |
222 | | bool |
223 | | DisplayItemBlueprintStack::Output(std::stringstream& aSs) const |
224 | 0 | { |
225 | 0 | const bool output = mPrevious ? mPrevious->Output(aSs) : false; |
226 | 0 | if (mItem) { |
227 | 0 | if (output) { |
228 | 0 | aSs << " > "; |
229 | 0 | } |
230 | 0 | aSs << mItem->mIndexString; |
231 | 0 | return true; |
232 | 0 | } |
233 | 0 | return output; |
234 | 0 | } |
235 | | |
236 | | std::string |
237 | | DisplayListBlueprint::Find(const nsIFrame* aFrame, |
238 | | uint32_t aPerFrameKey, |
239 | | const DisplayItemBlueprintStack& aStack) const |
240 | 0 | { |
241 | 0 | for (const DisplayItemBlueprint& item : mItems) { |
242 | 0 | if (item.mFrame == aFrame && item.mPerFrameKey == aPerFrameKey) { |
243 | 0 | std::stringstream ss; |
244 | 0 | if (aStack.Output(ss)) { |
245 | 0 | ss << " > "; |
246 | 0 | } |
247 | 0 | ss << item.mDescription; |
248 | 0 | return ss.str(); |
249 | 0 | } |
250 | 0 | const DisplayItemBlueprintStack stack = { &aStack, &item }; |
251 | 0 | std::string s = item.mChildren.Find(aFrame, aPerFrameKey, stack); |
252 | 0 | if (!s.empty()) { |
253 | 0 | return s; |
254 | 0 | } |
255 | 0 | } |
256 | 0 | return ""; |
257 | 0 | } |
258 | | |
259 | | bool |
260 | | DisplayListBlueprint::CompareList( |
261 | | const DisplayListBlueprint& aRoot, |
262 | | const DisplayListBlueprint& aOther, |
263 | | const DisplayListBlueprint& aOtherRoot, |
264 | | std::stringstream& aDiff, |
265 | | const DisplayItemBlueprintStack& aStack, |
266 | | const DisplayItemBlueprintStack& aStackOther) const |
267 | 0 | { |
268 | 0 | bool same = true; |
269 | 0 | unsigned previousFoundIndex = 0; |
270 | 0 | const DisplayItemBlueprint* previousFoundItemBefore = nullptr; |
271 | 0 | const DisplayItemBlueprint* previousFoundItemAfter = nullptr; |
272 | 0 | for (const DisplayItemBlueprint& itemBefore : mItems) { |
273 | 0 | bool found = false; |
274 | 0 | unsigned foundIndex = 0; |
275 | 0 | for (const DisplayItemBlueprint& itemAfter : aOther.mItems) { |
276 | 0 | if (itemBefore.CompareItem(itemAfter, aDiff)) { |
277 | 0 | found = true; |
278 | 0 |
|
279 | 0 | if (mVerifyOrder) { |
280 | 0 | if (foundIndex < previousFoundIndex) { |
281 | 0 | same = false; |
282 | 0 | aDiff << "\n"; |
283 | 0 | if (aStack.Output(aDiff)) { |
284 | 0 | aDiff << " > "; |
285 | 0 | } |
286 | 0 | aDiff << itemBefore.mDescription; |
287 | 0 | aDiff << "\n * Corresponding item in unexpected order: "; |
288 | 0 | if (aStackOther.Output(aDiff)) { |
289 | 0 | aDiff << " > "; |
290 | 0 | } |
291 | 0 | aDiff << itemAfter.mDescription; |
292 | 0 | aDiff << "\n * Was expected after: "; |
293 | 0 | if (aStackOther.Output(aDiff)) { |
294 | 0 | aDiff << " > "; |
295 | 0 | } |
296 | 0 | MOZ_ASSERT(previousFoundItemAfter); |
297 | 0 | aDiff << previousFoundItemAfter->mDescription; |
298 | 0 | aDiff << "\n which corresponds to: "; |
299 | 0 | if (aStack.Output(aDiff)) { |
300 | 0 | aDiff << " > "; |
301 | 0 | } |
302 | 0 | MOZ_ASSERT(previousFoundItemBefore); |
303 | 0 | aDiff << previousFoundItemBefore->mDescription; |
304 | 0 | } |
305 | 0 | previousFoundIndex = foundIndex; |
306 | 0 | previousFoundItemBefore = &itemBefore; |
307 | 0 | previousFoundItemAfter = &itemAfter; |
308 | 0 | } |
309 | 0 |
|
310 | 0 | const DisplayItemBlueprintStack stack = { &aStack, &itemBefore }; |
311 | 0 | const DisplayItemBlueprintStack stackOther = { &aStackOther, |
312 | 0 | &itemAfter }; |
313 | 0 | if (!itemBefore.mChildren.CompareList(aRoot, |
314 | 0 | itemAfter.mChildren, |
315 | 0 | aOtherRoot, |
316 | 0 | aDiff, |
317 | 0 | stack, |
318 | 0 | stackOther)) { |
319 | 0 | same = false; |
320 | 0 | } |
321 | 0 | break; |
322 | 0 | } |
323 | 0 | ++foundIndex; |
324 | 0 | } |
325 | 0 | if (!found) { |
326 | 0 | same = false; |
327 | 0 | aDiff << "\n"; |
328 | 0 | if (aStack.Output(aDiff)) { |
329 | 0 | aDiff << " > "; |
330 | 0 | } |
331 | 0 | aDiff << itemBefore.mDescription; |
332 | 0 | aDiff << "\n * Cannot find corresponding item under "; |
333 | 0 | if (!aStackOther.Output(aDiff)) { |
334 | 0 | if (!aOtherRoot.mItems.empty()) { |
335 | 0 | aDiff << aOtherRoot.mItems[0].mListName; |
336 | 0 | } else { |
337 | 0 | aDiff << "other root"; |
338 | 0 | } |
339 | 0 | } |
340 | 0 | std::string elsewhere = |
341 | 0 | aOtherRoot.Find(itemBefore.mFrame, itemBefore.mPerFrameKey); |
342 | 0 | if (!elsewhere.empty()) { |
343 | 0 | aDiff << "\n * But found: " << elsewhere; |
344 | 0 | } |
345 | 0 | } |
346 | 0 | } |
347 | 0 | return same; |
348 | 0 | } |
349 | | |
350 | | void |
351 | | DisplayListBlueprint::Dump(std::stringstream& aSs, unsigned aDepth) const |
352 | 0 | { |
353 | 0 | for (const DisplayItemBlueprint& item : mItems) { |
354 | 0 | item.Dump(aSs, aDepth); |
355 | 0 | } |
356 | 0 | } |
357 | | |
358 | | void |
359 | | DisplayItemBlueprint::Dump(std::stringstream& aSs, unsigned aDepth) const |
360 | 0 | { |
361 | 0 | aSs << "\n" << mIndexStringFW << " "; |
362 | 0 | for (unsigned i = 0; i < aDepth; ++i) { |
363 | 0 | aSs << " "; |
364 | 0 | } |
365 | 0 | aSs << mDescription; |
366 | 0 | mChildren.Dump(aSs, aDepth + 1); |
367 | 0 | } |
368 | | |
369 | | DisplayListChecker::DisplayListChecker() |
370 | | : mBlueprint(nullptr) |
371 | 0 | { |
372 | 0 | } |
373 | | |
374 | | DisplayListChecker::DisplayListChecker(nsDisplayList* aList, const char* aName) |
375 | | : mBlueprint(MakeUnique<DisplayListBlueprint>(aList, aName)) |
376 | 0 | { |
377 | 0 | } |
378 | | |
379 | 0 | DisplayListChecker::~DisplayListChecker() = default; |
380 | | |
381 | | void |
382 | | DisplayListChecker::Set(nsDisplayList* aList, const char* aName) |
383 | 0 | { |
384 | 0 | mBlueprint = MakeUnique<DisplayListBlueprint>(aList, aName); |
385 | 0 | } |
386 | | |
387 | | // Compare this list with another one, output differences between the two |
388 | | // into aDiff. |
389 | | // Differences include: Display items from one tree for which a corresponding |
390 | | // item (same frame and per-frame key) cannot be found under corresponding |
391 | | // parent items. |
392 | | // Returns true if trees are similar, false if different. |
393 | | bool |
394 | | DisplayListChecker::CompareList(const DisplayListChecker& aOther, |
395 | | std::stringstream& aDiff) const |
396 | 0 | { |
397 | 0 | MOZ_ASSERT(mBlueprint); |
398 | 0 | MOZ_ASSERT(aOther.mBlueprint); |
399 | 0 | return mBlueprint->CompareList(*aOther.mBlueprint, aDiff); |
400 | 0 | } |
401 | | |
402 | | void |
403 | | DisplayListChecker::Dump(std::stringstream& aSs) const |
404 | 0 | { |
405 | 0 | MOZ_ASSERT(mBlueprint); |
406 | 0 | mBlueprint->Dump(aSs); |
407 | 0 | } |
408 | | |
409 | | } // namespace mozilla |