michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* utilities for regression tests based on frame tree comparison */ michael@0: michael@0: #include "nsIFrameUtil.h" michael@0: #include "nsFrame.h" michael@0: #include "nsString.h" michael@0: #include "nsRect.h" michael@0: #include michael@0: #include "plstr.h" michael@0: michael@0: michael@0: #ifdef DEBUG michael@0: class nsFrameUtil : public nsIFrameUtil { michael@0: public: michael@0: nsFrameUtil(); michael@0: virtual ~nsFrameUtil(); michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD CompareRegressionData(FILE* aFile1, FILE* aFile2,int32_t aRegressionOutput=0); michael@0: NS_IMETHOD DumpRegressionData(FILE* aInputFile, FILE* aOutputFile); michael@0: michael@0: struct Node; michael@0: struct Tag; michael@0: michael@0: struct NodeList { michael@0: NodeList(); michael@0: ~NodeList(); michael@0: michael@0: static void Destroy(NodeList* aLists); michael@0: michael@0: NodeList* next; // for lists of lists michael@0: Node* node; michael@0: char* name; michael@0: }; michael@0: michael@0: struct Node { michael@0: Node(); michael@0: ~Node(); michael@0: michael@0: static void Destroy(Node* aNode); michael@0: michael@0: static Node* Read(FILE* aFile, Tag* aTag); michael@0: michael@0: static Node* ReadTree(FILE* aFile); michael@0: michael@0: Node* next; michael@0: char* type; michael@0: uint32_t state; michael@0: nsRect bbox; michael@0: nsCString styleData; michael@0: NodeList* lists; michael@0: }; michael@0: michael@0: struct Tag { michael@0: Tag(); michael@0: ~Tag(); michael@0: michael@0: static Tag* Parse(FILE* aFile); michael@0: michael@0: void AddAttr(char* aAttr, char* aValue); michael@0: michael@0: const char* GetAttr(const char* aAttr); michael@0: michael@0: void ReadAttrs(FILE* aFile); michael@0: michael@0: void ToString(nsString& aResult); michael@0: michael@0: enum Type { michael@0: open, michael@0: close, michael@0: openClose michael@0: }; michael@0: michael@0: char* name; michael@0: Type type; michael@0: char** attributes; michael@0: int32_t num; michael@0: int32_t size; michael@0: char** values; michael@0: }; michael@0: michael@0: static char* Copy(const char* aString); michael@0: michael@0: static void DumpNode(Node* aNode, FILE* aOutputFile, int32_t aIndent); michael@0: static void DumpTree(Node* aNode, FILE* aOutputFile, int32_t aIndent); michael@0: static bool CompareTrees(Node* aNode1, Node* aNode2); michael@0: }; michael@0: michael@0: char* michael@0: nsFrameUtil::Copy(const char* aString) michael@0: { michael@0: if (aString) { michael@0: int l = ::strlen(aString); michael@0: char* c = new char[l+1]; michael@0: if (!c) michael@0: return nullptr; michael@0: memcpy(c, aString, l+1); michael@0: return c; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: nsFrameUtil::NodeList::NodeList() michael@0: : next(nullptr), node(nullptr), name(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsFrameUtil::NodeList::~NodeList() michael@0: { michael@0: if (nullptr != name) { michael@0: delete name; michael@0: } michael@0: if (nullptr != node) { michael@0: Node::Destroy(node); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFrameUtil::NodeList::Destroy(NodeList* aLists) michael@0: { michael@0: while (nullptr != aLists) { michael@0: NodeList* next = aLists->next; michael@0: delete aLists; michael@0: aLists = next; michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: nsFrameUtil::Node::Node() michael@0: : next(nullptr), type(nullptr), state(0), lists(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsFrameUtil::Node::~Node() michael@0: { michael@0: if (nullptr != type) { michael@0: delete type; michael@0: } michael@0: if (nullptr != lists) { michael@0: NodeList::Destroy(lists); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFrameUtil::Node::Destroy(Node* aList) michael@0: { michael@0: while (nullptr != aList) { michael@0: Node* next = aList->next; michael@0: delete aList; michael@0: aList = next; michael@0: } michael@0: } michael@0: michael@0: static int32_t GetInt(nsFrameUtil::Tag* aTag, const char* aAttr) michael@0: { michael@0: const char* value = aTag->GetAttr(aAttr); michael@0: if (nullptr != value) { michael@0: return int32_t( atoi(value) ); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: nsFrameUtil::Node* michael@0: nsFrameUtil::Node::ReadTree(FILE* aFile) michael@0: { michael@0: Tag* tag = Tag::Parse(aFile); michael@0: if (nullptr == tag) { michael@0: return nullptr; michael@0: } michael@0: if (PL_strcmp(tag->name, "frame") != 0) { michael@0: delete tag; michael@0: return nullptr; michael@0: } michael@0: Node* result = Read(aFile, tag); michael@0: fclose(aFile); michael@0: return result; michael@0: } michael@0: michael@0: nsFrameUtil::Node* michael@0: nsFrameUtil::Node::Read(FILE* aFile, Tag* tag) michael@0: { michael@0: Node* node = new Node; michael@0: node->type = Copy(tag->GetAttr("type")); michael@0: if (!node->type) { michael@0: /* crash() */ michael@0: } michael@0: node->state = GetInt(tag, "state"); michael@0: delete tag; michael@0: michael@0: for (;;) { michael@0: tag = Tag::Parse(aFile); michael@0: if (nullptr == tag) break; michael@0: if (PL_strcmp(tag->name, "frame") == 0) { michael@0: delete tag; michael@0: break; michael@0: } michael@0: if (PL_strcmp(tag->name, "bbox") == 0) { michael@0: nscoord x = nscoord( GetInt(tag, "x") ); michael@0: nscoord y = nscoord( GetInt(tag, "y") ); michael@0: nscoord w = nscoord( GetInt(tag, "w") ); michael@0: nscoord h = nscoord( GetInt(tag, "h") ); michael@0: node->bbox.SetRect(x, y, w, h); michael@0: } michael@0: else if (PL_strcmp(tag->name, "child-list") == 0) { michael@0: NodeList* list = new NodeList(); michael@0: list->name = Copy(tag->GetAttr("name")); michael@0: if (!list->name) { michael@0: /* crash() */ michael@0: } michael@0: list->next = node->lists; michael@0: node->lists = list; michael@0: delete tag; michael@0: michael@0: Node** tailp = &list->node; michael@0: for (;;) { michael@0: tag = Tag::Parse(aFile); michael@0: if (nullptr == tag) { michael@0: break; michael@0: } michael@0: if (PL_strcmp(tag->name, "child-list") == 0) { michael@0: break; michael@0: } michael@0: if (PL_strcmp(tag->name, "frame") != 0) { michael@0: break; michael@0: } michael@0: Node* child = Node::Read(aFile, tag); michael@0: if (nullptr == child) { michael@0: break; michael@0: } michael@0: *tailp = child; michael@0: tailp = &child->next; michael@0: } michael@0: } michael@0: else if((PL_strcmp(tag->name, "font") == 0) || michael@0: (PL_strcmp(tag->name, "color") == 0) || michael@0: (PL_strcmp(tag->name, "spacing") == 0) || michael@0: (PL_strcmp(tag->name, "list") == 0) || michael@0: (PL_strcmp(tag->name, "position") == 0) || michael@0: (PL_strcmp(tag->name, "text") == 0) || michael@0: (PL_strcmp(tag->name, "display") == 0) || michael@0: (PL_strcmp(tag->name, "table") == 0) || michael@0: (PL_strcmp(tag->name, "content") == 0) || michael@0: (PL_strcmp(tag->name, "UI") == 0) || michael@0: (PL_strcmp(tag->name, "print") == 0)) { michael@0: const char* attr = tag->GetAttr("data"); michael@0: node->styleData.Append('|'); michael@0: node->styleData.Append(attr ? attr : "null attr"); michael@0: } michael@0: michael@0: delete tag; michael@0: } michael@0: return node; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: nsFrameUtil::Tag::Tag() michael@0: : name(nullptr), type(open), attributes(nullptr), num(0), size(0), michael@0: values(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsFrameUtil::Tag::~Tag() michael@0: { michael@0: int32_t i, n = num; michael@0: if (0 != n) { michael@0: for (i = 0; i < n; i++) { michael@0: delete attributes[i]; michael@0: delete values[i]; michael@0: } michael@0: delete attributes; michael@0: delete values; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFrameUtil::Tag::AddAttr(char* aAttr, char* aValue) michael@0: { michael@0: if (num == size) { michael@0: int32_t newSize = size * 2 + 4; michael@0: char** a = new char*[newSize]; michael@0: char** v = new char*[newSize]; michael@0: if (0 != num) { michael@0: memcpy(a, attributes, num * sizeof(char*)); michael@0: memcpy(v, values, num * sizeof(char*)); michael@0: delete attributes; michael@0: delete values; michael@0: } michael@0: attributes = a; michael@0: values = v; michael@0: size = newSize; michael@0: } michael@0: attributes[num] = aAttr; michael@0: values[num] = aValue; michael@0: num = num + 1; michael@0: } michael@0: michael@0: const char* michael@0: nsFrameUtil::Tag::GetAttr(const char* aAttr) michael@0: { michael@0: int32_t i, n = num; michael@0: for (i = 0; i < n; i++) { michael@0: if (PL_strcmp(attributes[i], aAttr) == 0) { michael@0: return values[i]; michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: static inline int IsWhiteSpace(int c) { michael@0: return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'); michael@0: } michael@0: michael@0: static bool EatWS(FILE* aFile) michael@0: { michael@0: for (;;) { michael@0: int c = getc(aFile); michael@0: if (c < 0) { michael@0: return false; michael@0: } michael@0: if (!IsWhiteSpace(c)) { michael@0: ungetc(c, aFile); michael@0: break; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool Expect(FILE* aFile, char aChar) michael@0: { michael@0: int c = getc(aFile); michael@0: if (c < 0) return false; michael@0: if (c != aChar) { michael@0: ungetc(c, aFile); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static char* ReadIdent(FILE* aFile) michael@0: { michael@0: char id[1000]; michael@0: char* ip = id; michael@0: char* end = ip + sizeof(id) - 1; michael@0: while (ip < end) { michael@0: int c = fgetc(aFile); michael@0: if (c < 0) return nullptr; michael@0: if ((c == '=') || (c == '>') || (c == '/') || IsWhiteSpace(c)) { michael@0: ungetc(c, aFile); michael@0: break; michael@0: } michael@0: *ip++ = char(c); michael@0: } michael@0: *ip = '\0'; michael@0: return nsFrameUtil::Copy(id); michael@0: /* may return a null pointer */ michael@0: } michael@0: michael@0: static char* ReadString(FILE* aFile) michael@0: { michael@0: if (!Expect(aFile, '\"')) { michael@0: return nullptr; michael@0: } michael@0: char id[1000]; michael@0: char* ip = id; michael@0: char* end = ip + sizeof(id) - 1; michael@0: while (ip < end) { michael@0: int c = fgetc(aFile); michael@0: if (c < 0) return nullptr; michael@0: if (c == '\"') { michael@0: break; michael@0: } michael@0: *ip++ = char(c); michael@0: } michael@0: *ip = '\0'; michael@0: return nsFrameUtil::Copy(id); michael@0: /* may return a null pointer */ michael@0: } michael@0: michael@0: void michael@0: nsFrameUtil::Tag::ReadAttrs(FILE* aFile) michael@0: { michael@0: for (;;) { michael@0: if (!EatWS(aFile)) { michael@0: break; michael@0: } michael@0: int c = getc(aFile); michael@0: if (c < 0) break; michael@0: if (c == '/') { michael@0: if (!EatWS(aFile)) { michael@0: return; michael@0: } michael@0: if (Expect(aFile, '>')) { michael@0: type = openClose; michael@0: break; michael@0: } michael@0: } michael@0: else if (c == '>') { michael@0: break; michael@0: } michael@0: ungetc(c, aFile); michael@0: char* attr = ReadIdent(aFile); michael@0: if ((nullptr == attr) || !EatWS(aFile)) { michael@0: break; michael@0: } michael@0: char* value = nullptr; michael@0: if (Expect(aFile, '=')) { michael@0: value = ReadString(aFile); michael@0: if (nullptr == value) { michael@0: delete [] attr; michael@0: break; michael@0: } michael@0: } michael@0: AddAttr(attr, value); michael@0: } michael@0: } michael@0: michael@0: nsFrameUtil::Tag* michael@0: nsFrameUtil::Tag::Parse(FILE* aFile) michael@0: { michael@0: if (!EatWS(aFile)) { michael@0: return nullptr; michael@0: } michael@0: if (Expect(aFile, '<')) { michael@0: Tag* tag = new Tag; michael@0: if (Expect(aFile, '/')) { michael@0: tag->type = close; michael@0: } michael@0: else { michael@0: tag->type = open; michael@0: } michael@0: tag->name = ReadIdent(aFile); michael@0: tag->ReadAttrs(aFile); michael@0: return tag; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsFrameUtil::Tag::ToString(nsString& aResult) michael@0: { michael@0: aResult.Truncate(); michael@0: aResult.Append(char16_t('<')); michael@0: if (type == close) { michael@0: aResult.Append(char16_t('/')); michael@0: } michael@0: aResult.AppendASCII(name); michael@0: if (0 != num) { michael@0: int32_t i, n = num; michael@0: for (i = 0; i < n; i++) { michael@0: aResult.Append(char16_t(' ')); michael@0: aResult.AppendASCII(attributes[i]); michael@0: if (values[i]) { michael@0: aResult.AppendLiteral("=\""); michael@0: aResult.AppendASCII(values[i]); michael@0: aResult.Append(char16_t('\"')); michael@0: } michael@0: } michael@0: } michael@0: if (type == openClose) { michael@0: aResult.Append(char16_t('/')); michael@0: } michael@0: aResult.Append(char16_t('>')); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: NS_NewFrameUtil(nsIFrameUtil** aResult) michael@0: { michael@0: NS_PRECONDITION(nullptr != aResult, "null pointer"); michael@0: if (nullptr == aResult) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: nsFrameUtil* it = new nsFrameUtil(); michael@0: michael@0: NS_ADDREF(*aResult = it); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsFrameUtil::nsFrameUtil() michael@0: { michael@0: } michael@0: michael@0: nsFrameUtil::~nsFrameUtil() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsFrameUtil, nsIFrameUtil) michael@0: michael@0: void michael@0: nsFrameUtil::DumpNode(Node* aNode, FILE* aOutputFile, int32_t aIndent) michael@0: { michael@0: nsFrame::IndentBy(aOutputFile, aIndent); michael@0: fprintf(aOutputFile, "%s 0x%x %d,%d,%d,%d, %s\n", aNode->type, aNode->state, michael@0: aNode->bbox.x, aNode->bbox.y, michael@0: aNode->bbox.width, aNode->bbox.height, michael@0: aNode->styleData.get()); michael@0: } michael@0: michael@0: void michael@0: nsFrameUtil::DumpTree(Node* aNode, FILE* aOutputFile, int32_t aIndent) michael@0: { michael@0: while (nullptr != aNode) { michael@0: DumpNode(aNode, aOutputFile, aIndent); michael@0: nsFrameUtil::NodeList* lists = aNode->lists; michael@0: if (nullptr != lists) { michael@0: while (nullptr != lists) { michael@0: nsFrame::IndentBy(aOutputFile, aIndent); michael@0: fprintf(aOutputFile, " list: %s\n", michael@0: lists->name ? lists->name : "primary"); michael@0: DumpTree(lists->node, aOutputFile, aIndent + 1); michael@0: lists = lists->next; michael@0: } michael@0: } michael@0: aNode = aNode->next; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsFrameUtil::CompareTrees(Node* tree1, Node* tree2) michael@0: { michael@0: bool result = true; michael@0: for (;; tree1 = tree1->next, tree2 = tree2->next) { michael@0: // Make sure both nodes are non-null, or at least agree with each other michael@0: if (nullptr == tree1) { michael@0: if (nullptr == tree2) { michael@0: break; michael@0: } michael@0: printf("first tree prematurely ends\n"); michael@0: return false; michael@0: } michael@0: else if (nullptr == tree2) { michael@0: printf("second tree prematurely ends\n"); michael@0: return false; michael@0: } michael@0: michael@0: // Check the attributes that we care about michael@0: if (0 != PL_strcmp(tree1->type, tree2->type)) { michael@0: printf("frame type mismatch: %s vs. %s\n", tree1->type, tree2->type); michael@0: printf("Node 1:\n"); michael@0: DumpNode(tree1, stdout, 1); michael@0: printf("Node 2:\n"); michael@0: DumpNode(tree2, stdout, 1); michael@0: return false; michael@0: } michael@0: michael@0: // Ignore the XUL scrollbar frames michael@0: static const char kScrollbarFrame[] = "ScrollbarFrame"; michael@0: if (0 == PL_strncmp(tree1->type, kScrollbarFrame, sizeof(kScrollbarFrame) - 1)) michael@0: continue; michael@0: michael@0: if (tree1->state != tree2->state) { michael@0: printf("frame state mismatch: 0x%x vs. 0x%x\n", michael@0: tree1->state, tree2->state); michael@0: printf("Node 1:\n"); michael@0: DumpNode(tree1, stdout, 1); michael@0: printf("Node 2:\n"); michael@0: DumpNode(tree2, stdout, 1); michael@0: result = false; // we have a non-critical failure, so remember that but continue michael@0: } michael@0: if (tree1->bbox.IsEqualInterior(tree2->bbox)) { michael@0: printf("frame bbox mismatch: %d,%d,%d,%d vs. %d,%d,%d,%d\n", michael@0: tree1->bbox.x, tree1->bbox.y, michael@0: tree1->bbox.width, tree1->bbox.height, michael@0: tree2->bbox.x, tree2->bbox.y, michael@0: tree2->bbox.width, tree2->bbox.height); michael@0: printf("Node 1:\n"); michael@0: DumpNode(tree1, stdout, 1); michael@0: printf("Node 2:\n"); michael@0: DumpNode(tree2, stdout, 1); michael@0: result = false; // we have a non-critical failure, so remember that but continue michael@0: } michael@0: if (tree1->styleData != tree2->styleData) { michael@0: printf("frame style data mismatch: %s vs. %s\n", michael@0: tree1->styleData.get(), michael@0: tree2->styleData.get()); michael@0: } michael@0: michael@0: // Check child lists too michael@0: NodeList* list1 = tree1->lists; michael@0: NodeList* list2 = tree2->lists; michael@0: for (;;) { michael@0: if (nullptr == list1) { michael@0: if (nullptr != list2) { michael@0: printf("first tree prematurely ends (no child lists)\n"); michael@0: printf("Node 1:\n"); michael@0: DumpNode(tree1, stdout, 1); michael@0: printf("Node 2:\n"); michael@0: DumpNode(tree2, stdout, 1); michael@0: return false; michael@0: } michael@0: else { michael@0: break; michael@0: } michael@0: } michael@0: if (nullptr == list2) { michael@0: printf("second tree prematurely ends (no child lists)\n"); michael@0: printf("Node 1:\n"); michael@0: DumpNode(tree1, stdout, 1); michael@0: printf("Node 2:\n"); michael@0: DumpNode(tree2, stdout, 1); michael@0: return false; michael@0: } michael@0: if (0 != PL_strcmp(list1->name, list2->name)) { michael@0: printf("child-list name mismatch: %s vs. %s\n", michael@0: list1->name ? list1->name : "(null)", michael@0: list2->name ? list2->name : "(null)"); michael@0: result = false; // we have a non-critical failure, so remember that but continue michael@0: } michael@0: else { michael@0: bool equiv = CompareTrees(list1->node, list2->node); michael@0: if (!equiv) { michael@0: return equiv; michael@0: } michael@0: } michael@0: list1 = list1->next; michael@0: list2 = list2->next; michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFrameUtil::CompareRegressionData(FILE* aFile1, FILE* aFile2,int32_t aRegressionOutput) michael@0: { michael@0: Node* tree1 = Node::ReadTree(aFile1); michael@0: Node* tree2 = Node::ReadTree(aFile2); michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (!CompareTrees(tree1, tree2)) { michael@0: // only output this if aRegressionOutput is 0 michael@0: if( 0 == aRegressionOutput ){ michael@0: printf("Regression data 1:\n"); michael@0: DumpTree(tree1, stdout, 0); michael@0: printf("Regression data 2:\n"); michael@0: DumpTree(tree2, stdout, 0); michael@0: } michael@0: rv = NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: Node::Destroy(tree1); michael@0: Node::Destroy(tree2); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFrameUtil::DumpRegressionData(FILE* aInputFile, FILE* aOutputFile) michael@0: { michael@0: Node* tree1 = Node::ReadTree(aInputFile); michael@0: if (nullptr != tree1) { michael@0: DumpTree(tree1, aOutputFile, 0); michael@0: Node::Destroy(tree1); michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: #endif