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 "core/rendering/RenderTreeAsText.h" 28 29 #include "HTMLNames.h" 30 #include "core/css/StylePropertySet.h" 31 #include "core/dom/Document.h" 32 #include "core/editing/FrameSelection.h" 33 #include "core/html/HTMLElement.h" 34 #include "core/frame/Frame.h" 35 #include "core/frame/FrameView.h" 36 #include "core/page/PrintContext.h" 37 #include "core/rendering/CompositedLayerMapping.h" 38 #include "core/rendering/FlowThreadController.h" 39 #include "core/rendering/InlineTextBox.h" 40 #include "core/rendering/RenderBR.h" 41 #include "core/rendering/RenderDetailsMarker.h" 42 #include "core/rendering/RenderFileUploadControl.h" 43 #include "core/rendering/RenderInline.h" 44 #include "core/rendering/RenderLayer.h" 45 #include "core/rendering/RenderListItem.h" 46 #include "core/rendering/RenderListMarker.h" 47 #include "core/rendering/RenderNamedFlowThread.h" 48 #include "core/rendering/RenderPart.h" 49 #include "core/rendering/RenderRegion.h" 50 #include "core/rendering/RenderTableCell.h" 51 #include "core/rendering/RenderView.h" 52 #include "core/rendering/RenderWidget.h" 53 #include "core/rendering/svg/RenderSVGContainer.h" 54 #include "core/rendering/svg/RenderSVGGradientStop.h" 55 #include "core/rendering/svg/RenderSVGImage.h" 56 #include "core/rendering/svg/RenderSVGInlineText.h" 57 #include "core/rendering/svg/RenderSVGPath.h" 58 #include "core/rendering/svg/RenderSVGRoot.h" 59 #include "core/rendering/svg/RenderSVGText.h" 60 #include "core/rendering/svg/SVGRenderTreeAsText.h" 61 #include "wtf/HexNumber.h" 62 #include "wtf/Vector.h" 63 #include "wtf/unicode/CharacterNames.h" 64 65 namespace WebCore { 66 67 using namespace HTMLNames; 68 69 static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle) 70 { 71 switch (borderStyle) { 72 case BNONE: 73 ts << "none"; 74 break; 75 case BHIDDEN: 76 ts << "hidden"; 77 break; 78 case INSET: 79 ts << "inset"; 80 break; 81 case GROOVE: 82 ts << "groove"; 83 break; 84 case RIDGE: 85 ts << "ridge"; 86 break; 87 case OUTSET: 88 ts << "outset"; 89 break; 90 case DOTTED: 91 ts << "dotted"; 92 break; 93 case DASHED: 94 ts << "dashed"; 95 break; 96 case SOLID: 97 ts << "solid"; 98 break; 99 case DOUBLE: 100 ts << "double"; 101 break; 102 } 103 104 ts << " "; 105 } 106 107 static String getTagName(Node* n) 108 { 109 if (n->isDocumentNode()) 110 return ""; 111 if (n->nodeType() == Node::COMMENT_NODE) 112 return "COMMENT"; 113 return n->nodeName(); 114 } 115 116 static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node) 117 { 118 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag)) 119 return false; 120 121 const HTMLElement* elem = toHTMLElement(node); 122 if (elem->getAttribute(classAttr) != "Apple-style-span") 123 return false; 124 125 if (!node->hasChildNodes()) 126 return true; 127 128 const StylePropertySet* inlineStyleDecl = elem->inlineStyle(); 129 return (!inlineStyleDecl || inlineStyleDecl->isEmpty()); 130 } 131 132 String quoteAndEscapeNonPrintables(const String& s) 133 { 134 StringBuilder result; 135 result.append('"'); 136 for (unsigned i = 0; i != s.length(); ++i) { 137 UChar c = s[i]; 138 if (c == '\\') { 139 result.append('\\'); 140 result.append('\\'); 141 } else if (c == '"') { 142 result.append('\\'); 143 result.append('"'); 144 } else if (c == '\n' || c == noBreakSpace) 145 result.append(' '); 146 else { 147 if (c >= 0x20 && c < 0x7F) 148 result.append(c); 149 else { 150 result.append('\\'); 151 result.append('x'); 152 result.append('{'); 153 appendUnsignedAsHex(c, result); 154 result.append('}'); 155 } 156 } 157 } 158 result.append('"'); 159 return result.toString(); 160 } 161 162 void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior) 163 { 164 ts << o.renderName(); 165 166 if (behavior & RenderAsTextShowAddresses) 167 ts << " " << static_cast<const void*>(&o); 168 169 if (o.style() && o.style()->zIndex()) 170 ts << " zI: " << o.style()->zIndex(); 171 172 if (o.node()) { 173 String tagName = getTagName(o.node()); 174 // FIXME: Temporary hack to make tests pass by simulating the old generated content output. 175 if (o.isPseudoElement() || (o.parent() && o.parent()->isPseudoElement())) 176 tagName = emptyAtom; 177 if (!tagName.isEmpty()) { 178 ts << " {" << tagName << "}"; 179 // flag empty or unstyled AppleStyleSpan because we never 180 // want to leave them in the DOM 181 if (isEmptyOrUnstyledAppleStyleSpan(o.node())) 182 ts << " *empty or unstyled AppleStyleSpan*"; 183 } 184 } 185 186 RenderBlock* cb = o.containingBlock(); 187 bool adjustForTableCells = cb ? cb->isTableCell() : false; 188 189 LayoutRect 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 = LayoutRect(cell.x(), cell.y() + cell.intrinsicPaddingBefore(), cell.width(), cell.height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter()); 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())->intrinsicPaddingBefore()); 215 216 ts << " " << r; 217 218 if (!(o.isText() && !o.isBR())) { 219 if (o.isFileUploadControl()) 220 ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue()); 221 222 if (o.parent()) { 223 Color color = o.resolveColor(CSSPropertyColor); 224 if (o.parent()->resolveColor(CSSPropertyColor) != color) 225 ts << " [color=" << color.nameForRenderTreeAsText() << "]"; 226 227 // Do not dump invalid or transparent backgrounds, since that is the default. 228 Color backgroundColor = o.resolveColor(CSSPropertyBackgroundColor); 229 if (o.parent()->resolveColor(CSSPropertyBackgroundColor) != backgroundColor 230 && backgroundColor.isValid() && backgroundColor.rgb()) 231 ts << " [bgcolor=" << backgroundColor.nameForRenderTreeAsText() << "]"; 232 233 Color textFillColor = o.resolveColor(CSSPropertyWebkitTextFillColor); 234 if (o.parent()->resolveColor(CSSPropertyWebkitTextFillColor) != textFillColor 235 && textFillColor.isValid() && textFillColor != color && textFillColor.rgb()) 236 ts << " [textFillColor=" << textFillColor.nameForRenderTreeAsText() << "]"; 237 238 Color textStrokeColor = o.resolveColor(CSSPropertyWebkitTextStrokeColor); 239 if (o.parent()->resolveColor(CSSPropertyWebkitTextStrokeColor) != textStrokeColor 240 && textStrokeColor.isValid() && textStrokeColor != color && textStrokeColor.rgb()) 241 ts << " [textStrokeColor=" << textStrokeColor.nameForRenderTreeAsText() << "]"; 242 243 if (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth() && o.style()->textStrokeWidth() > 0) 244 ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]"; 245 } 246 247 if (!o.isBoxModelObject()) 248 return; 249 250 const RenderBoxModelObject& box = *toRenderBoxModelObject(&o); 251 if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) { 252 ts << " [border:"; 253 254 BorderValue prevBorder = o.style()->borderTop(); 255 if (!box.borderTop()) 256 ts << " none"; 257 else { 258 ts << " (" << box.borderTop() << "px "; 259 printBorderStyle(ts, o.style()->borderTopStyle()); 260 Color col = o.resolveColor(CSSPropertyBorderTopColor); 261 ts << col.nameForRenderTreeAsText() << ")"; 262 } 263 264 if (o.style()->borderRight() != prevBorder) { 265 prevBorder = o.style()->borderRight(); 266 if (!box.borderRight()) 267 ts << " none"; 268 else { 269 ts << " (" << box.borderRight() << "px "; 270 printBorderStyle(ts, o.style()->borderRightStyle()); 271 Color col = o.resolveColor(CSSPropertyBorderRightColor); 272 ts << col.nameForRenderTreeAsText() << ")"; 273 } 274 } 275 276 if (o.style()->borderBottom() != prevBorder) { 277 prevBorder = box.style()->borderBottom(); 278 if (!box.borderBottom()) 279 ts << " none"; 280 else { 281 ts << " (" << box.borderBottom() << "px "; 282 printBorderStyle(ts, o.style()->borderBottomStyle()); 283 Color col = o.resolveColor(CSSPropertyBorderBottomColor); 284 ts << col.nameForRenderTreeAsText() << ")"; 285 } 286 } 287 288 if (o.style()->borderLeft() != prevBorder) { 289 prevBorder = o.style()->borderLeft(); 290 if (!box.borderLeft()) 291 ts << " none"; 292 else { 293 ts << " (" << box.borderLeft() << "px "; 294 printBorderStyle(ts, o.style()->borderLeftStyle()); 295 Color col = o.resolveColor(CSSPropertyBorderLeftColor); 296 ts << col.nameForRenderTreeAsText() << ")"; 297 } 298 } 299 300 ts << "]"; 301 } 302 } 303 304 if (o.isTableCell()) { 305 const RenderTableCell& c = *toRenderTableCell(&o); 306 ts << " [r=" << c.rowIndex() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]"; 307 } 308 309 if (o.isDetailsMarker()) { 310 ts << ": "; 311 switch (toRenderDetailsMarker(&o)->orientation()) { 312 case RenderDetailsMarker::Left: 313 ts << "left"; 314 break; 315 case RenderDetailsMarker::Right: 316 ts << "right"; 317 break; 318 case RenderDetailsMarker::Up: 319 ts << "up"; 320 break; 321 case RenderDetailsMarker::Down: 322 ts << "down"; 323 break; 324 } 325 } 326 327 if (o.isListMarker()) { 328 String text = toRenderListMarker(&o)->text(); 329 if (!text.isEmpty()) { 330 if (text.length() != 1) 331 text = quoteAndEscapeNonPrintables(text); 332 else { 333 switch (text[0]) { 334 case bullet: 335 text = "bullet"; 336 break; 337 case blackSquare: 338 text = "black square"; 339 break; 340 case whiteBullet: 341 text = "white bullet"; 342 break; 343 default: 344 text = quoteAndEscapeNonPrintables(text); 345 } 346 } 347 ts << ": " << text; 348 } 349 } 350 351 if (behavior & RenderAsTextShowIDAndClass) { 352 if (Node* node = o.node()) { 353 if (node->hasID()) 354 ts << " id=\"" + toElement(node)->getIdAttribute() + "\""; 355 356 if (node->hasClass()) { 357 ts << " class=\""; 358 Element* element = toElement(node); 359 for (size_t i = 0; i < element->classNames().size(); ++i) { 360 if (i > 0) 361 ts << " "; 362 ts << element->classNames()[i]; 363 } 364 ts << "\""; 365 } 366 } 367 } 368 369 if (behavior & RenderAsTextShowLayoutState) { 370 bool needsLayout = o.selfNeedsLayout() || o.needsPositionedMovementLayout() || o.posChildNeedsLayout() || o.normalChildNeedsLayout(); 371 if (needsLayout) 372 ts << " (needs layout:"; 373 374 bool havePrevious = false; 375 if (o.selfNeedsLayout()) { 376 ts << " self"; 377 havePrevious = true; 378 } 379 380 if (o.needsPositionedMovementLayout()) { 381 if (havePrevious) 382 ts << ","; 383 havePrevious = true; 384 ts << " positioned movement"; 385 } 386 387 if (o.normalChildNeedsLayout()) { 388 if (havePrevious) 389 ts << ","; 390 havePrevious = true; 391 ts << " child"; 392 } 393 394 if (o.posChildNeedsLayout()) { 395 if (havePrevious) 396 ts << ","; 397 ts << " positioned child"; 398 } 399 400 if (needsLayout) 401 ts << ")"; 402 } 403 } 404 405 static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run) 406 { 407 // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder 408 // to detect any changes caused by the conversion to floating point. :( 409 int x = run.x(); 410 int y = run.y(); 411 int logicalWidth = ceilf(run.left() + run.logicalWidth()) - x; 412 413 // FIXME: Table cell adjustment is temporary until results can be updated. 414 if (o.containingBlock()->isTableCell()) 415 y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore(); 416 417 ts << "text run at (" << x << "," << y << ") width " << logicalWidth; 418 if (!run.isLeftToRightDirection() || run.dirOverride()) { 419 ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR"); 420 if (run.dirOverride()) 421 ts << " override"; 422 } 423 ts << ": " 424 << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len())); 425 if (run.hasHyphen()) 426 ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style()->hyphenString()); 427 ts << "\n"; 428 } 429 430 void write(TextStream& ts, const RenderObject& o, int indent, RenderAsTextBehavior behavior) 431 { 432 if (o.isSVGShape()) { 433 write(ts, *toRenderSVGShape(&o), indent); 434 return; 435 } 436 if (o.isSVGGradientStop()) { 437 writeSVGGradientStop(ts, *toRenderSVGGradientStop(&o), indent); 438 return; 439 } 440 if (o.isSVGResourceContainer()) { 441 writeSVGResourceContainer(ts, o, indent); 442 return; 443 } 444 if (o.isSVGContainer()) { 445 writeSVGContainer(ts, o, indent); 446 return; 447 } 448 if (o.isSVGRoot()) { 449 write(ts, *toRenderSVGRoot(&o), indent); 450 return; 451 } 452 if (o.isSVGText()) { 453 writeSVGText(ts, *toRenderSVGText(&o), indent); 454 return; 455 } 456 if (o.isSVGInlineText()) { 457 writeSVGInlineText(ts, *toRenderSVGInlineText(&o), indent); 458 return; 459 } 460 if (o.isSVGImage()) { 461 writeSVGImage(ts, *toRenderSVGImage(&o), indent); 462 return; 463 } 464 465 writeIndent(ts, indent); 466 467 RenderTreeAsText::writeRenderObject(ts, o, behavior); 468 ts << "\n"; 469 470 if (o.isText() && !o.isBR()) { 471 const RenderText& text = *toRenderText(&o); 472 for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) { 473 writeIndent(ts, indent + 1); 474 writeTextRun(ts, text, *box); 475 } 476 } 477 478 for (RenderObject* child = o.firstChild(); child; child = child->nextSibling()) { 479 if (child->hasLayer()) 480 continue; 481 write(ts, *child, indent + 1, behavior); 482 } 483 484 if (o.isWidget()) { 485 Widget* widget = toRenderWidget(&o)->widget(); 486 if (widget && widget->isFrameView()) { 487 FrameView* view = toFrameView(widget); 488 RenderView* root = view->frame().contentRenderer(); 489 if (root) { 490 view->layout(); 491 RenderLayer* l = root->layer(); 492 if (l) 493 RenderTreeAsText::writeLayers(ts, l, l, l->rect(), indent + 1, behavior); 494 } 495 } 496 } 497 } 498 499 enum LayerPaintPhase { 500 LayerPaintPhaseAll = 0, 501 LayerPaintPhaseBackground = -1, 502 LayerPaintPhaseForeground = 1 503 }; 504 505 static void write(TextStream& ts, RenderLayer& l, 506 const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, const LayoutRect& clipRect, const LayoutRect& outlineClipRect, 507 LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal) 508 { 509 IntRect adjustedLayoutBounds = pixelSnappedIntRect(layerBounds); 510 IntRect adjustedBackgroundClipRect = pixelSnappedIntRect(backgroundClipRect); 511 IntRect adjustedClipRect = pixelSnappedIntRect(clipRect); 512 IntRect adjustedOutlineClipRect = pixelSnappedIntRect(outlineClipRect); 513 514 writeIndent(ts, indent); 515 516 if (l.renderer()->style()->visibility() == HIDDEN) 517 ts << "hidden "; 518 519 ts << "layer "; 520 521 if (behavior & RenderAsTextShowAddresses) 522 ts << static_cast<const void*>(&l) << " "; 523 524 ts << adjustedLayoutBounds; 525 526 if (!adjustedLayoutBounds.isEmpty()) { 527 if (!adjustedBackgroundClipRect.contains(adjustedLayoutBounds)) 528 ts << " backgroundClip " << adjustedBackgroundClipRect; 529 if (!adjustedClipRect.contains(adjustedLayoutBounds)) 530 ts << " clip " << adjustedClipRect; 531 if (!adjustedOutlineClipRect.contains(adjustedLayoutBounds)) 532 ts << " outlineClip " << adjustedOutlineClipRect; 533 } 534 if (l.isTransparent()) 535 ts << " transparent"; 536 537 if (l.renderer()->hasOverflowClip()) { 538 if (l.scrollableArea()->scrollXOffset()) 539 ts << " scrollX " << l.scrollableArea()->scrollXOffset(); 540 if (l.scrollableArea()->scrollYOffset()) 541 ts << " scrollY " << l.scrollableArea()->scrollYOffset(); 542 if (l.renderBox() && l.renderBox()->pixelSnappedClientWidth() != l.renderBox()->scrollWidth()) 543 ts << " scrollWidth " << l.renderBox()->scrollWidth(); 544 if (l.renderBox() && l.renderBox()->pixelSnappedClientHeight() != l.renderBox()->scrollHeight()) 545 ts << " scrollHeight " << l.renderBox()->scrollHeight(); 546 } 547 548 if (paintPhase == LayerPaintPhaseBackground) 549 ts << " layerType: background only"; 550 else if (paintPhase == LayerPaintPhaseForeground) 551 ts << " layerType: foreground only"; 552 553 if (behavior & RenderAsTextShowCompositedLayers) { 554 if (l.hasCompositedLayerMapping()) { 555 ts << " (composited, bounds=" 556 << l.compositedLayerMapping()->compositedBounds() 557 << ", drawsContent=" 558 << l.compositedLayerMapping()->mainGraphicsLayer()->drawsContent() 559 << ", paints into ancestor=" 560 << l.compositedLayerMapping()->paintsIntoCompositedAncestor() 561 << ")"; 562 } 563 } 564 565 ts << "\n"; 566 567 if (paintPhase != LayerPaintPhaseBackground) 568 write(ts, *l.renderer(), indent + 1, behavior); 569 } 570 571 static void writeRenderRegionList(const RenderRegionList& flowThreadRegionList, TextStream& ts, int indent) 572 { 573 for (RenderRegionList::const_iterator itRR = flowThreadRegionList.begin(); itRR != flowThreadRegionList.end(); ++itRR) { 574 const RenderRegion* renderRegion = *itRR; 575 576 writeIndent(ts, indent); 577 ts << renderRegion->renderName(); 578 579 Node* generatingNodeForRegion = renderRegion->generatingNodeForRegion(); 580 if (generatingNodeForRegion) { 581 if (renderRegion->hasCustomRegionStyle()) 582 ts << " region style: 1"; 583 if (renderRegion->hasAutoLogicalHeight()) 584 ts << " hasAutoLogicalHeight"; 585 586 bool isRenderNamedFlowFragment = renderRegion->isRenderNamedFlowFragment(); 587 if (isRenderNamedFlowFragment) 588 ts << " (anonymous child of"; 589 590 StringBuilder tagName; 591 tagName.append(generatingNodeForRegion->nodeName()); 592 593 Node* nodeForRegion = renderRegion->nodeForRegion(); 594 if (nodeForRegion->isPseudoElement()) { 595 if (nodeForRegion->isBeforePseudoElement()) 596 tagName.append("::before"); 597 else if (nodeForRegion->isAfterPseudoElement()) 598 tagName.append("::after"); 599 } 600 601 ts << " {" << tagName.toString() << "}"; 602 603 if (generatingNodeForRegion->isElementNode() && generatingNodeForRegion->hasID()) { 604 Element* element = toElement(generatingNodeForRegion); 605 ts << " #" << element->idForStyleResolution(); 606 } 607 608 if (isRenderNamedFlowFragment) 609 ts << ")"; 610 } 611 612 if (!renderRegion->isValid()) 613 ts << " invalid"; 614 615 ts << "\n"; 616 } 617 } 618 619 static void writeRenderNamedFlowThreads(TextStream& ts, RenderView* renderView, const RenderLayer* rootLayer, 620 const LayoutRect& paintRect, int indent, RenderAsTextBehavior behavior) 621 { 622 if (!renderView->hasRenderNamedFlowThreads()) 623 return; 624 625 const RenderNamedFlowThreadList* list = renderView->flowThreadController()->renderNamedFlowThreadList(); 626 627 writeIndent(ts, indent); 628 ts << "Named flows\n"; 629 630 for (RenderNamedFlowThreadList::const_iterator iter = list->begin(); iter != list->end(); ++iter) { 631 const RenderNamedFlowThread* renderFlowThread = *iter; 632 633 writeIndent(ts, indent + 1); 634 ts << "Named flow '" << renderFlowThread->flowThreadName() << "'\n"; 635 636 RenderLayer* layer = renderFlowThread->layer(); 637 RenderTreeAsText::writeLayers(ts, rootLayer, layer, paintRect, indent + 2, behavior); 638 639 // Display the valid and invalid render regions attached to this flow thread. 640 const RenderRegionList& validRegionsList = renderFlowThread->renderRegionList(); 641 if (!validRegionsList.isEmpty()) { 642 writeIndent(ts, indent + 2); 643 ts << "Regions for named flow '" << renderFlowThread->flowThreadName() << "'\n"; 644 writeRenderRegionList(validRegionsList, ts, indent + 3); 645 } 646 647 const RenderRegionList& invalidRegionsList = renderFlowThread->invalidRenderRegionList(); 648 if (!invalidRegionsList.isEmpty()) { 649 writeIndent(ts, indent + 2); 650 ts << "Invalid regions for named flow '" << renderFlowThread->flowThreadName() << "'\n"; 651 writeRenderRegionList(invalidRegionsList, ts, indent + 3); 652 } 653 } 654 } 655 656 void RenderTreeAsText::writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* layer, 657 const LayoutRect& paintRect, int indent, RenderAsTextBehavior behavior) 658 { 659 // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh. 660 LayoutRect paintDirtyRect(paintRect); 661 if (rootLayer == layer) { 662 paintDirtyRect.setWidth(max<LayoutUnit>(paintDirtyRect.width(), rootLayer->renderBox()->layoutOverflowRect().maxX())); 663 paintDirtyRect.setHeight(max<LayoutUnit>(paintDirtyRect.height(), rootLayer->renderBox()->layoutOverflowRect().maxY())); 664 layer->setSize(layer->size().expandedTo(pixelSnappedIntSize(layer->renderBox()->maxLayoutOverflow(), LayoutPoint(0, 0)))); 665 } 666 667 // Calculate the clip rects we should use. 668 LayoutRect layerBounds; 669 ClipRect damageRect, clipRectToApply, outlineRect; 670 layer->calculateRects(ClipRectsContext(rootLayer, 0, TemporaryClipRects), paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect); 671 672 // Ensure our lists are up-to-date. 673 layer->stackingNode()->updateLayerListsIfNeeded(); 674 675 bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : layer->intersectsDamageRect(layerBounds, damageRect.rect(), rootLayer); 676 677 Vector<RenderLayerStackingNode*>* negList = layer->stackingNode()->negZOrderList(); 678 bool paintsBackgroundSeparately = negList && negList->size() > 0; 679 if (shouldPaint && paintsBackgroundSeparately) 680 write(ts, *layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), outlineRect.rect(), LayerPaintPhaseBackground, indent, behavior); 681 682 if (negList) { 683 int currIndent = indent; 684 if (behavior & RenderAsTextShowLayerNesting) { 685 writeIndent(ts, indent); 686 ts << " negative z-order list(" << negList->size() << ")\n"; 687 ++currIndent; 688 } 689 for (unsigned i = 0; i != negList->size(); ++i) 690 writeLayers(ts, rootLayer, negList->at(i)->layer(), paintDirtyRect, currIndent, behavior); 691 } 692 693 if (shouldPaint) 694 write(ts, *layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), outlineRect.rect(), paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior); 695 696 if (Vector<RenderLayerStackingNode*>* normalFlowList = layer->stackingNode()->normalFlowList()) { 697 int currIndent = indent; 698 if (behavior & RenderAsTextShowLayerNesting) { 699 writeIndent(ts, indent); 700 ts << " normal flow list(" << normalFlowList->size() << ")\n"; 701 ++currIndent; 702 } 703 for (unsigned i = 0; i != normalFlowList->size(); ++i) 704 writeLayers(ts, rootLayer, normalFlowList->at(i)->layer(), paintDirtyRect, currIndent, behavior); 705 } 706 707 if (Vector<RenderLayerStackingNode*>* posList = layer->stackingNode()->posZOrderList()) { 708 int currIndent = indent; 709 if (behavior & RenderAsTextShowLayerNesting) { 710 writeIndent(ts, indent); 711 ts << " positive z-order list(" << posList->size() << ")\n"; 712 ++currIndent; 713 } 714 for (unsigned i = 0; i != posList->size(); ++i) 715 writeLayers(ts, rootLayer, posList->at(i)->layer(), paintDirtyRect, currIndent, behavior); 716 } 717 718 // Altough the RenderFlowThread requires a layer, it is not collected by its parent, 719 // so we have to treat it as a special case. 720 if (layer->renderer()->isRenderView()) { 721 RenderView* renderView = toRenderView(layer->renderer()); 722 writeRenderNamedFlowThreads(ts, renderView, rootLayer, paintDirtyRect, indent, behavior); 723 } 724 } 725 726 static String nodePosition(Node* node) 727 { 728 StringBuilder result; 729 730 Element* body = node->document().body(); 731 Node* parent; 732 for (Node* n = node; n; n = parent) { 733 parent = n->parentOrShadowHostNode(); 734 if (n != node) 735 result.appendLiteral(" of "); 736 if (parent) { 737 if (body && n == body) { 738 // We don't care what offset body may be in the document. 739 result.appendLiteral("body"); 740 break; 741 } 742 if (n->isShadowRoot()) { 743 result.append('{'); 744 result.append(getTagName(n)); 745 result.append('}'); 746 } else { 747 result.appendLiteral("child "); 748 result.appendNumber(n->nodeIndex()); 749 result.appendLiteral(" {"); 750 result.append(getTagName(n)); 751 result.append('}'); 752 } 753 } else 754 result.appendLiteral("document"); 755 } 756 757 return result.toString(); 758 } 759 760 static void writeSelection(TextStream& ts, const RenderObject* o) 761 { 762 Node* n = o->node(); 763 if (!n || !n->isDocumentNode()) 764 return; 765 766 Document* doc = toDocument(n); 767 Frame* frame = doc->frame(); 768 if (!frame) 769 return; 770 771 VisibleSelection selection = frame->selection().selection(); 772 if (selection.isCaret()) { 773 ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()); 774 if (selection.affinity() == UPSTREAM) 775 ts << " (upstream affinity)"; 776 ts << "\n"; 777 } else if (selection.isRange()) 778 ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()) << "\n" 779 << "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().deprecatedNode()) << "\n"; 780 } 781 782 static String externalRepresentation(RenderBox* renderer, RenderAsTextBehavior behavior) 783 { 784 TextStream ts; 785 if (!renderer->hasLayer()) 786 return ts.release(); 787 788 RenderLayer* layer = renderer->layer(); 789 RenderTreeAsText::writeLayers(ts, layer, layer, layer->rect(), 0, behavior); 790 writeSelection(ts, renderer); 791 return ts.release(); 792 } 793 794 String externalRepresentation(Frame* frame, RenderAsTextBehavior behavior) 795 { 796 if (!(behavior & RenderAsTextDontUpdateLayout)) 797 frame->document()->updateLayout(); 798 799 RenderObject* renderer = frame->contentRenderer(); 800 if (!renderer || !renderer->isBox()) 801 return String(); 802 803 PrintContext printContext(frame); 804 if (behavior & RenderAsTextPrintingMode) 805 printContext.begin(toRenderBox(renderer)->width()); 806 807 return externalRepresentation(toRenderBox(renderer), behavior); 808 } 809 810 String externalRepresentation(Element* element, RenderAsTextBehavior behavior) 811 { 812 // Doesn't support printing mode. 813 ASSERT(!(behavior & RenderAsTextPrintingMode)); 814 if (!(behavior & RenderAsTextDontUpdateLayout)) 815 element->document().updateLayout(); 816 817 RenderObject* renderer = element->renderer(); 818 if (!renderer || !renderer->isBox()) 819 return String(); 820 821 return externalRepresentation(toRenderBox(renderer), behavior | RenderAsTextShowAllLayers); 822 } 823 824 static void writeCounterValuesFromChildren(TextStream& stream, RenderObject* parent, bool& isFirstCounter) 825 { 826 for (RenderObject* child = parent->firstChild(); child; child = child->nextSibling()) { 827 if (child->isCounter()) { 828 if (!isFirstCounter) 829 stream << " "; 830 isFirstCounter = false; 831 String str(toRenderText(child)->text()); 832 stream << str; 833 } 834 } 835 } 836 837 String counterValueForElement(Element* element) 838 { 839 // Make sure the element is not freed during the layout. 840 RefPtr<Element> elementRef(element); 841 element->document().updateLayout(); 842 TextStream stream; 843 bool isFirstCounter = true; 844 // The counter renderers should be children of :before or :after pseudo-elements. 845 if (RenderObject* before = element->pseudoElementRenderer(BEFORE)) 846 writeCounterValuesFromChildren(stream, before, isFirstCounter); 847 if (RenderObject* after = element->pseudoElementRenderer(AFTER)) 848 writeCounterValuesFromChildren(stream, after, isFirstCounter); 849 return stream.release(); 850 } 851 852 String markerTextForListItem(Element* element) 853 { 854 // Make sure the element is not freed during the layout. 855 RefPtr<Element> elementRef(element); 856 element->document().updateLayout(); 857 858 RenderObject* renderer = element->renderer(); 859 if (!renderer || !renderer->isListItem()) 860 return String(); 861 862 return toRenderListItem(renderer)->markerText(); 863 } 864 865 } // namespace WebCore 866