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