2 * Copyright (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com)
3 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
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.
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.
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.
23 #include "core/layout/LayoutCounter.h"
25 #include "core/HTMLNames.h"
26 #include "core/dom/Element.h"
27 #include "core/dom/ElementTraversal.h"
28 #include "core/html/HTMLOListElement.h"
29 #include "core/layout/CounterNode.h"
30 #include "core/layout/LayoutListItem.h"
31 #include "core/layout/LayoutListMarker.h"
32 #include "core/layout/LayoutView.h"
33 #include "core/style/ComputedStyle.h"
34 #include "wtf/StdLibExtras.h"
42 using namespace HTMLNames
;
44 typedef HashMap
<AtomicString
, RefPtr
<CounterNode
>> CounterMap
;
45 typedef HashMap
<const LayoutObject
*, OwnPtr
<CounterMap
>> CounterMaps
;
47 static CounterNode
* makeCounterNode(LayoutObject
&, const AtomicString
& identifier
, bool alwaysCreateCounter
);
49 // See class definition as to why we have this map.
50 static CounterMaps
& counterMaps()
52 DEFINE_STATIC_LOCAL(CounterMaps
, staticCounterMaps
, ());
53 return staticCounterMaps
;
56 // This function processes the layoutObject tree in the order of the DOM tree
57 // including pseudo elements as defined in CSS 2.1.
58 static LayoutObject
* previousInPreOrder(const LayoutObject
& object
)
60 Element
* self
= toElement(object
.node());
62 Element
* previous
= ElementTraversal::previousIncludingPseudo(*self
);
63 while (previous
&& !previous
->layoutObject())
64 previous
= ElementTraversal::previousIncludingPseudo(*previous
);
65 return previous
? previous
->layoutObject() : 0;
68 // This function processes the layoutObject tree in the order of the DOM tree
69 // including pseudo elements as defined in CSS 2.1.
70 static LayoutObject
* previousSiblingOrParent(const LayoutObject
& object
)
72 Element
* self
= toElement(object
.node());
74 Element
* previous
= ElementTraversal::pseudoAwarePreviousSibling(*self
);
75 while (previous
&& !previous
->layoutObject())
76 previous
= ElementTraversal::pseudoAwarePreviousSibling(*previous
);
78 return previous
->layoutObject();
79 previous
= self
->parentElement();
80 return previous
? previous
->layoutObject() : 0;
83 static inline Element
* parentElement(LayoutObject
& object
)
85 return toElement(object
.node())->parentElement();
88 static inline bool areLayoutObjectsElementsSiblings(LayoutObject
& first
, LayoutObject
& second
)
90 return parentElement(first
) == parentElement(second
);
93 // This function processes the layoutObject tree in the order of the DOM tree
94 // including pseudo elements as defined in CSS 2.1.
95 static LayoutObject
* nextInPreOrder(const LayoutObject
& object
, const Element
* stayWithin
, bool skipDescendants
= false)
97 Element
* self
= toElement(object
.node());
99 Element
* next
= skipDescendants
? ElementTraversal::nextIncludingPseudoSkippingChildren(*self
, stayWithin
) : ElementTraversal::nextIncludingPseudo(*self
, stayWithin
);
100 while (next
&& !next
->layoutObject())
101 next
= skipDescendants
? ElementTraversal::nextIncludingPseudoSkippingChildren(*next
, stayWithin
) : ElementTraversal::nextIncludingPseudo(*next
, stayWithin
);
102 return next
? next
->layoutObject() : 0;
105 static bool planCounter(LayoutObject
& object
, const AtomicString
& identifier
, bool& isReset
, int& value
)
107 // Real text nodes don't have their own style so they can't have counters.
108 // We can't even look at their styles or we'll see extra resets and increments!
109 if (object
.isText() && !object
.isBR())
111 Node
* generatingNode
= object
.generatingNode();
112 // We must have a generating node or else we cannot have a counter.
115 const ComputedStyle
& style
= object
.styleRef();
117 switch (style
.styleType()) {
119 // Sometimes nodes have more then one layoutObject. Only the first one gets the counter
120 // LayoutTests/http/tests/css/counter-crash.html
121 if (generatingNode
->layoutObject() != &object
)
128 return false; // Counters are forbidden from all other pseudo elements.
131 const CounterDirectives directives
= style
.getCounterDirectives(identifier
);
132 if (directives
.isDefined()) {
133 value
= directives
.combinedValue();
134 isReset
= directives
.isReset();
138 if (identifier
== "list-item") {
139 if (object
.isListItem()) {
140 if (toLayoutListItem(object
).hasExplicitValue()) {
141 value
= toLayoutListItem(object
).explicitValue();
149 if (Node
* e
= object
.node()) {
150 if (isHTMLOListElement(*e
)) {
151 value
= toHTMLOListElement(e
)->start();
155 if (isHTMLUListElement(*e
) || isHTMLMenuElement(*e
) || isHTMLDirectoryElement(*e
)) {
166 // - Finds the insertion point for the counter described by counterOwner, isReset and
167 // identifier in the CounterNode tree for identifier and sets parent and
168 // previousSibling accordingly.
169 // - The function returns true if the counter whose insertion point is searched is NOT
170 // the root of the tree.
171 // - The root of the tree is a counter reference that is not in the scope of any other
172 // counter with the same identifier.
173 // - All the counter references with the same identifier as this one that are in
174 // children or subsequent siblings of the layoutObject that owns the root of the tree
175 // form the rest of of the nodes of the tree.
176 // - The root of the tree is always a reset type reference.
177 // - A subtree rooted at any reset node in the tree is equivalent to all counter
178 // references that are in the scope of the counter or nested counter defined by that
180 // - Non-reset CounterNodes cannot have descendants.
182 static bool findPlaceForCounter(LayoutObject
& counterOwner
, const AtomicString
& identifier
, bool isReset
, RefPtr
<CounterNode
>& parent
, RefPtr
<CounterNode
>& previousSibling
)
184 // We cannot stop searching for counters with the same identifier before we also
185 // check this layoutObject, because it may affect the positioning in the tree of our counter.
186 LayoutObject
* searchEndLayoutObject
= previousSiblingOrParent(counterOwner
);
187 // We check layoutObjects in preOrder from the layoutObject that our counter is attached to
188 // towards the begining of the document for counters with the same identifier as the one
189 // we are trying to find a place for. This is the next layoutObject to be checked.
190 LayoutObject
* currentLayoutObject
= previousInPreOrder(counterOwner
);
191 previousSibling
= nullptr;
192 RefPtr
<CounterNode
> previousSiblingProtector
= nullptr;
194 while (currentLayoutObject
) {
195 CounterNode
* currentCounter
= makeCounterNode(*currentLayoutObject
, identifier
, false);
196 if (searchEndLayoutObject
== currentLayoutObject
) {
197 // We may be at the end of our search.
198 if (currentCounter
) {
199 // We have a suitable counter on the EndSearchLayoutObject.
200 if (previousSiblingProtector
) { // But we already found another counter that we come after.
201 if (currentCounter
->actsAsReset()) {
202 // We found a reset counter that is on a layoutObject that is a sibling of ours or a parent.
203 if (isReset
&& areLayoutObjectsElementsSiblings(*currentLayoutObject
, counterOwner
)) {
204 // We are also a reset counter and the previous reset was on a sibling layoutObject
205 // hence we are the next sibling of that counter if that reset is not a root or
206 // we are a root node if that reset is a root.
207 parent
= currentCounter
->parent();
208 previousSibling
= parent
? currentCounter
: 0;
211 // We are not a reset node or the previous reset must be on an ancestor of our owner layoutObject
212 // hence we must be a child of that reset counter.
213 parent
= currentCounter
;
214 // In some cases layoutObjects can be reparented (ex. nodes inside a table but not in a column or row).
215 // In these cases the identified previousSibling will be invalid as its parent is different from
216 // our identified parent.
217 if (previousSiblingProtector
->parent() != currentCounter
)
218 previousSiblingProtector
= nullptr;
220 previousSibling
= previousSiblingProtector
.get();
223 // CurrentCounter, the counter at the EndSearchLayoutObject, is not reset.
224 if (!isReset
|| !areLayoutObjectsElementsSiblings(*currentLayoutObject
, counterOwner
)) {
225 // If the node we are placing is not reset or we have found a counter that is attached
226 // to an ancestor of the placed counter's owner layoutObject we know we are a sibling of that node.
227 if (currentCounter
->parent() != previousSiblingProtector
->parent())
230 parent
= currentCounter
->parent();
231 previousSibling
= previousSiblingProtector
.get();
235 // We are at the potential end of the search, but we had no previous sibling candidate
236 // In this case we follow pretty much the same logic as above but no ASSERTs about
237 // previousSibling, and when we are a sibling of the end counter we must set previousSibling
238 // to currentCounter.
239 if (currentCounter
->actsAsReset()) {
240 if (isReset
&& areLayoutObjectsElementsSiblings(*currentLayoutObject
, counterOwner
)) {
241 parent
= currentCounter
->parent();
242 previousSibling
= currentCounter
;
245 parent
= currentCounter
;
246 previousSibling
= previousSiblingProtector
.get();
249 if (!isReset
|| !areLayoutObjectsElementsSiblings(*currentLayoutObject
, counterOwner
)) {
250 parent
= currentCounter
->parent();
251 previousSibling
= currentCounter
;
254 previousSiblingProtector
= currentCounter
;
257 // We come here if the previous sibling or parent of our owner layoutObject had no
258 // good counter, or we are a reset node and the counter on the previous sibling
259 // of our owner layoutObject was not a reset counter.
260 // Set a new goal for the end of the search.
261 searchEndLayoutObject
= previousSiblingOrParent(*currentLayoutObject
);
263 // We are searching descendants of a previous sibling of the layoutObject that the
264 // counter being placed is attached to.
265 if (currentCounter
) {
266 // We found a suitable counter.
267 if (previousSiblingProtector
) {
268 // Since we had a suitable previous counter before, we should only consider this one as our
269 // previousSibling if it is a reset counter and hence the current previousSibling is its child.
270 if (currentCounter
->actsAsReset()) {
271 previousSiblingProtector
= currentCounter
;
272 // We are no longer interested in previous siblings of the currentLayoutObject or their children
273 // as counters they may have attached cannot be the previous sibling of the counter we are placing.
274 currentLayoutObject
= parentElement(*currentLayoutObject
)->layoutObject();
278 previousSiblingProtector
= currentCounter
;
280 currentLayoutObject
= previousSiblingOrParent(*currentLayoutObject
);
284 // This function is designed so that the same test is not done twice in an iteration, except for this one
285 // which may be done twice in some cases. Rearranging the decision points though, to accommodate this
286 // performance improvement would create more code duplication than is worthwhile in my oppinion and may further
287 // impede the readability of this already complex algorithm.
288 if (previousSiblingProtector
)
289 currentLayoutObject
= previousSiblingOrParent(*currentLayoutObject
);
291 currentLayoutObject
= previousInPreOrder(*currentLayoutObject
);
296 static CounterNode
* makeCounterNode(LayoutObject
& object
, const AtomicString
& identifier
, bool alwaysCreateCounter
)
298 if (object
.hasCounterNodeMap()) {
299 if (CounterMap
* nodeMap
= counterMaps().get(&object
)) {
300 if (CounterNode
* node
= nodeMap
->get(identifier
))
305 bool isReset
= false;
307 if (!planCounter(object
, identifier
, isReset
, value
) && !alwaysCreateCounter
)
310 RefPtr
<CounterNode
> newParent
= nullptr;
311 RefPtr
<CounterNode
> newPreviousSibling
= nullptr;
312 RefPtr
<CounterNode
> newNode
= CounterNode::create(object
, isReset
, value
);
313 if (findPlaceForCounter(object
, identifier
, isReset
, newParent
, newPreviousSibling
))
314 newParent
->insertAfter(newNode
.get(), newPreviousSibling
.get(), identifier
);
316 if (object
.hasCounterNodeMap()) {
317 nodeMap
= counterMaps().get(&object
);
319 nodeMap
= new CounterMap
;
320 counterMaps().set(&object
, adoptPtr(nodeMap
));
321 object
.setHasCounterNodeMap(true);
323 nodeMap
->set(identifier
, newNode
);
324 if (newNode
->parent())
325 return newNode
.get();
326 // Checking if some nodes that were previously counter tree root nodes
327 // should become children of this node now.
328 CounterMaps
& maps
= counterMaps();
329 Element
* stayWithin
= parentElement(object
);
330 bool skipDescendants
;
331 for (LayoutObject
* currentLayoutObject
= nextInPreOrder(object
, stayWithin
); currentLayoutObject
; currentLayoutObject
= nextInPreOrder(*currentLayoutObject
, stayWithin
, skipDescendants
)) {
332 skipDescendants
= false;
333 if (!currentLayoutObject
->hasCounterNodeMap())
335 CounterNode
* currentCounter
= maps
.get(currentLayoutObject
)->get(identifier
);
338 skipDescendants
= true;
339 if (currentCounter
->parent())
341 if (stayWithin
== parentElement(*currentLayoutObject
) && currentCounter
->hasResetType())
343 newNode
->insertAfter(currentCounter
, newNode
->lastChild(), identifier
);
345 return newNode
.get();
348 LayoutCounter::LayoutCounter(Document
* node
, const CounterContent
& counter
)
349 : LayoutText(node
, StringImpl::empty())
351 , m_counterNode(nullptr)
352 , m_nextForSameCounter(nullptr)
354 view()->addLayoutCounter();
357 LayoutCounter::~LayoutCounter()
361 void LayoutCounter::willBeDestroyed()
364 m_counterNode
->removeLayoutObject(this);
365 ASSERT(!m_counterNode
);
368 view()->removeLayoutCounter();
369 LayoutText::willBeDestroyed();
372 PassRefPtr
<StringImpl
> LayoutCounter::originalText() const
374 if (!m_counterNode
) {
375 LayoutObject
* beforeAfterContainer
= parent();
377 if (!beforeAfterContainer
)
379 if (!beforeAfterContainer
->isAnonymous() && !beforeAfterContainer
->isPseudoElement())
380 return nullptr; // LayoutCounters are restricted to before and after pseudo elements
381 PseudoId containerStyle
= beforeAfterContainer
->style()->styleType();
382 if ((containerStyle
== BEFORE
) || (containerStyle
== AFTER
))
384 beforeAfterContainer
= beforeAfterContainer
->parent();
386 makeCounterNode(*beforeAfterContainer
, m_counter
.identifier(), true)->addLayoutObject(const_cast<LayoutCounter
*>(this));
387 ASSERT(m_counterNode
);
389 CounterNode
* child
= m_counterNode
;
390 int value
= child
->actsAsReset() ? child
->value() : child
->countInParent();
392 String text
= listMarkerText(m_counter
.listStyle(), value
);
394 if (!m_counter
.separator().isNull()) {
395 if (!child
->actsAsReset())
396 child
= child
->parent();
397 while (CounterNode
* parent
= child
->parent()) {
398 text
= listMarkerText(m_counter
.listStyle(), child
->countInParent())
399 + m_counter
.separator() + text
;
407 void LayoutCounter::updateCounter()
409 setText(originalText());
412 void LayoutCounter::invalidate()
414 m_counterNode
->removeLayoutObject(this);
415 ASSERT(!m_counterNode
);
416 if (documentBeingDestroyed())
418 setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(LayoutInvalidationReason::CountersChanged
);
421 static void destroyCounterNodeWithoutMapRemoval(const AtomicString
& identifier
, CounterNode
* node
)
423 CounterNode
* previous
;
424 for (RefPtr
<CounterNode
> child
= node
->lastDescendant(); child
&& child
!= node
; child
= previous
) {
425 previous
= child
->previousInPreOrder();
426 child
->parent()->removeChild(child
.get());
427 ASSERT(counterMaps().get(&child
->owner())->get(identifier
) == child
);
428 counterMaps().get(&child
->owner())->remove(identifier
);
430 if (CounterNode
* parent
= node
->parent())
431 parent
->removeChild(node
);
434 void LayoutCounter::destroyCounterNodes(LayoutObject
& owner
)
436 CounterMaps
& maps
= counterMaps();
437 CounterMaps::iterator mapsIterator
= maps
.find(&owner
);
438 if (mapsIterator
== maps
.end())
440 CounterMap
* map
= mapsIterator
->value
.get();
441 CounterMap::const_iterator end
= map
->end();
442 for (CounterMap::const_iterator it
= map
->begin(); it
!= end
; ++it
) {
443 destroyCounterNodeWithoutMapRemoval(it
->key
, it
->value
.get());
445 maps
.remove(mapsIterator
);
446 owner
.setHasCounterNodeMap(false);
449 void LayoutCounter::destroyCounterNode(LayoutObject
& owner
, const AtomicString
& identifier
)
451 CounterMap
* map
= counterMaps().get(&owner
);
454 CounterMap::iterator mapIterator
= map
->find(identifier
);
455 if (mapIterator
== map
->end())
457 destroyCounterNodeWithoutMapRemoval(identifier
, mapIterator
->value
.get());
458 map
->remove(mapIterator
);
459 // We do not delete "map" here even if empty because we expect to reuse
460 // it soon. In order for a layoutObject to lose all its counters permanently,
461 // a style change for the layoutObject involving removal of all counter
462 // directives must occur, in which case, LayoutCounter::destroyCounterNodes()
464 // The destruction of the LayoutObject (possibly caused by the removal of its
465 // associated DOM node) is the other case that leads to the permanent
466 // destruction of all counters attached to a LayoutObject. In this case
467 // LayoutCounter::destroyCounterNodes() must be and is now called, too.
468 // LayoutCounter::destroyCounterNodes() handles destruction of the counter
469 // map associated with a layoutObject, so there is no risk in leaking the map.
472 void LayoutCounter::layoutObjectSubtreeWillBeDetached(LayoutObject
* layoutObject
)
474 ASSERT(layoutObject
->view());
475 if (!layoutObject
->view()->hasLayoutCounters())
477 LayoutObject
* currentLayoutObject
= layoutObject
->lastLeafChild();
478 if (!currentLayoutObject
)
479 currentLayoutObject
= layoutObject
;
481 destroyCounterNodes(*currentLayoutObject
);
482 if (currentLayoutObject
== layoutObject
)
484 currentLayoutObject
= currentLayoutObject
->previousInPreOrder();
488 static void updateCounters(LayoutObject
& layoutObject
)
490 ASSERT(layoutObject
.style());
491 const CounterDirectiveMap
* directiveMap
= layoutObject
.style()->counterDirectives();
494 CounterDirectiveMap::const_iterator end
= directiveMap
->end();
495 if (!layoutObject
.hasCounterNodeMap()) {
496 for (CounterDirectiveMap::const_iterator it
= directiveMap
->begin(); it
!= end
; ++it
)
497 makeCounterNode(layoutObject
, it
->key
, false);
500 CounterMap
* counterMap
= counterMaps().get(&layoutObject
);
502 for (CounterDirectiveMap::const_iterator it
= directiveMap
->begin(); it
!= end
; ++it
) {
503 RefPtr
<CounterNode
> node
= counterMap
->get(it
->key
);
505 makeCounterNode(layoutObject
, it
->key
, false);
508 RefPtr
<CounterNode
> newParent
= nullptr;
509 RefPtr
<CounterNode
> newPreviousSibling
= nullptr;
511 findPlaceForCounter(layoutObject
, it
->key
, node
->hasResetType(), newParent
, newPreviousSibling
);
512 if (node
!= counterMap
->get(it
->key
))
514 CounterNode
* parent
= node
->parent();
515 if (newParent
== parent
&& newPreviousSibling
== node
->previousSibling())
518 parent
->removeChild(node
.get());
520 newParent
->insertAfter(node
.get(), newPreviousSibling
.get(), it
->key
);
524 void LayoutCounter::layoutObjectSubtreeAttached(LayoutObject
* layoutObject
)
526 ASSERT(layoutObject
->view());
527 if (!layoutObject
->view()->hasLayoutCounters())
529 Node
* node
= layoutObject
->node();
531 node
= node
->parentNode();
533 node
= layoutObject
->generatingNode();
534 if (node
&& node
->needsAttach())
535 return; // No need to update if the parent is not attached yet
536 for (LayoutObject
* descendant
= layoutObject
; descendant
; descendant
= descendant
->nextInPreOrder(layoutObject
))
537 updateCounters(*descendant
);
540 void LayoutCounter::layoutObjectStyleChanged(LayoutObject
& layoutObject
, const ComputedStyle
* oldStyle
, const ComputedStyle
& newStyle
)
542 Node
* node
= layoutObject
.generatingNode();
543 if (!node
|| node
->needsAttach())
544 return; // cannot have generated content or if it can have, it will be handled during attaching
545 const CounterDirectiveMap
* oldCounterDirectives
= oldStyle
? oldStyle
->counterDirectives() : 0;
546 const CounterDirectiveMap
* newCounterDirectives
= newStyle
.counterDirectives();
547 if (oldCounterDirectives
) {
548 if (newCounterDirectives
) {
549 CounterDirectiveMap::const_iterator newMapEnd
= newCounterDirectives
->end();
550 CounterDirectiveMap::const_iterator oldMapEnd
= oldCounterDirectives
->end();
551 for (CounterDirectiveMap::const_iterator it
= newCounterDirectives
->begin(); it
!= newMapEnd
; ++it
) {
552 CounterDirectiveMap::const_iterator oldMapIt
= oldCounterDirectives
->find(it
->key
);
553 if (oldMapIt
!= oldMapEnd
) {
554 if (oldMapIt
->value
== it
->value
)
556 LayoutCounter::destroyCounterNode(layoutObject
, it
->key
);
558 // We must create this node here, because the changed node may be a node with no display such as
559 // as those created by the increment or reset directives and the re-layout that will happen will
560 // not catch the change if the node had no children.
561 makeCounterNode(layoutObject
, it
->key
, false);
563 // Destroying old counters that do not exist in the new counterDirective map.
564 for (CounterDirectiveMap::const_iterator it
= oldCounterDirectives
->begin(); it
!=oldMapEnd
; ++it
) {
565 if (!newCounterDirectives
->contains(it
->key
))
566 LayoutCounter::destroyCounterNode(layoutObject
, it
->key
);
569 if (layoutObject
.hasCounterNodeMap())
570 LayoutCounter::destroyCounterNodes(layoutObject
);
572 } else if (newCounterDirectives
) {
573 if (layoutObject
.hasCounterNodeMap())
574 LayoutCounter::destroyCounterNodes(layoutObject
);
575 CounterDirectiveMap::const_iterator newMapEnd
= newCounterDirectives
->end();
576 for (CounterDirectiveMap::const_iterator it
= newCounterDirectives
->begin(); it
!= newMapEnd
; ++it
) {
577 // We must create this node here, because the added node may be a node with no display such as
578 // as those created by the increment or reset directives and the re-layout that will happen will
579 // not catch the change if the node had no children.
580 makeCounterNode(layoutObject
, it
->key
, false);
589 void showCounterLayoutObjectTree(const blink::LayoutObject
* layoutObject
, const char* counterName
)
593 const blink::LayoutObject
* root
= layoutObject
;
594 while (root
->parent())
595 root
= root
->parent();
597 AtomicString
identifier(counterName
);
598 for (const blink::LayoutObject
* current
= root
; current
; current
= current
->nextInPreOrder()) {
599 fprintf(stderr
, "%c", (current
== layoutObject
) ? '*' : ' ');
600 for (const blink::LayoutObject
* parent
= current
; parent
&& parent
!= root
; parent
= parent
->parent())
601 fprintf(stderr
, " ");
602 fprintf(stderr
, "%p N:%p P:%p PS:%p NS:%p C:%p\n",
603 current
, current
->node(), current
->parent(), current
->previousSibling(),
604 current
->nextSibling(), current
->hasCounterNodeMap() ?
605 counterName
? blink::counterMaps().get(current
)->get(identifier
) : (blink::CounterNode
*)1 : (blink::CounterNode
*)0);