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