Bug 449371 Firefox/Thunderbird crashes at exit [@ gdk_display_x11_finalize], p=Brian...
[wine-gecko.git] / layout / generic / nsFrameUtil.cpp
blob1a406f40dc01e4b7b882fcf43344e2ce18651e3c
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
24 * Alternatively, the contents of this file may be used under the terms of
25 * either of the GNU General Public License Version 2 or later (the "GPL"),
26 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 /* utilities for regression tests based on frame tree comparison */
40 #include "nsIFrameUtil.h"
41 #include "nsFrame.h"
42 #include "nsString.h"
43 #include "nsRect.h"
44 #include <stdlib.h>
45 #include "plstr.h"
48 #ifdef NS_DEBUG
49 class nsFrameUtil : public nsIFrameUtil {
50 public:
51 nsFrameUtil();
52 virtual ~nsFrameUtil();
54 NS_DECL_ISUPPORTS
56 NS_IMETHOD CompareRegressionData(FILE* aFile1, FILE* aFile2,PRInt32 aRegressionOutput=0);
57 NS_IMETHOD DumpRegressionData(FILE* aInputFile, FILE* aOutputFile);
59 struct Node;
60 struct Tag;
62 struct NodeList {
63 NodeList();
64 ~NodeList();
66 static void Destroy(NodeList* aLists);
68 NodeList* next; // for lists of lists
69 Node* node;
70 char* name;
73 struct Node {
74 Node();
75 ~Node();
77 static void Destroy(Node* aNode);
79 static Node* Read(FILE* aFile, Tag* aTag);
81 static Node* ReadTree(FILE* aFile);
83 Node* next;
84 char* type;
85 PRUint32 state;
86 nsRect bbox;
87 nsCString styleData;
88 NodeList* lists;
91 struct Tag {
92 Tag();
93 ~Tag();
95 static Tag* Parse(FILE* aFile);
97 void AddAttr(char* aAttr, char* aValue);
99 const char* GetAttr(const char* aAttr);
101 void ReadAttrs(FILE* aFile);
103 void ToString(nsString& aResult);
105 enum Type {
106 open,
107 close,
108 openClose
111 char* name;
112 Type type;
113 char** attributes;
114 PRInt32 num;
115 PRInt32 size;
116 char** values;
119 static char* Copy(const char* aString);
121 static void DumpNode(Node* aNode, FILE* aOutputFile, PRInt32 aIndent);
122 static void DumpTree(Node* aNode, FILE* aOutputFile, PRInt32 aIndent);
123 static PRBool CompareTrees(Node* aNode1, Node* aNode2);
126 char*
127 nsFrameUtil::Copy(const char* aString)
129 if (aString) {
130 int l = ::strlen(aString);
131 char* c = new char[l+1];
132 if (!c)
133 return nsnull;
134 memcpy(c, aString, l+1);
135 return c;
137 return nsnull;
140 //----------------------------------------------------------------------
142 nsFrameUtil::NodeList::NodeList()
143 : next(nsnull), node(nsnull), name(nsnull)
147 nsFrameUtil::NodeList::~NodeList()
149 if (nsnull != name) {
150 delete name;
152 if (nsnull != node) {
153 Node::Destroy(node);
157 void
158 nsFrameUtil::NodeList::Destroy(NodeList* aLists)
160 while (nsnull != aLists) {
161 NodeList* next = aLists->next;
162 delete aLists;
163 aLists = next;
167 //----------------------------------------------------------------------
169 nsFrameUtil::Node::Node()
170 : next(nsnull), type(nsnull), state(0), lists(nsnull)
174 nsFrameUtil::Node::~Node()
176 if (nsnull != type) {
177 delete type;
179 if (nsnull != lists) {
180 NodeList::Destroy(lists);
184 void
185 nsFrameUtil::Node::Destroy(Node* aList)
187 while (nsnull != aList) {
188 Node* next = aList->next;
189 delete aList;
190 aList = next;
194 static PRInt32 GetInt(nsFrameUtil::Tag* aTag, const char* aAttr)
196 const char* value = aTag->GetAttr(aAttr);
197 if (nsnull != value) {
198 return PRInt32( atoi(value) );
200 return 0;
203 nsFrameUtil::Node*
204 nsFrameUtil::Node::ReadTree(FILE* aFile)
206 Tag* tag = Tag::Parse(aFile);
207 if (nsnull == tag) {
208 return nsnull;
210 if (PL_strcmp(tag->name, "frame") != 0) {
211 delete tag;
212 return nsnull;
214 Node* result = Read(aFile, tag);
215 fclose(aFile);
216 return result;
219 nsFrameUtil::Node*
220 nsFrameUtil::Node::Read(FILE* aFile, Tag* tag)
222 Node* node = new Node;
223 if (!node) {
224 /* crash() */
226 node->type = Copy(tag->GetAttr("type"));
227 if (!node->type) {
228 /* crash() */
230 node->state = GetInt(tag, "state");
231 delete tag;
233 for (;;) {
234 tag = Tag::Parse(aFile);
235 if (nsnull == tag) break;
236 if (PL_strcmp(tag->name, "frame") == 0) {
237 delete tag;
238 break;
240 if (PL_strcmp(tag->name, "bbox") == 0) {
241 nscoord x = nscoord( GetInt(tag, "x") );
242 nscoord y = nscoord( GetInt(tag, "y") );
243 nscoord w = nscoord( GetInt(tag, "w") );
244 nscoord h = nscoord( GetInt(tag, "h") );
245 node->bbox.SetRect(x, y, w, h);
247 else if (PL_strcmp(tag->name, "child-list") == 0) {
248 NodeList* list = new NodeList();
249 list->name = Copy(tag->GetAttr("name"));
250 if (!list->name) {
251 /* crash() */
253 list->next = node->lists;
254 node->lists = list;
255 delete tag;
257 Node** tailp = &list->node;
258 for (;;) {
259 tag = Tag::Parse(aFile);
260 if (nsnull == tag) {
261 break;
263 if (PL_strcmp(tag->name, "child-list") == 0) {
264 break;
266 if (PL_strcmp(tag->name, "frame") != 0) {
267 break;
269 Node* child = Node::Read(aFile, tag);
270 if (nsnull == child) {
271 break;
273 *tailp = child;
274 tailp = &child->next;
277 else if((PL_strcmp(tag->name, "font") == 0) ||
278 (PL_strcmp(tag->name, "color") == 0) ||
279 (PL_strcmp(tag->name, "spacing") == 0) ||
280 (PL_strcmp(tag->name, "list") == 0) ||
281 (PL_strcmp(tag->name, "position") == 0) ||
282 (PL_strcmp(tag->name, "text") == 0) ||
283 (PL_strcmp(tag->name, "display") == 0) ||
284 (PL_strcmp(tag->name, "table") == 0) ||
285 (PL_strcmp(tag->name, "content") == 0) ||
286 (PL_strcmp(tag->name, "UI") == 0) ||
287 (PL_strcmp(tag->name, "print") == 0)) {
288 const char* attr = tag->GetAttr("data");
289 node->styleData.Append('|');
290 node->styleData.Append(attr ? attr : "null attr");
293 delete tag;
295 return node;
298 //----------------------------------------------------------------------
300 nsFrameUtil::Tag::Tag()
301 : name(nsnull), type(open), attributes(nsnull), num(0), size(0),
302 values(nsnull)
306 nsFrameUtil::Tag::~Tag()
308 PRInt32 i, n = num;
309 if (0 != n) {
310 for (i = 0; i < n; i++) {
311 delete attributes[i];
312 delete values[i];
314 delete attributes;
315 delete values;
319 void
320 nsFrameUtil::Tag::AddAttr(char* aAttr, char* aValue)
322 if (num == size) {
323 PRInt32 newSize = size * 2 + 4;
324 char** a = new char*[newSize];
325 char** v = new char*[newSize];
326 if (0 != num) {
327 memcpy(a, attributes, num * sizeof(char*));
328 memcpy(v, values, num * sizeof(char*));
329 delete attributes;
330 delete values;
332 attributes = a;
333 values = v;
334 size = newSize;
336 attributes[num] = aAttr;
337 values[num] = aValue;
338 num = num + 1;
341 const char*
342 nsFrameUtil::Tag::GetAttr(const char* aAttr)
344 PRInt32 i, n = num;
345 for (i = 0; i < n; i++) {
346 if (PL_strcmp(attributes[i], aAttr) == 0) {
347 return values[i];
350 return nsnull;
353 static inline int IsWhiteSpace(int c) {
354 return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r');
357 static PRBool EatWS(FILE* aFile)
359 for (;;) {
360 int c = getc(aFile);
361 if (c < 0) {
362 return PR_FALSE;
364 if (!IsWhiteSpace(c)) {
365 ungetc(c, aFile);
366 break;
369 return PR_TRUE;
372 static PRBool Expect(FILE* aFile, char aChar)
374 int c = getc(aFile);
375 if (c < 0) return PR_FALSE;
376 if (c != aChar) {
377 ungetc(c, aFile);
378 return PR_FALSE;
380 return PR_TRUE;
383 static char* ReadIdent(FILE* aFile)
385 char id[1000];
386 char* ip = id;
387 char* end = ip + sizeof(id) - 1;
388 while (ip < end) {
389 int c = fgetc(aFile);
390 if (c < 0) return nsnull;
391 if ((c == '=') || (c == '>') || (c == '/') || IsWhiteSpace(c)) {
392 ungetc(c, aFile);
393 break;
395 *ip++ = char(c);
397 *ip = '\0';
398 return nsFrameUtil::Copy(id);
399 /* may return a null pointer */
402 static char* ReadString(FILE* aFile)
404 if (!Expect(aFile, '\"')) {
405 return nsnull;
407 char id[1000];
408 char* ip = id;
409 char* end = ip + sizeof(id) - 1;
410 while (ip < end) {
411 int c = fgetc(aFile);
412 if (c < 0) return nsnull;
413 if (c == '\"') {
414 break;
416 *ip++ = char(c);
418 *ip = '\0';
419 return nsFrameUtil::Copy(id);
420 /* may return a null pointer */
423 void
424 nsFrameUtil::Tag::ReadAttrs(FILE* aFile)
426 for (;;) {
427 if (!EatWS(aFile)) {
428 break;
430 int c = getc(aFile);
431 if (c < 0) break;
432 if (c == '/') {
433 if (!EatWS(aFile)) {
434 return;
436 if (Expect(aFile, '>')) {
437 type = openClose;
438 break;
441 else if (c == '>') {
442 break;
444 ungetc(c, aFile);
445 char* attr = ReadIdent(aFile);
446 if ((nsnull == attr) || !EatWS(aFile)) {
447 break;
449 char* value = nsnull;
450 if (Expect(aFile, '=')) {
451 value = ReadString(aFile);
452 if (nsnull == value) {
453 delete [] attr;
454 break;
457 AddAttr(attr, value);
461 nsFrameUtil::Tag*
462 nsFrameUtil::Tag::Parse(FILE* aFile)
464 if (!EatWS(aFile)) {
465 return nsnull;
467 if (Expect(aFile, '<')) {
468 Tag* tag = new Tag;
469 if (Expect(aFile, '/')) {
470 tag->type = close;
472 else {
473 tag->type = open;
475 tag->name = ReadIdent(aFile);
476 tag->ReadAttrs(aFile);
477 return tag;
479 return nsnull;
482 void
483 nsFrameUtil::Tag::ToString(nsString& aResult)
485 aResult.Truncate();
486 aResult.Append(PRUnichar('<'));
487 if (type == close) {
488 aResult.Append(PRUnichar('/'));
490 aResult.AppendASCII(name);
491 if (0 != num) {
492 PRInt32 i, n = num;
493 for (i = 0; i < n; i++) {
494 aResult.Append(PRUnichar(' '));
495 aResult.AppendASCII(attributes[i]);
496 if (values[i]) {
497 aResult.AppendLiteral("=\"");
498 aResult.AppendASCII(values[i]);
499 aResult.Append(PRUnichar('\"'));
503 if (type == openClose) {
504 aResult.Append(PRUnichar('/'));
506 aResult.Append(PRUnichar('>'));
509 //----------------------------------------------------------------------
511 nsresult NS_NewFrameUtil(nsIFrameUtil** aResult);
512 nsresult
513 NS_NewFrameUtil(nsIFrameUtil** aResult)
515 NS_PRECONDITION(nsnull != aResult, "null pointer");
516 if (nsnull == aResult) {
517 return NS_ERROR_NULL_POINTER;
519 *aResult = nsnull;
521 nsFrameUtil* it = new nsFrameUtil();
522 if (nsnull == it) {
523 return NS_ERROR_OUT_OF_MEMORY;
525 return it->QueryInterface(NS_GET_IID(nsIFrameUtil), (void**) aResult);
528 nsFrameUtil::nsFrameUtil()
532 nsFrameUtil::~nsFrameUtil()
536 NS_IMPL_ISUPPORTS1(nsFrameUtil, nsIFrameUtil)
538 void
539 nsFrameUtil::DumpNode(Node* aNode, FILE* aOutputFile, PRInt32 aIndent)
541 nsFrame::IndentBy(aOutputFile, aIndent);
542 fprintf(aOutputFile, "%s 0x%x %d,%d,%d,%d, %s\n", aNode->type, aNode->state,
543 aNode->bbox.x, aNode->bbox.y,
544 aNode->bbox.width, aNode->bbox.height,
545 aNode->styleData.get());
548 void
549 nsFrameUtil::DumpTree(Node* aNode, FILE* aOutputFile, PRInt32 aIndent)
551 while (nsnull != aNode) {
552 DumpNode(aNode, aOutputFile, aIndent);
553 nsFrameUtil::NodeList* lists = aNode->lists;
554 if (nsnull != lists) {
555 while (nsnull != lists) {
556 nsFrame::IndentBy(aOutputFile, aIndent);
557 fprintf(aOutputFile, " list: %s\n",
558 lists->name ? lists->name : "primary");
559 DumpTree(lists->node, aOutputFile, aIndent + 1);
560 lists = lists->next;
563 aNode = aNode->next;
567 PRBool
568 nsFrameUtil::CompareTrees(Node* tree1, Node* tree2)
570 PRBool result = PR_TRUE;
571 for (;; tree1 = tree1->next, tree2 = tree2->next) {
572 // Make sure both nodes are non-null, or at least agree with each other
573 if (nsnull == tree1) {
574 if (nsnull == tree2) {
575 break;
577 printf("first tree prematurely ends\n");
578 return PR_FALSE;
580 else if (nsnull == tree2) {
581 printf("second tree prematurely ends\n");
582 return PR_FALSE;
585 // Check the attributes that we care about
586 if (0 != PL_strcmp(tree1->type, tree2->type)) {
587 printf("frame type mismatch: %s vs. %s\n", tree1->type, tree2->type);
588 printf("Node 1:\n");
589 DumpNode(tree1, stdout, 1);
590 printf("Node 2:\n");
591 DumpNode(tree2, stdout, 1);
592 return PR_FALSE;
595 // Ignore the XUL scrollbar frames
596 static const char kScrollbarFrame[] = "ScrollbarFrame";
597 if (0 == PL_strncmp(tree1->type, kScrollbarFrame, sizeof(kScrollbarFrame) - 1))
598 continue;
600 if (tree1->state != tree2->state) {
601 printf("frame state mismatch: 0x%x vs. 0x%x\n",
602 tree1->state, tree2->state);
603 printf("Node 1:\n");
604 DumpNode(tree1, stdout, 1);
605 printf("Node 2:\n");
606 DumpNode(tree2, stdout, 1);
607 result = PR_FALSE; // we have a non-critical failure, so remember that but continue
609 if (tree1->bbox != tree2->bbox) {
610 printf("frame bbox mismatch: %d,%d,%d,%d vs. %d,%d,%d,%d\n",
611 tree1->bbox.x, tree1->bbox.y,
612 tree1->bbox.width, tree1->bbox.height,
613 tree2->bbox.x, tree2->bbox.y,
614 tree2->bbox.width, tree2->bbox.height);
615 printf("Node 1:\n");
616 DumpNode(tree1, stdout, 1);
617 printf("Node 2:\n");
618 DumpNode(tree2, stdout, 1);
619 result = PR_FALSE; // we have a non-critical failure, so remember that but continue
621 if (tree1->styleData != tree2->styleData) {
622 printf("frame style data mismatch: %s vs. %s\n",
623 tree1->styleData.get(),
624 tree2->styleData.get());
627 // Check child lists too
628 NodeList* list1 = tree1->lists;
629 NodeList* list2 = tree2->lists;
630 for (;;) {
631 if (nsnull == list1) {
632 if (nsnull != list2) {
633 printf("first tree prematurely ends (no child lists)\n");
634 printf("Node 1:\n");
635 DumpNode(tree1, stdout, 1);
636 printf("Node 2:\n");
637 DumpNode(tree2, stdout, 1);
638 return PR_FALSE;
640 else {
641 break;
644 if (nsnull == list2) {
645 printf("second tree prematurely ends (no child lists)\n");
646 printf("Node 1:\n");
647 DumpNode(tree1, stdout, 1);
648 printf("Node 2:\n");
649 DumpNode(tree2, stdout, 1);
650 return PR_FALSE;
652 if (0 != PL_strcmp(list1->name, list2->name)) {
653 printf("child-list name mismatch: %s vs. %s\n",
654 list1->name ? list1->name : "(null)",
655 list2->name ? list2->name : "(null)");
656 result = PR_FALSE; // we have a non-critical failure, so remember that but continue
658 else {
659 PRBool equiv = CompareTrees(list1->node, list2->node);
660 if (!equiv) {
661 return equiv;
664 list1 = list1->next;
665 list2 = list2->next;
668 return result;
671 NS_IMETHODIMP
672 nsFrameUtil::CompareRegressionData(FILE* aFile1, FILE* aFile2,PRInt32 aRegressionOutput)
674 Node* tree1 = Node::ReadTree(aFile1);
675 Node* tree2 = Node::ReadTree(aFile2);
677 nsresult rv = NS_OK;
678 if (!CompareTrees(tree1, tree2)) {
679 // only output this if aRegressionOutput is 0
680 if( 0 == aRegressionOutput ){
681 printf("Regression data 1:\n");
682 DumpTree(tree1, stdout, 0);
683 printf("Regression data 2:\n");
684 DumpTree(tree2, stdout, 0);
686 rv = NS_ERROR_FAILURE;
689 Node::Destroy(tree1);
690 Node::Destroy(tree2);
692 return rv;
695 NS_IMETHODIMP
696 nsFrameUtil::DumpRegressionData(FILE* aInputFile, FILE* aOutputFile)
698 Node* tree1 = Node::ReadTree(aInputFile);
699 if (nsnull != tree1) {
700 DumpTree(tree1, aOutputFile, 0);
701 Node::Destroy(tree1);
702 return NS_OK;
704 return NS_ERROR_FAILURE;
706 #endif