1/**
2 * Copyright (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com)
3 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB.  If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 */
21
22#include "config.h"
23#include "RenderCounter.h"
24
25#include "CounterNode.h"
26#include "Document.h"
27#include "Element.h"
28#include "ElementTraversal.h"
29#include "HTMLNames.h"
30#include "HTMLOListElement.h"
31#include "PseudoElement.h"
32#include "RenderListItem.h"
33#include "RenderListMarker.h"
34#include "RenderStyle.h"
35#include "RenderView.h"
36#include <wtf/StdLibExtras.h>
37
38#ifndef NDEBUG
39#include <stdio.h>
40#endif
41
42namespace WebCore {
43
44using namespace HTMLNames;
45
46typedef HashMap<AtomicString, RefPtr<CounterNode>> CounterMap;
47typedef HashMap<const RenderObject*, std::unique_ptr<CounterMap>> CounterMaps;
48
49static CounterNode* makeCounterNode(RenderObject*, const AtomicString& identifier, bool alwaysCreateCounter);
50
51static CounterMaps& counterMaps()
52{
53    DEPRECATED_DEFINE_STATIC_LOCAL(CounterMaps, staticCounterMaps, ());
54    return staticCounterMaps;
55}
56
57// This function processes the renderer tree in the order of the DOM tree
58// including pseudo elements as defined in CSS 2.1.
59static RenderObject* previousInPreOrder(const RenderObject* object)
60{
61    Element* self = toElement(object->node());
62    Element* previous = ElementTraversal::previousIncludingPseudo(self);
63    while (previous && !previous->renderer())
64        previous = ElementTraversal::previousIncludingPseudo(previous);
65    return previous ? previous->renderer() : 0;
66}
67
68static inline Element* parentOrPseudoHostElement(const RenderObject* object)
69{
70    if (object->node()->isPseudoElement())
71        return toPseudoElement(object->node())->hostElement();
72    return toElement(object->node())->parentElement();
73}
74
75// This function processes the renderer tree in the order of the DOM tree
76// including pseudo elements as defined in CSS 2.1.
77static RenderObject* previousSiblingOrParent(const RenderObject* object)
78{
79    Element* self = toElement(object->node());
80    Element* previous = ElementTraversal::pseudoAwarePreviousSibling(self);
81    while (previous && !previous->renderer())
82        previous = ElementTraversal::pseudoAwarePreviousSibling(previous);
83    if (previous)
84        return previous->renderer();
85    previous = parentOrPseudoHostElement(object);
86    return previous ? previous->renderer() : 0;
87}
88
89static inline bool areRenderersElementsSiblings(RenderObject* first, RenderObject* second)
90{
91    return parentOrPseudoHostElement(first) == parentOrPseudoHostElement(second);
92}
93
94// This function processes the renderer tree in the order of the DOM tree
95// including pseudo elements as defined in CSS 2.1.
96static RenderElement* nextInPreOrder(const RenderElement* element, const Element* stayWithin, bool skipDescendants = false)
97{
98    Element* self = element->element();
99    Element* next = skipDescendants ? ElementTraversal::nextIncludingPseudoSkippingChildren(self, stayWithin) : ElementTraversal::nextIncludingPseudo(self, stayWithin);
100    while (next && !next->renderer())
101        next = skipDescendants ? ElementTraversal::nextIncludingPseudoSkippingChildren(next, stayWithin) : ElementTraversal::nextIncludingPseudo(next, stayWithin);
102    return next ? next->renderer() : 0;
103}
104
105static bool planCounter(RenderElement* object, const AtomicString& identifier, bool& isReset, int& value)
106{
107    ASSERT(object);
108
109    // We must have a generating node or else we cannot have a counter.
110    Element* generatingElement = object->generatingElement();
111    if (!generatingElement)
112        return false;
113
114    const RenderStyle& style = object->style();
115
116    switch (style.styleType()) {
117    case NOPSEUDO:
118        // Sometimes elements have more then one renderer. Only the first one gets the counter
119        // LayoutTests/http/tests/css/counter-crash.html
120        if (generatingElement->renderer() != object)
121            return false;
122        break;
123    case BEFORE:
124    case AFTER:
125        break;
126    default:
127        return false; // Counters are forbidden from all other pseudo elements.
128    }
129
130    const CounterDirectives directives = style.getCounterDirectives(identifier);
131    if (directives.isDefined()) {
132        value = directives.combinedValue();
133        isReset = directives.isReset();
134        return true;
135    }
136
137    if (identifier == "list-item") {
138        if (object->isListItem()) {
139            if (toRenderListItem(object)->hasExplicitValue()) {
140                value = toRenderListItem(object)->explicitValue();
141                isReset = true;
142                return true;
143            }
144            value = 1;
145            isReset = false;
146            return true;
147        }
148        if (Element* e = object->element()) {
149            if (e->hasTagName(olTag)) {
150                value = toHTMLOListElement(e)->start();
151                isReset = true;
152                return true;
153            }
154            if (e->hasTagName(ulTag) || e->hasTagName(menuTag) || e->hasTagName(dirTag)) {
155                value = 0;
156                isReset = true;
157                return true;
158            }
159        }
160    }
161
162    return false;
163}
164
165// - Finds the insertion point for the counter described by counterOwner, isReset and
166// identifier in the CounterNode tree for identifier and sets parent and
167// previousSibling accordingly.
168// - The function returns true if the counter whose insertion point is searched is NOT
169// the root of the tree.
170// - The root of the tree is a counter reference that is not in the scope of any other
171// counter with the same identifier.
172// - All the counter references with the same identifier as this one that are in
173// children or subsequent siblings of the renderer that owns the root of the tree
174// form the rest of of the nodes of the tree.
175// - The root of the tree is always a reset type reference.
176// - A subtree rooted at any reset node in the tree is equivalent to all counter
177// references that are in the scope of the counter or nested counter defined by that
178// reset node.
179// - Non-reset CounterNodes cannot have descendants.
180
181static bool findPlaceForCounter(RenderObject* counterOwner, const AtomicString& identifier, bool isReset, RefPtr<CounterNode>& parent, RefPtr<CounterNode>& previousSibling)
182{
183    // We cannot stop searching for counters with the same identifier before we also
184    // check this renderer, because it may affect the positioning in the tree of our counter.
185    RenderObject* searchEndRenderer = previousSiblingOrParent(counterOwner);
186    // We check renderers in preOrder from the renderer that our counter is attached to
187    // towards the begining of the document for counters with the same identifier as the one
188    // we are trying to find a place for. This is the next renderer to be checked.
189    RenderObject* currentRenderer = previousInPreOrder(counterOwner);
190    previousSibling = 0;
191    RefPtr<CounterNode> previousSiblingProtector = 0;
192
193    while (currentRenderer) {
194        CounterNode* currentCounter = makeCounterNode(currentRenderer, identifier, false);
195        if (searchEndRenderer == currentRenderer) {
196            // We may be at the end of our search.
197            if (currentCounter) {
198                // We have a suitable counter on the EndSearchRenderer.
199                if (previousSiblingProtector) { // But we already found another counter that we come after.
200                    if (currentCounter->actsAsReset()) {
201                        // We found a reset counter that is on a renderer that is a sibling of ours or a parent.
202                        if (isReset && areRenderersElementsSiblings(currentRenderer, counterOwner)) {
203                            // We are also a reset counter and the previous reset was on a sibling renderer
204                            // hence we are the next sibling of that counter if that reset is not a root or
205                            // we are a root node if that reset is a root.
206                            parent = currentCounter->parent();
207                            previousSibling = parent ? currentCounter : 0;
208                            return parent;
209                        }
210                        // We are not a reset node or the previous reset must be on an ancestor of our owner renderer
211                        // hence we must be a child of that reset counter.
212                        parent = currentCounter;
213                        // In some cases renders can be reparented (ex. nodes inside a table but not in a column or row).
214                        // In these cases the identified previousSibling will be invalid as its parent is different from
215                        // our identified parent.
216                        if (previousSiblingProtector->parent() != currentCounter)
217                            previousSiblingProtector = 0;
218
219                        previousSibling = previousSiblingProtector.get();
220                        return true;
221                    }
222                    // CurrentCounter, the counter at the EndSearchRenderer, is not reset.
223                    if (!isReset || !areRenderersElementsSiblings(currentRenderer, counterOwner)) {
224                        // If the node we are placing is not reset or we have found a counter that is attached
225                        // to an ancestor of the placed counter's owner renderer we know we are a sibling of that node.
226                        if (currentCounter->parent() != previousSiblingProtector->parent())
227                            return false;
228
229                        parent = currentCounter->parent();
230                        previousSibling = previousSiblingProtector.get();
231                        return true;
232                    }
233                } else {
234                    // We are at the potential end of the search, but we had no previous sibling candidate
235                    // In this case we follow pretty much the same logic as above but no ASSERTs about
236                    // previousSibling, and when we are a sibling of the end counter we must set previousSibling
237                    // to currentCounter.
238                    if (currentCounter->actsAsReset()) {
239                        if (isReset && areRenderersElementsSiblings(currentRenderer, counterOwner)) {
240                            parent = currentCounter->parent();
241                            previousSibling = currentCounter;
242                            return parent;
243                        }
244                        parent = currentCounter;
245                        previousSibling = previousSiblingProtector.get();
246                        return true;
247                    }
248                    if (!isReset || !areRenderersElementsSiblings(currentRenderer, counterOwner)) {
249                        parent = currentCounter->parent();
250                        previousSibling = currentCounter;
251                        return true;
252                    }
253                    previousSiblingProtector = currentCounter;
254                }
255            }
256            // We come here if the previous sibling or parent of our owner renderer had no
257            // good counter, or we are a reset node and the counter on the previous sibling
258            // of our owner renderer was not a reset counter.
259            // Set a new goal for the end of the search.
260            searchEndRenderer = previousSiblingOrParent(currentRenderer);
261        } else {
262            // We are searching descendants of a previous sibling of the renderer that the
263            // counter being placed is attached to.
264            if (currentCounter) {
265                // We found a suitable counter.
266                if (previousSiblingProtector) {
267                    // Since we had a suitable previous counter before, we should only consider this one as our
268                    // previousSibling if it is a reset counter and hence the current previousSibling is its child.
269                    if (currentCounter->actsAsReset()) {
270                        previousSiblingProtector = currentCounter;
271                        // We are no longer interested in previous siblings of the currentRenderer or their children
272                        // as counters they may have attached cannot be the previous sibling of the counter we are placing.
273                        currentRenderer = parentOrPseudoHostElement(currentRenderer)->renderer();
274                        continue;
275                    }
276                } else
277                    previousSiblingProtector = currentCounter;
278                currentRenderer = previousSiblingOrParent(currentRenderer);
279                continue;
280            }
281        }
282        // This function is designed so that the same test is not done twice in an iteration, except for this one
283        // which may be done twice in some cases. Rearranging the decision points though, to accommodate this
284        // performance improvement would create more code duplication than is worthwhile in my oppinion and may further
285        // impede the readability of this already complex algorithm.
286        if (previousSiblingProtector)
287            currentRenderer = previousSiblingOrParent(currentRenderer);
288        else
289            currentRenderer = previousInPreOrder(currentRenderer);
290    }
291    return false;
292}
293
294static CounterNode* makeCounterNode(RenderObject* object, const AtomicString& identifier, bool alwaysCreateCounter)
295{
296    ASSERT(object);
297
298    // Real text nodes don't have their own style so they can't have counters.
299    // We can't even look at their styles or we'll see extra resets and increments!
300    if (object->isText())
301        return nullptr;
302
303    RenderElement* element = toRenderElement(object);
304
305    if (element->hasCounterNodeMap()) {
306        if (CounterMap* nodeMap = counterMaps().get(element)) {
307            if (CounterNode* node = nodeMap->get(identifier))
308                return node;
309        }
310    }
311
312    bool isReset = false;
313    int value = 0;
314    if (!planCounter(element, identifier, isReset, value) && !alwaysCreateCounter)
315        return nullptr;
316
317    RefPtr<CounterNode> newParent = 0;
318    RefPtr<CounterNode> newPreviousSibling = 0;
319    RefPtr<CounterNode> newNode = CounterNode::create(element, isReset, value);
320    if (findPlaceForCounter(element, identifier, isReset, newParent, newPreviousSibling))
321        newParent->insertAfter(newNode.get(), newPreviousSibling.get(), identifier);
322    CounterMap* nodeMap;
323    if (element->hasCounterNodeMap())
324        nodeMap = counterMaps().get(element);
325    else {
326        nodeMap = new CounterMap;
327        counterMaps().set(element, std::unique_ptr<CounterMap>(nodeMap));
328        element->setHasCounterNodeMap(true);
329    }
330    nodeMap->set(identifier, newNode);
331    if (newNode->parent())
332        return newNode.get();
333    // Checking if some nodes that were previously counter tree root nodes
334    // should become children of this node now.
335    CounterMaps& maps = counterMaps();
336    Element* stayWithin = parentOrPseudoHostElement(element);
337    bool skipDescendants;
338    for (RenderElement* currentRenderer = nextInPreOrder(element, stayWithin); currentRenderer; currentRenderer = nextInPreOrder(currentRenderer, stayWithin, skipDescendants)) {
339        skipDescendants = false;
340        if (!currentRenderer->hasCounterNodeMap())
341            continue;
342        CounterNode* currentCounter = maps.get(currentRenderer)->get(identifier);
343        if (!currentCounter)
344            continue;
345        skipDescendants = true;
346        if (currentCounter->parent())
347            continue;
348        if (stayWithin == parentOrPseudoHostElement(currentRenderer) && currentCounter->hasResetType())
349            break;
350        newNode->insertAfter(currentCounter, newNode->lastChild(), identifier);
351    }
352    return newNode.get();
353}
354
355RenderCounter::RenderCounter(Document& document, const CounterContent& counter)
356    : RenderText(document, emptyString())
357    , m_counter(counter)
358    , m_counterNode(nullptr)
359    , m_nextForSameCounter(0)
360{
361    view().addRenderCounter();
362}
363
364RenderCounter::~RenderCounter()
365{
366    if (m_counterNode) {
367        m_counterNode->removeRenderer(this);
368        ASSERT(!m_counterNode);
369    }
370}
371
372void RenderCounter::willBeDestroyed()
373{
374    view().removeRenderCounter();
375    RenderText::willBeDestroyed();
376}
377
378const char* RenderCounter::renderName() const
379{
380    return "RenderCounter";
381}
382
383bool RenderCounter::isCounter() const
384{
385    return true;
386}
387
388String RenderCounter::originalText() const
389{
390    if (!m_counterNode) {
391        RenderElement* beforeAfterContainer = parent();
392        while (true) {
393            if (!beforeAfterContainer)
394                return String();
395            if (!beforeAfterContainer->isAnonymous() && !beforeAfterContainer->isPseudoElement())
396                return String(); // RenderCounters are restricted to before and after pseudo elements
397            PseudoId containerStyle = beforeAfterContainer->style().styleType();
398            if ((containerStyle == BEFORE) || (containerStyle == AFTER))
399                break;
400            beforeAfterContainer = beforeAfterContainer->parent();
401        }
402        makeCounterNode(beforeAfterContainer, m_counter.identifier(), true)->addRenderer(const_cast<RenderCounter*>(this));
403        ASSERT(m_counterNode);
404    }
405    CounterNode* child = m_counterNode;
406    int value = child->actsAsReset() ? child->value() : child->countInParent();
407
408    String text = listMarkerText(m_counter.listStyle(), value);
409
410    if (!m_counter.separator().isNull()) {
411        if (!child->actsAsReset())
412            child = child->parent();
413        while (CounterNode* parent = child->parent()) {
414            text = listMarkerText(m_counter.listStyle(), child->countInParent())
415                + m_counter.separator() + text;
416            child = parent;
417        }
418    }
419
420    return text;
421}
422
423void RenderCounter::updateCounter()
424{
425    computePreferredLogicalWidths(0);
426}
427
428void RenderCounter::computePreferredLogicalWidths(float lead)
429{
430#ifndef NDEBUG
431    // FIXME: We shouldn't be modifying the tree in computePreferredLogicalWidths.
432    // Instead, we should properly hook the appropriate changes in the DOM and modify
433    // the render tree then. When that's done, we also won't need to override
434    // computePreferredLogicalWidths at all.
435    // https://bugs.webkit.org/show_bug.cgi?id=104829
436    SetLayoutNeededForbiddenScope layoutForbiddenScope(this, false);
437#endif
438
439    setRenderedText(originalText());
440
441    RenderText::computePreferredLogicalWidths(lead);
442}
443
444void RenderCounter::invalidate()
445{
446    m_counterNode->removeRenderer(this);
447    ASSERT(!m_counterNode);
448    if (documentBeingDestroyed())
449        return;
450    setNeedsLayoutAndPrefWidthsRecalc();
451}
452
453static void destroyCounterNodeWithoutMapRemoval(const AtomicString& identifier, CounterNode* node)
454{
455    CounterNode* previous;
456    for (RefPtr<CounterNode> child = node->lastDescendant(); child && child != node; child = previous) {
457        previous = child->previousInPreOrder();
458        child->parent()->removeChild(child.get());
459        ASSERT(counterMaps().get(child->owner())->get(identifier) == child);
460        counterMaps().get(child->owner())->remove(identifier);
461    }
462    if (CounterNode* parent = node->parent())
463        parent->removeChild(node);
464}
465
466void RenderCounter::destroyCounterNodes(RenderObject* owner)
467{
468    CounterMaps& maps = counterMaps();
469    CounterMaps::iterator mapsIterator = maps.find(owner);
470    if (mapsIterator == maps.end())
471        return;
472    CounterMap* map = mapsIterator->value.get();
473    CounterMap::const_iterator end = map->end();
474    for (CounterMap::const_iterator it = map->begin(); it != end; ++it) {
475        destroyCounterNodeWithoutMapRemoval(it->key, it->value.get());
476    }
477    maps.remove(mapsIterator);
478    owner->setHasCounterNodeMap(false);
479}
480
481void RenderCounter::destroyCounterNode(RenderObject* owner, const AtomicString& identifier)
482{
483    CounterMap* map = counterMaps().get(owner);
484    if (!map)
485        return;
486    CounterMap::iterator mapIterator = map->find(identifier);
487    if (mapIterator == map->end())
488        return;
489    destroyCounterNodeWithoutMapRemoval(identifier, mapIterator->value.get());
490    map->remove(mapIterator);
491    // We do not delete "map" here even if empty because we expect to reuse
492    // it soon. In order for a renderer to lose all its counters permanently,
493    // a style change for the renderer involving removal of all counter
494    // directives must occur, in which case, RenderCounter::destroyCounterNodes()
495    // must be called.
496    // The destruction of the Renderer (possibly caused by the removal of its
497    // associated DOM node) is the other case that leads to the permanent
498    // destruction of all counters attached to a Renderer. In this case
499    // RenderCounter::destroyCounterNodes() must be and is now called, too.
500    // RenderCounter::destroyCounterNodes() handles destruction of the counter
501    // map associated with a renderer, so there is no risk in leaking the map.
502}
503
504void RenderCounter::rendererRemovedFromTree(RenderObject& renderer)
505{
506    if (!renderer.view().hasRenderCounters())
507        return;
508    RenderObject* currentRenderer = renderer.lastLeafChild();
509    if (!currentRenderer)
510        currentRenderer = &renderer;
511    while (true) {
512        destroyCounterNodes(currentRenderer);
513        if (currentRenderer == &renderer)
514            break;
515        currentRenderer = currentRenderer->previousInPreOrder();
516    }
517}
518
519static void updateCounters(RenderObject* renderer)
520{
521    const CounterDirectiveMap* directiveMap = renderer->style().counterDirectives();
522    if (!directiveMap)
523        return;
524    CounterDirectiveMap::const_iterator end = directiveMap->end();
525    if (!renderer->hasCounterNodeMap()) {
526        for (CounterDirectiveMap::const_iterator it = directiveMap->begin(); it != end; ++it)
527            makeCounterNode(renderer, it->key, false);
528        return;
529    }
530    CounterMap* counterMap = counterMaps().get(renderer);
531    ASSERT(counterMap);
532    for (CounterDirectiveMap::const_iterator it = directiveMap->begin(); it != end; ++it) {
533        RefPtr<CounterNode> node = counterMap->get(it->key);
534        if (!node) {
535            makeCounterNode(renderer, it->key, false);
536            continue;
537        }
538        RefPtr<CounterNode> newParent = 0;
539        RefPtr<CounterNode> newPreviousSibling = 0;
540
541        findPlaceForCounter(renderer, it->key, node->hasResetType(), newParent, newPreviousSibling);
542        if (node != counterMap->get(it->key))
543            continue;
544        CounterNode* parent = node->parent();
545        if (newParent == parent && newPreviousSibling == node->previousSibling())
546            continue;
547        if (parent)
548            parent->removeChild(node.get());
549        if (newParent)
550            newParent->insertAfter(node.get(), newPreviousSibling.get(), it->key);
551    }
552}
553
554void RenderCounter::rendererSubtreeAttached(RenderObject* renderer)
555{
556    if (!renderer->view().hasRenderCounters())
557        return;
558    Node* node = renderer->node();
559    if (node && !node->isPseudoElement())
560        node = node->parentNode();
561    else
562        node = renderer->generatingNode();
563    if (node && !node->renderer())
564        return; // No need to update if the parent is not attached yet
565    for (RenderObject* descendant = renderer; descendant; descendant = descendant->nextInPreOrder(renderer))
566        updateCounters(descendant);
567}
568
569void RenderCounter::rendererStyleChanged(RenderObject* renderer, const RenderStyle* oldStyle, const RenderStyle* newStyle)
570{
571    Node* node = renderer->generatingNode();
572    if (!node || !node->renderer())
573        return; // cannot have generated content or if it can have, it will be handled during attaching
574    const CounterDirectiveMap* newCounterDirectives;
575    const CounterDirectiveMap* oldCounterDirectives;
576    if (oldStyle && (oldCounterDirectives = oldStyle->counterDirectives())) {
577        if (newStyle && (newCounterDirectives = newStyle->counterDirectives())) {
578            CounterDirectiveMap::const_iterator newMapEnd = newCounterDirectives->end();
579            CounterDirectiveMap::const_iterator oldMapEnd = oldCounterDirectives->end();
580            for (CounterDirectiveMap::const_iterator it = newCounterDirectives->begin(); it != newMapEnd; ++it) {
581                CounterDirectiveMap::const_iterator oldMapIt = oldCounterDirectives->find(it->key);
582                if (oldMapIt != oldMapEnd) {
583                    if (oldMapIt->value == it->value)
584                        continue;
585                    RenderCounter::destroyCounterNode(renderer, it->key);
586                }
587                // We must create this node here, because the changed node may be a node with no display such as
588                // as those created by the increment or reset directives and the re-layout that will happen will
589                // not catch the change if the node had no children.
590                makeCounterNode(renderer, it->key, false);
591            }
592            // Destroying old counters that do not exist in the new counterDirective map.
593            for (CounterDirectiveMap::const_iterator it = oldCounterDirectives->begin(); it !=oldMapEnd; ++it) {
594                if (!newCounterDirectives->contains(it->key))
595                    RenderCounter::destroyCounterNode(renderer, it->key);
596            }
597        } else {
598            if (renderer->hasCounterNodeMap())
599                RenderCounter::destroyCounterNodes(renderer);
600        }
601    } else if (newStyle && (newCounterDirectives = newStyle->counterDirectives())) {
602        CounterDirectiveMap::const_iterator newMapEnd = newCounterDirectives->end();
603        for (CounterDirectiveMap::const_iterator it = newCounterDirectives->begin(); it != newMapEnd; ++it) {
604            // We must create this node here, because the added node may be a node with no display such as
605            // as those created by the increment or reset directives and the re-layout that will happen will
606            // not catch the change if the node had no children.
607            makeCounterNode(renderer, it->key, false);
608        }
609    }
610}
611
612} // namespace WebCore
613
614#ifndef NDEBUG
615
616void showCounterRendererTree(const WebCore::RenderObject* renderer, const char* counterName)
617{
618    if (!renderer)
619        return;
620    const WebCore::RenderObject* root = renderer;
621    while (root->parent())
622        root = root->parent();
623
624    AtomicString identifier(counterName);
625    for (const WebCore::RenderObject* current = root; current; current = current->nextInPreOrder()) {
626        fprintf(stderr, "%c", (current == renderer) ? '*' : ' ');
627        for (const WebCore::RenderObject* parent = current; parent && parent != root; parent = parent->parent())
628            fprintf(stderr, "    ");
629        fprintf(stderr, "%p N:%p P:%p PS:%p NS:%p C:%p\n",
630            current, current->node(), current->parent(), current->previousSibling(),
631            current->nextSibling(), current->hasCounterNodeMap() ?
632            counterName ? WebCore::counterMaps().get(current)->get(identifier) : (WebCore::CounterNode*)1 : (WebCore::CounterNode*)0);
633    }
634    fflush(stderr);
635}
636
637#endif // NDEBUG
638