[WTF] Import std::optional reference implementation as WTF::Optional
[WebKit-https.git] / Source / WebCore / rendering / RenderListBox.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2011, 2014-2015 Apple Inc. All rights reserved.
3  *               2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer. 
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution. 
14  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission. 
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "config.h"
31 #include "RenderListBox.h"
32
33 #include "AXObjectCache.h"
34 #include "CSSFontSelector.h"
35 #include "Document.h"
36 #include "DocumentEventQueue.h"
37 #include "EventHandler.h"
38 #include "FocusController.h"
39 #include "Frame.h"
40 #include "FrameSelection.h"
41 #include "FrameView.h"
42 #include "GraphicsContext.h"
43 #include "HTMLNames.h"
44 #include "HTMLOptionElement.h"
45 #include "HTMLOptGroupElement.h"
46 #include "HTMLSelectElement.h"
47 #include "HitTestResult.h"
48 #include "NodeRenderStyle.h"
49 #include "Page.h"
50 #include "PaintInfo.h"
51 #include "RenderLayer.h"
52 #include "RenderScrollbar.h"
53 #include "RenderText.h"
54 #include "RenderTheme.h"
55 #include "RenderView.h"
56 #include "ScrollAnimator.h"
57 #include "Scrollbar.h"
58 #include "ScrollbarTheme.h"
59 #include "Settings.h"
60 #include "SpatialNavigation.h"
61 #include "StyleResolver.h"
62 #include "StyleTreeResolver.h"
63 #include "WheelEventTestTrigger.h"
64 #include <math.h>
65 #include <wtf/StackStats.h>
66
67 namespace WebCore {
68
69 using namespace HTMLNames;
70  
71 const int rowSpacing = 1;
72
73 const int optionsSpacingHorizontal = 2;
74
75 // The minSize constant was originally defined to render scrollbars correctly.
76 // This might vary for different platforms.
77 const int minSize = 4;
78
79 // Default size when the multiple attribute is present but size attribute is absent.
80 const int defaultSize = 4;
81
82 // FIXME: This hardcoded baselineAdjustment is what we used to do for the old
83 // widget, but I'm not sure this is right for the new control.
84 const int baselineAdjustment = 7;
85
86 RenderListBox::RenderListBox(HTMLSelectElement& element, RenderStyle&& style)
87     : RenderBlockFlow(element, WTFMove(style))
88     , m_optionsChanged(true)
89     , m_scrollToRevealSelectionAfterLayout(false)
90     , m_inAutoscroll(false)
91     , m_optionsWidth(0)
92     , m_indexOffset(0)
93 {
94     view().frameView().addScrollableArea(this);
95 }
96
97 RenderListBox::~RenderListBox()
98 {
99     setHasVerticalScrollbar(false);
100     view().frameView().removeScrollableArea(this);
101 }
102
103 HTMLSelectElement& RenderListBox::selectElement() const
104 {
105     return downcast<HTMLSelectElement>(nodeForNonAnonymous());
106 }
107
108 void RenderListBox::updateFromElement()
109 {
110     if (m_optionsChanged) {
111         const Vector<HTMLElement*>& listItems = selectElement().listItems();
112         int size = numItems();
113         
114         float width = 0;
115         for (int i = 0; i < size; ++i) {
116             HTMLElement* element = listItems[i];
117             String text;
118             FontCascade itemFont = style().fontCascade();
119             if (is<HTMLOptionElement>(*element))
120                 text = downcast<HTMLOptionElement>(*element).textIndentedToRespectGroupLabel();
121             else if (is<HTMLOptGroupElement>(*element)) {
122                 text = downcast<HTMLOptGroupElement>(*element).groupLabelText();
123                 auto description = itemFont.fontDescription();
124                 description.setWeight(description.bolderWeight());
125                 itemFont = FontCascade(description, itemFont.letterSpacing(), itemFont.wordSpacing());
126                 itemFont.update(&document().fontSelector());
127             }
128
129             if (!text.isEmpty()) {
130                 applyTextTransform(style(), text, ' ');
131                 // FIXME: Why is this always LTR? Can't text direction affect the width?
132                 TextRun textRun = constructTextRun(text, style(), AllowTrailingExpansion);
133                 float textWidth = itemFont.width(textRun);
134                 width = std::max(width, textWidth);
135             }
136         }
137         m_optionsWidth = static_cast<int>(ceilf(width));
138         m_optionsChanged = false;
139         
140         setHasVerticalScrollbar(true);
141
142         computeFirstIndexesVisibleInPaddingTopBottomAreas();
143
144         setNeedsLayoutAndPrefWidthsRecalc();
145     }
146 }
147
148 void RenderListBox::selectionChanged()
149 {
150     repaint();
151     if (!m_inAutoscroll) {
152         if (m_optionsChanged || needsLayout())
153             m_scrollToRevealSelectionAfterLayout = true;
154         else
155             scrollToRevealSelection();
156     }
157     
158     if (AXObjectCache* cache = document().existingAXObjectCache())
159         cache->selectedChildrenChanged(this);
160 }
161
162 void RenderListBox::layout()
163 {
164     StackStats::LayoutCheckPoint layoutCheckPoint;
165     RenderBlockFlow::layout();
166
167     if (m_vBar) {
168         bool enabled = numVisibleItems() < numItems();
169         m_vBar->setEnabled(enabled);
170         m_vBar->setSteps(1, std::max(1, numVisibleItems() - 1), itemHeight());
171         m_vBar->setProportion(numVisibleItems(), numItems());
172         if (!enabled) {
173             scrollToOffsetWithoutAnimation(VerticalScrollbar, 0);
174             m_indexOffset = 0;
175         }
176     }
177
178     if (m_scrollToRevealSelectionAfterLayout) {
179         LayoutStateDisabler layoutStateDisabler(view());
180         scrollToRevealSelection();
181     }
182 }
183
184 void RenderListBox::scrollToRevealSelection()
185 {    
186     m_scrollToRevealSelectionAfterLayout = false;
187
188     int firstIndex = selectElement().activeSelectionStartListIndex();
189     if (firstIndex >= 0 && !listIndexIsVisible(selectElement().activeSelectionEndListIndex()))
190         scrollToRevealElementAtListIndex(firstIndex);
191 }
192
193 void RenderListBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
194 {
195     maxLogicalWidth = m_optionsWidth + 2 * optionsSpacingHorizontal;
196     if (m_vBar)
197         maxLogicalWidth += m_vBar->width();
198     if (!style().width().isPercentOrCalculated())
199         minLogicalWidth = maxLogicalWidth;
200 }
201
202 void RenderListBox::computePreferredLogicalWidths()
203 {
204     // Nested style recal do not fire post recal callbacks. see webkit.org/b/153767
205     ASSERT(!m_optionsChanged || Style::postResolutionCallbacksAreSuspended());
206
207     m_minPreferredLogicalWidth = 0;
208     m_maxPreferredLogicalWidth = 0;
209
210     if (style().width().isFixed() && style().width().value() > 0)
211         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style().width().value());
212     else
213         computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
214
215     if (style().minWidth().isFixed() && style().minWidth().value() > 0) {
216         m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
217         m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
218     }
219
220     if (style().maxWidth().isFixed()) {
221         m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
222         m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
223     }
224
225     LayoutUnit toAdd = horizontalBorderAndPaddingExtent();
226     m_minPreferredLogicalWidth += toAdd;
227     m_maxPreferredLogicalWidth += toAdd;
228                                 
229     setPreferredLogicalWidthsDirty(false);
230 }
231
232 int RenderListBox::size() const
233 {
234     int specifiedSize = selectElement().size();
235     if (specifiedSize > 1)
236         return std::max(minSize, specifiedSize);
237
238     return defaultSize;
239 }
240
241 int RenderListBox::numVisibleItems(ConsiderPadding considerPadding) const
242 {
243     // Only count fully visible rows. But don't return 0 even if only part of a row shows.
244     int visibleItemsExcludingPadding = std::max<int>(1, (contentHeight() + rowSpacing) / itemHeight());
245     if (considerPadding == ConsiderPadding::No)
246         return visibleItemsExcludingPadding;
247
248     return numberOfVisibleItemsInPaddingTop() + visibleItemsExcludingPadding + numberOfVisibleItemsInPaddingBottom();
249 }
250
251 int RenderListBox::numItems() const
252 {
253     return selectElement().listItems().size();
254 }
255
256 LayoutUnit RenderListBox::listHeight() const
257 {
258     return itemHeight() * numItems() - rowSpacing;
259 }
260
261 void RenderListBox::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
262 {
263     LayoutUnit height = itemHeight() * size() - rowSpacing + verticalBorderAndPaddingExtent();
264     RenderBox::computeLogicalHeight(height, logicalTop, computedValues);
265 }
266
267 int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const
268 {
269     return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode) - baselineAdjustment;
270 }
271
272 LayoutRect RenderListBox::itemBoundingBoxRect(const LayoutPoint& additionalOffset, int index)
273 {
274     LayoutUnit x = additionalOffset.x() + borderLeft() + paddingLeft();
275     if (shouldPlaceBlockDirectionScrollbarOnLeft() && m_vBar)
276         x += m_vBar->occupiedWidth();
277     LayoutUnit y = additionalOffset.y() + borderTop() + paddingTop() + itemHeight() * (index - m_indexOffset);
278     return LayoutRect(x, y, contentWidth(), itemHeight());
279 }
280
281 void RenderListBox::paintItem(PaintInfo& paintInfo, const LayoutPoint& paintOffset, PaintFunction paintFunction)
282 {
283     int listItemsSize = numItems();
284     int firstVisibleItem = m_indexOfFirstVisibleItemInsidePaddingTopArea.value_or(m_indexOffset);
285     int endIndex = firstVisibleItem + numVisibleItems(ConsiderPadding::Yes);
286     for (int i = firstVisibleItem; i < listItemsSize && i < endIndex; ++i)
287         paintFunction(paintInfo, paintOffset, i);
288 }
289
290 void RenderListBox::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
291 {
292     if (style().visibility() != VISIBLE)
293         return;
294     
295     if (paintInfo.phase == PaintPhaseForeground) {
296         paintItem(paintInfo, paintOffset, [this](PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listItemIndex) {
297             paintItemForeground(paintInfo, paintOffset, listItemIndex);
298         });
299     }
300
301     // Paint the children.
302     RenderBlockFlow::paintObject(paintInfo, paintOffset);
303
304     switch (paintInfo.phase) {
305     // Depending on whether we have overlay scrollbars they
306     // get rendered in the foreground or background phases
307     case PaintPhaseForeground:
308         if (m_vBar->isOverlayScrollbar())
309             paintScrollbar(paintInfo, paintOffset);
310         break;
311     case PaintPhaseBlockBackground:
312         if (!m_vBar->isOverlayScrollbar())
313             paintScrollbar(paintInfo, paintOffset);
314         break;
315     case PaintPhaseChildBlockBackground:
316     case PaintPhaseChildBlockBackgrounds: {
317         paintItem(paintInfo, paintOffset, [this](PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listItemIndex) {
318             paintItemBackground(paintInfo, paintOffset, listItemIndex);
319         });
320         break;
321     }
322     default:
323         break;
324     }
325 }
326
327 void RenderListBox::addFocusRingRects(Vector<LayoutRect>& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject* paintContainer)
328 {
329     if (!selectElement().allowsNonContiguousSelection())
330         return RenderBlockFlow::addFocusRingRects(rects, additionalOffset, paintContainer);
331
332     // Focus the last selected item.
333     int selectedItem = selectElement().activeSelectionEndListIndex();
334     if (selectedItem >= 0) {
335         rects.append(snappedIntRect(itemBoundingBoxRect(additionalOffset, selectedItem)));
336         return;
337     }
338
339     // No selected items, find the first non-disabled item.
340     int size = numItems();
341     const Vector<HTMLElement*>& listItems = selectElement().listItems();
342     for (int i = 0; i < size; ++i) {
343         HTMLElement* element = listItems[i];
344         if (is<HTMLOptionElement>(*element) && !element->isDisabledFormControl()) {
345             selectElement().setActiveSelectionEndIndex(i);
346             rects.append(itemBoundingBoxRect(additionalOffset, i));
347             return;
348         }
349     }
350 }
351
352 void RenderListBox::paintScrollbar(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
353 {
354     if (!m_vBar)
355         return;
356
357     LayoutUnit left = paintOffset.x() + (shouldPlaceBlockDirectionScrollbarOnLeft() ? borderLeft() : width() - borderRight() - m_vBar->width());
358     LayoutUnit top = paintOffset.y() + borderTop();
359     LayoutUnit width = m_vBar->width();
360     LayoutUnit height = this->height() - (borderTop() + borderBottom());
361     IntRect scrollRect = snappedIntRect(left, top, width, height);
362     m_vBar->setFrameRect(scrollRect);
363     m_vBar->paint(paintInfo.context(), snappedIntRect(paintInfo.rect));
364 }
365
366 static LayoutSize itemOffsetForAlignment(TextRun textRun, const RenderStyle* itemStyle, FontCascade itemFont, LayoutRect itemBoudingBox)
367 {
368     ETextAlign actualAlignment = itemStyle->textAlign();
369     // FIXME: Firefox doesn't respect JUSTIFY. Should we?
370     // FIXME: Handle TAEND here
371     if (actualAlignment == TASTART || actualAlignment == JUSTIFY)
372       actualAlignment = itemStyle->isLeftToRightDirection() ? LEFT : RIGHT;
373
374     LayoutSize offset = LayoutSize(0, itemFont.fontMetrics().ascent());
375     if (actualAlignment == RIGHT || actualAlignment == WEBKIT_RIGHT) {
376         float textWidth = itemFont.width(textRun);
377         offset.setWidth(itemBoudingBox.width() - textWidth - optionsSpacingHorizontal);
378     } else if (actualAlignment == CENTER || actualAlignment == WEBKIT_CENTER) {
379         float textWidth = itemFont.width(textRun);
380         offset.setWidth((itemBoudingBox.width() - textWidth) / 2);
381     } else
382         offset.setWidth(optionsSpacingHorizontal);
383     return offset;
384 }
385
386 void RenderListBox::paintItemForeground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
387 {
388     const Vector<HTMLElement*>& listItems = selectElement().listItems();
389     HTMLElement* listItemElement = listItems[listIndex];
390
391     auto& itemStyle = *listItemElement->computedStyle();
392
393     if (itemStyle.visibility() == HIDDEN)
394         return;
395
396     String itemText;
397     bool isOptionElement = is<HTMLOptionElement>(*listItemElement);
398     if (isOptionElement)
399         itemText = downcast<HTMLOptionElement>(*listItemElement).textIndentedToRespectGroupLabel();
400     else if (is<HTMLOptGroupElement>(*listItemElement))
401         itemText = downcast<HTMLOptGroupElement>(*listItemElement).groupLabelText();
402     applyTextTransform(style(), itemText, ' ');
403
404     Color textColor = itemStyle.visitedDependentColor(CSSPropertyColor);
405     if (isOptionElement && downcast<HTMLOptionElement>(*listItemElement).selected()) {
406         if (frame().selection().isFocusedAndActive() && document().focusedElement() == &selectElement())
407             textColor = theme().activeListBoxSelectionForegroundColor();
408         // Honor the foreground color for disabled items
409         else if (!listItemElement->isDisabledFormControl() && !selectElement().isDisabledFormControl())
410             textColor = theme().inactiveListBoxSelectionForegroundColor();
411     }
412
413     paintInfo.context().setFillColor(textColor);
414
415     TextRun textRun(itemText, 0, 0, AllowTrailingExpansion, itemStyle.direction(), isOverride(itemStyle.unicodeBidi()), true);
416     FontCascade itemFont = style().fontCascade();
417     LayoutRect r = itemBoundingBoxRect(paintOffset, listIndex);
418     r.move(itemOffsetForAlignment(textRun, &itemStyle, itemFont, r));
419
420     if (is<HTMLOptGroupElement>(*listItemElement)) {
421         auto description = itemFont.fontDescription();
422         description.setWeight(description.bolderWeight());
423         itemFont = FontCascade(description, itemFont.letterSpacing(), itemFont.wordSpacing());
424         itemFont.update(&document().fontSelector());
425     }
426
427     // Draw the item text
428     paintInfo.context().drawBidiText(itemFont, textRun, roundedIntPoint(r.location()));
429 }
430
431 void RenderListBox::paintItemBackground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
432 {
433     const Vector<HTMLElement*>& listItems = selectElement().listItems();
434     HTMLElement* listItemElement = listItems[listIndex];
435     auto& itemStyle = *listItemElement->computedStyle();
436
437     Color backColor;
438     if (is<HTMLOptionElement>(*listItemElement) && downcast<HTMLOptionElement>(*listItemElement).selected()) {
439         if (frame().selection().isFocusedAndActive() && document().focusedElement() == &selectElement())
440             backColor = theme().activeListBoxSelectionBackgroundColor();
441         else
442             backColor = theme().inactiveListBoxSelectionBackgroundColor();
443     } else
444         backColor = itemStyle.visitedDependentColor(CSSPropertyBackgroundColor);
445
446     // Draw the background for this list box item
447     if (itemStyle.visibility() == HIDDEN)
448         return;
449
450     LayoutRect itemRect = itemBoundingBoxRect(paintOffset, listIndex);
451     itemRect.intersect(controlClipRect(paintOffset));
452     paintInfo.context().fillRect(snappedIntRect(itemRect), backColor);
453 }
454
455 bool RenderListBox::isPointInOverflowControl(HitTestResult& result, const LayoutPoint& locationInContainer, const LayoutPoint& accumulatedOffset)
456 {
457     if (!m_vBar || !m_vBar->shouldParticipateInHitTesting())
458         return false;
459
460     LayoutUnit x = accumulatedOffset.x() + (shouldPlaceBlockDirectionScrollbarOnLeft() ? borderLeft() : width() - borderRight() - m_vBar->width());
461     LayoutUnit y = accumulatedOffset.y() + borderTop();
462     LayoutUnit width = m_vBar->width();
463     LayoutUnit height = this->height() - borderTop() - borderBottom();
464     LayoutRect vertRect(x, y, width, height);
465
466     if (!vertRect.contains(locationInContainer))
467         return false;
468
469     result.setScrollbar(m_vBar.get());
470     return true;
471 }
472
473 int RenderListBox::listIndexAtOffset(const LayoutSize& offset)
474 {
475     if (!numItems())
476         return -1;
477
478     if (offset.height() < borderTop() || offset.height() > height() - borderBottom())
479         return -1;
480
481     int scrollbarWidth = m_vBar ? m_vBar->width() : 0;
482     if (shouldPlaceBlockDirectionScrollbarOnLeft() && (offset.width() < borderLeft() + paddingLeft() + scrollbarWidth || offset.width() > width() - borderRight() - paddingRight()))
483         return -1;
484     if (!shouldPlaceBlockDirectionScrollbarOnLeft() && (offset.width() < borderLeft() + paddingLeft() || offset.width() > width() - borderRight() - paddingRight() - scrollbarWidth))
485         return -1;
486
487     int newOffset = (offset.height() - borderTop() - paddingTop()) / itemHeight() + m_indexOffset;
488     return newOffset < numItems() ? newOffset : -1;
489 }
490
491 void RenderListBox::panScroll(const IntPoint& panStartMousePosition)
492 {
493     const int maxSpeed = 20;
494     const int iconRadius = 7;
495     const int speedReducer = 4;
496
497     // FIXME: This doesn't work correctly with transforms.
498     FloatPoint absOffset = localToAbsolute();
499
500     IntPoint lastKnownMousePosition = frame().eventHandler().lastKnownMousePosition();
501     // We need to check if the last known mouse position is out of the window. When the mouse is out of the window, the position is incoherent
502     static IntPoint previousMousePosition;
503     if (lastKnownMousePosition.y() < 0)
504         lastKnownMousePosition = previousMousePosition;
505     else
506         previousMousePosition = lastKnownMousePosition;
507
508     int yDelta = lastKnownMousePosition.y() - panStartMousePosition.y();
509
510     // If the point is too far from the center we limit the speed
511     yDelta = std::max<int>(std::min<int>(yDelta, maxSpeed), -maxSpeed);
512     
513     if (abs(yDelta) < iconRadius) // at the center we let the space for the icon
514         return;
515
516     if (yDelta > 0)
517         //offsetY = view()->viewHeight();
518         absOffset.move(0, listHeight());
519     else if (yDelta < 0)
520         yDelta--;
521
522     // Let's attenuate the speed
523     yDelta /= speedReducer;
524
525     IntPoint scrollPoint(0, 0);
526     scrollPoint.setY(absOffset.y() + yDelta);
527     int newOffset = scrollToward(scrollPoint);
528     if (newOffset < 0) 
529         return;
530
531     m_inAutoscroll = true;
532     selectElement().updateListBoxSelection(!selectElement().multiple());
533     m_inAutoscroll = false;
534 }
535
536 int RenderListBox::scrollToward(const IntPoint& destination)
537 {
538     // FIXME: This doesn't work correctly with transforms.
539     FloatPoint absPos = localToAbsolute();
540     IntSize positionOffset = roundedIntSize(destination - absPos);
541
542     int rows = numVisibleItems();
543     int offset = m_indexOffset;
544     
545     if (positionOffset.height() < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1))
546         return offset - 1;
547     
548     if (positionOffset.height() > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows))
549         return offset + rows - 1;
550     
551     return listIndexAtOffset(positionOffset);
552 }
553
554 void RenderListBox::autoscroll(const IntPoint&)
555 {
556     IntPoint pos = frame().view()->windowToContents(frame().eventHandler().lastKnownMousePosition());
557
558     int endIndex = scrollToward(pos);
559     if (selectElement().isDisabledFormControl())
560         return;
561
562     if (endIndex >= 0) {
563         m_inAutoscroll = true;
564
565         if (!selectElement().multiple())
566             selectElement().setActiveSelectionAnchorIndex(endIndex);
567
568         selectElement().setActiveSelectionEndIndex(endIndex);
569         selectElement().updateListBoxSelection(!selectElement().multiple());
570         m_inAutoscroll = false;
571     }
572 }
573
574 void RenderListBox::stopAutoscroll()
575 {
576     if (selectElement().isDisabledFormControl())
577         return;
578
579     selectElement().listBoxOnChange();
580 }
581
582 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
583 {
584     if (index < 0 || index >= numItems() || listIndexIsVisible(index))
585         return false;
586
587     int newOffset;
588     if (index < m_indexOffset)
589         newOffset = index;
590     else
591         newOffset = index - numVisibleItems() + 1;
592
593     scrollToOffsetWithoutAnimation(VerticalScrollbar, newOffset);
594
595     return true;
596 }
597
598 bool RenderListBox::listIndexIsVisible(int index)
599 {
600     int firstIndex = m_indexOfFirstVisibleItemInsidePaddingTopArea.value_or(m_indexOffset);
601     int endIndex = m_indexOfFirstVisibleItemInsidePaddingBottomArea
602         ? m_indexOfFirstVisibleItemInsidePaddingBottomArea.value() + numberOfVisibleItemsInPaddingBottom()
603         : m_indexOffset + numVisibleItems();
604
605     return index >= firstIndex && index < endIndex;
606 }
607
608 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Element**, RenderBox*, const IntPoint&)
609 {
610     return ScrollableArea::scroll(direction, granularity, multiplier);
611 }
612
613 bool RenderListBox::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Element**)
614 {
615     return ScrollableArea::scroll(logicalToPhysical(direction, style().isHorizontalWritingMode(), style().isFlippedBlocksWritingMode()), granularity, multiplier);
616 }
617
618 void RenderListBox::valueChanged(unsigned listIndex)
619 {
620     selectElement().setSelectedIndex(selectElement().listToOptionIndex(listIndex));
621     selectElement().dispatchFormControlChangeEvent();
622 }
623
624 int RenderListBox::scrollSize(ScrollbarOrientation orientation) const
625 {
626     return ((orientation == VerticalScrollbar) && m_vBar) ? (m_vBar->totalSize() - m_vBar->visibleSize()) : 0;
627 }
628
629 int RenderListBox::scrollOffset(ScrollbarOrientation) const
630 {
631     return m_indexOffset;
632 }
633
634 ScrollPosition RenderListBox::minimumScrollPosition() const
635 {
636     return { 0, 0 };
637 }
638
639 ScrollPosition RenderListBox::maximumScrollPosition() const
640 {
641     return { 0, numItems() - numVisibleItems() };
642 }
643
644 void RenderListBox::setScrollOffset(const ScrollOffset& offset)
645 {
646     scrollTo(offset.y());
647 }
648
649 int RenderListBox::maximumNumberOfItemsThatFitInPaddingBottomArea() const
650 {
651     return paddingBottom() / itemHeight();
652 }
653
654 int RenderListBox::numberOfVisibleItemsInPaddingTop() const
655 {
656     if (!m_indexOfFirstVisibleItemInsidePaddingTopArea)
657         return 0;
658
659     return m_indexOffset - m_indexOfFirstVisibleItemInsidePaddingTopArea.value();
660 }
661
662 int RenderListBox::numberOfVisibleItemsInPaddingBottom() const
663 {
664     if (!m_indexOfFirstVisibleItemInsidePaddingBottomArea)
665         return 0;
666
667     return std::min(maximumNumberOfItemsThatFitInPaddingBottomArea(), numItems() - m_indexOffset - numVisibleItems());
668 }
669
670 void RenderListBox::computeFirstIndexesVisibleInPaddingTopBottomAreas()
671 {
672     m_indexOfFirstVisibleItemInsidePaddingTopArea = std::nullopt;
673     m_indexOfFirstVisibleItemInsidePaddingBottomArea = std::nullopt;
674
675     int maximumNumberOfItemsThatFitInPaddingTopArea = paddingTop() / itemHeight();
676     if (maximumNumberOfItemsThatFitInPaddingTopArea) {
677         if (m_indexOffset)
678             m_indexOfFirstVisibleItemInsidePaddingTopArea = std::max(0, m_indexOffset - maximumNumberOfItemsThatFitInPaddingTopArea);
679     }
680
681     if (maximumNumberOfItemsThatFitInPaddingBottomArea()) {
682         if (numItems() > (m_indexOffset + numVisibleItems()))
683             m_indexOfFirstVisibleItemInsidePaddingBottomArea = m_indexOffset + numVisibleItems();
684     }
685 }
686
687 void RenderListBox::scrollTo(int newOffset)
688 {
689     if (newOffset == m_indexOffset)
690         return;
691
692     m_indexOffset = newOffset;
693
694     computeFirstIndexesVisibleInPaddingTopBottomAreas();
695
696     repaint();
697     document().eventQueue().enqueueOrDispatchScrollEvent(selectElement());
698 }
699
700 LayoutUnit RenderListBox::itemHeight() const
701 {
702     return style().fontMetrics().height() + rowSpacing;
703 }
704
705 int RenderListBox::verticalScrollbarWidth() const
706 {
707     return m_vBar ? m_vBar->occupiedWidth() : 0;
708 }
709
710 // FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's
711 // how the control currently paints.
712 int RenderListBox::scrollWidth() const
713 {
714     // There is no horizontal scrolling allowed.
715     return roundToInt(clientWidth());
716 }
717
718 int RenderListBox::scrollHeight() const
719 {
720     return roundToInt(std::max(clientHeight(), listHeight()));
721 }
722
723 int RenderListBox::scrollLeft() const
724 {
725     return 0;
726 }
727
728 void RenderListBox::setScrollLeft(int)
729 {
730 }
731
732 int RenderListBox::scrollTop() const
733 {
734     return m_indexOffset * itemHeight();
735 }
736
737 static void setupWheelEventTestTrigger(RenderListBox& renderer, Frame* frame)
738 {
739     if (!frame)
740         return;
741
742     Page* page = frame->page();
743     if (!page || !page->expectsWheelEventTriggers())
744         return;
745
746     renderer.scrollAnimator().setWheelEventTestTrigger(page->testTrigger());
747 }
748
749 void RenderListBox::setScrollTop(int newTop)
750 {
751     // Determine an index and scroll to it.    
752     int index = newTop / itemHeight();
753     if (index < 0 || index >= numItems() || index == m_indexOffset)
754         return;
755     setupWheelEventTestTrigger(*this, document().frame());
756     scrollToOffsetWithoutAnimation(VerticalScrollbar, index);
757 }
758
759 bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
760 {
761     if (!RenderBlockFlow::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction))
762         return false;
763     const Vector<HTMLElement*>& listItems = selectElement().listItems();
764     int size = numItems();
765     LayoutPoint adjustedLocation = accumulatedOffset + location();
766
767     for (int i = 0; i < size; ++i) {
768         if (!itemBoundingBoxRect(adjustedLocation, i).contains(locationInContainer.point()))
769             continue;
770         if (Element* node = listItems[i]) {
771             result.setInnerNode(node);
772             if (!result.innerNonSharedNode())
773                 result.setInnerNonSharedNode(node);
774             result.setLocalPoint(locationInContainer.point() - toLayoutSize(adjustedLocation));
775             break;
776         }
777     }
778
779     return true;
780 }
781
782 LayoutRect RenderListBox::controlClipRect(const LayoutPoint& additionalOffset) const
783 {
784     // Clip against the padding box, to give <option>s and overlay scrollbar some extra space
785     // to get painted.
786     LayoutRect clipRect = paddingBoxRect();
787     if (shouldPlaceBlockDirectionScrollbarOnLeft())
788         clipRect.move(m_vBar->occupiedWidth(), 0);
789     clipRect.moveBy(additionalOffset);
790     return clipRect;
791 }
792
793 bool RenderListBox::isActive() const
794 {
795     Page* page = frame().page();
796     return page && page->focusController().isActive();
797 }
798
799 void RenderListBox::invalidateScrollbarRect(Scrollbar& scrollbar, const IntRect& rect)
800 {
801     IntRect scrollRect = rect;
802     scrollRect.move(shouldPlaceBlockDirectionScrollbarOnLeft() ? borderLeft() : width() - borderRight() - scrollbar.width(), borderTop());
803     repaintRectangle(scrollRect);
804 }
805
806 IntRect RenderListBox::convertFromScrollbarToContainingView(const Scrollbar& scrollbar, const IntRect& scrollbarRect) const
807 {
808     IntRect rect = scrollbarRect;
809     int scrollbarLeft = shouldPlaceBlockDirectionScrollbarOnLeft() ? borderLeft() : width() - borderRight() - scrollbar.width();
810     int scrollbarTop = borderTop();
811     rect.move(scrollbarLeft, scrollbarTop);
812     return view().frameView().convertFromRendererToContainingView(this, rect);
813 }
814
815 IntRect RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar& scrollbar, const IntRect& parentRect) const
816 {
817     IntRect rect = view().frameView().convertFromContainingViewToRenderer(this, parentRect);
818     int scrollbarLeft = shouldPlaceBlockDirectionScrollbarOnLeft() ? borderLeft() : width() - borderRight() - scrollbar.width();
819     int scrollbarTop = borderTop();
820     rect.move(-scrollbarLeft, -scrollbarTop);
821     return rect;
822 }
823
824 IntPoint RenderListBox::convertFromScrollbarToContainingView(const Scrollbar& scrollbar, const IntPoint& scrollbarPoint) const
825 {
826     IntPoint point = scrollbarPoint;
827     int scrollbarLeft = shouldPlaceBlockDirectionScrollbarOnLeft() ? borderLeft() : width() - borderRight() - scrollbar.width();
828     int scrollbarTop = borderTop();
829     point.move(scrollbarLeft, scrollbarTop);
830     return view().frameView().convertFromRendererToContainingView(this, point);
831 }
832
833 IntPoint RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar& scrollbar, const IntPoint& parentPoint) const
834 {
835     IntPoint point = view().frameView().convertFromContainingViewToRenderer(this, parentPoint);
836     int scrollbarLeft = shouldPlaceBlockDirectionScrollbarOnLeft() ? borderLeft() : width() - borderRight() - scrollbar.width();
837     int scrollbarTop = borderTop();
838     point.move(-scrollbarLeft, -scrollbarTop);
839     return point;
840 }
841
842 IntSize RenderListBox::contentsSize() const
843 {
844     return IntSize(scrollWidth(), scrollHeight());
845 }
846
847 IntPoint RenderListBox::lastKnownMousePosition() const
848 {
849     return view().frameView().lastKnownMousePosition();
850 }
851
852 bool RenderListBox::isHandlingWheelEvent() const
853 {
854     return view().frameView().isHandlingWheelEvent();
855 }
856
857 bool RenderListBox::shouldSuspendScrollAnimations() const
858 {
859     return view().frameView().shouldSuspendScrollAnimations();
860 }
861
862 bool RenderListBox::forceUpdateScrollbarsOnMainThreadForPerformanceTesting() const
863 {
864     Page* page = frame().page();
865     return page && page->settings().forceUpdateScrollbarsOnMainThreadForPerformanceTesting();
866 }
867
868 ScrollableArea* RenderListBox::enclosingScrollableArea() const
869 {
870     // FIXME: Return a RenderLayer that's scrollable.
871     return nullptr;
872 }
873
874 bool RenderListBox::isScrollableOrRubberbandable()
875 {
876     return m_vBar;
877 }
878
879 bool RenderListBox::hasScrollableOrRubberbandableAncestor()
880 {
881     return enclosingLayer() && enclosingLayer()->hasScrollableOrRubberbandableAncestor();
882 }
883
884 IntRect RenderListBox::scrollableAreaBoundingBox(bool*) const
885 {
886     return absoluteBoundingBoxRect();
887 }
888
889 bool RenderListBox::usesMockScrollAnimator() const
890 {
891     return Settings::usesMockScrollAnimator();
892 }
893
894 void RenderListBox::logMockScrollAnimatorMessage(const String& message) const
895 {
896     document().addConsoleMessage(MessageSource::Other, MessageLevel::Debug, "RenderListBox: " + message);
897 }
898
899 Ref<Scrollbar> RenderListBox::createScrollbar()
900 {
901     RefPtr<Scrollbar> widget;
902     bool hasCustomScrollbarStyle = style().hasPseudoStyle(SCROLLBAR);
903     if (hasCustomScrollbarStyle)
904         widget = RenderScrollbar::createCustomScrollbar(*this, VerticalScrollbar, &selectElement());
905     else {
906         widget = Scrollbar::createNativeScrollbar(*this, VerticalScrollbar, theme().scrollbarControlSizeForPart(ListboxPart));
907         didAddScrollbar(widget.get(), VerticalScrollbar);
908         if (Page* page = frame().page()) {
909             if (page->expectsWheelEventTriggers())
910                 scrollAnimator().setWheelEventTestTrigger(page->testTrigger());
911         }
912     }
913     view().frameView().addChild(widget.get());
914     return widget.releaseNonNull();
915 }
916
917 void RenderListBox::destroyScrollbar()
918 {
919     if (!m_vBar)
920         return;
921
922     if (!m_vBar->isCustomScrollbar())
923         ScrollableArea::willRemoveScrollbar(m_vBar.get(), VerticalScrollbar);
924     m_vBar->removeFromParent();
925     m_vBar = nullptr;
926 }
927
928 void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar)
929 {
930     if (hasScrollbar == (m_vBar != nullptr))
931         return;
932
933     if (hasScrollbar)
934         m_vBar = createScrollbar();
935     else
936         destroyScrollbar();
937
938     if (m_vBar)
939         m_vBar->styleChanged();
940
941     // Force an update since we know the scrollbars have changed things.
942 #if ENABLE(DASHBOARD_SUPPORT)
943     if (document().hasAnnotatedRegions())
944         document().setAnnotatedRegionsDirty(true);
945 #endif
946 }
947
948 bool RenderListBox::scrolledToTop() const
949 {
950     if (Scrollbar* vbar = verticalScrollbar())
951     return vbar->value() <= 0;
952
953     return true;
954 }
955
956 bool RenderListBox::scrolledToBottom() const
957 {
958     Scrollbar* vbar = verticalScrollbar();
959     if (!vbar)
960         return true;
961
962     return vbar->value() >= vbar->maximum();
963 }
964
965 bool RenderListBox::scrolledToLeft() const
966 {
967     // We do not scroll horizontally in a select element, so always report
968     // that we are at the full extent of the scroll.
969     return true;
970 }
971
972 bool RenderListBox::scrolledToRight() const
973 {
974     // We do not scroll horizontally in a select element, so always report
975     // that we are at the full extent of the scroll.
976     return true;
977 }
978     
979 } // namespace WebCore