1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 /* utilities for regression tests based on frame tree comparison */
7 
8 #include "nsIFrameUtil.h"
9 #include "nsFrame.h"
10 #include "nsString.h"
11 #include "nsRect.h"
12 #include <stdlib.h>
13 #include "plstr.h"
14 
15 
16 #ifdef DEBUG
17 class nsFrameUtil : public nsIFrameUtil {
18 protected:
19   virtual ~nsFrameUtil();
20 
21 public:
22   nsFrameUtil();
23 
24   NS_DECL_ISUPPORTS
25 
26   NS_IMETHOD CompareRegressionData(FILE* aFile1, FILE* aFile2,int32_t aRegressionOutput=0) override;
27   NS_IMETHOD DumpRegressionData(FILE* aInputFile, FILE* aOutputFile) override;
28 
29   struct Node;
30   struct Tag;
31 
32   struct NodeList {
33     NodeList();
34     ~NodeList();
35 
36     static void Destroy(NodeList* aLists);
37 
38     NodeList* next;            // for lists of lists
39     Node* node;
40     char* name;
41   };
42 
43   struct Node {
44     Node();
45     ~Node();
46 
47     static void Destroy(Node* aNode);
48 
49     static Node* Read(FILE* aFile, Tag* aTag);
50 
51     static Node* ReadTree(FILE* aFile);
52 
53     Node* next;
54     char* type;
55     uint32_t state;
56     nsRect bbox;
57     nsCString styleData;
58     NodeList* lists;
59   };
60 
61   struct Tag {
62     Tag();
63     ~Tag();
64 
65     static Tag* Parse(FILE* aFile);
66 
67     void AddAttr(char* aAttr, char* aValue);
68 
69     const char* GetAttr(const char* aAttr);
70 
71     void ReadAttrs(FILE* aFile);
72 
73     void ToString(nsString& aResult);
74 
75     enum Type {
76       open,
77       close,
78       openClose
79     };
80 
81     char* name;
82     Type type;
83     char** attributes;
84     int32_t num;
85     int32_t size;
86     char** values;
87   };
88 
89   static char* Copy(const char* aString);
90 
91   static void DumpNode(Node* aNode, FILE* aOutputFile, int32_t aIndent);
92   static void DumpTree(Node* aNode, FILE* aOutputFile, int32_t aIndent);
93   static bool CompareTrees(Node* aNode1, Node* aNode2);
94 };
95 
96 char*
Copy(const char * aString)97 nsFrameUtil::Copy(const char* aString)
98 {
99   if (aString) {
100     int l = ::strlen(aString);
101     char* c = new char[l+1];
102     if (!c)
103       return nullptr;
104     memcpy(c, aString, l+1);
105     return c;
106   }
107   return nullptr;
108 }
109 
110 //----------------------------------------------------------------------
111 
NodeList()112 nsFrameUtil::NodeList::NodeList()
113   : next(nullptr), node(nullptr), name(nullptr)
114 {
115 }
116 
~NodeList()117 nsFrameUtil::NodeList::~NodeList()
118 {
119   if (nullptr != name) {
120     delete name;
121   }
122   if (nullptr != node) {
123     Node::Destroy(node);
124   }
125 }
126 
127 void
Destroy(NodeList * aLists)128 nsFrameUtil::NodeList::Destroy(NodeList* aLists)
129 {
130   while (nullptr != aLists) {
131     NodeList* next = aLists->next;
132     delete aLists;
133     aLists = next;
134   }
135 }
136 
137 //----------------------------------------------------------------------
138 
Node()139 nsFrameUtil::Node::Node()
140   : next(nullptr), type(nullptr), state(0), lists(nullptr)
141 {
142 }
143 
~Node()144 nsFrameUtil::Node::~Node()
145 {
146   if (nullptr != type) {
147     delete type;
148   }
149   if (nullptr != lists) {
150     NodeList::Destroy(lists);
151   }
152 }
153 
154 void
Destroy(Node * aList)155 nsFrameUtil::Node::Destroy(Node* aList)
156 {
157   while (nullptr != aList) {
158     Node* next = aList->next;
159     delete aList;
160     aList = next;
161   }
162 }
163 
GetInt(nsFrameUtil::Tag * aTag,const char * aAttr)164 static int32_t GetInt(nsFrameUtil::Tag* aTag, const char* aAttr)
165 {
166   const char* value = aTag->GetAttr(aAttr);
167   if (nullptr != value) {
168     return int32_t( atoi(value) );
169   }
170   return 0;
171 }
172 
173 nsFrameUtil::Node*
ReadTree(FILE * aFile)174 nsFrameUtil::Node::ReadTree(FILE* aFile)
175 {
176   Tag* tag = Tag::Parse(aFile);
177   if (nullptr == tag) {
178     return nullptr;
179   }
180   if (PL_strcmp(tag->name, "frame") != 0) {
181     delete tag;
182     return nullptr;
183   }
184   Node* result = Read(aFile, tag);
185   fclose(aFile);
186   return result;
187 }
188 
189 nsFrameUtil::Node*
Read(FILE * aFile,Tag * tag)190 nsFrameUtil::Node::Read(FILE* aFile, Tag* tag)
191 {
192   Node* node = new Node;
193   node->type = Copy(tag->GetAttr("type"));
194   if (!node->type) {
195     /* crash() */
196   }
197   node->state = GetInt(tag, "state");
198   delete tag;
199 
200   for (;;) {
201     tag = Tag::Parse(aFile);
202     if (nullptr == tag) break;
203     if (PL_strcmp(tag->name, "frame") == 0) {
204       delete tag;
205       break;
206     }
207     if (PL_strcmp(tag->name, "bbox") == 0) {
208       nscoord x = nscoord( GetInt(tag, "x") );
209       nscoord y = nscoord( GetInt(tag, "y") );
210       nscoord w = nscoord( GetInt(tag, "w") );
211       nscoord h = nscoord( GetInt(tag, "h") );
212       node->bbox.SetRect(x, y, w, h);
213     }
214     else if (PL_strcmp(tag->name, "child-list") == 0) {
215       NodeList* list = new NodeList();
216       list->name = Copy(tag->GetAttr("name"));
217       if (!list->name) {
218         /* crash() */
219       }
220       list->next = node->lists;
221       node->lists = list;
222       delete tag;
223 
224       Node** tailp = &list->node;
225       for (;;) {
226         tag = Tag::Parse(aFile);
227         if (nullptr == tag) {
228           break;
229         }
230         if (PL_strcmp(tag->name, "child-list") == 0) {
231           break;
232         }
233         if (PL_strcmp(tag->name, "frame") != 0) {
234           break;
235         }
236         Node* child = Node::Read(aFile, tag);
237         if (nullptr == child) {
238           break;
239         }
240         *tailp = child;
241         tailp = &child->next;
242       }
243     }
244     else if((PL_strcmp(tag->name, "font") == 0) ||
245             (PL_strcmp(tag->name, "color") == 0) ||
246             (PL_strcmp(tag->name, "spacing") == 0) ||
247             (PL_strcmp(tag->name, "list") == 0) ||
248             (PL_strcmp(tag->name, "position") == 0) ||
249             (PL_strcmp(tag->name, "text") == 0) ||
250             (PL_strcmp(tag->name, "display") == 0) ||
251             (PL_strcmp(tag->name, "table") == 0) ||
252             (PL_strcmp(tag->name, "content") == 0) ||
253             (PL_strcmp(tag->name, "UI") == 0) ||
254             (PL_strcmp(tag->name, "print") == 0)) {
255       const char* attr = tag->GetAttr("data");
256       node->styleData.Append('|');
257       node->styleData.Append(attr ? attr : "null attr");
258     }
259 
260     delete tag;
261   }
262   return node;
263 }
264 
265 //----------------------------------------------------------------------
266 
Tag()267 nsFrameUtil::Tag::Tag()
268   : name(nullptr), type(open), attributes(nullptr), num(0), size(0),
269     values(nullptr)
270 {
271 }
272 
~Tag()273 nsFrameUtil::Tag::~Tag()
274 {
275   int32_t i, n = num;
276   if (0 != n) {
277     for (i = 0; i < n; i++) {
278       delete attributes[i];
279       delete values[i];
280     }
281     delete attributes;
282     delete values;
283   }
284 }
285 
286 void
AddAttr(char * aAttr,char * aValue)287 nsFrameUtil::Tag::AddAttr(char* aAttr, char* aValue)
288 {
289   if (num == size) {
290     int32_t newSize = size * 2 + 4;
291     char** a = new char*[newSize];
292     char** v = new char*[newSize];
293     if (0 != num) {
294       memcpy(a, attributes, num * sizeof(char*));
295       memcpy(v, values, num * sizeof(char*));
296       delete attributes;
297       delete values;
298     }
299     attributes = a;
300     values = v;
301     size = newSize;
302   }
303   attributes[num] = aAttr;
304   values[num] = aValue;
305   num = num + 1;
306 }
307 
308 const char*
GetAttr(const char * aAttr)309 nsFrameUtil::Tag::GetAttr(const char* aAttr)
310 {
311   int32_t i, n = num;
312   for (i = 0; i < n; i++) {
313     if (PL_strcmp(attributes[i], aAttr) == 0) {
314       return values[i];
315     }
316   }
317   return nullptr;
318 }
319 
IsWhiteSpace(int c)320 static inline int IsWhiteSpace(int c) {
321   return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r');
322 }
323 
EatWS(FILE * aFile)324 static bool EatWS(FILE* aFile)
325 {
326   for (;;) {
327     int c = getc(aFile);
328     if (c < 0) {
329       return false;
330     }
331     if (!IsWhiteSpace(c)) {
332       ungetc(c, aFile);
333       break;
334     }
335   }
336   return true;
337 }
338 
Expect(FILE * aFile,char aChar)339 static bool Expect(FILE* aFile, char aChar)
340 {
341   int c = getc(aFile);
342   if (c < 0) return false;
343   if (c != aChar) {
344     ungetc(c, aFile);
345     return false;
346   }
347   return true;
348 }
349 
ReadIdent(FILE * aFile)350 static char* ReadIdent(FILE* aFile)
351 {
352   char id[1000];
353   char* ip = id;
354   char* end = ip + sizeof(id) - 1;
355   while (ip < end) {
356     int c = fgetc(aFile);
357     if (c < 0) return nullptr;
358     if ((c == '=') || (c == '>') || (c == '/') || IsWhiteSpace(c)) {
359       ungetc(c, aFile);
360       break;
361     }
362     *ip++ = char(c);
363   }
364   *ip = '\0';
365   return nsFrameUtil::Copy(id);
366   /* may return a null pointer */
367 }
368 
ReadString(FILE * aFile)369 static char* ReadString(FILE* aFile)
370 {
371   if (!Expect(aFile, '\"')) {
372     return nullptr;
373   }
374   char id[1000];
375   char* ip = id;
376   char* end = ip + sizeof(id) - 1;
377   while (ip < end) {
378     int c = fgetc(aFile);
379     if (c < 0) return nullptr;
380     if (c == '\"') {
381       break;
382     }
383     *ip++ = char(c);
384   }
385   *ip = '\0';
386   return nsFrameUtil::Copy(id);
387   /* may return a null pointer */
388 }
389 
390 void
ReadAttrs(FILE * aFile)391 nsFrameUtil::Tag::ReadAttrs(FILE* aFile)
392 {
393   for (;;) {
394     if (!EatWS(aFile)) {
395       break;
396     }
397     int c = getc(aFile);
398     if (c < 0) break;
399     if (c == '/') {
400       if (!EatWS(aFile)) {
401         return;
402       }
403       if (Expect(aFile, '>')) {
404         type = openClose;
405         break;
406       }
407     }
408     else if (c == '>') {
409       break;
410     }
411     ungetc(c, aFile);
412     char* attr = ReadIdent(aFile);
413     if ((nullptr == attr) || !EatWS(aFile)) {
414       break;
415     }
416     char* value = nullptr;
417     if (Expect(aFile, '=')) {
418       value = ReadString(aFile);
419       if (nullptr == value) {
420         delete [] attr;
421         break;
422       }
423     }
424     AddAttr(attr, value);
425   }
426 }
427 
428 nsFrameUtil::Tag*
Parse(FILE * aFile)429 nsFrameUtil::Tag::Parse(FILE* aFile)
430 {
431   if (!EatWS(aFile)) {
432     return nullptr;
433   }
434   if (Expect(aFile, '<')) {
435     Tag* tag = new Tag;
436     if (Expect(aFile, '/')) {
437       tag->type = close;
438     }
439     else {
440       tag->type = open;
441     }
442     tag->name = ReadIdent(aFile);
443     tag->ReadAttrs(aFile);
444     return tag;
445   }
446   return nullptr;
447 }
448 
449 void
ToString(nsString & aResult)450 nsFrameUtil::Tag::ToString(nsString& aResult)
451 {
452   aResult.Truncate();
453   aResult.Append(char16_t('<'));
454   if (type == close) {
455     aResult.Append(char16_t('/'));
456   }
457   aResult.AppendASCII(name);
458   if (0 != num) {
459     int32_t i, n = num;
460     for (i = 0; i < n; i++) {
461       aResult.Append(char16_t(' '));
462       aResult.AppendASCII(attributes[i]);
463       if (values[i]) {
464         aResult.AppendLiteral("=\"");
465         aResult.AppendASCII(values[i]);
466         aResult.Append(char16_t('\"'));
467       }
468     }
469   }
470   if (type == openClose) {
471     aResult.Append(char16_t('/'));
472   }
473   aResult.Append(char16_t('>'));
474 }
475 
476 //----------------------------------------------------------------------
477 
478 nsresult
NS_NewFrameUtil(nsIFrameUtil ** aResult)479 NS_NewFrameUtil(nsIFrameUtil** aResult)
480 {
481   NS_PRECONDITION(nullptr != aResult, "null pointer");
482   if (nullptr == aResult) {
483     return NS_ERROR_NULL_POINTER;
484   }
485 
486   nsFrameUtil* it = new nsFrameUtil();
487 
488   NS_ADDREF(*aResult = it);
489   return NS_OK;
490 }
491 
nsFrameUtil()492 nsFrameUtil::nsFrameUtil()
493 {
494 }
495 
~nsFrameUtil()496 nsFrameUtil::~nsFrameUtil()
497 {
498 }
499 
NS_IMPL_ISUPPORTS(nsFrameUtil,nsIFrameUtil)500 NS_IMPL_ISUPPORTS(nsFrameUtil, nsIFrameUtil)
501 
502 void
503 nsFrameUtil::DumpNode(Node* aNode, FILE* aOutputFile, int32_t aIndent)
504 {
505   nsFrame::IndentBy(aOutputFile, aIndent);
506   fprintf(aOutputFile, "%s 0x%x %d,%d,%d,%d, %s\n", aNode->type, aNode->state,
507           aNode->bbox.x, aNode->bbox.y,
508           aNode->bbox.width, aNode->bbox.height,
509           aNode->styleData.get());
510 }
511 
512 void
DumpTree(Node * aNode,FILE * aOutputFile,int32_t aIndent)513 nsFrameUtil::DumpTree(Node* aNode, FILE* aOutputFile, int32_t aIndent)
514 {
515   while (nullptr != aNode) {
516     DumpNode(aNode, aOutputFile, aIndent);
517     nsFrameUtil::NodeList* lists = aNode->lists;
518     if (nullptr != lists) {
519       while (nullptr != lists) {
520         nsFrame::IndentBy(aOutputFile, aIndent);
521         fprintf(aOutputFile, " list: %s\n",
522                 lists->name ? lists->name : "primary");
523         DumpTree(lists->node, aOutputFile, aIndent + 1);
524         lists = lists->next;
525       }
526     }
527     aNode = aNode->next;
528   }
529 }
530 
531 bool
CompareTrees(Node * tree1,Node * tree2)532 nsFrameUtil::CompareTrees(Node* tree1, Node* tree2)
533 {
534   bool result = true;
535   for (;; tree1 = tree1->next, tree2 = tree2->next) {
536     // Make sure both nodes are non-null, or at least agree with each other
537     if (nullptr == tree1) {
538       if (nullptr == tree2) {
539         break;
540       }
541       printf("first tree prematurely ends\n");
542       return false;
543     }
544     else if (nullptr == tree2) {
545       printf("second tree prematurely ends\n");
546       return false;
547     }
548 
549     // Check the attributes that we care about
550     if (0 != PL_strcmp(tree1->type, tree2->type)) {
551       printf("frame type mismatch: %s vs. %s\n", tree1->type, tree2->type);
552       printf("Node 1:\n");
553       DumpNode(tree1, stdout, 1);
554       printf("Node 2:\n");
555       DumpNode(tree2, stdout, 1);
556       return false;
557     }
558 
559     // Ignore the XUL scrollbar frames
560     static const char kScrollbarFrame[] = "ScrollbarFrame";
561     if (0 == PL_strncmp(tree1->type, kScrollbarFrame, sizeof(kScrollbarFrame) - 1))
562       continue;
563 
564     if (tree1->state != tree2->state) {
565       printf("frame state mismatch: 0x%x vs. 0x%x\n",
566              tree1->state, tree2->state);
567       printf("Node 1:\n");
568       DumpNode(tree1, stdout, 1);
569       printf("Node 2:\n");
570       DumpNode(tree2, stdout, 1);
571       result = false; // we have a non-critical failure, so remember that but continue
572     }
573     if (tree1->bbox.IsEqualInterior(tree2->bbox)) {
574       printf("frame bbox mismatch: %d,%d,%d,%d vs. %d,%d,%d,%d\n",
575              tree1->bbox.x, tree1->bbox.y,
576              tree1->bbox.width, tree1->bbox.height,
577              tree2->bbox.x, tree2->bbox.y,
578              tree2->bbox.width, tree2->bbox.height);
579       printf("Node 1:\n");
580       DumpNode(tree1, stdout, 1);
581       printf("Node 2:\n");
582       DumpNode(tree2, stdout, 1);
583       result = false; // we have a non-critical failure, so remember that but continue
584     }
585     if (tree1->styleData != tree2->styleData) {
586       printf("frame style data mismatch: %s vs. %s\n",
587         tree1->styleData.get(),
588         tree2->styleData.get());
589     }
590 
591     // Check child lists too
592     NodeList* list1 = tree1->lists;
593     NodeList* list2 = tree2->lists;
594     for (;;) {
595       if (nullptr == list1) {
596         if (nullptr != list2) {
597           printf("first tree prematurely ends (no child lists)\n");
598           printf("Node 1:\n");
599           DumpNode(tree1, stdout, 1);
600           printf("Node 2:\n");
601           DumpNode(tree2, stdout, 1);
602           return false;
603         }
604         else {
605           break;
606         }
607       }
608       if (nullptr == list2) {
609         printf("second tree prematurely ends (no child lists)\n");
610         printf("Node 1:\n");
611         DumpNode(tree1, stdout, 1);
612         printf("Node 2:\n");
613         DumpNode(tree2, stdout, 1);
614         return false;
615       }
616       if (0 != PL_strcmp(list1->name, list2->name)) {
617         printf("child-list name mismatch: %s vs. %s\n",
618                list1->name ? list1->name : "(null)",
619                list2->name ? list2->name : "(null)");
620         result = false; // we have a non-critical failure, so remember that but continue
621       }
622       else {
623         bool equiv = CompareTrees(list1->node, list2->node);
624         if (!equiv) {
625           return equiv;
626         }
627       }
628       list1 = list1->next;
629       list2 = list2->next;
630     }
631   }
632   return result;
633 }
634 
635 NS_IMETHODIMP
CompareRegressionData(FILE * aFile1,FILE * aFile2,int32_t aRegressionOutput)636 nsFrameUtil::CompareRegressionData(FILE* aFile1, FILE* aFile2,int32_t aRegressionOutput)
637 {
638   Node* tree1 = Node::ReadTree(aFile1);
639   Node* tree2 = Node::ReadTree(aFile2);
640 
641   nsresult rv = NS_OK;
642   if (!CompareTrees(tree1, tree2)) {
643     // only output this if aRegressionOutput is 0
644     if( 0 == aRegressionOutput ){
645       printf("Regression data 1:\n");
646       DumpTree(tree1, stdout, 0);
647       printf("Regression data 2:\n");
648       DumpTree(tree2, stdout, 0);
649     }
650     rv = NS_ERROR_FAILURE;
651   }
652 
653   Node::Destroy(tree1);
654   Node::Destroy(tree2);
655 
656   return rv;
657 }
658 
659 NS_IMETHODIMP
DumpRegressionData(FILE * aInputFile,FILE * aOutputFile)660 nsFrameUtil::DumpRegressionData(FILE* aInputFile, FILE* aOutputFile)
661 {
662   Node* tree1 = Node::ReadTree(aInputFile);
663   if (nullptr != tree1) {
664     DumpTree(tree1, aOutputFile, 0);
665     Node::Destroy(tree1);
666     return NS_OK;
667   }
668   return NS_ERROR_FAILURE;
669 }
670 #endif
671