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
41namespace WebCore {
42
43using namespace HTMLNames;
44
45WTF_MAKE_ISO_ALLOCATED_IMPL(RenderListItem);
46
47RenderListItem::RenderListItem(Element& element, RenderStyle&& style)
48 : RenderBlockFlow(element, WTFMove(style))
49{
50 setInline(false);
51}
52
53RenderListItem::~RenderListItem()
54{
55 // Do not add any code here. Add it to willBeDestroyed() instead.
56 ASSERT(!m_marker);
57}
58
59RenderStyle 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(WTFMove(fontDescription));
73 parentStyle.fontCascade().update(&document().fontSelector());
74 if (auto markerStyle = getCachedPseudoStyle(PseudoId::Marker, &parentStyle))
75 return RenderStyle::clone(*markerStyle);
76 auto markerStyle = RenderStyle::create();
77 markerStyle.inheritFrom(parentStyle);
78 return markerStyle;
79}
80
81void RenderListItem::insertedIntoTree()
82{
83 RenderBlockFlow::insertedIntoTree();
84
85 updateListMarkerNumbers();
86}
87
88void RenderListItem::willBeRemovedFromTree()
89{
90 RenderBlockFlow::willBeRemovedFromTree();
91
92 updateListMarkerNumbers();
93}
94
95bool 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.
101static 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
116static 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
147static inline RenderListItem* nextListItem(const Element& list, const RenderListItem& item)
148{
149 return nextListItemHelper(list, item.element());
150}
151
152static inline RenderListItem* firstListItem(const Element& list)
153{
154 return nextListItemHelper(list, list);
155}
156
157static 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
189void RenderListItem::updateItemValuesForOrderedList(const HTMLOListElement& list)
190{
191 for (auto* listItem = firstListItem(list); listItem; listItem = nextListItem(list, *listItem))
192 listItem->updateValue();
193}
194
195unsigned 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
203void 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
232void RenderListItem::updateValue()
233{
234 if (!m_valueWasSetExplicitly) {
235 m_value = WTF::nullopt;
236 if (m_marker)
237 m_marker->setNeedsLayoutAndPrefWidthsRecalc();
238 }
239}
240
241void RenderListItem::layout()
242{
243 StackStats::LayoutCheckPoint layoutCheckPoint;
244 ASSERT(needsLayout());
245
246 RenderBlockFlow::layout();
247}
248
249void RenderListItem::addOverflowFromChildren()
250{
251 positionListMarker();
252 RenderBlockFlow::addOverflowFromChildren();
253}
254
255void 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
264void 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;
274 LayoutUnit lineOffset;
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
360void RenderListItem::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
361{
362 if (!logicalHeight() && hasOverflowClip())
363 return;
364
365 RenderBlockFlow::paint(paintInfo, paintOffset);
366}
367
368const String& RenderListItem::markerText() const
369{
370 if (m_marker)
371 return m_marker->text();
372 return nullAtom().string();
373}
374
375String 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
387void 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
401void RenderListItem::setExplicitValue(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.hasValue();
411 m_value = value;
412 explicitValueChanged();
413}
414
415void 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
436bool RenderListItem::isInReversedOrderedList() const
437{
438 auto* list = enclosingList(*this);
439 return is<HTMLOListElement>(list) && downcast<HTMLOListElement>(*list).isReversed();
440}
441
442} // namespace WebCore
443