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 "Document.h"
31 #include "Frame.h"
32 #include "FrameView.h"
33 #include "HTMLElement.h"
34 #include "HTMLNames.h"
35 #include "InlineTextBox.h"
36 #include "PrintContext.h"
37 #include "RenderBR.h"
38 #include "RenderDetailsMarker.h"
39 #include "RenderFileUploadControl.h"
40 #include "RenderInline.h"
41 #include "RenderLayer.h"
42 #include "RenderListItem.h"
43 #include "RenderListMarker.h"
44 #include "RenderPart.h"
45 #include "RenderTableCell.h"
46 #include "RenderView.h"
47 #include "RenderWidget.h"
48 #include "SelectionController.h"
49 #include <wtf/HexNumber.h>
50 #include <wtf/UnusedParam.h>
51 #include <wtf/Vector.h>
52 #include <wtf/unicode/CharacterNames.h>
53
54 #if ENABLE(SVG)
55 #include "RenderSVGContainer.h"
56 #include "RenderSVGGradientStop.h"
57 #include "RenderSVGImage.h"
58 #include "RenderSVGInlineText.h"
59 #include "RenderSVGPath.h"
60 #include "RenderSVGRoot.h"
61 #include "RenderSVGText.h"
62 #include "SVGRenderTreeAsText.h"
63 #endif
64
65 #if USE(ACCELERATED_COMPOSITING)
66 #include "RenderLayerBacking.h"
67 #endif
68
69 #if PLATFORM(QT)
70 #include <QWidget>
71 #endif
72
73 namespace WebCore {
74
75 using namespace HTMLNames;
76
77 static void writeLayers(TextStream&, const RenderLayer* rootLayer, RenderLayer*, const IntRect& paintDirtyRect, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal);
78
hasFractions(double val)79 bool hasFractions(double val)
80 {
81 static const double s_epsilon = 0.0001;
82 int ival = static_cast<int>(val);
83 double dval = static_cast<double>(ival);
84 return fabs(val - dval) > s_epsilon;
85 }
86
operator <<(TextStream & ts,const IntRect & r)87 TextStream& operator<<(TextStream& ts, const IntRect& r)
88 {
89 return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height();
90 }
91
operator <<(TextStream & ts,const IntPoint & p)92 TextStream& operator<<(TextStream& ts, const IntPoint& p)
93 {
94 return ts << "(" << p.x() << "," << p.y() << ")";
95 }
96
operator <<(TextStream & ts,const FloatPoint & p)97 TextStream& operator<<(TextStream& ts, const FloatPoint& p)
98 {
99 ts << "(";
100 if (hasFractions(p.x()))
101 ts << p.x();
102 else
103 ts << int(p.x());
104 ts << ",";
105 if (hasFractions(p.y()))
106 ts << p.y();
107 else
108 ts << int(p.y());
109 return ts << ")";
110 }
111
operator <<(TextStream & ts,const FloatSize & s)112 TextStream& operator<<(TextStream& ts, const FloatSize& s)
113 {
114 ts << "width=";
115 if (hasFractions(s.width()))
116 ts << s.width();
117 else
118 ts << int(s.width());
119 ts << " height=";
120 if (hasFractions(s.height()))
121 ts << s.height();
122 else
123 ts << int(s.height());
124 return ts;
125 }
126
writeIndent(TextStream & ts,int indent)127 void writeIndent(TextStream& ts, int indent)
128 {
129 for (int i = 0; i != indent; ++i)
130 ts << " ";
131 }
132
printBorderStyle(TextStream & ts,const EBorderStyle borderStyle)133 static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle)
134 {
135 switch (borderStyle) {
136 case BNONE:
137 ts << "none";
138 break;
139 case BHIDDEN:
140 ts << "hidden";
141 break;
142 case INSET:
143 ts << "inset";
144 break;
145 case GROOVE:
146 ts << "groove";
147 break;
148 case RIDGE:
149 ts << "ridge";
150 break;
151 case OUTSET:
152 ts << "outset";
153 break;
154 case DOTTED:
155 ts << "dotted";
156 break;
157 case DASHED:
158 ts << "dashed";
159 break;
160 case SOLID:
161 ts << "solid";
162 break;
163 case DOUBLE:
164 ts << "double";
165 break;
166 }
167
168 ts << " ";
169 }
170
getTagName(Node * n)171 static String getTagName(Node* n)
172 {
173 if (n->isDocumentNode())
174 return "";
175 if (n->isCommentNode())
176 return "COMMENT";
177 return n->nodeName();
178 }
179
isEmptyOrUnstyledAppleStyleSpan(const Node * node)180 static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node)
181 {
182 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
183 return false;
184
185 const HTMLElement* elem = static_cast<const HTMLElement*>(node);
186 if (elem->getAttribute(classAttr) != "Apple-style-span")
187 return false;
188
189 if (!node->hasChildNodes())
190 return true;
191
192 CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
193 return (!inlineStyleDecl || inlineStyleDecl->length() == 0);
194 }
195
quoteAndEscapeNonPrintables(const String & s)196 String quoteAndEscapeNonPrintables(const String& s)
197 {
198 Vector<UChar> result;
199 result.append('"');
200 for (unsigned i = 0; i != s.length(); ++i) {
201 UChar c = s[i];
202 if (c == '\\') {
203 result.append('\\');
204 result.append('\\');
205 } else if (c == '"') {
206 result.append('\\');
207 result.append('"');
208 } else if (c == '\n' || c == noBreakSpace)
209 result.append(' ');
210 else {
211 if (c >= 0x20 && c < 0x7F)
212 result.append(c);
213 else {
214 result.append('\\');
215 result.append('x');
216 result.append('{');
217 appendUnsignedAsHex(c, result);
218 result.append('}');
219 }
220 }
221 }
222 result.append('"');
223 return String::adopt(result);
224 }
225
writeRenderObject(TextStream & ts,const RenderObject & o,RenderAsTextBehavior behavior)226 void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior)
227 {
228 ts << o.renderName();
229
230 if (behavior & RenderAsTextShowAddresses)
231 ts << " " << static_cast<const void*>(&o);
232
233 if (o.style() && o.style()->zIndex())
234 ts << " zI: " << o.style()->zIndex();
235
236 if (o.node()) {
237 String tagName = getTagName(o.node());
238 if (!tagName.isEmpty()) {
239 ts << " {" << tagName << "}";
240 // flag empty or unstyled AppleStyleSpan because we never
241 // want to leave them in the DOM
242 if (isEmptyOrUnstyledAppleStyleSpan(o.node()))
243 ts << " *empty or unstyled AppleStyleSpan*";
244 }
245 }
246
247 bool adjustForTableCells = o.containingBlock()->isTableCell();
248
249 IntRect r;
250 if (o.isText()) {
251 // 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
252 // many test results.
253 const RenderText& text = *toRenderText(&o);
254 IntRect linesBox = text.linesBoundingBox();
255 r = IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height());
256 if (adjustForTableCells && !text.firstTextBox())
257 adjustForTableCells = false;
258 } else if (o.isRenderInline()) {
259 // FIXME: Would be better not to just dump 0, 0 as the x and y here.
260 const RenderInline& inlineFlow = *toRenderInline(&o);
261 r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height());
262 adjustForTableCells = false;
263 } else if (o.isTableCell()) {
264 // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect. We'd like
265 // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are
266 // captured by the results.
267 const RenderTableCell& cell = *toRenderTableCell(&o);
268 r = IntRect(cell.x(), cell.y() + cell.intrinsicPaddingBefore(), cell.width(), cell.height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter());
269 } else if (o.isBox())
270 r = toRenderBox(&o)->frameRect();
271
272 // FIXME: Temporary in order to ensure compatibility with existing layout test results.
273 if (adjustForTableCells)
274 r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore());
275
276 ts << " " << r;
277
278 if (!(o.isText() && !o.isBR())) {
279 if (o.isFileUploadControl())
280 ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue());
281
282 if (o.parent() && (o.parent()->style()->color() != o.style()->color()))
283 ts << " [color=" << o.style()->color().nameForRenderTreeAsText() << "]";
284
285 if (o.parent() && (o.parent()->style()->backgroundColor() != o.style()->backgroundColor()) &&
286 o.style()->backgroundColor().isValid() && o.style()->backgroundColor().rgb())
287 // Do not dump invalid or transparent backgrounds, since that is the default.
288 ts << " [bgcolor=" << o.style()->backgroundColor().nameForRenderTreeAsText() << "]";
289
290 if (o.parent() && (o.parent()->style()->textFillColor() != o.style()->textFillColor()) &&
291 o.style()->textFillColor().isValid() && o.style()->textFillColor() != o.style()->color() &&
292 o.style()->textFillColor().rgb())
293 ts << " [textFillColor=" << o.style()->textFillColor().nameForRenderTreeAsText() << "]";
294
295 if (o.parent() && (o.parent()->style()->textStrokeColor() != o.style()->textStrokeColor()) &&
296 o.style()->textStrokeColor().isValid() && o.style()->textStrokeColor() != o.style()->color() &&
297 o.style()->textStrokeColor().rgb())
298 ts << " [textStrokeColor=" << o.style()->textStrokeColor().nameForRenderTreeAsText() << "]";
299
300 if (o.parent() && (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth()) &&
301 o.style()->textStrokeWidth() > 0)
302 ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]";
303
304 if (!o.isBoxModelObject())
305 return;
306
307 const RenderBoxModelObject& box = *toRenderBoxModelObject(&o);
308 if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) {
309 ts << " [border:";
310
311 BorderValue prevBorder;
312 if (o.style()->borderTop() != prevBorder) {
313 prevBorder = o.style()->borderTop();
314 if (!box.borderTop())
315 ts << " none";
316 else {
317 ts << " (" << box.borderTop() << "px ";
318 printBorderStyle(ts, o.style()->borderTopStyle());
319 Color col = o.style()->borderTopColor();
320 if (!col.isValid())
321 col = o.style()->color();
322 ts << col.nameForRenderTreeAsText() << ")";
323 }
324 }
325
326 if (o.style()->borderRight() != prevBorder) {
327 prevBorder = o.style()->borderRight();
328 if (!box.borderRight())
329 ts << " none";
330 else {
331 ts << " (" << box.borderRight() << "px ";
332 printBorderStyle(ts, o.style()->borderRightStyle());
333 Color col = o.style()->borderRightColor();
334 if (!col.isValid())
335 col = o.style()->color();
336 ts << col.nameForRenderTreeAsText() << ")";
337 }
338 }
339
340 if (o.style()->borderBottom() != prevBorder) {
341 prevBorder = box.style()->borderBottom();
342 if (!box.borderBottom())
343 ts << " none";
344 else {
345 ts << " (" << box.borderBottom() << "px ";
346 printBorderStyle(ts, o.style()->borderBottomStyle());
347 Color col = o.style()->borderBottomColor();
348 if (!col.isValid())
349 col = o.style()->color();
350 ts << col.nameForRenderTreeAsText() << ")";
351 }
352 }
353
354 if (o.style()->borderLeft() != prevBorder) {
355 prevBorder = o.style()->borderLeft();
356 if (!box.borderLeft())
357 ts << " none";
358 else {
359 ts << " (" << box.borderLeft() << "px ";
360 printBorderStyle(ts, o.style()->borderLeftStyle());
361 Color col = o.style()->borderLeftColor();
362 if (!col.isValid())
363 col = o.style()->color();
364 ts << col.nameForRenderTreeAsText() << ")";
365 }
366 }
367
368 ts << "]";
369 }
370 }
371
372 if (o.isTableCell()) {
373 const RenderTableCell& c = *toRenderTableCell(&o);
374 ts << " [r=" << c.row() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]";
375 }
376
377 #if ENABLE(DETAILS)
378 if (o.isDetailsMarker()) {
379 ts << ": ";
380 switch (toRenderDetailsMarker(&o)->orientation()) {
381 case RenderDetailsMarker::Left:
382 ts << "left";
383 break;
384 case RenderDetailsMarker::Right:
385 ts << "right";
386 break;
387 case RenderDetailsMarker::Up:
388 ts << "up";
389 break;
390 case RenderDetailsMarker::Down:
391 ts << "down";
392 break;
393 }
394 }
395 #endif
396
397 if (o.isListMarker()) {
398 String text = toRenderListMarker(&o)->text();
399 if (!text.isEmpty()) {
400 if (text.length() != 1)
401 text = quoteAndEscapeNonPrintables(text);
402 else {
403 switch (text[0]) {
404 case bullet:
405 text = "bullet";
406 break;
407 case blackSquare:
408 text = "black square";
409 break;
410 case whiteBullet:
411 text = "white bullet";
412 break;
413 default:
414 text = quoteAndEscapeNonPrintables(text);
415 }
416 }
417 ts << ": " << text;
418 }
419 }
420
421 if (behavior & RenderAsTextShowIDAndClass) {
422 if (Node* node = o.node()) {
423 if (node->hasID())
424 ts << " id=\"" + static_cast<Element*>(node)->getIdAttribute() + "\"";
425
426 if (node->hasClass()) {
427 StyledElement* styledElement = static_cast<StyledElement*>(node);
428 String classes;
429 for (size_t i = 0; i < styledElement->classNames().size(); ++i) {
430 if (i > 0)
431 classes += " ";
432 classes += styledElement->classNames()[i];
433 }
434 ts << " class=\"" + classes + "\"";
435 }
436 }
437 }
438
439 if (behavior & RenderAsTextShowLayoutState) {
440 bool needsLayout = o.selfNeedsLayout() || o.needsPositionedMovementLayout() || o.posChildNeedsLayout() || o.normalChildNeedsLayout();
441 if (needsLayout)
442 ts << " (needs layout:";
443
444 bool havePrevious = false;
445 if (o.selfNeedsLayout()) {
446 ts << " self";
447 havePrevious = true;
448 }
449
450 if (o.needsPositionedMovementLayout()) {
451 if (havePrevious)
452 ts << ",";
453 havePrevious = true;
454 ts << " positioned movement";
455 }
456
457 if (o.normalChildNeedsLayout()) {
458 if (havePrevious)
459 ts << ",";
460 havePrevious = true;
461 ts << " child";
462 }
463
464 if (o.posChildNeedsLayout()) {
465 if (havePrevious)
466 ts << ",";
467 ts << " positioned child";
468 }
469
470 if (needsLayout)
471 ts << ")";
472 }
473
474 #if PLATFORM(QT)
475 // Print attributes of embedded QWidgets. E.g. when the WebCore::Widget
476 // is invisible the QWidget should be invisible too.
477 if (o.isRenderPart()) {
478 const RenderPart* part = toRenderPart(const_cast<RenderObject*>(&o));
479 if (part->widget() && part->widget()->platformWidget()) {
480 QWidget* wid = part->widget()->platformWidget();
481
482 ts << " [QT: ";
483 ts << "geometry: {" << wid->geometry() << "} ";
484 ts << "isHidden: " << wid->isHidden() << " ";
485 ts << "isSelfVisible: " << part->widget()->isSelfVisible() << " ";
486 ts << "isParentVisible: " << part->widget()->isParentVisible() << " ";
487 ts << "mask: {" << wid->mask().boundingRect() << "} ] ";
488 }
489 }
490 #endif
491 }
492
writeTextRun(TextStream & ts,const RenderText & o,const InlineTextBox & run)493 static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run)
494 {
495 // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder
496 // to detect any changes caused by the conversion to floating point. :(
497 int x = run.m_x;
498 int y = run.m_y;
499 int logicalWidth = ceilf(run.m_x + run.m_logicalWidth) - x;
500
501 // FIXME: Table cell adjustment is temporary until results can be updated.
502 if (o.containingBlock()->isTableCell())
503 y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore();
504
505 ts << "text run at (" << x << "," << y << ") width " << logicalWidth;
506 if (!run.isLeftToRightDirection() || run.m_dirOverride) {
507 ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR");
508 if (run.m_dirOverride)
509 ts << " override";
510 }
511 ts << ": "
512 << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len()));
513 if (run.hasHyphen())
514 ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style()->hyphenString());
515 ts << "\n";
516 }
517
write(TextStream & ts,const RenderObject & o,int indent,RenderAsTextBehavior behavior)518 void write(TextStream& ts, const RenderObject& o, int indent, RenderAsTextBehavior behavior)
519 {
520 #if ENABLE(SVG)
521 if (o.isSVGPath()) {
522 write(ts, *toRenderSVGPath(&o), indent);
523 return;
524 }
525 if (o.isSVGGradientStop()) {
526 writeSVGGradientStop(ts, *toRenderSVGGradientStop(&o), indent);
527 return;
528 }
529 if (o.isSVGResourceContainer()) {
530 writeSVGResourceContainer(ts, o, indent);
531 return;
532 }
533 if (o.isSVGContainer()) {
534 writeSVGContainer(ts, o, indent);
535 return;
536 }
537 if (o.isSVGRoot()) {
538 write(ts, *toRenderSVGRoot(&o), indent);
539 return;
540 }
541 if (o.isSVGText()) {
542 writeSVGText(ts, *toRenderBlock(&o), indent);
543 return;
544 }
545 if (o.isSVGInlineText()) {
546 writeSVGInlineText(ts, *toRenderText(&o), indent);
547 return;
548 }
549 if (o.isSVGImage()) {
550 writeSVGImage(ts, *toRenderSVGImage(&o), indent);
551 return;
552 }
553 #endif
554
555 writeIndent(ts, indent);
556
557 RenderTreeAsText::writeRenderObject(ts, o, behavior);
558 ts << "\n";
559
560 if (o.isText() && !o.isBR()) {
561 const RenderText& text = *toRenderText(&o);
562 for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
563 writeIndent(ts, indent + 1);
564 writeTextRun(ts, text, *box);
565 }
566 }
567
568 for (RenderObject* child = o.firstChild(); child; child = child->nextSibling()) {
569 if (child->hasLayer())
570 continue;
571 write(ts, *child, indent + 1, behavior);
572 }
573
574 if (o.isWidget()) {
575 Widget* widget = toRenderWidget(&o)->widget();
576 if (widget && widget->isFrameView()) {
577 FrameView* view = static_cast<FrameView*>(widget);
578 RenderView* root = view->frame()->contentRenderer();
579 if (root) {
580 view->layout();
581 RenderLayer* l = root->layer();
582 if (l)
583 writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), indent + 1, behavior);
584 }
585 }
586 }
587 }
588
589 enum LayerPaintPhase {
590 LayerPaintPhaseAll = 0,
591 LayerPaintPhaseBackground = -1,
592 LayerPaintPhaseForeground = 1
593 };
594
write(TextStream & ts,RenderLayer & l,const IntRect & layerBounds,const IntRect & backgroundClipRect,const IntRect & clipRect,const IntRect & outlineClipRect,LayerPaintPhase paintPhase=LayerPaintPhaseAll,int indent=0,RenderAsTextBehavior behavior=RenderAsTextBehaviorNormal)595 static void write(TextStream& ts, RenderLayer& l,
596 const IntRect& layerBounds, const IntRect& backgroundClipRect, const IntRect& clipRect, const IntRect& outlineClipRect,
597 LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal)
598 {
599 writeIndent(ts, indent);
600
601 ts << "layer ";
602
603 if (behavior & RenderAsTextShowAddresses)
604 ts << static_cast<const void*>(&l) << " ";
605
606 ts << layerBounds;
607
608 if (!layerBounds.isEmpty()) {
609 if (!backgroundClipRect.contains(layerBounds))
610 ts << " backgroundClip " << backgroundClipRect;
611 if (!clipRect.contains(layerBounds))
612 ts << " clip " << clipRect;
613 if (!outlineClipRect.contains(layerBounds))
614 ts << " outlineClip " << outlineClipRect;
615 }
616
617 if (l.renderer()->hasOverflowClip()) {
618 if (l.scrollXOffset())
619 ts << " scrollX " << l.scrollXOffset();
620 if (l.scrollYOffset())
621 ts << " scrollY " << l.scrollYOffset();
622 if (l.renderBox() && l.renderBox()->clientWidth() != l.scrollWidth())
623 ts << " scrollWidth " << l.scrollWidth();
624 if (l.renderBox() && l.renderBox()->clientHeight() != l.scrollHeight())
625 ts << " scrollHeight " << l.scrollHeight();
626 }
627
628 if (paintPhase == LayerPaintPhaseBackground)
629 ts << " layerType: background only";
630 else if (paintPhase == LayerPaintPhaseForeground)
631 ts << " layerType: foreground only";
632
633 #if USE(ACCELERATED_COMPOSITING)
634 if (behavior & RenderAsTextShowCompositedLayers) {
635 if (l.isComposited())
636 ts << " (composited, bounds " << l.backing()->compositedBounds() << ")";
637 }
638 #else
639 UNUSED_PARAM(behavior);
640 #endif
641
642 ts << "\n";
643
644 if (paintPhase != LayerPaintPhaseBackground)
645 write(ts, *l.renderer(), indent + 1, behavior);
646 }
647
writeLayers(TextStream & ts,const RenderLayer * rootLayer,RenderLayer * l,const IntRect & paintRect,int indent,RenderAsTextBehavior behavior)648 static void writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* l,
649 const IntRect& paintRect, int indent, RenderAsTextBehavior behavior)
650 {
651 // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh.
652 IntRect paintDirtyRect(paintRect);
653 if (rootLayer == l) {
654 paintDirtyRect.setWidth(max(paintDirtyRect.width(), rootLayer->renderBox()->maxXLayoutOverflow()));
655 paintDirtyRect.setHeight(max(paintDirtyRect.height(), rootLayer->renderBox()->maxYLayoutOverflow()));
656 l->setWidth(max(l->width(), l->renderBox()->maxXLayoutOverflow()));
657 l->setHeight(max(l->height(), l->renderBox()->maxYLayoutOverflow()));
658 }
659
660 // Calculate the clip rects we should use.
661 IntRect layerBounds, damageRect, clipRectToApply, outlineRect;
662 l->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, true);
663
664 // Ensure our lists are up-to-date.
665 l->updateZOrderLists();
666 l->updateNormalFlowList();
667
668 bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : l->intersectsDamageRect(layerBounds, damageRect, rootLayer);
669 Vector<RenderLayer*>* negList = l->negZOrderList();
670 bool paintsBackgroundSeparately = negList && negList->size() > 0;
671 if (shouldPaint && paintsBackgroundSeparately)
672 write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, LayerPaintPhaseBackground, indent, behavior);
673
674 if (negList) {
675 int currIndent = indent;
676 if (behavior & RenderAsTextShowLayerNesting) {
677 writeIndent(ts, indent);
678 ts << " negative z-order list(" << negList->size() << ")\n";
679 ++currIndent;
680 }
681 for (unsigned i = 0; i != negList->size(); ++i)
682 writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, currIndent, behavior);
683 }
684
685 if (shouldPaint)
686 write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior);
687
688 if (Vector<RenderLayer*>* normalFlowList = l->normalFlowList()) {
689 int currIndent = indent;
690 if (behavior & RenderAsTextShowLayerNesting) {
691 writeIndent(ts, indent);
692 ts << " normal flow list(" << normalFlowList->size() << ")\n";
693 ++currIndent;
694 }
695 for (unsigned i = 0; i != normalFlowList->size(); ++i)
696 writeLayers(ts, rootLayer, normalFlowList->at(i), paintDirtyRect, currIndent, behavior);
697 }
698
699 if (Vector<RenderLayer*>* posList = l->posZOrderList()) {
700 int currIndent = indent;
701 if (behavior & RenderAsTextShowLayerNesting) {
702 writeIndent(ts, indent);
703 ts << " positive z-order list(" << posList->size() << ")\n";
704 ++currIndent;
705 }
706 for (unsigned i = 0; i != posList->size(); ++i)
707 writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, currIndent, behavior);
708 }
709 }
710
nodePosition(Node * node)711 static String nodePosition(Node* node)
712 {
713 String result;
714
715 Element* body = node->document()->body();
716 Node* parent;
717 for (Node* n = node; n; n = parent) {
718 parent = n->parentOrHostNode();
719 if (n != node)
720 result += " of ";
721 if (parent) {
722 if (body && n == body) {
723 // We don't care what offset body may be in the document.
724 result += "body";
725 break;
726 }
727 if (n->isShadowBoundary())
728 result += "{" + getTagName(n) + "}";
729 else
730 result += "child " + String::number(n->nodeIndex()) + " {" + getTagName(n) + "}";
731 } else
732 result += "document";
733 }
734
735 return result;
736 }
737
writeSelection(TextStream & ts,const RenderObject * o)738 static void writeSelection(TextStream& ts, const RenderObject* o)
739 {
740 Node* n = o->node();
741 if (!n || !n->isDocumentNode())
742 return;
743
744 Document* doc = static_cast<Document*>(n);
745 Frame* frame = doc->frame();
746 if (!frame)
747 return;
748
749 VisibleSelection selection = frame->selection()->selection();
750 if (selection.isCaret()) {
751 ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode());
752 if (selection.affinity() == UPSTREAM)
753 ts << " (upstream affinity)";
754 ts << "\n";
755 } else if (selection.isRange())
756 ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()) << "\n"
757 << "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().deprecatedNode()) << "\n";
758 }
759
externalRepresentation(Frame * frame,RenderAsTextBehavior behavior)760 String externalRepresentation(Frame* frame, RenderAsTextBehavior behavior)
761 {
762 PrintContext printContext(frame);
763 if (behavior & RenderAsTextPrintingMode) {
764 if (!frame->contentRenderer())
765 return String();
766 printContext.begin(frame->contentRenderer()->width());
767 }
768
769 if (!(behavior & RenderAsTextDontUpdateLayout))
770 frame->document()->updateLayout();
771
772 RenderObject* o = frame->contentRenderer();
773 if (!o)
774 return String();
775
776 TextStream ts;
777 if (o->hasLayer()) {
778 RenderLayer* l = toRenderBox(o)->layer();
779 writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), 0, behavior);
780 writeSelection(ts, o);
781 }
782 return ts.release();
783 }
784
writeCounterValuesFromChildren(TextStream & stream,RenderObject * parent,bool & isFirstCounter)785 static void writeCounterValuesFromChildren(TextStream& stream, RenderObject* parent, bool& isFirstCounter)
786 {
787 for (RenderObject* child = parent->firstChild(); child; child = child->nextSibling()) {
788 if (child->isCounter()) {
789 if (!isFirstCounter)
790 stream << " ";
791 isFirstCounter = false;
792 String str(toRenderText(child)->text());
793 stream << str;
794 }
795 }
796 }
797
counterValueForElement(Element * element)798 String counterValueForElement(Element* element)
799 {
800 // Make sure the element is not freed during the layout.
801 RefPtr<Element> elementRef(element);
802 element->document()->updateLayout();
803 TextStream stream;
804 bool isFirstCounter = true;
805 // The counter renderers should be children of :before or :after pseudo-elements.
806 if (RenderObject* renderer = element->renderer()) {
807 if (RenderObject* pseudoElement = renderer->beforePseudoElementRenderer())
808 writeCounterValuesFromChildren(stream, pseudoElement, isFirstCounter);
809 if (RenderObject* pseudoElement = renderer->afterPseudoElementRenderer())
810 writeCounterValuesFromChildren(stream, pseudoElement, isFirstCounter);
811 }
812 return stream.release();
813 }
814
markerTextForListItem(Element * element)815 String markerTextForListItem(Element* element)
816 {
817 // Make sure the element is not freed during the layout.
818 RefPtr<Element> elementRef(element);
819 element->document()->updateLayout();
820
821 RenderObject* renderer = element->renderer();
822 if (!renderer || !renderer->isListItem())
823 return String();
824
825 return toRenderListItem(renderer)->markerText();
826 }
827
828 } // namespace WebCore
829