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