• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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