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 (o.isDetailsMarker()) {
378 ts << ": ";
379 switch (toRenderDetailsMarker(&o)->orientation()) {
380 case RenderDetailsMarker::Left:
381 ts << "left";
382 break;
383 case RenderDetailsMarker::Right:
384 ts << "right";
385 break;
386 case RenderDetailsMarker::Up:
387 ts << "up";
388 break;
389 case RenderDetailsMarker::Down:
390 ts << "down";
391 break;
392 }
393 }
394
395 if (o.isListMarker()) {
396 String text = toRenderListMarker(&o)->text();
397 if (!text.isEmpty()) {
398 if (text.length() != 1)
399 text = quoteAndEscapeNonPrintables(text);
400 else {
401 switch (text[0]) {
402 case bullet:
403 text = "bullet";
404 break;
405 case blackSquare:
406 text = "black square";
407 break;
408 case whiteBullet:
409 text = "white bullet";
410 break;
411 default:
412 text = quoteAndEscapeNonPrintables(text);
413 }
414 }
415 ts << ": " << text;
416 }
417 }
418
419 if (behavior & RenderAsTextShowIDAndClass) {
420 if (Node* node = o.node()) {
421 if (node->hasID())
422 ts << " id=\"" + static_cast<Element*>(node)->getIdAttribute() + "\"";
423
424 if (node->hasClass()) {
425 StyledElement* styledElement = static_cast<StyledElement*>(node);
426 String classes;
427 for (size_t i = 0; i < styledElement->classNames().size(); ++i) {
428 if (i > 0)
429 classes += " ";
430 classes += styledElement->classNames()[i];
431 }
432 ts << " class=\"" + classes + "\"";
433 }
434 }
435 }
436
437 if (behavior & RenderAsTextShowLayoutState) {
438 bool needsLayout = o.selfNeedsLayout() || o.needsPositionedMovementLayout() || o.posChildNeedsLayout() || o.normalChildNeedsLayout();
439 if (needsLayout)
440 ts << " (needs layout:";
441
442 bool havePrevious = false;
443 if (o.selfNeedsLayout()) {
444 ts << " self";
445 havePrevious = true;
446 }
447
448 if (o.needsPositionedMovementLayout()) {
449 if (havePrevious)
450 ts << ",";
451 havePrevious = true;
452 ts << " positioned movement";
453 }
454
455 if (o.normalChildNeedsLayout()) {
456 if (havePrevious)
457 ts << ",";
458 havePrevious = true;
459 ts << " child";
460 }
461
462 if (o.posChildNeedsLayout()) {
463 if (havePrevious)
464 ts << ",";
465 ts << " positioned child";
466 }
467
468 if (needsLayout)
469 ts << ")";
470 }
471
472 #if PLATFORM(QT)
473 // Print attributes of embedded QWidgets. E.g. when the WebCore::Widget
474 // is invisible the QWidget should be invisible too.
475 if (o.isRenderPart()) {
476 const RenderPart* part = toRenderPart(const_cast<RenderObject*>(&o));
477 if (part->widget() && part->widget()->platformWidget()) {
478 QWidget* wid = part->widget()->platformWidget();
479
480 ts << " [QT: ";
481 ts << "geometry: {" << wid->geometry() << "} ";
482 ts << "isHidden: " << wid->isHidden() << " ";
483 ts << "isSelfVisible: " << part->widget()->isSelfVisible() << " ";
484 ts << "isParentVisible: " << part->widget()->isParentVisible() << " ";
485 ts << "mask: {" << wid->mask().boundingRect() << "} ] ";
486 }
487 }
488 #endif
489 }
490
writeTextRun(TextStream & ts,const RenderText & o,const InlineTextBox & run)491 static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run)
492 {
493 // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder
494 // to detect any changes caused by the conversion to floating point. :(
495 int x = run.m_x;
496 int y = run.m_y;
497 int logicalWidth = ceilf(run.m_x + run.m_logicalWidth) - x;
498
499 // FIXME: Table cell adjustment is temporary until results can be updated.
500 if (o.containingBlock()->isTableCell())
501 y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore();
502
503 ts << "text run at (" << x << "," << y << ") width " << logicalWidth;
504 if (!run.isLeftToRightDirection() || run.m_dirOverride) {
505 ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR");
506 if (run.m_dirOverride)
507 ts << " override";
508 }
509 ts << ": "
510 << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len()));
511 if (run.hasHyphen())
512 ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style()->hyphenString());
513 ts << "\n";
514 }
515
write(TextStream & ts,const RenderObject & o,int indent,RenderAsTextBehavior behavior)516 void write(TextStream& ts, const RenderObject& o, int indent, RenderAsTextBehavior behavior)
517 {
518 #if ENABLE(SVG)
519 if (o.isSVGPath()) {
520 write(ts, *toRenderSVGPath(&o), indent);
521 return;
522 }
523 if (o.isSVGGradientStop()) {
524 writeSVGGradientStop(ts, *toRenderSVGGradientStop(&o), indent);
525 return;
526 }
527 if (o.isSVGResourceContainer()) {
528 writeSVGResourceContainer(ts, o, indent);
529 return;
530 }
531 if (o.isSVGContainer()) {
532 writeSVGContainer(ts, o, indent);
533 return;
534 }
535 if (o.isSVGRoot()) {
536 write(ts, *toRenderSVGRoot(&o), indent);
537 return;
538 }
539 if (o.isSVGText()) {
540 writeSVGText(ts, *toRenderBlock(&o), indent);
541 return;
542 }
543 if (o.isSVGInlineText()) {
544 writeSVGInlineText(ts, *toRenderText(&o), indent);
545 return;
546 }
547 if (o.isSVGImage()) {
548 writeSVGImage(ts, *toRenderSVGImage(&o), indent);
549 return;
550 }
551 #endif
552
553 writeIndent(ts, indent);
554
555 RenderTreeAsText::writeRenderObject(ts, o, behavior);
556 ts << "\n";
557
558 if (o.isText() && !o.isBR()) {
559 const RenderText& text = *toRenderText(&o);
560 for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
561 writeIndent(ts, indent + 1);
562 writeTextRun(ts, text, *box);
563 }
564 }
565
566 for (RenderObject* child = o.firstChild(); child; child = child->nextSibling()) {
567 if (child->hasLayer())
568 continue;
569 write(ts, *child, indent + 1, behavior);
570 }
571
572 if (o.isWidget()) {
573 Widget* widget = toRenderWidget(&o)->widget();
574 if (widget && widget->isFrameView()) {
575 FrameView* view = static_cast<FrameView*>(widget);
576 RenderView* root = view->frame()->contentRenderer();
577 if (root) {
578 view->layout();
579 RenderLayer* l = root->layer();
580 if (l)
581 writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), indent + 1, behavior);
582 }
583 }
584 }
585 }
586
587 enum LayerPaintPhase {
588 LayerPaintPhaseAll = 0,
589 LayerPaintPhaseBackground = -1,
590 LayerPaintPhaseForeground = 1
591 };
592
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)593 static void write(TextStream& ts, RenderLayer& l,
594 const IntRect& layerBounds, const IntRect& backgroundClipRect, const IntRect& clipRect, const IntRect& outlineClipRect,
595 LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal)
596 {
597 writeIndent(ts, indent);
598
599 ts << "layer ";
600
601 if (behavior & RenderAsTextShowAddresses)
602 ts << static_cast<const void*>(&l) << " ";
603
604 ts << layerBounds;
605
606 if (!layerBounds.isEmpty()) {
607 if (!backgroundClipRect.contains(layerBounds))
608 ts << " backgroundClip " << backgroundClipRect;
609 if (!clipRect.contains(layerBounds))
610 ts << " clip " << clipRect;
611 if (!outlineClipRect.contains(layerBounds))
612 ts << " outlineClip " << outlineClipRect;
613 }
614
615 if (l.renderer()->hasOverflowClip()) {
616 if (l.scrollXOffset())
617 ts << " scrollX " << l.scrollXOffset();
618 if (l.scrollYOffset())
619 ts << " scrollY " << l.scrollYOffset();
620 if (l.renderBox() && l.renderBox()->clientWidth() != l.scrollWidth())
621 ts << " scrollWidth " << l.scrollWidth();
622 if (l.renderBox() && l.renderBox()->clientHeight() != l.scrollHeight())
623 ts << " scrollHeight " << l.scrollHeight();
624 }
625
626 if (paintPhase == LayerPaintPhaseBackground)
627 ts << " layerType: background only";
628 else if (paintPhase == LayerPaintPhaseForeground)
629 ts << " layerType: foreground only";
630
631 #if USE(ACCELERATED_COMPOSITING)
632 if (behavior & RenderAsTextShowCompositedLayers) {
633 if (l.isComposited())
634 ts << " (composited, bounds " << l.backing()->compositedBounds() << ")";
635 }
636 #else
637 UNUSED_PARAM(behavior);
638 #endif
639
640 ts << "\n";
641
642 if (paintPhase != LayerPaintPhaseBackground)
643 write(ts, *l.renderer(), indent + 1, behavior);
644 }
645
writeLayers(TextStream & ts,const RenderLayer * rootLayer,RenderLayer * l,const IntRect & paintRect,int indent,RenderAsTextBehavior behavior)646 static void writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* l,
647 const IntRect& paintRect, int indent, RenderAsTextBehavior behavior)
648 {
649 // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh.
650 IntRect paintDirtyRect(paintRect);
651 if (rootLayer == l) {
652 paintDirtyRect.setWidth(max(paintDirtyRect.width(), rootLayer->renderBox()->maxXLayoutOverflow()));
653 paintDirtyRect.setHeight(max(paintDirtyRect.height(), rootLayer->renderBox()->maxYLayoutOverflow()));
654 l->setWidth(max(l->width(), l->renderBox()->maxXLayoutOverflow()));
655 l->setHeight(max(l->height(), l->renderBox()->maxYLayoutOverflow()));
656 }
657
658 // Calculate the clip rects we should use.
659 IntRect layerBounds, damageRect, clipRectToApply, outlineRect;
660 l->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, true);
661
662 // Ensure our lists are up-to-date.
663 l->updateZOrderLists();
664 l->updateNormalFlowList();
665
666 bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : l->intersectsDamageRect(layerBounds, damageRect, rootLayer);
667 Vector<RenderLayer*>* negList = l->negZOrderList();
668 bool paintsBackgroundSeparately = negList && negList->size() > 0;
669 if (shouldPaint && paintsBackgroundSeparately)
670 write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, LayerPaintPhaseBackground, indent, behavior);
671
672 if (negList) {
673 int currIndent = indent;
674 if (behavior & RenderAsTextShowLayerNesting) {
675 writeIndent(ts, indent);
676 ts << " negative z-order list(" << negList->size() << ")\n";
677 ++currIndent;
678 }
679 for (unsigned i = 0; i != negList->size(); ++i)
680 writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, currIndent, behavior);
681 }
682
683 if (shouldPaint)
684 write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior);
685
686 if (Vector<RenderLayer*>* normalFlowList = l->normalFlowList()) {
687 int currIndent = indent;
688 if (behavior & RenderAsTextShowLayerNesting) {
689 writeIndent(ts, indent);
690 ts << " normal flow list(" << normalFlowList->size() << ")\n";
691 ++currIndent;
692 }
693 for (unsigned i = 0; i != normalFlowList->size(); ++i)
694 writeLayers(ts, rootLayer, normalFlowList->at(i), paintDirtyRect, currIndent, behavior);
695 }
696
697 if (Vector<RenderLayer*>* posList = l->posZOrderList()) {
698 int currIndent = indent;
699 if (behavior & RenderAsTextShowLayerNesting) {
700 writeIndent(ts, indent);
701 ts << " positive z-order list(" << posList->size() << ")\n";
702 ++currIndent;
703 }
704 for (unsigned i = 0; i != posList->size(); ++i)
705 writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, currIndent, behavior);
706 }
707 }
708
nodePosition(Node * node)709 static String nodePosition(Node* node)
710 {
711 String result;
712
713 Element* body = node->document()->body();
714 Node* parent;
715 for (Node* n = node; n; n = parent) {
716 parent = n->parentOrHostNode();
717 if (n != node)
718 result += " of ";
719 if (parent) {
720 if (body && n == body) {
721 // We don't care what offset body may be in the document.
722 result += "body";
723 break;
724 }
725 if (n->isShadowBoundary())
726 result += "{" + getTagName(n) + "}";
727 else
728 result += "child " + String::number(n->nodeIndex()) + " {" + getTagName(n) + "}";
729 } else
730 result += "document";
731 }
732
733 return result;
734 }
735
writeSelection(TextStream & ts,const RenderObject * o)736 static void writeSelection(TextStream& ts, const RenderObject* o)
737 {
738 Node* n = o->node();
739 if (!n || !n->isDocumentNode())
740 return;
741
742 Document* doc = static_cast<Document*>(n);
743 Frame* frame = doc->frame();
744 if (!frame)
745 return;
746
747 VisibleSelection selection = frame->selection()->selection();
748 if (selection.isCaret()) {
749 ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode());
750 if (selection.affinity() == UPSTREAM)
751 ts << " (upstream affinity)";
752 ts << "\n";
753 } else if (selection.isRange())
754 ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()) << "\n"
755 << "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().deprecatedNode()) << "\n";
756 }
757
externalRepresentation(Frame * frame,RenderAsTextBehavior behavior)758 String externalRepresentation(Frame* frame, RenderAsTextBehavior behavior)
759 {
760 PrintContext printContext(frame);
761 if (behavior & RenderAsTextPrintingMode) {
762 if (!frame->contentRenderer())
763 return String();
764 printContext.begin(frame->contentRenderer()->width());
765 }
766
767 if (!(behavior & RenderAsTextDontUpdateLayout))
768 frame->document()->updateLayout();
769
770 RenderObject* o = frame->contentRenderer();
771 if (!o)
772 return String();
773
774 TextStream ts;
775 if (o->hasLayer()) {
776 RenderLayer* l = toRenderBox(o)->layer();
777 writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), 0, behavior);
778 writeSelection(ts, o);
779 }
780 return ts.release();
781 }
782
writeCounterValuesFromChildren(TextStream & stream,RenderObject * parent,bool & isFirstCounter)783 static void writeCounterValuesFromChildren(TextStream& stream, RenderObject* parent, bool& isFirstCounter)
784 {
785 for (RenderObject* child = parent->firstChild(); child; child = child->nextSibling()) {
786 if (child->isCounter()) {
787 if (!isFirstCounter)
788 stream << " ";
789 isFirstCounter = false;
790 String str(toRenderText(child)->text());
791 stream << str;
792 }
793 }
794 }
795
counterValueForElement(Element * element)796 String counterValueForElement(Element* element)
797 {
798 // Make sure the element is not freed during the layout.
799 RefPtr<Element> elementRef(element);
800 element->document()->updateLayout();
801 TextStream stream;
802 bool isFirstCounter = true;
803 // The counter renderers should be children of :before or :after pseudo-elements.
804 if (RenderObject* renderer = element->renderer()) {
805 if (RenderObject* pseudoElement = renderer->beforePseudoElementRenderer())
806 writeCounterValuesFromChildren(stream, pseudoElement, isFirstCounter);
807 if (RenderObject* pseudoElement = renderer->afterPseudoElementRenderer())
808 writeCounterValuesFromChildren(stream, pseudoElement, isFirstCounter);
809 }
810 return stream.release();
811 }
812
markerTextForListItem(Element * element)813 String markerTextForListItem(Element* element)
814 {
815 // Make sure the element is not freed during the layout.
816 RefPtr<Element> elementRef(element);
817 element->document()->updateLayout();
818
819 RenderObject* renderer = element->renderer();
820 if (!renderer || !renderer->isListItem())
821 return String();
822
823 return toRenderListItem(renderer)->markerText();
824 }
825
826 } // namespace WebCore
827