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