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 (RenderBox* o = m_marker->parentBox(); o != this; o = o->parentBox()) { 276 blockOffset += o->logicalTop(); 277 lineOffset += o->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