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 "CString.h" 32 #include "Document.h" 33 #include "Frame.h" 34 #include "FrameView.h" 35 #include "HTMLElement.h" 36 #include "HTMLNames.h" 37 #include "InlineTextBox.h" 38 #include "RenderBR.h" 39 #include "RenderFileUploadControl.h" 40 #include "RenderInline.h" 41 #include "RenderListMarker.h" 42 #include "RenderPart.h" 43 #include "RenderTableCell.h" 44 #include "RenderView.h" 45 #include "RenderWidget.h" 46 #include "SelectionController.h" 47 #include "TextStream.h" 48 #include <wtf/UnusedParam.h> 49 #include <wtf/Vector.h> 50 51 #if ENABLE(SVG) 52 #include "RenderPath.h" 53 #include "RenderSVGContainer.h" 54 #include "RenderSVGImage.h" 55 #include "RenderSVGInlineText.h" 56 #include "RenderSVGRoot.h" 57 #include "RenderSVGText.h" 58 #include "SVGRenderTreeAsText.h" 59 #endif 60 61 #if USE(ACCELERATED_COMPOSITING) 62 #include "RenderLayerBacking.h" 63 #endif 64 65 #if PLATFORM(QT) 66 #include <QWidget> 67 #endif 68 69 namespace WebCore { 70 71 using namespace HTMLNames; 72 73 static void writeLayers(TextStream&, const RenderLayer* rootLayer, RenderLayer*, const IntRect& paintDirtyRect, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal); 74 75 #if !ENABLE(SVG) 76 static TextStream &operator<<(TextStream& ts, const IntRect& r) 77 { 78 return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height(); 79 } 80 #endif 81 82 static void writeIndent(TextStream& ts, int indent) 83 { 84 for (int i = 0; i != indent; ++i) 85 ts << " "; 86 } 87 88 static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle) 89 { 90 switch (borderStyle) { 91 case BNONE: 92 ts << "none"; 93 break; 94 case BHIDDEN: 95 ts << "hidden"; 96 break; 97 case INSET: 98 ts << "inset"; 99 break; 100 case GROOVE: 101 ts << "groove"; 102 break; 103 case RIDGE: 104 ts << "ridge"; 105 break; 106 case OUTSET: 107 ts << "outset"; 108 break; 109 case DOTTED: 110 ts << "dotted"; 111 break; 112 case DASHED: 113 ts << "dashed"; 114 break; 115 case SOLID: 116 ts << "solid"; 117 break; 118 case DOUBLE: 119 ts << "double"; 120 break; 121 } 122 123 ts << " "; 124 } 125 126 static String getTagName(Node* n) 127 { 128 if (n->isDocumentNode()) 129 return ""; 130 if (n->isCommentNode()) 131 return "COMMENT"; 132 return n->nodeName(); 133 } 134 135 static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node) 136 { 137 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag)) 138 return false; 139 140 const HTMLElement* elem = static_cast<const HTMLElement*>(node); 141 if (elem->getAttribute(classAttr) != "Apple-style-span") 142 return false; 143 144 if (!node->hasChildNodes()) 145 return true; 146 147 CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl(); 148 return (!inlineStyleDecl || inlineStyleDecl->length() == 0); 149 } 150 151 String quoteAndEscapeNonPrintables(const String& s) 152 { 153 Vector<UChar> result; 154 result.append('"'); 155 for (unsigned i = 0; i != s.length(); ++i) { 156 UChar c = s[i]; 157 if (c == '\\') { 158 result.append('\\'); 159 result.append('\\'); 160 } else if (c == '"') { 161 result.append('\\'); 162 result.append('"'); 163 } else if (c == '\n' || c == noBreakSpace) 164 result.append(' '); 165 else { 166 if (c >= 0x20 && c < 0x7F) 167 result.append(c); 168 else { 169 unsigned u = c; 170 String hex = String::format("\\x{%X}", u); 171 unsigned len = hex.length(); 172 for (unsigned i = 0; i < len; ++i) 173 result.append(hex[i]); 174 } 175 } 176 } 177 result.append('"'); 178 return String::adopt(result); 179 } 180 181 static TextStream &operator<<(TextStream& ts, const RenderObject& o) 182 { 183 ts << o.renderName(); 184 185 if (o.style() && o.style()->zIndex()) 186 ts << " zI: " << o.style()->zIndex(); 187 188 if (o.node()) { 189 String tagName = getTagName(o.node()); 190 if (!tagName.isEmpty()) { 191 ts << " {" << tagName << "}"; 192 // flag empty or unstyled AppleStyleSpan because we never 193 // want to leave them in the DOM 194 if (isEmptyOrUnstyledAppleStyleSpan(o.node())) 195 ts << " *empty or unstyled AppleStyleSpan*"; 196 } 197 } 198 199 bool adjustForTableCells = o.containingBlock()->isTableCell(); 200 201 IntRect r; 202 if (o.isText()) { 203 // 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 204 // many test results. 205 const RenderText& text = *toRenderText(&o); 206 IntRect linesBox = text.linesBoundingBox(); 207 r = IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height()); 208 if (adjustForTableCells && !text.firstTextBox()) 209 adjustForTableCells = false; 210 } else if (o.isRenderInline()) { 211 // FIXME: Would be better not to just dump 0, 0 as the x and y here. 212 const RenderInline& inlineFlow = *toRenderInline(&o); 213 r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height()); 214 adjustForTableCells = false; 215 } else if (o.isTableCell()) { 216 // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect. We'd like 217 // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are 218 // captured by the results. 219 const RenderTableCell& cell = *toRenderTableCell(&o); 220 r = IntRect(cell.x(), cell.y() + cell.intrinsicPaddingTop(), cell.width(), cell.height() - cell.intrinsicPaddingTop() - cell.intrinsicPaddingBottom()); 221 } else if (o.isBox()) 222 r = toRenderBox(&o)->frameRect(); 223 224 // FIXME: Temporary in order to ensure compatibility with existing layout test results. 225 if (adjustForTableCells) 226 r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingTop()); 227 228 ts << " " << r; 229 230 if (!(o.isText() && !o.isBR())) { 231 if (o.isFileUploadControl()) { 232 ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue()); 233 } 234 if (o.parent() && (o.parent()->style()->color() != o.style()->color())) 235 ts << " [color=" << o.style()->color().name() << "]"; 236 237 if (o.parent() && (o.parent()->style()->backgroundColor() != o.style()->backgroundColor()) && 238 o.style()->backgroundColor().isValid() && o.style()->backgroundColor().rgb()) 239 // Do not dump invalid or transparent backgrounds, since that is the default. 240 ts << " [bgcolor=" << o.style()->backgroundColor().name() << "]"; 241 242 if (o.parent() && (o.parent()->style()->textFillColor() != o.style()->textFillColor()) && 243 o.style()->textFillColor().isValid() && o.style()->textFillColor() != o.style()->color() && 244 o.style()->textFillColor().rgb()) 245 ts << " [textFillColor=" << o.style()->textFillColor().name() << "]"; 246 247 if (o.parent() && (o.parent()->style()->textStrokeColor() != o.style()->textStrokeColor()) && 248 o.style()->textStrokeColor().isValid() && o.style()->textStrokeColor() != o.style()->color() && 249 o.style()->textStrokeColor().rgb()) 250 ts << " [textStrokeColor=" << o.style()->textStrokeColor().name() << "]"; 251 252 if (o.parent() && (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth()) && 253 o.style()->textStrokeWidth() > 0) 254 ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]"; 255 256 if (!o.isBoxModelObject()) 257 return ts; 258 259 const RenderBoxModelObject& box = *toRenderBoxModelObject(&o); 260 if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) { 261 ts << " [border:"; 262 263 BorderValue prevBorder; 264 if (o.style()->borderTop() != prevBorder) { 265 prevBorder = o.style()->borderTop(); 266 if (!box.borderTop()) 267 ts << " none"; 268 else { 269 ts << " (" << box.borderTop() << "px "; 270 printBorderStyle(ts, o.style()->borderTopStyle()); 271 Color col = o.style()->borderTopColor(); 272 if (!col.isValid()) 273 col = o.style()->color(); 274 ts << col.name() << ")"; 275 } 276 } 277 278 if (o.style()->borderRight() != prevBorder) { 279 prevBorder = o.style()->borderRight(); 280 if (!box.borderRight()) 281 ts << " none"; 282 else { 283 ts << " (" << box.borderRight() << "px "; 284 printBorderStyle(ts, o.style()->borderRightStyle()); 285 Color col = o.style()->borderRightColor(); 286 if (!col.isValid()) 287 col = o.style()->color(); 288 ts << col.name() << ")"; 289 } 290 } 291 292 if (o.style()->borderBottom() != prevBorder) { 293 prevBorder = box.style()->borderBottom(); 294 if (!box.borderBottom()) 295 ts << " none"; 296 else { 297 ts << " (" << box.borderBottom() << "px "; 298 printBorderStyle(ts, o.style()->borderBottomStyle()); 299 Color col = o.style()->borderBottomColor(); 300 if (!col.isValid()) 301 col = o.style()->color(); 302 ts << col.name() << ")"; 303 } 304 } 305 306 if (o.style()->borderLeft() != prevBorder) { 307 prevBorder = o.style()->borderLeft(); 308 if (!box.borderLeft()) 309 ts << " none"; 310 else { 311 ts << " (" << box.borderLeft() << "px "; 312 printBorderStyle(ts, o.style()->borderLeftStyle()); 313 Color col = o.style()->borderLeftColor(); 314 if (!col.isValid()) 315 col = o.style()->color(); 316 ts << col.name() << ")"; 317 } 318 } 319 320 ts << "]"; 321 } 322 } 323 324 if (o.isTableCell()) { 325 const RenderTableCell& c = *toRenderTableCell(&o); 326 ts << " [r=" << c.row() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]"; 327 } 328 329 if (o.isListMarker()) { 330 String text = toRenderListMarker(&o)->text(); 331 if (!text.isEmpty()) { 332 if (text.length() != 1) 333 text = quoteAndEscapeNonPrintables(text); 334 else { 335 switch (text[0]) { 336 case bullet: 337 text = "bullet"; 338 break; 339 case blackSquare: 340 text = "black square"; 341 break; 342 case whiteBullet: 343 text = "white bullet"; 344 break; 345 default: 346 text = quoteAndEscapeNonPrintables(text); 347 } 348 } 349 ts << ": " << text; 350 } 351 } 352 353 #if PLATFORM(QT) 354 // Print attributes of embedded QWidgets. E.g. when the WebCore::Widget 355 // is invisible the QWidget should be invisible too. 356 if (o.isRenderPart()) { 357 const RenderPart* part = toRenderPart(const_cast<RenderObject*>(&o)); 358 if (part->widget() && part->widget()->platformWidget()) { 359 QWidget* wid = part->widget()->platformWidget(); 360 361 ts << " [QT: "; 362 ts << "geometry: {" << wid->geometry() << "} "; 363 ts << "isHidden: " << wid->isHidden() << " "; 364 ts << "isSelfVisible: " << part->widget()->isSelfVisible() << " "; 365 ts << "isParentVisible: " << part->widget()->isParentVisible() << " "; 366 ts << "mask: {" << wid->mask().boundingRect() << "} ] "; 367 } 368 } 369 #endif 370 371 return ts; 372 } 373 374 static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run) 375 { 376 // FIXME: Table cell adjustment is temporary until results can be updated. 377 int y = run.m_y; 378 if (o.containingBlock()->isTableCell()) 379 y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingTop(); 380 ts << "text run at (" << run.m_x << "," << y << ") width " << run.m_width; 381 if (run.direction() == RTL || run.m_dirOverride) { 382 ts << (run.direction() == RTL ? " RTL" : " LTR"); 383 if (run.m_dirOverride) 384 ts << " override"; 385 } 386 ts << ": " 387 << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len())) 388 << "\n"; 389 } 390 391 void write(TextStream& ts, const RenderObject& o, int indent) 392 { 393 #if ENABLE(SVG) 394 if (o.isRenderPath()) { 395 write(ts, *toRenderPath(&o), indent); 396 return; 397 } 398 if (o.isSVGContainer()) { 399 writeSVGContainer(ts, o, indent); 400 return; 401 } 402 if (o.isSVGRoot()) { 403 write(ts, *toRenderSVGRoot(&o), indent); 404 return; 405 } 406 if (o.isSVGText()) { 407 if (!o.isText()) 408 writeSVGText(ts, *toRenderBlock(&o), indent); 409 else 410 writeSVGInlineText(ts, *toRenderText(&o), indent); 411 return; 412 } 413 if (o.isSVGImage()) { 414 writeSVGImage(ts, *toRenderImage(&o), indent); 415 return; 416 } 417 #endif 418 419 writeIndent(ts, indent); 420 421 ts << o << "\n"; 422 423 if (o.isText() && !o.isBR()) { 424 const RenderText& text = *toRenderText(&o); 425 for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) { 426 writeIndent(ts, indent + 1); 427 writeTextRun(ts, text, *box); 428 } 429 } 430 431 for (RenderObject* child = o.firstChild(); child; child = child->nextSibling()) { 432 if (child->hasLayer()) 433 continue; 434 write(ts, *child, indent + 1); 435 } 436 437 if (o.isWidget()) { 438 Widget* widget = toRenderWidget(&o)->widget(); 439 if (widget && widget->isFrameView()) { 440 FrameView* view = static_cast<FrameView*>(widget); 441 RenderView* root = view->frame()->contentRenderer(); 442 if (root) { 443 view->layout(); 444 RenderLayer* l = root->layer(); 445 if (l) 446 writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), indent + 1); 447 } 448 } 449 } 450 } 451 452 enum LayerPaintPhase { 453 LayerPaintPhaseAll = 0, 454 LayerPaintPhaseBackground = -1, 455 LayerPaintPhaseForeground = 1 456 }; 457 458 static void write(TextStream& ts, RenderLayer& l, 459 const IntRect& layerBounds, const IntRect& backgroundClipRect, const IntRect& clipRect, const IntRect& outlineClipRect, 460 LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal) 461 { 462 writeIndent(ts, indent); 463 464 ts << "layer " << layerBounds; 465 466 if (!layerBounds.isEmpty()) { 467 if (!backgroundClipRect.contains(layerBounds)) 468 ts << " backgroundClip " << backgroundClipRect; 469 if (!clipRect.contains(layerBounds)) 470 ts << " clip " << clipRect; 471 if (!outlineClipRect.contains(layerBounds)) 472 ts << " outlineClip " << outlineClipRect; 473 } 474 475 if (l.renderer()->hasOverflowClip()) { 476 if (l.scrollXOffset()) 477 ts << " scrollX " << l.scrollXOffset(); 478 if (l.scrollYOffset()) 479 ts << " scrollY " << l.scrollYOffset(); 480 if (l.renderBox() && l.renderBox()->clientWidth() != l.scrollWidth()) 481 ts << " scrollWidth " << l.scrollWidth(); 482 if (l.renderBox() && l.renderBox()->clientHeight() != l.scrollHeight()) 483 ts << " scrollHeight " << l.scrollHeight(); 484 } 485 486 if (paintPhase == LayerPaintPhaseBackground) 487 ts << " layerType: background only"; 488 else if (paintPhase == LayerPaintPhaseForeground) 489 ts << " layerType: foreground only"; 490 491 #if USE(ACCELERATED_COMPOSITING) 492 if (behavior & RenderAsTextShowCompositedLayers) { 493 if (l.isComposited()) 494 ts << " (composited, bounds " << l.backing()->compositedBounds() << ")"; 495 } 496 #else 497 UNUSED_PARAM(behavior); 498 #endif 499 500 ts << "\n"; 501 502 if (paintPhase != LayerPaintPhaseBackground) 503 write(ts, *l.renderer(), indent + 1); 504 } 505 506 static void writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* l, 507 const IntRect& paintDirtyRect, int indent, RenderAsTextBehavior behavior) 508 { 509 // Calculate the clip rects we should use. 510 IntRect layerBounds, damageRect, clipRectToApply, outlineRect; 511 l->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, true); 512 513 // Ensure our lists are up-to-date. 514 l->updateZOrderLists(); 515 l->updateNormalFlowList(); 516 517 bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : l->intersectsDamageRect(layerBounds, damageRect, rootLayer); 518 Vector<RenderLayer*>* negList = l->negZOrderList(); 519 bool paintsBackgroundSeparately = negList && negList->size() > 0; 520 if (shouldPaint && paintsBackgroundSeparately) 521 write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, LayerPaintPhaseBackground, indent, behavior); 522 523 if (negList) { 524 int currIndent = indent; 525 if (behavior & RenderAsTextShowLayerNesting) { 526 writeIndent(ts, indent); 527 ts << " negative z-order list(" << negList->size() << ")\n"; 528 ++currIndent; 529 } 530 for (unsigned i = 0; i != negList->size(); ++i) 531 writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, currIndent, behavior); 532 } 533 534 if (shouldPaint) 535 write(ts, *l, layerBounds, damageRect, clipRectToApply, outlineRect, paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior); 536 537 if (Vector<RenderLayer*>* normalFlowList = l->normalFlowList()) { 538 int currIndent = indent; 539 if (behavior & RenderAsTextShowLayerNesting) { 540 writeIndent(ts, indent); 541 ts << " normal flow list(" << normalFlowList->size() << ")\n"; 542 ++currIndent; 543 } 544 for (unsigned i = 0; i != normalFlowList->size(); ++i) 545 writeLayers(ts, rootLayer, normalFlowList->at(i), paintDirtyRect, currIndent, behavior); 546 } 547 548 if (Vector<RenderLayer*>* posList = l->posZOrderList()) { 549 int currIndent = indent; 550 if (behavior & RenderAsTextShowLayerNesting) { 551 writeIndent(ts, indent); 552 ts << " positive z-order list(" << posList->size() << ")\n"; 553 ++currIndent; 554 } 555 for (unsigned i = 0; i != posList->size(); ++i) 556 writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, currIndent, behavior); 557 } 558 } 559 560 static String nodePosition(Node* node) 561 { 562 String result; 563 564 Node* parent; 565 for (Node* n = node; n; n = parent) { 566 parent = n->parentNode(); 567 if (!parent) 568 parent = n->shadowParentNode(); 569 if (n != node) 570 result += " of "; 571 if (parent) 572 result += "child " + String::number(n->nodeIndex()) + " {" + getTagName(n) + "}"; 573 else 574 result += "document"; 575 } 576 577 return result; 578 } 579 580 static void writeSelection(TextStream& ts, const RenderObject* o) 581 { 582 Node* n = o->node(); 583 if (!n || !n->isDocumentNode()) 584 return; 585 586 Document* doc = static_cast<Document*>(n); 587 Frame* frame = doc->frame(); 588 if (!frame) 589 return; 590 591 VisibleSelection selection = frame->selection()->selection(); 592 if (selection.isCaret()) { 593 ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().node()); 594 if (selection.affinity() == UPSTREAM) 595 ts << " (upstream affinity)"; 596 ts << "\n"; 597 } else if (selection.isRange()) 598 ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().node()) << "\n" 599 << "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().node()) << "\n"; 600 } 601 602 String externalRepresentation(Frame* frame, RenderAsTextBehavior behavior) 603 { 604 frame->document()->updateLayout(); 605 606 RenderObject* o = frame->contentRenderer(); 607 if (!o) 608 return String(); 609 610 TextStream ts; 611 #if ENABLE(SVG) 612 writeRenderResources(ts, o->document()); 613 #endif 614 if (o->hasLayer()) { 615 RenderLayer* l = toRenderBox(o)->layer(); 616 writeLayers(ts, l, l, IntRect(l->x(), l->y(), l->width(), l->height()), 0, behavior); 617 writeSelection(ts, o); 618 } 619 return ts.release(); 620 } 621 622 static void writeCounterValuesFromChildren(TextStream& stream, RenderObject* parent, bool& isFirstCounter) 623 { 624 for (RenderObject* child = parent->firstChild(); child; child = child->nextSibling()) { 625 if (child->isCounter()) { 626 if (!isFirstCounter) 627 stream << " "; 628 isFirstCounter = false; 629 String str(toRenderText(child)->text()); 630 stream << str; 631 } 632 } 633 } 634 635 String counterValueForElement(Element* element) 636 { 637 // Make sure the element is not freed during the layout. 638 RefPtr<Element> elementRef(element); 639 element->document()->updateLayout(); 640 TextStream stream; 641 bool isFirstCounter = true; 642 // The counter renderers should be children of anonymous children 643 // (i.e., :before or :after pseudo-elements). 644 if (RenderObject* renderer = element->renderer()) { 645 for (RenderObject* child = renderer->firstChild(); child; child = child->nextSibling()) { 646 if (child->isAnonymous()) 647 writeCounterValuesFromChildren(stream, child, isFirstCounter); 648 } 649 } 650 return stream.release(); 651 } 652 653 } // namespace WebCore 654