Home | History | Annotate | Download | only in rendering
      1 /**
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  * Copyright (C) 2003, 2004, 2005, 2006, 2010 Apple Inc. All rights reserved.
      5  * Copyright (C) 2006 Andrew Wellington (proton (at) 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 "core/rendering/RenderListItem.h"
     26 
     27 #include "core/HTMLNames.h"
     28 #include "core/dom/NodeRenderingTraversal.h"
     29 #include "core/html/HTMLOListElement.h"
     30 #include "core/rendering/RenderListMarker.h"
     31 #include "core/rendering/RenderView.h"
     32 #include "core/rendering/TextAutosizer.h"
     33 #include "wtf/StdLibExtras.h"
     34 #include "wtf/text/StringBuilder.h"
     35 
     36 namespace blink {
     37 
     38 using namespace HTMLNames;
     39 
     40 RenderListItem::RenderListItem(Element* element)
     41     : RenderBlockFlow(element)
     42     , m_marker(nullptr)
     43     , m_hasExplicitValue(false)
     44     , m_isValueUpToDate(false)
     45     , m_notInList(false)
     46 {
     47     setInline(false);
     48 }
     49 
     50 void RenderListItem::trace(Visitor* visitor)
     51 {
     52     visitor->trace(m_marker);
     53     RenderBlockFlow::trace(visitor);
     54 }
     55 
     56 void RenderListItem::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
     57 {
     58     RenderBlockFlow::styleDidChange(diff, oldStyle);
     59 
     60     if (style()->listStyleType() != NoneListStyle
     61         || (style()->listStyleImage() && !style()->listStyleImage()->errorOccurred())) {
     62         RefPtr<RenderStyle> newStyle = RenderStyle::create();
     63         // The marker always inherits from the list item, regardless of where it might end
     64         // up (e.g., in some deeply nested line box). See CSS3 spec.
     65         newStyle->inheritFrom(style());
     66         if (!m_marker)
     67             m_marker = RenderListMarker::createAnonymous(this);
     68         m_marker->setStyle(newStyle.release());
     69     } else if (m_marker) {
     70         m_marker->destroy();
     71         m_marker = nullptr;
     72     }
     73 }
     74 
     75 void RenderListItem::willBeDestroyed()
     76 {
     77     if (m_marker) {
     78         m_marker->destroy();
     79         m_marker = nullptr;
     80     }
     81     RenderBlockFlow::willBeDestroyed();
     82 }
     83 
     84 void RenderListItem::insertedIntoTree()
     85 {
     86     RenderBlockFlow::insertedIntoTree();
     87 
     88     updateListMarkerNumbers();
     89 }
     90 
     91 void RenderListItem::willBeRemovedFromTree()
     92 {
     93     RenderBlockFlow::willBeRemovedFromTree();
     94 
     95     updateListMarkerNumbers();
     96 }
     97 
     98 static bool isList(const Node& node)
     99 {
    100     return isHTMLUListElement(node) || isHTMLOListElement(node);
    101 }
    102 
    103 // Returns the enclosing list with respect to the DOM order.
    104 static Node* enclosingList(const RenderListItem* listItem)
    105 {
    106     Node* listItemNode = listItem->node();
    107     Node* firstNode = 0;
    108     // We use parentNode because the enclosing list could be a ShadowRoot that's not Element.
    109     for (Node* parent = NodeRenderingTraversal::parent(listItemNode); parent; parent = NodeRenderingTraversal::parent(parent)) {
    110         if (isList(*parent))
    111             return parent;
    112         if (!firstNode)
    113             firstNode = parent;
    114     }
    115 
    116     // If there's no actual <ul> or <ol> list element, then the first found
    117     // node acts as our list for purposes of determining what other list items
    118     // should be numbered as part of the same list.
    119     return firstNode;
    120 }
    121 
    122 // Returns the next list item with respect to the DOM order.
    123 static RenderListItem* nextListItem(const Node* listNode, const RenderListItem* item = 0)
    124 {
    125     if (!listNode)
    126         return 0;
    127 
    128     const Node* current = item ? item->node() : listNode;
    129     ASSERT(current);
    130     ASSERT(!current->document().childNeedsDistributionRecalc());
    131     current = NodeRenderingTraversal::next(current, listNode);
    132 
    133     while (current) {
    134         if (isList(*current)) {
    135             // We've found a nested, independent list: nothing to do here.
    136             current = NodeRenderingTraversal::next(current, listNode);
    137             continue;
    138         }
    139 
    140         RenderObject* renderer = current->renderer();
    141         if (renderer && renderer->isListItem())
    142             return toRenderListItem(renderer);
    143 
    144         // FIXME: Can this be optimized to skip the children of the elements without a renderer?
    145         current = NodeRenderingTraversal::next(current, listNode);
    146     }
    147 
    148     return 0;
    149 }
    150 
    151 // Returns the previous list item with respect to the DOM order.
    152 static RenderListItem* previousListItem(const Node* listNode, const RenderListItem* item)
    153 {
    154     Node* current = item->node();
    155     ASSERT(current);
    156     ASSERT(!current->document().childNeedsDistributionRecalc());
    157     for (current = NodeRenderingTraversal::previous(current, listNode); current && current != listNode; current = NodeRenderingTraversal::previous(current, listNode)) {
    158         RenderObject* renderer = current->renderer();
    159         if (!renderer || (renderer && !renderer->isListItem()))
    160             continue;
    161         Node* otherList = enclosingList(toRenderListItem(renderer));
    162         // This item is part of our current list, so it's what we're looking for.
    163         if (listNode == otherList)
    164             return toRenderListItem(renderer);
    165         // We found ourself inside another list; lets skip the rest of it.
    166         // Use nextIncludingPseudo() here because the other list itself may actually
    167         // be a list item itself. We need to examine it, so we do this to counteract
    168         // the previousIncludingPseudo() that will be done by the loop.
    169         if (otherList)
    170             current = NodeRenderingTraversal::next(otherList, listNode);
    171     }
    172     return 0;
    173 }
    174 
    175 void RenderListItem::updateItemValuesForOrderedList(const HTMLOListElement* listNode)
    176 {
    177     ASSERT(listNode);
    178 
    179     for (RenderListItem* listItem = nextListItem(listNode); listItem; listItem = nextListItem(listNode, listItem))
    180         listItem->updateValue();
    181 }
    182 
    183 unsigned RenderListItem::itemCountForOrderedList(const HTMLOListElement* listNode)
    184 {
    185     ASSERT(listNode);
    186 
    187     unsigned itemCount = 0;
    188     for (RenderListItem* listItem = nextListItem(listNode); listItem; listItem = nextListItem(listNode, listItem))
    189         itemCount++;
    190 
    191     return itemCount;
    192 }
    193 
    194 inline int RenderListItem::calcValue() const
    195 {
    196     if (m_hasExplicitValue)
    197         return m_explicitValue;
    198 
    199     Node* list = enclosingList(this);
    200     HTMLOListElement* oListElement = isHTMLOListElement(list) ? toHTMLOListElement(list) : 0;
    201     int valueStep = 1;
    202     if (oListElement && oListElement->isReversed())
    203         valueStep = -1;
    204 
    205     // FIXME: This recurses to a possible depth of the length of the list.
    206     // That's not good -- we need to change this to an iterative algorithm.
    207     if (RenderListItem* previousItem = previousListItem(list, this))
    208         return previousItem->value() + valueStep;
    209 
    210     if (oListElement)
    211         return oListElement->start();
    212 
    213     return 1;
    214 }
    215 
    216 void RenderListItem::updateValueNow() const
    217 {
    218     m_value = calcValue();
    219     m_isValueUpToDate = true;
    220 }
    221 
    222 bool RenderListItem::isEmpty() const
    223 {
    224     return lastChild() == m_marker;
    225 }
    226 
    227 static RenderObject* getParentOfFirstLineBox(RenderBlockFlow* curr, RenderObject* marker)
    228 {
    229     RenderObject* firstChild = curr->firstChild();
    230     if (!firstChild)
    231         return 0;
    232 
    233     bool inQuirksMode = curr->document().inQuirksMode();
    234     for (RenderObject* currChild = firstChild; currChild; currChild = currChild->nextSibling()) {
    235         if (currChild == marker)
    236             continue;
    237 
    238         if (currChild->isInline() && (!currChild->isRenderInline() || curr->generatesLineBoxesForInlineChild(currChild)))
    239             return curr;
    240 
    241         if (currChild->isFloating() || currChild->isOutOfFlowPositioned())
    242             continue;
    243 
    244         if (!currChild->isRenderBlockFlow() || (currChild->isBox() && toRenderBox(currChild)->isWritingModeRoot()))
    245             break;
    246 
    247         if (curr->isListItem() && inQuirksMode && currChild->node() &&
    248             (isHTMLUListElement(*currChild->node()) || isHTMLOListElement(*currChild->node())))
    249             break;
    250 
    251         RenderObject* lineBox = getParentOfFirstLineBox(toRenderBlockFlow(currChild), marker);
    252         if (lineBox)
    253             return lineBox;
    254     }
    255 
    256     return 0;
    257 }
    258 
    259 void RenderListItem::updateValue()
    260 {
    261     if (!m_hasExplicitValue) {
    262         m_isValueUpToDate = false;
    263         if (m_marker)
    264             m_marker->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation();
    265     }
    266 }
    267 
    268 static RenderObject* firstNonMarkerChild(RenderObject* parent)
    269 {
    270     RenderObject* result = parent->slowFirstChild();
    271     while (result && result->isListMarker())
    272         result = result->nextSibling();
    273     return result;
    274 }
    275 
    276 void RenderListItem::updateMarkerLocationAndInvalidateWidth()
    277 {
    278     ASSERT(m_marker);
    279 
    280     // FIXME: We should not modify the structure of the render tree
    281     // during layout. crbug.com/370461
    282     DeprecatedDisableModifyRenderTreeStructureAsserts disabler;
    283     if (updateMarkerLocation()) {
    284         // If the marker is inside we need to redo the preferred width calculations
    285         // as the size of the item now includes the size of the list marker.
    286         if (m_marker->isInside())
    287             containingBlock()->updateLogicalWidth();
    288     }
    289 }
    290 
    291 bool RenderListItem::updateMarkerLocation()
    292 {
    293     ASSERT(m_marker);
    294     RenderObject* markerParent = m_marker->parent();
    295     RenderObject* lineBoxParent = getParentOfFirstLineBox(this, m_marker);
    296     if (!lineBoxParent) {
    297         // If the marker is currently contained inside an anonymous box, then we
    298         // are the only item in that anonymous box (since no line box parent was
    299         // found). It's ok to just leave the marker where it is in this case.
    300         if (markerParent && markerParent->isAnonymousBlock())
    301             lineBoxParent = markerParent;
    302         else
    303             lineBoxParent = this;
    304     }
    305 
    306     if (markerParent != lineBoxParent) {
    307         m_marker->remove();
    308         lineBoxParent->addChild(m_marker, firstNonMarkerChild(lineBoxParent));
    309         m_marker->updateMarginsAndContent();
    310         // If markerParent is an anonymous block with no children, destroy it.
    311         if (markerParent && markerParent->isAnonymousBlock() && !toRenderBlock(markerParent)->firstChild() && !toRenderBlock(markerParent)->continuation())
    312             markerParent->destroy();
    313         return true;
    314     }
    315 
    316     return false;
    317 }
    318 
    319 void RenderListItem::layout()
    320 {
    321     ASSERT(needsLayout());
    322 
    323     if (m_marker) {
    324         // The marker must be autosized before calling
    325         // updateMarkerLocationAndInvalidateWidth. It cannot be done in the
    326         // parent's beginLayout because it is not yet in the render tree.
    327         if (TextAutosizer* textAutosizer = document().textAutosizer())
    328             textAutosizer->inflateListItem(this, m_marker);
    329 
    330         updateMarkerLocationAndInvalidateWidth();
    331     }
    332 
    333     RenderBlockFlow::layout();
    334 }
    335 
    336 void RenderListItem::addOverflowFromChildren()
    337 {
    338     RenderBlockFlow::addOverflowFromChildren();
    339     positionListMarker();
    340 }
    341 
    342 void RenderListItem::positionListMarker()
    343 {
    344     if (m_marker && m_marker->parent()->isBox() && !m_marker->isInside() && m_marker->inlineBoxWrapper()) {
    345         LayoutUnit markerOldLogicalLeft = m_marker->logicalLeft();
    346         LayoutUnit blockOffset = 0;
    347         LayoutUnit lineOffset = 0;
    348         for (RenderBox* o = m_marker->parentBox(); o != this; o = o->parentBox()) {
    349             blockOffset += o->logicalTop();
    350             lineOffset += o->logicalLeft();
    351         }
    352 
    353         bool adjustOverflow = false;
    354         LayoutUnit markerLogicalLeft;
    355         RootInlineBox& root = m_marker->inlineBoxWrapper()->root();
    356         bool hitSelfPaintingLayer = false;
    357 
    358         LayoutUnit lineTop = root.lineTop();
    359         LayoutUnit lineBottom = root.lineBottom();
    360 
    361         // FIXME: Need to account for relative positioning in the layout overflow.
    362         if (style()->isLeftToRightDirection()) {
    363             LayoutUnit leftLineOffset = logicalLeftOffsetForLine(blockOffset, logicalLeftOffsetForLine(blockOffset, false), false);
    364             markerLogicalLeft = leftLineOffset - lineOffset - paddingStart() - borderStart() + m_marker->marginStart();
    365             m_marker->inlineBoxWrapper()->adjustLineDirectionPosition((markerLogicalLeft - markerOldLogicalLeft).toFloat());
    366             for (InlineFlowBox* box = m_marker->inlineBoxWrapper()->parent(); box; box = box->parent()) {
    367                 LayoutRect newLogicalVisualOverflowRect = box->logicalVisualOverflowRect(lineTop, lineBottom);
    368                 LayoutRect newLogicalLayoutOverflowRect = box->logicalLayoutOverflowRect(lineTop, lineBottom);
    369                 if (markerLogicalLeft < newLogicalVisualOverflowRect.x() && !hitSelfPaintingLayer) {
    370                     newLogicalVisualOverflowRect.setWidth(newLogicalVisualOverflowRect.maxX() - markerLogicalLeft);
    371                     newLogicalVisualOverflowRect.setX(markerLogicalLeft);
    372                     if (box == root)
    373                         adjustOverflow = true;
    374                 }
    375                 if (markerLogicalLeft < newLogicalLayoutOverflowRect.x()) {
    376                     newLogicalLayoutOverflowRect.setWidth(newLogicalLayoutOverflowRect.maxX() - markerLogicalLeft);
    377                     newLogicalLayoutOverflowRect.setX(markerLogicalLeft);
    378                     if (box == root)
    379                         adjustOverflow = true;
    380                 }
    381                 box->setOverflowFromLogicalRects(newLogicalLayoutOverflowRect, newLogicalVisualOverflowRect, lineTop, lineBottom);
    382                 if (box->boxModelObject()->hasSelfPaintingLayer())
    383                     hitSelfPaintingLayer = true;
    384             }
    385         } else {
    386             LayoutUnit rightLineOffset = logicalRightOffsetForLine(blockOffset, logicalRightOffsetForLine(blockOffset, false), false);
    387             markerLogicalLeft = rightLineOffset - lineOffset + paddingStart() + borderStart() + m_marker->marginEnd();
    388             m_marker->inlineBoxWrapper()->adjustLineDirectionPosition((markerLogicalLeft - markerOldLogicalLeft).toFloat());
    389             for (InlineFlowBox* box = m_marker->inlineBoxWrapper()->parent(); box; box = box->parent()) {
    390                 LayoutRect newLogicalVisualOverflowRect = box->logicalVisualOverflowRect(lineTop, lineBottom);
    391                 LayoutRect newLogicalLayoutOverflowRect = box->logicalLayoutOverflowRect(lineTop, lineBottom);
    392                 if (markerLogicalLeft + m_marker->logicalWidth() > newLogicalVisualOverflowRect.maxX() && !hitSelfPaintingLayer) {
    393                     newLogicalVisualOverflowRect.setWidth(markerLogicalLeft + m_marker->logicalWidth() - newLogicalVisualOverflowRect.x());
    394                     if (box == root)
    395                         adjustOverflow = true;
    396                 }
    397                 if (markerLogicalLeft + m_marker->logicalWidth() > newLogicalLayoutOverflowRect.maxX()) {
    398                     newLogicalLayoutOverflowRect.setWidth(markerLogicalLeft + m_marker->logicalWidth() - newLogicalLayoutOverflowRect.x());
    399                     if (box == root)
    400                         adjustOverflow = true;
    401                 }
    402                 box->setOverflowFromLogicalRects(newLogicalLayoutOverflowRect, newLogicalVisualOverflowRect, lineTop, lineBottom);
    403 
    404                 if (box->boxModelObject()->hasSelfPaintingLayer())
    405                     hitSelfPaintingLayer = true;
    406             }
    407         }
    408 
    409         if (adjustOverflow) {
    410             LayoutRect markerRect(markerLogicalLeft + lineOffset, blockOffset, m_marker->width(), m_marker->height());
    411             if (!style()->isHorizontalWritingMode())
    412                 markerRect = markerRect.transposedRect();
    413             RenderBox* o = m_marker;
    414             bool propagateVisualOverflow = true;
    415             bool propagateLayoutOverflow = true;
    416             do {
    417                 o = o->parentBox();
    418                 if (o->isRenderBlock()) {
    419                     if (propagateVisualOverflow)
    420                         toRenderBlock(o)->addContentsVisualOverflow(markerRect);
    421                     if (propagateLayoutOverflow)
    422                         toRenderBlock(o)->addLayoutOverflow(markerRect);
    423                 }
    424                 if (o->hasOverflowClip()) {
    425                     propagateLayoutOverflow = false;
    426                     propagateVisualOverflow = false;
    427                 }
    428                 if (o->hasSelfPaintingLayer())
    429                     propagateVisualOverflow = false;
    430                 markerRect.moveBy(-o->location());
    431             } while (o != this && propagateVisualOverflow && propagateLayoutOverflow);
    432         }
    433     }
    434 }
    435 
    436 void RenderListItem::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
    437 {
    438     if (!logicalHeight() && hasOverflowClip())
    439         return;
    440 
    441     RenderBlockFlow::paint(paintInfo, paintOffset);
    442 }
    443 
    444 const String& RenderListItem::markerText() const
    445 {
    446     if (m_marker)
    447         return m_marker->text();
    448     return nullAtom.string();
    449 }
    450 
    451 void RenderListItem::explicitValueChanged()
    452 {
    453     if (m_marker)
    454         m_marker->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation();
    455     Node* listNode = enclosingList(this);
    456     for (RenderListItem* item = this; item; item = nextListItem(listNode, item))
    457         item->updateValue();
    458 }
    459 
    460 void RenderListItem::setExplicitValue(int value)
    461 {
    462     ASSERT(node());
    463 
    464     if (m_hasExplicitValue && m_explicitValue == value)
    465         return;
    466     m_explicitValue = value;
    467     m_value = value;
    468     m_hasExplicitValue = true;
    469     explicitValueChanged();
    470 }
    471 
    472 void RenderListItem::clearExplicitValue()
    473 {
    474     ASSERT(node());
    475 
    476     if (!m_hasExplicitValue)
    477         return;
    478     m_hasExplicitValue = false;
    479     m_isValueUpToDate = false;
    480     explicitValueChanged();
    481 }
    482 
    483 void RenderListItem::setNotInList(bool notInList)
    484 {
    485     m_notInList = notInList;
    486     if (m_marker)
    487         updateMarkerLocation();
    488 }
    489 
    490 static RenderListItem* previousOrNextItem(bool isListReversed, Node* list, RenderListItem* item)
    491 {
    492     return isListReversed ? previousListItem(list, item) : nextListItem(list, item);
    493 }
    494 
    495 void RenderListItem::updateListMarkerNumbers()
    496 {
    497     // If distribution recalc is needed, updateListMarkerNumber will be re-invoked
    498     // after distribution is calculated.
    499     if (node()->document().childNeedsDistributionRecalc())
    500         return;
    501 
    502     Node* listNode = enclosingList(this);
    503     ASSERT(listNode);
    504 
    505     bool isListReversed = false;
    506     HTMLOListElement* oListElement = isHTMLOListElement(listNode) ? toHTMLOListElement(listNode) : 0;
    507     if (oListElement) {
    508         oListElement->itemCountChanged();
    509         isListReversed = oListElement->isReversed();
    510     }
    511 
    512     // FIXME: The n^2 protection below doesn't help if the elements were inserted after the
    513     // the list had already been displayed.
    514 
    515     // Avoid an O(n^2) walk over the children below when they're all known to be attaching.
    516     if (listNode->needsAttach())
    517         return;
    518 
    519     for (RenderListItem* item = previousOrNextItem(isListReversed, listNode, this); item; item = previousOrNextItem(isListReversed, listNode, item)) {
    520         if (!item->m_isValueUpToDate) {
    521             // If an item has been marked for update before, we can safely
    522             // assume that all the following ones have too.
    523             // This gives us the opportunity to stop here and avoid
    524             // marking the same nodes again.
    525             break;
    526         }
    527         item->updateValue();
    528     }
    529 }
    530 
    531 } // namespace blink
    532