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