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