1 /*
2 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "RenderTreeAsText.h"
28
29 #include "CSSMutableStyleDeclaration.h"
30 #include "CharacterNames.h"
31 #include "Document.h"
32 #include "Frame.h"
33 #include "FrameView.h"
34 #include "HTMLElement.h"
35 #include "HTMLNames.h"
36 #include "InlineTextBox.h"
37 #include "RenderBR.h"
38 #include "RenderInline.h"
39 #include "RenderListMarker.h"
40 #include "RenderTableCell.h"
41 #include "RenderView.h"
42 #include "RenderWidget.h"
43 #include "SelectionController.h"
44 #include "TextStream.h"
45 #include <wtf/Vector.h>
46
47 #if ENABLE(SVG)
48 #include "RenderPath.h"
49 #include "RenderSVGContainer.h"
50 #include "RenderSVGImage.h"
51 #include "RenderSVGInlineText.h"
52 #include "RenderSVGRoot.h"
53 #include "RenderSVGText.h"
54 #include "SVGRenderTreeAsText.h"
55 #endif
56
57 namespace WebCore {
58
59 using namespace HTMLNames;
60
61 static void writeLayers(TextStream&, const RenderLayer* rootLayer, RenderLayer*, const IntRect& paintDirtyRect, int indent = 0);
62
63 #if !ENABLE(SVG)
operator <<(TextStream & ts,const IntRect & r)64 static TextStream &operator<<(TextStream& ts, const IntRect& r)
65 {
66 return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height();
67 }
68 #endif
69
writeIndent(TextStream & ts,int indent)70 static void writeIndent(TextStream& ts, int indent)
71 {
72 for (int i = 0; i != indent; ++i)
73 ts << " ";
74 }
75
printBorderStyle(TextStream & ts,const EBorderStyle borderStyle)76 static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle)
77 {
78 switch (borderStyle) {
79 case BNONE:
80 ts << "none";
81 break;
82 case BHIDDEN:
83 ts << "hidden";
84 break;
85 case INSET:
86 ts << "inset";
87 break;
88 case GROOVE:
89 ts << "groove";
90 break;
91 case RIDGE:
92 ts << "ridge";
93 break;
94 case OUTSET:
95 ts << "outset";
96 break;
97 case DOTTED:
98 ts << "dotted";
99 break;
100 case DASHED:
101 ts << "dashed";
102 break;
103 case SOLID:
104 ts << "solid";
105 break;
106 case DOUBLE:
107 ts << "double";
108 break;
109 }
110
111 ts << " ";
112 }
113
getTagName(Node * n)114 static String getTagName(Node* n)
115 {
116 if (n->isDocumentNode())
117 return "";
118 if (n->isCommentNode())
119 return "COMMENT";
120 return n->nodeName();
121 }
122
isEmptyOrUnstyledAppleStyleSpan(const Node * node)123 static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node)
124 {
125 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
126 return false;
127
128 const HTMLElement* elem = static_cast<const HTMLElement*>(node);
129 if (elem->getAttribute(classAttr) != "Apple-style-span")
130 return false;
131
132 if (!node->hasChildNodes())
133 return true;
134
135 CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
136 return (!inlineStyleDecl || inlineStyleDecl->length() == 0);
137 }
138
quoteAndEscapeNonPrintables(const String & s)139 String quoteAndEscapeNonPrintables(const String& s)
140 {
141 Vector<UChar> result;
142 result.append('"');
143 for (unsigned i = 0; i != s.length(); ++i) {
144 UChar c = s[i];
145 if (c == '\\') {
146 result.append('\\');
147 result.append('\\');
148 } else if (c == '"') {
149 result.append('\\');
150 result.append('"');
151 } else if (c == '\n' || c == noBreakSpace)
152 result.append(' ');
153 else {
154 if (c >= 0x20 && c < 0x7F)
155 result.append(c);
156 else {
157 unsigned u = c;
158 String hex = String::format("\\x{%X}", u);
159 unsigned len = hex.length();
160 for (unsigned i = 0; i < len; ++i)
161 result.append(hex[i]);
162 }
163 }
164 }
165 result.append('"');
166 return String::adopt(result);
167 }
168
operator <<(TextStream & ts,const RenderObject & o)169 static TextStream &operator<<(TextStream& ts, const RenderObject& o)
170 {
171 ts << o.renderName();
172
173 if (o.style() && o.style()->zIndex())
174 ts << " zI: " << o.style()->zIndex();
175
176 if (o.node()) {
177 String tagName = getTagName(o.node());
178 if (!tagName.isEmpty()) {
179 ts << " {" << tagName << "}";
180 // flag empty or unstyled AppleStyleSpan because we never
181 // want to leave them in the DOM
182 if (isEmptyOrUnstyledAppleStyleSpan(o.node()))
183 ts << " *empty or unstyled AppleStyleSpan*";
184 }
185 }
186
187 bool adjustForTableCells = o.containingBlock()->isTableCell();
188
189 IntRect r;
190 if (o.isText()) {
191 // FIXME: Would be better to dump the bounding box x and y rather than the first run's x and y, but that would involve updating
192 // many test results.
193 const RenderText& text = *toRenderText(&o);
194 IntRect linesBox = text.linesBoundingBox();
195 r = IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height());
196 if (adjustForTableCells && !text.firstTextBox())
197 adjustForTableCells = false;
198 } else if (o.isRenderInline()) {
199 // FIXME: Would be better not to just dump 0, 0 as the x and y here.
200 const RenderInline& inlineFlow = *toRenderInline(&o);
201 r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height());
202 adjustForTableCells = false;
203 } else if (o.isTableCell()) {
204 // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect. We'd like
205 // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are
206 // captured by the results.
207 const RenderTableCell& cell = *toRenderTableCell(&o);
208 r = IntRect(cell.x(), cell.y() + cell.intrinsicPaddingTop(), cell.width(), cell.height() - cell.intrinsicPaddingTop() - cell.intrinsicPaddingBottom());
209 } else if (o.isBox())
210 r = toRenderBox(&o)->frameRect();
211
212 // FIXME: Temporary in order to ensure compatibility with existing layout test results.
213 if (adjustForTableCells)
214 r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingTop());
215
216 ts << " " << r;
217
218 if (!(o.isText() && !o.isBR())) {
219 if (o.parent() && (o.parent()->style()->color() != o.style()->color()))
220 ts << " [color=" << o.style()->color().name() << "]";
221
222 if (o.parent() && (o.parent()->style()->backgroundColor() != o.style()->backgroundColor()) &&
223 o.style()->backgroundColor().isValid() && o.style()->backgroundColor().rgb())
224 // Do not dump invalid or transparent backgrounds, since that is the default.
225 ts << " [bgcolor=" << o.style()->backgroundColor().name() << "]";
226
227 if (o.parent() && (o.parent()->style()->textFillColor() != o.style()->textFillColor()) &&
228 o.style()->textFillColor().isValid() && o.style()->textFillColor() != o.style()->color() &&
229 o.style()->textFillColor().rgb())
230 ts << " [textFillColor=" << o.style()->textFillColor().name() << "]";
231
232 if (o.parent() && (o.parent()->style()->textStrokeColor() != o.style()->textStrokeColor()) &&
233 o.style()->textStrokeColor().isValid() && o.style()->textStrokeColor() != o.style()->color() &&
234 o.style()->textStrokeColor().rgb())
235 ts << " [textStrokeColor=" << o.style()->textStrokeColor().name() << "]";
236
237 if (o.parent() && (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth()) &&
238 o.style()->textStrokeWidth() > 0)
239 ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]";
240
241 if (!o.isBoxModelObject())
242 return ts;
243
244 const RenderBoxModelObject& box = *toRenderBoxModelObject(&o);
245 if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) {
246 ts << " [border:";
247
248 BorderValue prevBorder;
249 if (o.style()->borderTop() != prevBorder) {
250 prevBorder = o.style()->borderTop();
251 if (!box.borderTop())
252 ts << " none";
253 else {
254 ts << " (" << box.borderTop() << "px ";
255 printBorderStyle(ts, o.style()->borderTopStyle());
256 Color col = o.style()->borderTopColor();
257 if (!col.isValid())
258 col = o.style()->color();
259 ts << col.name() << ")";
260 }
261 }
262
263 if (o.style()->borderRight() != prevBorder) {
264 prevBorder = o.style()->borderRight();
265 if (!box.borderRight())
266 ts << " none";
267 else {
268 ts << " (" << box.borderRight() << "px ";
269 printBorderStyle(ts, o.style()->borderRightStyle());
270 Color col = o.style()->borderRightColor();
271 if (!col.isValid())
272 col = o.style()->color();
273 ts << col.name() << ")";
274 }
275 }
276
277 if (o.style()->borderBottom() != prevBorder) {
278 prevBorder = box.style()->borderBottom();
279 if (!box.borderBottom())
280 ts << " none";
281 else {
282 ts << " (" << box.borderBottom() << "px ";
283 printBorderStyle(ts, o.style()->borderBottomStyle());
284 Color col = o.style()->borderBottomColor();
285 if (!col.isValid())
286 col = o.style()->color();
287 ts << col.name() << ")";
288 }
289 }
290
291 if (o.style()->borderLeft() != prevBorder) {
292 prevBorder = o.style()->borderLeft();
293 if (!box.borderLeft())
294 ts << " none";
295 else {
296 ts << " (" << box.borderLeft() << "px ";
297 printBorderStyle(ts, o.style()->borderLeftStyle());
298 Color col = o.style()->borderLeftColor();
299 if (!col.isValid())
300 col = o.style()->color();
301 ts << col.name() << ")";
302 }
303 }
304
305 ts << "]";
306 }
307 }
308
309 if (o.isTableCell()) {
310 const RenderTableCell& c = *toRenderTableCell(&o);
311 ts << " [r=" << c.row() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]";
312 }
313
314 if (o.isListMarker()) {
315 String text = toRenderListMarker(&o)->text();
316 if (!text.isEmpty()) {
317 if (text.length() != 1)
318 text = quoteAndEscapeNonPrintables(text);
319 else {
320 switch (text[0]) {
321 case bullet:
322 text = "bullet";
323 break;
324 case blackSquare:
325 text = "black square";
326 break;
327 case whiteBullet:
328 text = "white bullet";
329 break;
330 default:
331 text = quoteAndEscapeNonPrintables(text);
332 }
333 }
334 ts << ": " << text;
335 }
336 }
337
338 return ts;
339 }
340
writeTextRun(TextStream & ts,const RenderText & o,const InlineTextBox & run)341 static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run)
342 {
343 // FIXME: Table cell adjustment is temporary until results can be updated.
344 int y = run.m_y;
345 if (o.containingBlock()->isTableCell())
346 y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingTop();
347 ts << "text run at (" << run.m_x << "," << y << ") width " << run.m_width;
348 if (run.direction() == RTL || run.m_dirOverride) {
349 ts << (run.direction() == RTL ? " RTL" : " LTR");
350 if (run.m_dirOverride)
351 ts << " override";
352 }
353 ts << ": "
354 << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len()))
355 << "\n";
356 }
357
write(TextStream & ts,const RenderObject & o,int indent)358 void write(TextStream& ts, const RenderObject& o, int indent)
359 {
360 #if ENABLE(SVG)
361 if (o.isRenderPath()) {
362 write(ts, *toRenderPath(&o), indent);
363 return;
364 }
365 if (o.isSVGContainer()) {
366 writeSVGContainer(ts, o, indent);
367 return;
368 }
369 if (o.isSVGRoot()) {
370 write(ts, *toRenderSVGRoot(&o), indent);
371 return;
372 }
373 if (o.isSVGText()) {
374 if (!o.isText())
375 writeSVGText(ts, *toRenderBlock(&o), indent);
376 else
377 writeSVGInlineText(ts, *toRenderText(&o), indent);
378 return;
379 }
380 if (o.isSVGImage()) {
381 writeSVGImage(ts, *toRenderImage(&o), indent);
382 return;
383 }
384 #endif
385
386 writeIndent(ts, indent);
387
388 ts << o << "\n";
389
390 if (o.isText() && !o.isBR()) {
391 const RenderText& text = *toRenderText(&o);
392 for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
393 writeIndent(ts, indent + 1);
394 writeTextRun(ts, text, *box);
395 }
396 }
397
398 for (RenderObject* child = o.firstChild(); child; child = child->nextSibling()) {
399 if (child->hasLayer())
400 continue;
401 write(ts, *child, indent + 1);
402 }
403
404 if (o.isWidget()) {
405 Widget* widget = toRenderWidget(&o)->widget();
406 if (widget && widget->isFrameView()) {
407 FrameView* view = static_cast<FrameView*>(widget);
408 RenderView* root = view->frame()->contentRenderer();
409 if (root) {
410 view->layout();
411 RenderLayer* l = root->layer();
412 if (l)
413 writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), indent + 1);
414 }
415 }
416 }
417 }
418
write(TextStream & ts,RenderLayer & l,const IntRect & layerBounds,const IntRect & backgroundClipRect,const IntRect & clipRect,const IntRect & outlineClipRect,int layerType=0,int indent=0)419 static void write(TextStream& ts, RenderLayer& l,
420 const IntRect& layerBounds, const IntRect& backgroundClipRect, const IntRect& clipRect, const IntRect& outlineClipRect,
421 int layerType = 0, int indent = 0)
422 {
423 writeIndent(ts, indent);
424
425 ts << "layer " << layerBounds;
426
427 if (!layerBounds.isEmpty()) {
428 if (!backgroundClipRect.contains(layerBounds))
429 ts << " backgroundClip " << backgroundClipRect;
430 if (!clipRect.contains(layerBounds))
431 ts << " clip " << clipRect;
432 if (!outlineClipRect.contains(layerBounds))
433 ts << " outlineClip " << outlineClipRect;
434 }
435
436 if (l.renderer()->hasOverflowClip()) {
437 if (l.scrollXOffset())
438 ts << " scrollX " << l.scrollXOffset();
439 if (l.scrollYOffset())
440 ts << " scrollY " << l.scrollYOffset();
441 if (l.renderBox() && l.renderBox()->clientWidth() != l.scrollWidth())
442 ts << " scrollWidth " << l.scrollWidth();
443 if (l.renderBox() && l.renderBox()->clientHeight() != l.scrollHeight())
444 ts << " scrollHeight " << l.scrollHeight();
445 }
446
447 if (layerType == -1)
448 ts << " layerType: background only";
449 else if (layerType == 1)
450 ts << " layerType: foreground only";
451
452 ts << "\n";
453
454 if (layerType != -1)
455 write(ts, *l.renderer(), indent + 1);
456 }
457
writeLayers(TextStream & ts,const RenderLayer * rootLayer,RenderLayer * l,const IntRect & paintDirtyRect,int indent)458 static void writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* l,
459 const IntRect& paintDirtyRect, int indent)
460 {
461 // Calculate the clip rects we should use.
462 IntRect layerBounds, damageRect, clipRectToApply, outlineRect;
463 l->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, true);
464
465 // Ensure our lists are up-to-date.
466 l->updateZOrderLists();
467 l->updateNormalFlowList();
468
469 bool shouldPaint = l->intersectsDamageRect(layerBounds, damageRect, rootLayer);
470 Vector<RenderLayer*>* negList = l->negZOrderList();
471 if (shouldPaint && negList && negList->size() > 0)
472 write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, -1, indent);
473
474 if (negList) {
475 for (unsigned i = 0; i != negList->size(); ++i)
476 writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, indent);
477 }
478
479 if (shouldPaint)
480 write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, negList && negList->size() > 0, indent);
481
482 Vector<RenderLayer*>* normalFlowList = l->normalFlowList();
483 if (normalFlowList) {
484 for (unsigned i = 0; i != normalFlowList->size(); ++i)
485 writeLayers(ts, rootLayer, normalFlowList->at(i), paintDirtyRect, indent);
486 }
487
488 Vector<RenderLayer*>* posList = l->posZOrderList();
489 if (posList) {
490 for (unsigned i = 0; i != posList->size(); ++i)
491 writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, indent);
492 }
493 }
494
nodePosition(Node * node)495 static String nodePosition(Node* node)
496 {
497 String result;
498
499 Node* parent;
500 for (Node* n = node; n; n = parent) {
501 parent = n->parentNode();
502 if (!parent)
503 parent = n->shadowParentNode();
504 if (n != node)
505 result += " of ";
506 if (parent)
507 result += "child " + String::number(n->nodeIndex()) + " {" + getTagName(n) + "}";
508 else
509 result += "document";
510 }
511
512 return result;
513 }
514
writeSelection(TextStream & ts,const RenderObject * o)515 static void writeSelection(TextStream& ts, const RenderObject* o)
516 {
517 Node* n = o->node();
518 if (!n || !n->isDocumentNode())
519 return;
520
521 Document* doc = static_cast<Document*>(n);
522 Frame* frame = doc->frame();
523 if (!frame)
524 return;
525
526 VisibleSelection selection = frame->selection()->selection();
527 if (selection.isCaret()) {
528 ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().node());
529 if (selection.affinity() == UPSTREAM)
530 ts << " (upstream affinity)";
531 ts << "\n";
532 } else if (selection.isRange())
533 ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().node()) << "\n"
534 << "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().node()) << "\n";
535 }
536
externalRepresentation(RenderObject * o)537 String externalRepresentation(RenderObject* o)
538 {
539 if (!o)
540 return String();
541
542 TextStream ts;
543 #if ENABLE(SVG)
544 writeRenderResources(ts, o->document());
545 #endif
546 if (o->view()->frameView())
547 o->view()->frameView()->layout();
548 if (o->hasLayer()) {
549 RenderLayer* l = toRenderBox(o)->layer();
550 writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()));
551 writeSelection(ts, o);
552 }
553 return ts.release();
554 }
555
556 } // namespace WebCore
557