2 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "core/rendering/RenderTreeAsText.h"
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/RenderFlowThread.h"
44 #include "core/rendering/RenderInline.h"
45 #include "core/rendering/RenderLayer.h"
46 #include "core/rendering/RenderListItem.h"
47 #include "core/rendering/RenderListMarker.h"
48 #include "core/rendering/RenderPart.h"
49 #include "core/rendering/RenderTableCell.h"
50 #include "core/rendering/RenderView.h"
51 #include "core/rendering/RenderWidget.h"
52 #include "core/rendering/svg/RenderSVGContainer.h"
53 #include "core/rendering/svg/RenderSVGGradientStop.h"
54 #include "core/rendering/svg/RenderSVGImage.h"
55 #include "core/rendering/svg/RenderSVGInlineText.h"
56 #include "core/rendering/svg/RenderSVGPath.h"
57 #include "core/rendering/svg/RenderSVGRoot.h"
58 #include "core/rendering/svg/RenderSVGText.h"
59 #include "core/rendering/svg/SVGRenderTreeAsText.h"
60 #include "wtf/HexNumber.h"
61 #include "wtf/Vector.h"
62 #include "wtf/unicode/CharacterNames.h"
66 using namespace HTMLNames;
68 static void printBorderStyle(TextStream& ts, const EBorderStyle borderStyle)
70 switch (borderStyle) {
106 static String getTagName(Node* n)
108 if (n->isDocumentNode())
110 if (n->nodeType() == Node::COMMENT_NODE)
112 return n->nodeName();
115 static bool isEmptyOrUnstyledAppleStyleSpan(const Node* node)
117 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
120 const HTMLElement* elem = toHTMLElement(node);
121 if (elem->getAttribute(classAttr) != "Apple-style-span")
124 if (!node->hasChildNodes())
127 const StylePropertySet* inlineStyleDecl = elem->inlineStyle();
128 return (!inlineStyleDecl || inlineStyleDecl->isEmpty());
131 String quoteAndEscapeNonPrintables(const String& s)
133 StringBuilder result;
135 for (unsigned i = 0; i != s.length(); ++i) {
140 } else if (c == '"') {
143 } else if (c == '\n' || c == noBreakSpace)
146 if (c >= 0x20 && c < 0x7F)
152 appendUnsignedAsHex(c, result);
158 return result.toString();
161 void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior)
163 ts << o.renderName();
165 if (behavior & RenderAsTextShowAddresses)
166 ts << " " << static_cast<const void*>(&o);
168 if (o.style() && o.style()->zIndex())
169 ts << " zI: " << o.style()->zIndex();
172 String tagName = getTagName(o.node());
173 // FIXME: Temporary hack to make tests pass by simulating the old generated content output.
174 if (o.isPseudoElement() || (o.parent() && o.parent()->isPseudoElement()))
176 if (!tagName.isEmpty()) {
177 ts << " {" << tagName << "}";
178 // flag empty or unstyled AppleStyleSpan because we never
179 // want to leave them in the DOM
180 if (isEmptyOrUnstyledAppleStyleSpan(o.node()))
181 ts << " *empty or unstyled AppleStyleSpan*";
185 RenderBlock* cb = o.containingBlock();
186 bool adjustForTableCells = cb ? cb->isTableCell() : false;
190 // 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
191 // many test results.
192 const RenderText& text = *toRenderText(&o);
193 IntRect linesBox = text.linesBoundingBox();
194 r = IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height());
195 if (adjustForTableCells && !text.firstTextBox())
196 adjustForTableCells = false;
197 } else if (o.isRenderInline()) {
198 // FIXME: Would be better not to just dump 0, 0 as the x and y here.
199 const RenderInline& inlineFlow = *toRenderInline(&o);
200 r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height());
201 adjustForTableCells = false;
202 } else if (o.isTableCell()) {
203 // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect. We'd like
204 // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are
205 // captured by the results.
206 const RenderTableCell& cell = *toRenderTableCell(&o);
207 r = LayoutRect(cell.x(), cell.y() + cell.intrinsicPaddingBefore(), cell.width(), cell.height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter());
208 } else if (o.isBox())
209 r = toRenderBox(&o)->frameRect();
211 // FIXME: Temporary in order to ensure compatibility with existing layout test results.
212 if (adjustForTableCells)
213 r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore());
217 if (!(o.isText() && !o.isBR())) {
218 if (o.isFileUploadControl())
219 ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue());
222 Color color = o.resolveColor(CSSPropertyColor);
223 if (o.parent()->resolveColor(CSSPropertyColor) != color)
224 ts << " [color=" << color.nameForRenderTreeAsText() << "]";
226 // Do not dump invalid or transparent backgrounds, since that is the default.
227 Color backgroundColor = o.resolveColor(CSSPropertyBackgroundColor);
228 if (o.parent()->resolveColor(CSSPropertyBackgroundColor) != backgroundColor
229 && backgroundColor.rgb())
230 ts << " [bgcolor=" << backgroundColor.nameForRenderTreeAsText() << "]";
232 Color textFillColor = o.resolveColor(CSSPropertyWebkitTextFillColor);
233 if (o.parent()->resolveColor(CSSPropertyWebkitTextFillColor) != textFillColor
234 && textFillColor != color && textFillColor.rgb())
235 ts << " [textFillColor=" << textFillColor.nameForRenderTreeAsText() << "]";
237 Color textStrokeColor = o.resolveColor(CSSPropertyWebkitTextStrokeColor);
238 if (o.parent()->resolveColor(CSSPropertyWebkitTextStrokeColor) != textStrokeColor
239 && textStrokeColor != color && textStrokeColor.rgb())
240 ts << " [textStrokeColor=" << textStrokeColor.nameForRenderTreeAsText() << "]";
242 if (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth() && o.style()->textStrokeWidth() > 0)
243 ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]";
246 if (!o.isBoxModelObject())
249 const RenderBoxModelObject& box = *toRenderBoxModelObject(&o);
250 if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) {
253 BorderValue prevBorder = o.style()->borderTop();
254 if (!box.borderTop())
257 ts << " (" << box.borderTop() << "px ";
258 printBorderStyle(ts, o.style()->borderTopStyle());
259 Color col = o.resolveColor(CSSPropertyBorderTopColor);
260 ts << col.nameForRenderTreeAsText() << ")";
263 if (o.style()->borderRight() != prevBorder) {
264 prevBorder = o.style()->borderRight();
265 if (!box.borderRight())
268 ts << " (" << box.borderRight() << "px ";
269 printBorderStyle(ts, o.style()->borderRightStyle());
270 Color col = o.resolveColor(CSSPropertyBorderRightColor);
271 ts << col.nameForRenderTreeAsText() << ")";
275 if (o.style()->borderBottom() != prevBorder) {
276 prevBorder = box.style()->borderBottom();
277 if (!box.borderBottom())
280 ts << " (" << box.borderBottom() << "px ";
281 printBorderStyle(ts, o.style()->borderBottomStyle());
282 Color col = o.resolveColor(CSSPropertyBorderBottomColor);
283 ts << col.nameForRenderTreeAsText() << ")";
287 if (o.style()->borderLeft() != prevBorder) {
288 prevBorder = o.style()->borderLeft();
289 if (!box.borderLeft())
292 ts << " (" << box.borderLeft() << "px ";
293 printBorderStyle(ts, o.style()->borderLeftStyle());
294 Color col = o.resolveColor(CSSPropertyBorderLeftColor);
295 ts << col.nameForRenderTreeAsText() << ")";
303 if (o.isTableCell()) {
304 const RenderTableCell& c = *toRenderTableCell(&o);
305 ts << " [r=" << c.rowIndex() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]";
308 if (o.isDetailsMarker()) {
310 switch (toRenderDetailsMarker(&o)->orientation()) {
311 case RenderDetailsMarker::Left:
314 case RenderDetailsMarker::Right:
317 case RenderDetailsMarker::Up:
320 case RenderDetailsMarker::Down:
326 if (o.isListMarker()) {
327 String text = toRenderListMarker(&o)->text();
328 if (!text.isEmpty()) {
329 if (text.length() != 1)
330 text = quoteAndEscapeNonPrintables(text);
337 text = "black square";
340 text = "white bullet";
343 text = quoteAndEscapeNonPrintables(text);
350 if (behavior & RenderAsTextShowIDAndClass) {
351 if (Node* node = o.node()) {
353 ts << " id=\"" + toElement(node)->getIdAttribute() + "\"";
355 if (node->hasClass()) {
357 Element* element = toElement(node);
358 for (size_t i = 0; i < element->classNames().size(); ++i) {
361 ts << element->classNames()[i];
368 if (behavior & RenderAsTextShowLayoutState) {
369 bool needsLayout = o.selfNeedsLayout() || o.needsPositionedMovementLayout() || o.posChildNeedsLayout() || o.normalChildNeedsLayout();
371 ts << " (needs layout:";
373 bool havePrevious = false;
374 if (o.selfNeedsLayout()) {
379 if (o.needsPositionedMovementLayout()) {
383 ts << " positioned movement";
386 if (o.normalChildNeedsLayout()) {
393 if (o.posChildNeedsLayout()) {
396 ts << " positioned child";
404 static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run)
406 // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder
407 // to detect any changes caused by the conversion to floating point. :(
410 int logicalWidth = ceilf(run.left() + run.logicalWidth()) - x;
412 // FIXME: Table cell adjustment is temporary until results can be updated.
413 if (o.containingBlock()->isTableCell())
414 y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore();
416 ts << "text run at (" << x << "," << y << ") width " << logicalWidth;
417 if (!run.isLeftToRightDirection() || run.dirOverride()) {
418 ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR");
419 if (run.dirOverride())
423 << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len()));
425 ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style()->hyphenString());
429 void write(TextStream& ts, const RenderObject& o, int indent, RenderAsTextBehavior behavior)
431 if (o.isSVGShape()) {
432 write(ts, *toRenderSVGShape(&o), indent);
435 if (o.isSVGGradientStop()) {
436 writeSVGGradientStop(ts, *toRenderSVGGradientStop(&o), indent);
439 if (o.isSVGResourceContainer()) {
440 writeSVGResourceContainer(ts, o, indent);
443 if (o.isSVGContainer()) {
444 writeSVGContainer(ts, o, indent);
448 write(ts, *toRenderSVGRoot(&o), indent);
452 writeSVGText(ts, *toRenderSVGText(&o), indent);
455 if (o.isSVGInlineText()) {
456 writeSVGInlineText(ts, *toRenderSVGInlineText(&o), indent);
459 if (o.isSVGImage()) {
460 writeSVGImage(ts, *toRenderSVGImage(&o), indent);
464 writeIndent(ts, indent);
466 RenderTreeAsText::writeRenderObject(ts, o, behavior);
469 if (o.isText() && !o.isBR()) {
470 const RenderText& text = *toRenderText(&o);
471 for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
472 writeIndent(ts, indent + 1);
473 writeTextRun(ts, text, *box);
477 for (RenderObject* child = o.firstChild(); child; child = child->nextSibling()) {
478 if (child->hasLayer())
480 write(ts, *child, indent + 1, behavior);
484 Widget* widget = toRenderWidget(&o)->widget();
485 if (widget && widget->isFrameView()) {
486 FrameView* view = toFrameView(widget);
487 RenderView* root = view->frame().contentRenderer();
490 RenderLayer* l = root->layer();
492 RenderTreeAsText::writeLayers(ts, l, l, l->rect(), indent + 1, behavior);
498 enum LayerPaintPhase {
499 LayerPaintPhaseAll = 0,
500 LayerPaintPhaseBackground = -1,
501 LayerPaintPhaseForeground = 1
504 static void write(TextStream& ts, RenderLayer& l,
505 const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, const LayoutRect& clipRect, const LayoutRect& outlineClipRect,
506 LayerPaintPhase paintPhase = LayerPaintPhaseAll, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal)
508 IntRect adjustedLayoutBounds = pixelSnappedIntRect(layerBounds);
509 IntRect adjustedBackgroundClipRect = pixelSnappedIntRect(backgroundClipRect);
510 IntRect adjustedClipRect = pixelSnappedIntRect(clipRect);
511 IntRect adjustedOutlineClipRect = pixelSnappedIntRect(outlineClipRect);
513 writeIndent(ts, indent);
515 if (l.renderer()->style()->visibility() == HIDDEN)
520 if (behavior & RenderAsTextShowAddresses)
521 ts << static_cast<const void*>(&l) << " ";
523 ts << adjustedLayoutBounds;
525 if (!adjustedLayoutBounds.isEmpty()) {
526 if (!adjustedBackgroundClipRect.contains(adjustedLayoutBounds))
527 ts << " backgroundClip " << adjustedBackgroundClipRect;
528 if (!adjustedClipRect.contains(adjustedLayoutBounds))
529 ts << " clip " << adjustedClipRect;
530 if (!adjustedOutlineClipRect.contains(adjustedLayoutBounds))
531 ts << " outlineClip " << adjustedOutlineClipRect;
533 if (l.isTransparent())
534 ts << " transparent";
536 if (l.renderer()->hasOverflowClip()) {
537 if (l.scrollableArea()->scrollXOffset())
538 ts << " scrollX " << l.scrollableArea()->scrollXOffset();
539 if (l.scrollableArea()->scrollYOffset())
540 ts << " scrollY " << l.scrollableArea()->scrollYOffset();
541 if (l.renderBox() && l.renderBox()->pixelSnappedClientWidth() != l.renderBox()->scrollWidth())
542 ts << " scrollWidth " << l.renderBox()->scrollWidth();
543 if (l.renderBox() && l.renderBox()->pixelSnappedClientHeight() != l.renderBox()->scrollHeight())
544 ts << " scrollHeight " << l.renderBox()->scrollHeight();
547 if (paintPhase == LayerPaintPhaseBackground)
548 ts << " layerType: background only";
549 else if (paintPhase == LayerPaintPhaseForeground)
550 ts << " layerType: foreground only";
552 if (behavior & RenderAsTextShowCompositedLayers) {
553 if (l.hasCompositedLayerMapping()) {
554 ts << " (composited, bounds="
555 << l.compositedLayerMapping()->compositedBounds()
557 << l.compositedLayerMapping()->mainGraphicsLayer()->drawsContent()
558 << ", paints into ancestor="
559 << l.compositedLayerMapping()->paintsIntoCompositedAncestor()
566 if (paintPhase != LayerPaintPhaseBackground)
567 write(ts, *l.renderer(), indent + 1, behavior);
570 void RenderTreeAsText::writeLayers(TextStream& ts, const RenderLayer* rootLayer, RenderLayer* layer,
571 const LayoutRect& paintRect, int indent, RenderAsTextBehavior behavior)
573 // FIXME: Apply overflow to the root layer to not break every test. Complete hack. Sigh.
574 LayoutRect paintDirtyRect(paintRect);
575 if (rootLayer == layer) {
576 paintDirtyRect.setWidth(max<LayoutUnit>(paintDirtyRect.width(), rootLayer->renderBox()->layoutOverflowRect().maxX()));
577 paintDirtyRect.setHeight(max<LayoutUnit>(paintDirtyRect.height(), rootLayer->renderBox()->layoutOverflowRect().maxY()));
578 layer->setSize(layer->size().expandedTo(pixelSnappedIntSize(layer->renderBox()->maxLayoutOverflow(), LayoutPoint(0, 0))));
581 // Calculate the clip rects we should use.
582 LayoutRect layerBounds;
583 ClipRect damageRect, clipRectToApply, outlineRect;
584 layer->clipper().calculateRects(ClipRectsContext(rootLayer, TemporaryClipRects), paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect);
586 // Ensure our lists are up-to-date.
587 layer->stackingNode()->updateLayerListsIfNeeded();
589 bool shouldPaint = (behavior & RenderAsTextShowAllLayers) ? true : layer->intersectsDamageRect(layerBounds, damageRect.rect(), rootLayer);
591 Vector<RenderLayerStackingNode*>* negList = layer->stackingNode()->negZOrderList();
592 bool paintsBackgroundSeparately = negList && negList->size() > 0;
593 if (shouldPaint && paintsBackgroundSeparately)
594 write(ts, *layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), outlineRect.rect(), LayerPaintPhaseBackground, indent, behavior);
597 int currIndent = indent;
598 if (behavior & RenderAsTextShowLayerNesting) {
599 writeIndent(ts, indent);
600 ts << " negative z-order list(" << negList->size() << ")\n";
603 for (unsigned i = 0; i != negList->size(); ++i)
604 writeLayers(ts, rootLayer, negList->at(i)->layer(), paintDirtyRect, currIndent, behavior);
608 write(ts, *layer, layerBounds, damageRect.rect(), clipRectToApply.rect(), outlineRect.rect(), paintsBackgroundSeparately ? LayerPaintPhaseForeground : LayerPaintPhaseAll, indent, behavior);
610 if (Vector<RenderLayerStackingNode*>* normalFlowList = layer->stackingNode()->normalFlowList()) {
611 int currIndent = indent;
612 if (behavior & RenderAsTextShowLayerNesting) {
613 writeIndent(ts, indent);
614 ts << " normal flow list(" << normalFlowList->size() << ")\n";
617 for (unsigned i = 0; i != normalFlowList->size(); ++i)
618 writeLayers(ts, rootLayer, normalFlowList->at(i)->layer(), paintDirtyRect, currIndent, behavior);
621 if (Vector<RenderLayerStackingNode*>* posList = layer->stackingNode()->posZOrderList()) {
622 int currIndent = indent;
623 if (behavior & RenderAsTextShowLayerNesting) {
624 writeIndent(ts, indent);
625 ts << " positive z-order list(" << posList->size() << ")\n";
628 for (unsigned i = 0; i != posList->size(); ++i)
629 writeLayers(ts, rootLayer, posList->at(i)->layer(), paintDirtyRect, currIndent, behavior);
633 static String nodePosition(Node* node)
635 StringBuilder result;
637 Element* body = node->document().body();
639 for (Node* n = node; n; n = parent) {
640 parent = n->parentOrShadowHostNode();
642 result.appendLiteral(" of ");
644 if (body && n == body) {
645 // We don't care what offset body may be in the document.
646 result.appendLiteral("body");
649 if (n->isShadowRoot()) {
651 result.append(getTagName(n));
654 result.appendLiteral("child ");
655 result.appendNumber(n->nodeIndex());
656 result.appendLiteral(" {");
657 result.append(getTagName(n));
661 result.appendLiteral("document");
664 return result.toString();
667 static void writeSelection(TextStream& ts, const RenderObject* o)
670 if (!n || !n->isDocumentNode())
673 Document* doc = toDocument(n);
674 Frame* frame = doc->frame();
678 VisibleSelection selection = frame->selection().selection();
679 if (selection.isCaret()) {
680 ts << "caret: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode());
681 if (selection.affinity() == UPSTREAM)
682 ts << " (upstream affinity)";
684 } else if (selection.isRange())
685 ts << "selection start: position " << selection.start().deprecatedEditingOffset() << " of " << nodePosition(selection.start().deprecatedNode()) << "\n"
686 << "selection end: position " << selection.end().deprecatedEditingOffset() << " of " << nodePosition(selection.end().deprecatedNode()) << "\n";
689 static String externalRepresentation(RenderBox* renderer, RenderAsTextBehavior behavior)
692 if (!renderer->hasLayer())
695 RenderLayer* layer = renderer->layer();
696 RenderTreeAsText::writeLayers(ts, layer, layer, layer->rect(), 0, behavior);
697 writeSelection(ts, renderer);
701 String externalRepresentation(Frame* frame, RenderAsTextBehavior behavior)
703 if (!(behavior & RenderAsTextDontUpdateLayout))
704 frame->document()->updateLayout();
706 RenderObject* renderer = frame->contentRenderer();
707 if (!renderer || !renderer->isBox())
710 PrintContext printContext(frame);
711 if (behavior & RenderAsTextPrintingMode)
712 printContext.begin(toRenderBox(renderer)->width());
714 return externalRepresentation(toRenderBox(renderer), behavior);
717 String externalRepresentation(Element* element, RenderAsTextBehavior behavior)
719 // Doesn't support printing mode.
720 ASSERT(!(behavior & RenderAsTextPrintingMode));
721 if (!(behavior & RenderAsTextDontUpdateLayout))
722 element->document().updateLayout();
724 RenderObject* renderer = element->renderer();
725 if (!renderer || !renderer->isBox())
728 return externalRepresentation(toRenderBox(renderer), behavior | RenderAsTextShowAllLayers);
731 static void writeCounterValuesFromChildren(TextStream& stream, RenderObject* parent, bool& isFirstCounter)
733 for (RenderObject* child = parent->firstChild(); child; child = child->nextSibling()) {
734 if (child->isCounter()) {
737 isFirstCounter = false;
738 String str(toRenderText(child)->text());
744 String counterValueForElement(Element* element)
746 // Make sure the element is not freed during the layout.
747 RefPtr<Element> elementRef(element);
748 element->document().updateLayout();
750 bool isFirstCounter = true;
751 // The counter renderers should be children of :before or :after pseudo-elements.
752 if (RenderObject* before = element->pseudoElementRenderer(BEFORE))
753 writeCounterValuesFromChildren(stream, before, isFirstCounter);
754 if (RenderObject* after = element->pseudoElementRenderer(AFTER))
755 writeCounterValuesFromChildren(stream, after, isFirstCounter);
756 return stream.release();
759 String markerTextForListItem(Element* element)
761 // Make sure the element is not freed during the layout.
762 RefPtr<Element> elementRef(element);
763 element->document().updateLayout();
765 RenderObject* renderer = element->renderer();
766 if (!renderer || !renderer->isListItem())
769 return toRenderListItem(renderer)->markerText();
772 } // namespace WebCore