RenderBox::parent/firstChild/nextSibling/previousSiblingBox() functions should type...
[WebKit-https.git] / Source / WebCore / rendering / RenderListItem.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  * Copyright (C) 2003-2018 Apple Inc. All rights reserved.
5  * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  */
23
24 #include "config.h"
25 #include "RenderListItem.h"
26
27 #include "CSSFontSelector.h"
28 #include "ElementTraversal.h"
29 #include "HTMLNames.h"
30 #include "HTMLOListElement.h"
31 #include "HTMLUListElement.h"
32 #include "InlineElementBox.h"
33 #include "PseudoElement.h"
34 #include "RenderTreeBuilder.h"
35 #include "RenderView.h"
36 #include "StyleInheritedData.h"
37 #include <wtf/IsoMallocInlines.h>
38 #include <wtf/StackStats.h>
39 #include <wtf/StdLibExtras.h>
40
41 namespace WebCore {
42
43 using namespace HTMLNames;
44
45 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderListItem);
46
47 RenderListItem::RenderListItem(Element& element, RenderStyle&& style)
48     : RenderBlockFlow(element, WTFMove(style))
49 {
50     setInline(false);
51 }
52
53 RenderListItem::~RenderListItem()
54 {
55     // Do not add any code here. Add it to willBeDestroyed() instead.
56     ASSERT(!m_marker);
57 }
58
59 RenderStyle RenderListItem::computeMarkerStyle() const
60 {
61     // The marker always inherits from the list item, regardless of where it might end
62     // up (e.g., in some deeply nested line box). See CSS3 spec.
63     // FIXME: The marker should only inherit all font properties and the color property
64     // according to the CSS Pseudo-Elements Module Level 4 spec.
65     //
66     // Although the CSS Pseudo-Elements Module Level 4 spec. saids to add ::marker to the UA sheet
67     // we apply it here as an optimization because it only applies to markers. That is, it does not
68     // apply to all elements.
69     RenderStyle parentStyle = RenderStyle::clone(style());
70     auto fontDescription = style().fontDescription();
71     fontDescription.setVariantNumericSpacing(FontVariantNumericSpacing::TabularNumbers);
72     parentStyle.setFontDescription(fontDescription);
73     parentStyle.fontCascade().update(&document().fontSelector());
74     if (auto markerStyle = getCachedPseudoStyle(MARKER, &parentStyle))
75         return RenderStyle::clone(*markerStyle);
76     auto markerStyle = RenderStyle::create();
77     markerStyle.inheritFrom(parentStyle);
78     return markerStyle;
79 }
80
81 void RenderListItem::insertedIntoTree()
82 {
83     RenderBlockFlow::insertedIntoTree();
84
85     updateListMarkerNumbers();
86 }
87
88 void RenderListItem::willBeRemovedFromTree()
89 {
90     RenderBlockFlow::willBeRemovedFromTree();
91
92     updateListMarkerNumbers();
93 }
94
95 bool isHTMLListElement(const Node& node)
96 {
97     return is<HTMLUListElement>(node) || is<HTMLOListElement>(node);
98 }
99
100 // Returns the enclosing list with respect to the DOM order.
101 static Element* enclosingList(const RenderListItem& listItem)
102 {
103     auto& element = listItem.element();
104     auto* parent = is<PseudoElement>(element) ? downcast<PseudoElement>(element).hostElement() : element.parentElement();
105     for (auto* ancestor = parent; ancestor; ancestor = ancestor->parentElement()) {
106         if (isHTMLListElement(*ancestor))
107             return ancestor;
108     }
109
110     // If there's no actual list element, then the parent element acts as our
111     // list for purposes of determining what other list items should be numbered as
112     // part of the same list.
113     return parent;
114 }
115
116 static RenderListItem* nextListItemHelper(const Element& list, const Element& element)
117 {
118     auto* current = &element;
119     auto advance = [&] {
120         current = ElementTraversal::nextIncludingPseudo(*current, &list);
121     };
122     advance();
123     while (current) {
124         auto* renderer = current->renderer();
125         if (!is<RenderListItem>(renderer)) {
126             advance();
127             continue;
128         }
129         auto& item = downcast<RenderListItem>(*renderer);
130         auto* otherList = enclosingList(item);
131         if (!otherList) {
132             advance();
133             continue;
134         }
135
136         // This item is part of our current list, so it's what we're looking for.
137         if (&list == otherList)
138             return &item;
139
140         // We found ourself inside another list; skip the rest of its contents.
141         current = ElementTraversal::nextIncludingPseudoSkippingChildren(*current, &list);
142     }
143
144     return nullptr;
145 }
146
147 static inline RenderListItem* nextListItem(const Element& list, const RenderListItem& item)
148 {
149     return nextListItemHelper(list, item.element());
150 }
151
152 static inline RenderListItem* firstListItem(const Element& list)
153 {
154     return nextListItemHelper(list, list);
155 }
156
157 static RenderListItem* previousListItem(const Element& list, const RenderListItem& item)
158 {
159     auto* current = &item.element();
160     auto advance = [&] {
161         current = ElementTraversal::previousIncludingPseudo(*current, &list);
162     };
163     advance();
164     while (current) {
165         auto* renderer = current->renderer();
166         if (!is<RenderListItem>(renderer)) {
167             advance();
168             continue;
169         }
170         auto& item = downcast<RenderListItem>(*renderer);
171         auto* otherList = enclosingList(item);
172         if (!otherList) {
173             advance();
174             continue;
175         }
176
177         // This item is part of our current list, so we found what we're looking for.
178         if (&list == otherList)
179             return &item;
180
181         // We found ourself inside another list; skip the rest of its contents by
182         // advancing to it. However, since the list itself might be a list item,
183         // don't advance past it.
184         current = otherList;
185     }
186     return nullptr;
187 }
188
189 void RenderListItem::updateItemValuesForOrderedList(const HTMLOListElement& list)
190 {
191     for (auto* listItem = firstListItem(list); listItem; listItem = nextListItem(list, *listItem))
192         listItem->updateValue();
193 }
194
195 unsigned RenderListItem::itemCountForOrderedList(const HTMLOListElement& list)
196 {
197     unsigned itemCount = 0;
198     for (auto* listItem = firstListItem(list); listItem; listItem = nextListItem(list, *listItem))
199         ++itemCount;
200     return itemCount;
201 }
202
203 void RenderListItem::updateValueNow() const
204 {
205     auto* list = enclosingList(*this);
206     auto* orderedList = is<HTMLOListElement>(list) ? downcast<HTMLOListElement>(list) : nullptr;
207
208     // The start item is either the closest item before this one in the list that already has a value,
209     // or the first item in the list if none have before this have values yet.
210     auto* startItem = this;
211     if (list) {
212         auto* item = this;
213         while ((item = previousListItem(*list, *item))) {
214             startItem = item;
215             if (item->m_value)
216                 break;
217         }
218     }
219
220     auto& startValue = startItem->m_value;
221     if (!startValue)
222         startValue = orderedList ? orderedList->start() : 1;
223     int value = *startValue;
224     int increment = (orderedList && orderedList->isReversed()) ? -1 : 1;
225
226     for (auto* item = startItem; item != this; ) {
227         item = nextListItem(*list, *item);
228         item->m_value = (value += increment);
229     }
230 }
231
232 void RenderListItem::updateValue()
233 {
234     if (!m_valueWasSetExplicitly) {
235         m_value = std::nullopt;
236         if (m_marker)
237             m_marker->setNeedsLayoutAndPrefWidthsRecalc();
238     }
239 }
240
241 void RenderListItem::layout()
242 {
243     StackStats::LayoutCheckPoint layoutCheckPoint;
244     ASSERT(needsLayout());
245
246     RenderBlockFlow::layout();
247 }
248
249 void RenderListItem::addOverflowFromChildren()
250 {
251     positionListMarker();
252     RenderBlockFlow::addOverflowFromChildren();
253 }
254
255 void RenderListItem::computePreferredLogicalWidths()
256 {
257     // FIXME: RenderListMarker::updateMargins() mutates margin style which affects preferred widths.
258     if (m_marker && m_marker->preferredLogicalWidthsDirty())
259         m_marker->updateMarginsAndContent();
260
261     RenderBlockFlow::computePreferredLogicalWidths();
262 }
263
264 void RenderListItem::positionListMarker()
265 {
266     if (!m_marker || !m_marker->parent() || !m_marker->parent()->isBox())
267         return;
268
269     if (m_marker->isInside() || !m_marker->inlineBoxWrapper())
270         return;
271
272     LayoutUnit markerOldLogicalLeft = m_marker->logicalLeft();
273     LayoutUnit blockOffset = 0;
274     LayoutUnit lineOffset = 0;
275     for (auto* ancestor = m_marker->parentBox(); ancestor && ancestor != this; ancestor = ancestor->parentBox()) {
276         blockOffset += ancestor->logicalTop();
277         lineOffset += ancestor->logicalLeft();
278     }
279
280     bool adjustOverflow = false;
281     LayoutUnit markerLogicalLeft;
282     bool hitSelfPaintingLayer = false;
283
284     const RootInlineBox& rootBox = m_marker->inlineBoxWrapper()->root();
285     LayoutUnit lineTop = rootBox.lineTop();
286     LayoutUnit lineBottom = rootBox.lineBottom();
287
288     // FIXME: Need to account for relative positioning in the layout overflow.
289     if (style().isLeftToRightDirection()) {
290         markerLogicalLeft = m_marker->lineOffsetForListItem() - lineOffset - paddingStart() - borderStart() + m_marker->marginStart();
291         m_marker->inlineBoxWrapper()->adjustLineDirectionPosition(markerLogicalLeft - markerOldLogicalLeft);
292         for (InlineFlowBox* box = m_marker->inlineBoxWrapper()->parent(); box; box = box->parent()) {
293             LayoutRect newLogicalVisualOverflowRect = box->logicalVisualOverflowRect(lineTop, lineBottom);
294             LayoutRect newLogicalLayoutOverflowRect = box->logicalLayoutOverflowRect(lineTop, lineBottom);
295             if (markerLogicalLeft < newLogicalVisualOverflowRect.x() && !hitSelfPaintingLayer) {
296                 newLogicalVisualOverflowRect.setWidth(newLogicalVisualOverflowRect.maxX() - markerLogicalLeft);
297                 newLogicalVisualOverflowRect.setX(markerLogicalLeft);
298                 if (box == &rootBox)
299                     adjustOverflow = true;
300             }
301             if (markerLogicalLeft < newLogicalLayoutOverflowRect.x()) {
302                 newLogicalLayoutOverflowRect.setWidth(newLogicalLayoutOverflowRect.maxX() - markerLogicalLeft);
303                 newLogicalLayoutOverflowRect.setX(markerLogicalLeft);
304                 if (box == &rootBox)
305                     adjustOverflow = true;
306             }
307             box->setOverflowFromLogicalRects(newLogicalLayoutOverflowRect, newLogicalVisualOverflowRect, lineTop, lineBottom);
308             if (box->renderer().hasSelfPaintingLayer())
309                 hitSelfPaintingLayer = true;
310         }
311     } else {
312         markerLogicalLeft = m_marker->lineOffsetForListItem() - lineOffset + paddingStart() + borderStart() + m_marker->marginEnd();
313         m_marker->inlineBoxWrapper()->adjustLineDirectionPosition(markerLogicalLeft - markerOldLogicalLeft);
314         for (InlineFlowBox* box = m_marker->inlineBoxWrapper()->parent(); box; box = box->parent()) {
315             LayoutRect newLogicalVisualOverflowRect = box->logicalVisualOverflowRect(lineTop, lineBottom);
316             LayoutRect newLogicalLayoutOverflowRect = box->logicalLayoutOverflowRect(lineTop, lineBottom);
317             if (markerLogicalLeft + m_marker->logicalWidth() > newLogicalVisualOverflowRect.maxX() && !hitSelfPaintingLayer) {
318                 newLogicalVisualOverflowRect.setWidth(markerLogicalLeft + m_marker->logicalWidth() - newLogicalVisualOverflowRect.x());
319                 if (box == &rootBox)
320                     adjustOverflow = true;
321             }
322             if (markerLogicalLeft + m_marker->logicalWidth() > newLogicalLayoutOverflowRect.maxX()) {
323                 newLogicalLayoutOverflowRect.setWidth(markerLogicalLeft + m_marker->logicalWidth() - newLogicalLayoutOverflowRect.x());
324                 if (box == &rootBox)
325                     adjustOverflow = true;
326             }
327             box->setOverflowFromLogicalRects(newLogicalLayoutOverflowRect, newLogicalVisualOverflowRect, lineTop, lineBottom);
328                 
329             if (box->renderer().hasSelfPaintingLayer())
330                 hitSelfPaintingLayer = true;
331         }
332     }
333
334     if (adjustOverflow) {
335         LayoutRect markerRect(markerLogicalLeft + lineOffset, blockOffset, m_marker->width(), m_marker->height());
336         if (!style().isHorizontalWritingMode())
337             markerRect = markerRect.transposedRect();
338         RenderBox* markerAncestor = m_marker.get();
339         bool propagateVisualOverflow = true;
340         bool propagateLayoutOverflow = true;
341         do {
342             markerAncestor = markerAncestor->parentBox();
343             if (markerAncestor->hasOverflowClip())
344                 propagateVisualOverflow = false;
345             if (is<RenderBlock>(*markerAncestor)) {
346                 if (propagateVisualOverflow)
347                     downcast<RenderBlock>(*markerAncestor).addVisualOverflow(markerRect);
348                 if (propagateLayoutOverflow)
349                     downcast<RenderBlock>(*markerAncestor).addLayoutOverflow(markerRect);
350             }
351             if (markerAncestor->hasOverflowClip())
352                 propagateLayoutOverflow = false;
353             if (markerAncestor->hasSelfPaintingLayer())
354                 propagateVisualOverflow = false;
355             markerRect.moveBy(-markerAncestor->location());
356         } while (markerAncestor != this && propagateVisualOverflow && propagateLayoutOverflow);
357     }
358 }
359
360 void RenderListItem::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
361 {
362     if (!logicalHeight() && hasOverflowClip())
363         return;
364
365     RenderBlockFlow::paint(paintInfo, paintOffset);
366 }
367
368 const String& RenderListItem::markerText() const
369 {
370     if (m_marker)
371         return m_marker->text();
372     return nullAtom().string();
373 }
374
375 String RenderListItem::markerTextWithSuffix() const
376 {
377     if (!m_marker)
378         return String();
379
380     // Append the suffix for the marker in the right place depending
381     // on the direction of the text (right-to-left or left-to-right).
382     if (m_marker->style().isLeftToRightDirection())
383         return m_marker->text() + m_marker->suffix();
384     return m_marker->suffix() + m_marker->text();
385 }
386
387 void RenderListItem::explicitValueChanged()
388 {
389     if (m_marker)
390         m_marker->setNeedsLayoutAndPrefWidthsRecalc();
391
392     updateValue();
393     auto* list = enclosingList(*this);
394     if (!list)
395         return;
396     auto* item = this;
397     while ((item = nextListItem(*list, *item)))
398         item->updateValue();
399 }
400
401 void RenderListItem::setExplicitValue(std::optional<int> value)
402 {
403     if (!value) {
404         if (!m_valueWasSetExplicitly)
405             return;
406     } else {
407         if (m_valueWasSetExplicitly && m_value == value)
408             return;
409     }
410     m_valueWasSetExplicitly = value.has_value();
411     m_value = value;
412     explicitValueChanged();
413 }
414
415 void RenderListItem::updateListMarkerNumbers()
416 {
417     auto* list = enclosingList(*this);
418     if (!list)
419         return;
420
421     bool isInReversedOrderedList = false;
422     if (is<HTMLOListElement>(*list)) {
423         auto& orderedList = downcast<HTMLOListElement>(*list);
424         orderedList.itemCountChanged();
425         isInReversedOrderedList = orderedList.isReversed();
426     }
427
428     // If an item has been marked for update before, we know that all following items have, too.
429     // This gives us the opportunity to stop and avoid marking the same nodes again.
430     auto* item = this;
431     auto subsequentListItem = isInReversedOrderedList ? previousListItem : nextListItem;
432     while ((item = subsequentListItem(*list, *item)) && item->m_value)
433         item->updateValue();
434 }
435
436 bool RenderListItem::isInReversedOrderedList() const
437 {
438     auto* list = enclosingList(*this);
439     return is<HTMLOListElement>(list) && downcast<HTMLOListElement>(*list).isReversed();
440 }
441
442 } // namespace WebCore