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)
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.
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.
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.
25 #include "RenderListItem.h"
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>
43 using namespace HTMLNames;
45 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderListItem);
47 RenderListItem::RenderListItem(Element& element, RenderStyle&& style)
48 : RenderBlockFlow(element, WTFMove(style))
53 RenderListItem::~RenderListItem()
55 // Do not add any code here. Add it to willBeDestroyed() instead.
59 RenderStyle RenderListItem::computeMarkerStyle() const
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.
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);
81 void RenderListItem::insertedIntoTree()
83 RenderBlockFlow::insertedIntoTree();
85 updateListMarkerNumbers();
88 void RenderListItem::willBeRemovedFromTree()
90 RenderBlockFlow::willBeRemovedFromTree();
92 updateListMarkerNumbers();
95 bool isHTMLListElement(const Node& node)
97 return is<HTMLUListElement>(node) || is<HTMLOListElement>(node);
100 // Returns the enclosing list with respect to the DOM order.
101 static Element* enclosingList(const RenderListItem& listItem)
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))
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.
116 static RenderListItem* nextListItemHelper(const Element& list, const Element& element)
118 auto* current = &element;
120 current = ElementTraversal::nextIncludingPseudo(*current, &list);
124 auto* renderer = current->renderer();
125 if (!is<RenderListItem>(renderer)) {
129 auto& item = downcast<RenderListItem>(*renderer);
130 auto* otherList = enclosingList(item);
136 // This item is part of our current list, so it's what we're looking for.
137 if (&list == otherList)
140 // We found ourself inside another list; skip the rest of its contents.
141 current = ElementTraversal::nextIncludingPseudoSkippingChildren(*current, &list);
147 static inline RenderListItem* nextListItem(const Element& list, const RenderListItem& item)
149 return nextListItemHelper(list, item.element());
152 static inline RenderListItem* firstListItem(const Element& list)
154 return nextListItemHelper(list, list);
157 static RenderListItem* previousListItem(const Element& list, const RenderListItem& item)
159 auto* current = &item.element();
161 current = ElementTraversal::previousIncludingPseudo(*current, &list);
165 auto* renderer = current->renderer();
166 if (!is<RenderListItem>(renderer)) {
170 auto& item = downcast<RenderListItem>(*renderer);
171 auto* otherList = enclosingList(item);
177 // This item is part of our current list, so we found what we're looking for.
178 if (&list == otherList)
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.
189 void RenderListItem::updateItemValuesForOrderedList(const HTMLOListElement& list)
191 for (auto* listItem = firstListItem(list); listItem; listItem = nextListItem(list, *listItem))
192 listItem->updateValue();
195 unsigned RenderListItem::itemCountForOrderedList(const HTMLOListElement& list)
197 unsigned itemCount = 0;
198 for (auto* listItem = firstListItem(list); listItem; listItem = nextListItem(list, *listItem))
203 void RenderListItem::updateValueNow() const
205 auto* list = enclosingList(*this);
206 auto* orderedList = is<HTMLOListElement>(list) ? downcast<HTMLOListElement>(list) : nullptr;
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;
213 while ((item = previousListItem(*list, *item))) {
220 auto& startValue = startItem->m_value;
222 startValue = orderedList ? orderedList->start() : 1;
223 int value = *startValue;
224 int increment = (orderedList && orderedList->isReversed()) ? -1 : 1;
226 for (auto* item = startItem; item != this; ) {
227 item = nextListItem(*list, *item);
228 item->m_value = (value += increment);
232 void RenderListItem::updateValue()
234 if (!m_valueWasSetExplicitly) {
235 m_value = std::nullopt;
237 m_marker->setNeedsLayoutAndPrefWidthsRecalc();
241 void RenderListItem::layout()
243 StackStats::LayoutCheckPoint layoutCheckPoint;
244 ASSERT(needsLayout());
246 RenderBlockFlow::layout();
249 void RenderListItem::addOverflowFromChildren()
251 positionListMarker();
252 RenderBlockFlow::addOverflowFromChildren();
255 void RenderListItem::computePreferredLogicalWidths()
257 // FIXME: RenderListMarker::updateMargins() mutates margin style which affects preferred widths.
258 if (m_marker && m_marker->preferredLogicalWidthsDirty())
259 m_marker->updateMarginsAndContent();
261 RenderBlockFlow::computePreferredLogicalWidths();
264 void RenderListItem::positionListMarker()
266 if (!m_marker || !m_marker->parent() || !m_marker->parent()->isBox())
269 if (m_marker->isInside() || !m_marker->inlineBoxWrapper())
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();
280 bool adjustOverflow = false;
281 LayoutUnit markerLogicalLeft;
282 bool hitSelfPaintingLayer = false;
284 const RootInlineBox& rootBox = m_marker->inlineBoxWrapper()->root();
285 LayoutUnit lineTop = rootBox.lineTop();
286 LayoutUnit lineBottom = rootBox.lineBottom();
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);
299 adjustOverflow = true;
301 if (markerLogicalLeft < newLogicalLayoutOverflowRect.x()) {
302 newLogicalLayoutOverflowRect.setWidth(newLogicalLayoutOverflowRect.maxX() - markerLogicalLeft);
303 newLogicalLayoutOverflowRect.setX(markerLogicalLeft);
305 adjustOverflow = true;
307 box->setOverflowFromLogicalRects(newLogicalLayoutOverflowRect, newLogicalVisualOverflowRect, lineTop, lineBottom);
308 if (box->renderer().hasSelfPaintingLayer())
309 hitSelfPaintingLayer = true;
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());
320 adjustOverflow = true;
322 if (markerLogicalLeft + m_marker->logicalWidth() > newLogicalLayoutOverflowRect.maxX()) {
323 newLogicalLayoutOverflowRect.setWidth(markerLogicalLeft + m_marker->logicalWidth() - newLogicalLayoutOverflowRect.x());
325 adjustOverflow = true;
327 box->setOverflowFromLogicalRects(newLogicalLayoutOverflowRect, newLogicalVisualOverflowRect, lineTop, lineBottom);
329 if (box->renderer().hasSelfPaintingLayer())
330 hitSelfPaintingLayer = true;
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;
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);
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);
360 void RenderListItem::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
362 if (!logicalHeight() && hasOverflowClip())
365 RenderBlockFlow::paint(paintInfo, paintOffset);
368 const String& RenderListItem::markerText() const
371 return m_marker->text();
372 return nullAtom().string();
375 String RenderListItem::markerTextWithSuffix() const
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();
387 void RenderListItem::explicitValueChanged()
390 m_marker->setNeedsLayoutAndPrefWidthsRecalc();
393 auto* list = enclosingList(*this);
397 while ((item = nextListItem(*list, *item)))
401 void RenderListItem::setExplicitValue(std::optional<int> value)
404 if (!m_valueWasSetExplicitly)
407 if (m_valueWasSetExplicitly && m_value == value)
410 m_valueWasSetExplicitly = value.has_value();
412 explicitValueChanged();
415 void RenderListItem::updateListMarkerNumbers()
417 auto* list = enclosingList(*this);
421 bool isInReversedOrderedList = false;
422 if (is<HTMLOListElement>(*list)) {
423 auto& orderedList = downcast<HTMLOListElement>(*list);
424 orderedList.itemCountChanged();
425 isInReversedOrderedList = orderedList.isReversed();
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.
431 auto subsequentListItem = isInReversedOrderedList ? previousListItem : nextListItem;
432 while ((item = subsequentListItem(*list, *item)) && item->m_value)
436 bool RenderListItem::isInReversedOrderedList() const
438 auto* list = enclosingList(*this);
439 return is<HTMLOListElement>(list) && downcast<HTMLOListElement>(*list).isReversed();
442 } // namespace WebCore