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/. */
6 /* utilities for regression tests based on frame tree comparison */
8 #include "nsIFrameUtil.h"
17 class nsFrameUtil
: public nsIFrameUtil
{
19 virtual ~nsFrameUtil();
26 NS_IMETHOD
CompareRegressionData(FILE* aFile1
, FILE* aFile2
,int32_t aRegressionOutput
=0) MOZ_OVERRIDE
;
27 NS_IMETHOD
DumpRegressionData(FILE* aInputFile
, FILE* aOutputFile
) MOZ_OVERRIDE
;
36 static void Destroy(NodeList
* aLists
);
38 NodeList
* next
; // for lists of lists
47 static void Destroy(Node
* aNode
);
49 static Node
* Read(FILE* aFile
, Tag
* aTag
);
51 static Node
* ReadTree(FILE* aFile
);
65 static Tag
* Parse(FILE* aFile
);
67 void AddAttr(char* aAttr
, char* aValue
);
69 const char* GetAttr(const char* aAttr
);
71 void ReadAttrs(FILE* aFile
);
73 void ToString(nsString
& aResult
);
89 static char* Copy(const char* aString
);
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
);
97 nsFrameUtil::Copy(const char* aString
)
100 int l
= ::strlen(aString
);
101 char* c
= new char[l
+1];
104 memcpy(c
, aString
, l
+1);
110 //----------------------------------------------------------------------
112 nsFrameUtil::NodeList::NodeList()
113 : next(nullptr), node(nullptr), name(nullptr)
117 nsFrameUtil::NodeList::~NodeList()
119 if (nullptr != name
) {
122 if (nullptr != node
) {
128 nsFrameUtil::NodeList::Destroy(NodeList
* aLists
)
130 while (nullptr != aLists
) {
131 NodeList
* next
= aLists
->next
;
137 //----------------------------------------------------------------------
139 nsFrameUtil::Node::Node()
140 : next(nullptr), type(nullptr), state(0), lists(nullptr)
144 nsFrameUtil::Node::~Node()
146 if (nullptr != type
) {
149 if (nullptr != lists
) {
150 NodeList::Destroy(lists
);
155 nsFrameUtil::Node::Destroy(Node
* aList
)
157 while (nullptr != aList
) {
158 Node
* next
= aList
->next
;
164 static int32_t GetInt(nsFrameUtil::Tag
* aTag
, const char* aAttr
)
166 const char* value
= aTag
->GetAttr(aAttr
);
167 if (nullptr != value
) {
168 return int32_t( atoi(value
) );
174 nsFrameUtil::Node::ReadTree(FILE* aFile
)
176 Tag
* tag
= Tag::Parse(aFile
);
177 if (nullptr == tag
) {
180 if (PL_strcmp(tag
->name
, "frame") != 0) {
184 Node
* result
= Read(aFile
, tag
);
190 nsFrameUtil::Node::Read(FILE* aFile
, Tag
* tag
)
192 Node
* node
= new Node
;
193 node
->type
= Copy(tag
->GetAttr("type"));
197 node
->state
= GetInt(tag
, "state");
201 tag
= Tag::Parse(aFile
);
202 if (nullptr == tag
) break;
203 if (PL_strcmp(tag
->name
, "frame") == 0) {
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
);
214 else if (PL_strcmp(tag
->name
, "child-list") == 0) {
215 NodeList
* list
= new NodeList();
216 list
->name
= Copy(tag
->GetAttr("name"));
220 list
->next
= node
->lists
;
224 Node
** tailp
= &list
->node
;
226 tag
= Tag::Parse(aFile
);
227 if (nullptr == tag
) {
230 if (PL_strcmp(tag
->name
, "child-list") == 0) {
233 if (PL_strcmp(tag
->name
, "frame") != 0) {
236 Node
* child
= Node::Read(aFile
, tag
);
237 if (nullptr == child
) {
241 tailp
= &child
->next
;
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");
265 //----------------------------------------------------------------------
267 nsFrameUtil::Tag::Tag()
268 : name(nullptr), type(open
), attributes(nullptr), num(0), size(0),
273 nsFrameUtil::Tag::~Tag()
277 for (i
= 0; i
< n
; i
++) {
278 delete attributes
[i
];
287 nsFrameUtil::Tag::AddAttr(char* aAttr
, char* aValue
)
290 int32_t newSize
= size
* 2 + 4;
291 char** a
= new char*[newSize
];
292 char** v
= new char*[newSize
];
294 memcpy(a
, attributes
, num
* sizeof(char*));
295 memcpy(v
, values
, num
* sizeof(char*));
303 attributes
[num
] = aAttr
;
304 values
[num
] = aValue
;
309 nsFrameUtil::Tag::GetAttr(const char* aAttr
)
312 for (i
= 0; i
< n
; i
++) {
313 if (PL_strcmp(attributes
[i
], aAttr
) == 0) {
320 static inline int IsWhiteSpace(int c
) {
321 return (c
== ' ') || (c
== '\t') || (c
== '\n') || (c
== '\r');
324 static bool EatWS(FILE* aFile
)
331 if (!IsWhiteSpace(c
)) {
339 static bool Expect(FILE* aFile
, char aChar
)
342 if (c
< 0) return false;
350 static char* ReadIdent(FILE* aFile
)
354 char* end
= ip
+ sizeof(id
) - 1;
356 int c
= fgetc(aFile
);
357 if (c
< 0) return nullptr;
358 if ((c
== '=') || (c
== '>') || (c
== '/') || IsWhiteSpace(c
)) {
365 return nsFrameUtil::Copy(id
);
366 /* may return a null pointer */
369 static char* ReadString(FILE* aFile
)
371 if (!Expect(aFile
, '\"')) {
376 char* end
= ip
+ sizeof(id
) - 1;
378 int c
= fgetc(aFile
);
379 if (c
< 0) return nullptr;
386 return nsFrameUtil::Copy(id
);
387 /* may return a null pointer */
391 nsFrameUtil::Tag::ReadAttrs(FILE* aFile
)
403 if (Expect(aFile
, '>')) {
412 char* attr
= ReadIdent(aFile
);
413 if ((nullptr == attr
) || !EatWS(aFile
)) {
416 char* value
= nullptr;
417 if (Expect(aFile
, '=')) {
418 value
= ReadString(aFile
);
419 if (nullptr == value
) {
424 AddAttr(attr
, value
);
429 nsFrameUtil::Tag::Parse(FILE* aFile
)
434 if (Expect(aFile
, '<')) {
436 if (Expect(aFile
, '/')) {
442 tag
->name
= ReadIdent(aFile
);
443 tag
->ReadAttrs(aFile
);
450 nsFrameUtil::Tag::ToString(nsString
& aResult
)
453 aResult
.Append(char16_t('<'));
455 aResult
.Append(char16_t('/'));
457 aResult
.AppendASCII(name
);
460 for (i
= 0; i
< n
; i
++) {
461 aResult
.Append(char16_t(' '));
462 aResult
.AppendASCII(attributes
[i
]);
464 aResult
.AppendLiteral("=\"");
465 aResult
.AppendASCII(values
[i
]);
466 aResult
.Append(char16_t('\"'));
470 if (type
== openClose
) {
471 aResult
.Append(char16_t('/'));
473 aResult
.Append(char16_t('>'));
476 //----------------------------------------------------------------------
479 NS_NewFrameUtil(nsIFrameUtil
** aResult
)
481 NS_PRECONDITION(nullptr != aResult
, "null pointer");
482 if (nullptr == aResult
) {
483 return NS_ERROR_NULL_POINTER
;
486 nsFrameUtil
* it
= new nsFrameUtil();
488 NS_ADDREF(*aResult
= it
);
492 nsFrameUtil::nsFrameUtil()
496 nsFrameUtil::~nsFrameUtil()
500 NS_IMPL_ISUPPORTS(nsFrameUtil
, nsIFrameUtil
)
503 nsFrameUtil::DumpNode(Node
* aNode
, FILE* aOutputFile
, int32_t aIndent
)
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());
513 nsFrameUtil::DumpTree(Node
* aNode
, FILE* aOutputFile
, int32_t aIndent
)
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);
532 nsFrameUtil::CompareTrees(Node
* tree1
, Node
* tree2
)
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
) {
541 printf("first tree prematurely ends\n");
544 else if (nullptr == tree2
) {
545 printf("second tree prematurely ends\n");
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
);
553 DumpNode(tree1
, stdout
, 1);
555 DumpNode(tree2
, stdout
, 1);
559 // Ignore the XUL scrollbar frames
560 static const char kScrollbarFrame
[] = "ScrollbarFrame";
561 if (0 == PL_strncmp(tree1
->type
, kScrollbarFrame
, sizeof(kScrollbarFrame
) - 1))
564 if (tree1
->state
!= tree2
->state
) {
565 printf("frame state mismatch: 0x%x vs. 0x%x\n",
566 tree1
->state
, tree2
->state
);
568 DumpNode(tree1
, stdout
, 1);
570 DumpNode(tree2
, stdout
, 1);
571 result
= false; // we have a non-critical failure, so remember that but continue
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
);
580 DumpNode(tree1
, stdout
, 1);
582 DumpNode(tree2
, stdout
, 1);
583 result
= false; // we have a non-critical failure, so remember that but continue
585 if (tree1
->styleData
!= tree2
->styleData
) {
586 printf("frame style data mismatch: %s vs. %s\n",
587 tree1
->styleData
.get(),
588 tree2
->styleData
.get());
591 // Check child lists too
592 NodeList
* list1
= tree1
->lists
;
593 NodeList
* list2
= tree2
->lists
;
595 if (nullptr == list1
) {
596 if (nullptr != list2
) {
597 printf("first tree prematurely ends (no child lists)\n");
599 DumpNode(tree1
, stdout
, 1);
601 DumpNode(tree2
, stdout
, 1);
608 if (nullptr == list2
) {
609 printf("second tree prematurely ends (no child lists)\n");
611 DumpNode(tree1
, stdout
, 1);
613 DumpNode(tree2
, stdout
, 1);
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
623 bool equiv
= CompareTrees(list1
->node
, list2
->node
);
636 nsFrameUtil::CompareRegressionData(FILE* aFile1
, FILE* aFile2
,int32_t aRegressionOutput
)
638 Node
* tree1
= Node::ReadTree(aFile1
);
639 Node
* tree2
= Node::ReadTree(aFile2
);
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);
650 rv
= NS_ERROR_FAILURE
;
653 Node::Destroy(tree1
);
654 Node::Destroy(tree2
);
660 nsFrameUtil::DumpRegressionData(FILE* aInputFile
, FILE* aOutputFile
)
662 Node
* tree1
= Node::ReadTree(aInputFile
);
663 if (nullptr != tree1
) {
664 DumpTree(tree1
, aOutputFile
, 0);
665 Node::Destroy(tree1
);
668 return NS_ERROR_FAILURE
;