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