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