• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
RenderListBox(Element * element)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 
~RenderListBox()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.
isChildAllowed(RenderObject * object,RenderStyle *) const109 bool RenderListBox::isChildAllowed(RenderObject* object, RenderStyle*) const
110 {
111     return object->isAnonymous() && !object->isRenderFullScreen();
112 }
113 
selectElement() const114 inline HTMLSelectElement* RenderListBox::selectElement() const
115 {
116     return toHTMLSelectElement(node());
117 }
118 
updateFromElement()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 
selectionChanged()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 
layout()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 
invalidateTreeAfterLayout(const RenderLayerModelObject & invalidationContainer)210 void RenderListBox::invalidateTreeAfterLayout(const RenderLayerModelObject& invalidationContainer)
211 {
212     repaintScrollbarIfNeeded();
213     RenderBox::invalidateTreeAfterLayout(invalidationContainer);
214 }
215 
scrollToRevealSelection()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 
computeIntrinsicLogicalWidths(LayoutUnit & minLogicalWidth,LayoutUnit & maxLogicalWidth) const228 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 
computePreferredLogicalWidths()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 
size() const265 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 
numVisibleItems() const274 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 
numItems() const280 int RenderListBox::numItems() const
281 {
282     return m_listItemCount;
283 }
284 
listHeight() const285 LayoutUnit RenderListBox::listHeight() const
286 {
287     return itemHeight() * numItems() - rowSpacing;
288 }
289 
computeLogicalHeight(LayoutUnit,LayoutUnit logicalTop,LogicalExtentComputedValues & computedValues) const290 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 
baselinePosition(FontBaseline baselineType,bool firstLine,LineDirectionMode lineDirection,LinePositionMode linePositionMode) const301 int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const
302 {
303     return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode) - baselineAdjustment;
304 }
305 
itemBoundingBoxRectInternal(const LayoutPoint & additionalOffset,int index) const306 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 
paintObject(PaintInfo & paintInfo,const LayoutPoint & paintOffset)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 
addFocusRingRects(Vector<IntRect> & rects,const LayoutPoint & additionalOffset,const RenderLayerModelObject * paintContainer)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 
scrollbarLeft() const384 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 
paintScrollbar(PaintInfo & paintInfo,const LayoutPoint & paintOffset)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 
itemOffsetForAlignment(TextRun textRun,RenderStyle * itemStyle,Font itemFont,LayoutRect itemBoudingBox)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 
paintItemForeground(PaintInfo & paintInfo,const LayoutPoint & paintOffset,int listIndex)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 
paintItemBackground(PaintInfo & paintInfo,const LayoutPoint & paintOffset,int listIndex)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 
isPointInOverflowControl(HitTestResult & result,const LayoutPoint & locationInContainer,const LayoutPoint & accumulatedOffset)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 
listIndexAtOffset(const LayoutSize & offset) const519 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 
panScroll(const IntPoint & panStartMousePosition)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 
scrollToward(const IntPoint & destination)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 
autoscroll(const IntPoint &)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 
stopAutoscroll()622 void RenderListBox::stopAutoscroll()
623 {
624     if (selectElement()->isDisabledFormControl())
625         return;
626 
627     selectElement()->listBoxOnChange();
628 }
629 
scrollToRevealElementAtListIndexInternal(int index)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 
listIndexIsVisible(int index) const646 bool RenderListBox::listIndexIsVisible(int index) const
647 {
648     return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
649 }
650 
scroll(ScrollDirection direction,ScrollGranularity granularity,float multiplier)651 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
652 {
653     return ScrollableArea::scroll(direction, granularity, multiplier);
654 }
655 
scrollSize(ScrollbarOrientation orientation) const656 int RenderListBox::scrollSize(ScrollbarOrientation orientation) const
657 {
658     return orientation == VerticalScrollbar ? (numItems() - numVisibleItems()) : 0;
659 }
660 
scrollPosition() const661 IntPoint RenderListBox::scrollPosition() const
662 {
663     return IntPoint(0, m_indexOffset);
664 }
665 
setScrollOffset(const IntPoint & offset)666 void RenderListBox::setScrollOffset(const IntPoint& offset)
667 {
668     scrollTo(offset.y());
669 }
670 
scrollTo(int newOffset)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 
itemHeight() const686 LayoutUnit RenderListBox::itemHeight() const
687 {
688     return style()->fontMetrics().height() + rowSpacing;
689 }
690 
verticalScrollbarWidth() const691 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.
scrollWidth() const698 LayoutUnit RenderListBox::scrollWidth() const
699 {
700     // There is no horizontal scrolling allowed.
701     return clientWidth();
702 }
703 
scrollHeight() const704 LayoutUnit RenderListBox::scrollHeight() const
705 {
706     return max(clientHeight(), listHeight());
707 }
708 
scrollLeft() const709 LayoutUnit RenderListBox::scrollLeft() const
710 {
711     return 0;
712 }
713 
setScrollLeft(LayoutUnit)714 void RenderListBox::setScrollLeft(LayoutUnit)
715 {
716 }
717 
scrollTop() const718 LayoutUnit RenderListBox::scrollTop() const
719 {
720     return m_indexOffset * itemHeight();
721 }
722 
setScrollTop(LayoutUnit newTop)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 
nodeAtPoint(const HitTestRequest & request,HitTestResult & result,const HitTestLocation & locationInContainer,const LayoutPoint & accumulatedOffset,HitTestAction hitTestAction)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 
controlClipRect(const LayoutPoint & additionalOffset) const756 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 
isActive() const766 bool RenderListBox::isActive() const
767 {
768     Page* page = frame()->page();
769     return page && page->focusController().isActive();
770 }
771 
invalidateScrollbarRect(Scrollbar * scrollbar,const IntRect & rect)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 
repaintScrollbarIfNeeded()788 void RenderListBox::repaintScrollbarIfNeeded()
789 {
790     if (!hasVerticalBarDamage())
791         return;
792     invalidatePaintRectangle(verticalBarDamage());
793 
794     resetScrollbarDamage();
795 }
796 
convertFromScrollbarToContainingView(const Scrollbar * scrollbar,const IntRect & scrollbarRect) const797 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 
convertFromContainingViewToScrollbar(const Scrollbar * scrollbar,const IntRect & parentRect) const811 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 
convertFromScrollbarToContainingView(const Scrollbar * scrollbar,const IntPoint & scrollbarPoint) const824 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 
convertFromContainingViewToScrollbar(const Scrollbar * scrollbar,const IntPoint & parentPoint) const838 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 
contentsSize() const851 IntSize RenderListBox::contentsSize() const
852 {
853     return IntSize(scrollWidth(), scrollHeight());
854 }
855 
visibleHeight() const856 int RenderListBox::visibleHeight() const
857 {
858     return height();
859 }
860 
visibleWidth() const861 int RenderListBox::visibleWidth() const
862 {
863     return width();
864 }
865 
lastKnownMousePosition() const866 IntPoint RenderListBox::lastKnownMousePosition() const
867 {
868     RenderView* view = this->view();
869     if (!view)
870         return IntPoint();
871     return view->frameView()->lastKnownMousePosition();
872 }
873 
shouldSuspendScrollAnimations() const874 bool RenderListBox::shouldSuspendScrollAnimations() const
875 {
876     RenderView* view = this->view();
877     if (!view)
878         return true;
879     return view->frameView()->shouldSuspendScrollAnimations();
880 }
881 
scrollbarsCanBeActive() const882 bool RenderListBox::scrollbarsCanBeActive() const
883 {
884     RenderView* view = this->view();
885     if (!view)
886         return false;
887     return view->frameView()->scrollbarsCanBeActive();
888 }
889 
minimumScrollPosition() const890 IntPoint RenderListBox::minimumScrollPosition() const
891 {
892     return IntPoint();
893 }
894 
maximumScrollPosition() const895 IntPoint RenderListBox::maximumScrollPosition() const
896 {
897     return IntPoint(0, std::max(numItems() - numVisibleItems(), 0));
898 }
899 
userInputScrollable(ScrollbarOrientation orientation) const900 bool RenderListBox::userInputScrollable(ScrollbarOrientation orientation) const
901 {
902     return orientation == VerticalScrollbar;
903 }
904 
shouldPlaceVerticalScrollbarOnLeft() const905 bool RenderListBox::shouldPlaceVerticalScrollbarOnLeft() const
906 {
907     return false;
908 }
909 
lineStep(ScrollbarOrientation) const910 int RenderListBox::lineStep(ScrollbarOrientation) const
911 {
912     return 1;
913 }
914 
pageStep(ScrollbarOrientation orientation) const915 int RenderListBox::pageStep(ScrollbarOrientation orientation) const
916 {
917     return max(1, numVisibleItems() - 1);
918 }
919 
pixelStep(ScrollbarOrientation) const920 float RenderListBox::pixelStep(ScrollbarOrientation) const
921 {
922     return 1.0f / itemHeight();
923 }
924 
scrollableAreaBoundingBox() const925 IntRect RenderListBox::scrollableAreaBoundingBox() const
926 {
927     return absoluteBoundingBoxRect();
928 }
929 
createScrollbar()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 
destroyScrollbar()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 
setHasVerticalScrollbar(bool hasScrollbar)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 
renderListBoxIndexToListIndex(int index) const974 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 
listIndexToRenderListBoxIndex(int index) const997 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 
itemBoundingBoxRect(const LayoutPoint & point,int index) const1019 LayoutRect RenderListBox::itemBoundingBoxRect(const LayoutPoint& point, int index) const
1020 {
1021     return itemBoundingBoxRectInternal(point, listIndexToRenderListBoxIndex(index));
1022 }
1023 
scrollToRevealElementAtListIndex(int index)1024 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
1025 {
1026     return scrollToRevealElementAtListIndexInternal(listIndexToRenderListBoxIndex(index));
1027 }
1028 
1029 } // namespace WebCore
1030