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