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