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