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/)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
31 #include "RenderListBox.h"
33 #include "AXObjectCache.h"
34 #include "CSSFontSelector.h"
36 #include "DocumentEventQueue.h"
37 #include "EventHandler.h"
38 #include "FocusController.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"
50 #include "PaintInfo.h"
51 #include "RenderLayer.h"
52 #include "RenderScrollbar.h"
53 #include "RenderText.h"
54 #include "RenderTheme.h"
55 #include "RenderView.h"
56 #include "ScrollAnimator.h"
57 #include "Scrollbar.h"
58 #include "ScrollbarTheme.h"
60 #include "SpatialNavigation.h"
61 #include "StyleResolver.h"
62 #include "StyleTreeResolver.h"
63 #include "WheelEventTestTrigger.h"
65 #include <wtf/StackStats.h>
69 using namespace HTMLNames;
71 const int rowSpacing = 1;
73 const int optionsSpacingHorizontal = 2;
75 // The minSize constant was originally defined to render scrollbars correctly.
76 // This might vary for different platforms.
77 const int minSize = 4;
79 // Default size when the multiple attribute is present but size attribute is absent.
80 const int defaultSize = 4;
82 // FIXME: This hardcoded baselineAdjustment is what we used to do for the old
83 // widget, but I'm not sure this is right for the new control.
84 const int baselineAdjustment = 7;
86 RenderListBox::RenderListBox(HTMLSelectElement& element, Ref<RenderStyle>&& style)
87 : RenderBlockFlow(element, WTFMove(style))
88 , m_optionsChanged(true)
89 , m_scrollToRevealSelectionAfterLayout(false)
90 , m_inAutoscroll(false)
94 view().frameView().addScrollableArea(this);
97 RenderListBox::~RenderListBox()
99 setHasVerticalScrollbar(false);
100 view().frameView().removeScrollableArea(this);
103 HTMLSelectElement& RenderListBox::selectElement() const
105 return downcast<HTMLSelectElement>(nodeForNonAnonymous());
108 void RenderListBox::updateFromElement()
110 if (m_optionsChanged) {
111 const Vector<HTMLElement*>& listItems = selectElement().listItems();
112 int size = numItems();
115 for (int i = 0; i < size; ++i) {
116 HTMLElement* element = listItems[i];
118 FontCascade itemFont = style().fontCascade();
119 if (is<HTMLOptionElement>(*element))
120 text = downcast<HTMLOptionElement>(*element).textIndentedToRespectGroupLabel();
121 else if (is<HTMLOptGroupElement>(*element)) {
122 text = downcast<HTMLOptGroupElement>(*element).groupLabelText();
123 auto description = itemFont.fontDescription();
124 description.setWeight(description.bolderWeight());
125 itemFont = FontCascade(description, itemFont.letterSpacing(), itemFont.wordSpacing());
126 itemFont.update(&document().fontSelector());
129 if (!text.isEmpty()) {
130 applyTextTransform(style(), text, ' ');
131 // FIXME: Why is this always LTR? Can't text direction affect the width?
132 TextRun textRun = constructTextRun(text, style(), AllowTrailingExpansion);
133 float textWidth = itemFont.width(textRun);
134 width = std::max(width, textWidth);
137 m_optionsWidth = static_cast<int>(ceilf(width));
138 m_optionsChanged = false;
140 setHasVerticalScrollbar(true);
142 setNeedsLayoutAndPrefWidthsRecalc();
146 void RenderListBox::selectionChanged()
149 if (!m_inAutoscroll) {
150 if (m_optionsChanged || needsLayout())
151 m_scrollToRevealSelectionAfterLayout = true;
153 scrollToRevealSelection();
156 if (AXObjectCache* cache = document().existingAXObjectCache())
157 cache->selectedChildrenChanged(this);
160 void RenderListBox::layout()
162 StackStats::LayoutCheckPoint layoutCheckPoint;
163 RenderBlockFlow::layout();
166 bool enabled = numVisibleItems() < numItems();
167 m_vBar->setEnabled(enabled);
168 m_vBar->setSteps(1, std::max(1, numVisibleItems() - 1), itemHeight());
169 m_vBar->setProportion(numVisibleItems(), numItems());
171 scrollToOffsetWithoutAnimation(VerticalScrollbar, 0);
176 if (m_scrollToRevealSelectionAfterLayout) {
177 LayoutStateDisabler layoutStateDisabler(view());
178 scrollToRevealSelection();
182 void RenderListBox::scrollToRevealSelection()
184 m_scrollToRevealSelectionAfterLayout = false;
186 int firstIndex = selectElement().activeSelectionStartListIndex();
187 if (firstIndex >= 0 && !listIndexIsVisible(selectElement().activeSelectionEndListIndex()))
188 scrollToRevealElementAtListIndex(firstIndex);
191 void RenderListBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
193 maxLogicalWidth = m_optionsWidth + 2 * optionsSpacingHorizontal;
195 maxLogicalWidth += m_vBar->width();
196 if (!style().width().isPercentOrCalculated())
197 minLogicalWidth = maxLogicalWidth;
200 void RenderListBox::computePreferredLogicalWidths()
202 // Nested style recal do not fire post recal callbacks. see webkit.org/b/153767
203 ASSERT(!m_optionsChanged || Style::postResolutionCallbacksAreSuspended());
205 m_minPreferredLogicalWidth = 0;
206 m_maxPreferredLogicalWidth = 0;
208 if (style().width().isFixed() && style().width().value() > 0)
209 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style().width().value());
211 computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
213 if (style().minWidth().isFixed() && style().minWidth().value() > 0) {
214 m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
215 m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
218 if (style().maxWidth().isFixed()) {
219 m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
220 m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
223 LayoutUnit toAdd = horizontalBorderAndPaddingExtent();
224 m_minPreferredLogicalWidth += toAdd;
225 m_maxPreferredLogicalWidth += toAdd;
227 setPreferredLogicalWidthsDirty(false);
230 int RenderListBox::size() const
232 int specifiedSize = selectElement().size();
233 if (specifiedSize > 1)
234 return std::max(minSize, specifiedSize);
239 int RenderListBox::numVisibleItems() const
241 // Only count fully visible rows. But don't return 0 even if only part of a row shows.
242 return std::max<int>(1, (contentHeight() + rowSpacing) / itemHeight());
245 int RenderListBox::numItems() const
247 return selectElement().listItems().size();
250 LayoutUnit RenderListBox::listHeight() const
252 return itemHeight() * numItems() - rowSpacing;
255 void RenderListBox::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
257 LayoutUnit height = itemHeight() * size() - rowSpacing + verticalBorderAndPaddingExtent();
258 RenderBox::computeLogicalHeight(height, logicalTop, computedValues);
261 int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const
263 return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode) - baselineAdjustment;
266 LayoutRect RenderListBox::itemBoundingBoxRect(const LayoutPoint& additionalOffset, int index)
268 return LayoutRect(additionalOffset.x() + borderLeft() + paddingLeft(),
269 additionalOffset.y() + borderTop() + paddingTop() + itemHeight() * (index - m_indexOffset),
270 contentWidth(), itemHeight());
273 void RenderListBox::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
275 if (style().visibility() != VISIBLE)
278 int listItemsSize = numItems();
280 if (paintInfo.phase == PaintPhaseForeground) {
281 int index = m_indexOffset;
282 while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
283 paintItemForeground(paintInfo, paintOffset, index);
288 // Paint the children.
289 RenderBlockFlow::paintObject(paintInfo, paintOffset);
291 switch (paintInfo.phase) {
292 // Depending on whether we have overlay scrollbars they
293 // get rendered in the foreground or background phases
294 case PaintPhaseForeground:
295 if (m_vBar->isOverlayScrollbar())
296 paintScrollbar(paintInfo, paintOffset);
298 case PaintPhaseBlockBackground:
299 if (!m_vBar->isOverlayScrollbar())
300 paintScrollbar(paintInfo, paintOffset);
302 case PaintPhaseChildBlockBackground:
303 case PaintPhaseChildBlockBackgrounds: {
304 int index = m_indexOffset;
305 while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
306 paintItemBackground(paintInfo, paintOffset, index);
316 void RenderListBox::addFocusRingRects(Vector<LayoutRect>& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject* paintContainer)
318 if (!selectElement().allowsNonContiguousSelection())
319 return RenderBlockFlow::addFocusRingRects(rects, additionalOffset, paintContainer);
321 // Focus the last selected item.
322 int selectedItem = selectElement().activeSelectionEndListIndex();
323 if (selectedItem >= 0) {
324 rects.append(snappedIntRect(itemBoundingBoxRect(additionalOffset, selectedItem)));
328 // No selected items, find the first non-disabled item.
329 int size = numItems();
330 const Vector<HTMLElement*>& listItems = selectElement().listItems();
331 for (int i = 0; i < size; ++i) {
332 HTMLElement* element = listItems[i];
333 if (is<HTMLOptionElement>(*element) && !element->isDisabledFormControl()) {
334 selectElement().setActiveSelectionEndIndex(i);
335 rects.append(itemBoundingBoxRect(additionalOffset, i));
341 void RenderListBox::paintScrollbar(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
344 IntRect scrollRect = snappedIntRect(paintOffset.x() + width() - borderRight() - m_vBar->width(),
345 paintOffset.y() + borderTop(),
347 height() - (borderTop() + borderBottom()));
348 m_vBar->setFrameRect(scrollRect);
349 m_vBar->paint(paintInfo.context(), snappedIntRect(paintInfo.rect));
353 static LayoutSize itemOffsetForAlignment(TextRun textRun, RenderStyle* itemStyle, FontCascade itemFont, LayoutRect itemBoudingBox)
355 ETextAlign actualAlignment = itemStyle->textAlign();
356 // FIXME: Firefox doesn't respect JUSTIFY. Should we?
357 // FIXME: Handle TAEND here
358 if (actualAlignment == TASTART || actualAlignment == JUSTIFY)
359 actualAlignment = itemStyle->isLeftToRightDirection() ? LEFT : RIGHT;
361 LayoutSize offset = LayoutSize(0, itemFont.fontMetrics().ascent());
362 if (actualAlignment == RIGHT || actualAlignment == WEBKIT_RIGHT) {
363 float textWidth = itemFont.width(textRun);
364 offset.setWidth(itemBoudingBox.width() - textWidth - optionsSpacingHorizontal);
365 } else if (actualAlignment == CENTER || actualAlignment == WEBKIT_CENTER) {
366 float textWidth = itemFont.width(textRun);
367 offset.setWidth((itemBoudingBox.width() - textWidth) / 2);
369 offset.setWidth(optionsSpacingHorizontal);
373 void RenderListBox::paintItemForeground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
375 const Vector<HTMLElement*>& listItems = selectElement().listItems();
376 HTMLElement* listItemElement = listItems[listIndex];
378 RenderStyle& itemStyle = *listItemElement->computedStyle();
380 if (itemStyle.visibility() == HIDDEN)
384 bool isOptionElement = is<HTMLOptionElement>(*listItemElement);
386 itemText = downcast<HTMLOptionElement>(*listItemElement).textIndentedToRespectGroupLabel();
387 else if (is<HTMLOptGroupElement>(*listItemElement))
388 itemText = downcast<HTMLOptGroupElement>(*listItemElement).groupLabelText();
389 applyTextTransform(style(), itemText, ' ');
391 Color textColor = itemStyle.visitedDependentColor(CSSPropertyColor);
392 if (isOptionElement && downcast<HTMLOptionElement>(*listItemElement).selected()) {
393 if (frame().selection().isFocusedAndActive() && document().focusedElement() == &selectElement())
394 textColor = theme().activeListBoxSelectionForegroundColor();
395 // Honor the foreground color for disabled items
396 else if (!listItemElement->isDisabledFormControl() && !selectElement().isDisabledFormControl())
397 textColor = theme().inactiveListBoxSelectionForegroundColor();
400 paintInfo.context().setFillColor(textColor);
402 TextRun textRun(itemText, 0, 0, AllowTrailingExpansion, itemStyle.direction(), isOverride(itemStyle.unicodeBidi()), true);
403 FontCascade itemFont = style().fontCascade();
404 LayoutRect r = itemBoundingBoxRect(paintOffset, listIndex);
405 r.move(itemOffsetForAlignment(textRun, &itemStyle, itemFont, r));
407 if (is<HTMLOptGroupElement>(*listItemElement)) {
408 auto description = itemFont.fontDescription();
409 description.setWeight(description.bolderWeight());
410 itemFont = FontCascade(description, itemFont.letterSpacing(), itemFont.wordSpacing());
411 itemFont.update(&document().fontSelector());
414 // Draw the item text
415 paintInfo.context().drawBidiText(itemFont, textRun, roundedIntPoint(r.location()));
418 void RenderListBox::paintItemBackground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
420 const Vector<HTMLElement*>& listItems = selectElement().listItems();
421 HTMLElement* listItemElement = listItems[listIndex];
422 RenderStyle& itemStyle = *listItemElement->computedStyle();
425 if (is<HTMLOptionElement>(*listItemElement) && downcast<HTMLOptionElement>(*listItemElement).selected()) {
426 if (frame().selection().isFocusedAndActive() && document().focusedElement() == &selectElement())
427 backColor = theme().activeListBoxSelectionBackgroundColor();
429 backColor = theme().inactiveListBoxSelectionBackgroundColor();
431 backColor = itemStyle.visitedDependentColor(CSSPropertyBackgroundColor);
433 // Draw the background for this list box item
434 if (itemStyle.visibility() != HIDDEN) {
435 LayoutRect itemRect = itemBoundingBoxRect(paintOffset, listIndex);
436 itemRect.intersect(controlClipRect(paintOffset));
437 paintInfo.context().fillRect(snappedIntRect(itemRect), backColor);
441 bool RenderListBox::isPointInOverflowControl(HitTestResult& result, const LayoutPoint& locationInContainer, const LayoutPoint& accumulatedOffset)
443 if (!m_vBar || !m_vBar->shouldParticipateInHitTesting())
446 LayoutRect vertRect(accumulatedOffset.x() + width() - borderRight() - m_vBar->width(),
447 accumulatedOffset.y() + borderTop(),
449 height() - borderTop() - borderBottom());
451 if (vertRect.contains(locationInContainer)) {
452 result.setScrollbar(m_vBar.get());
458 int RenderListBox::listIndexAtOffset(const LayoutSize& offset)
463 if (offset.height() < borderTop() + paddingTop() || offset.height() > height() - paddingBottom() - borderBottom())
466 int scrollbarWidth = m_vBar ? m_vBar->width() : 0;
467 if (offset.width() < borderLeft() + paddingLeft() || offset.width() > width() - borderRight() - paddingRight() - scrollbarWidth)
470 int newOffset = (offset.height() - borderTop() - paddingTop()) / itemHeight() + m_indexOffset;
471 return newOffset < numItems() ? newOffset : -1;
474 void RenderListBox::panScroll(const IntPoint& panStartMousePosition)
476 const int maxSpeed = 20;
477 const int iconRadius = 7;
478 const int speedReducer = 4;
480 // FIXME: This doesn't work correctly with transforms.
481 FloatPoint absOffset = localToAbsolute();
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;
489 previousMousePosition = lastKnownMousePosition;
491 int yDelta = lastKnownMousePosition.y() - panStartMousePosition.y();
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);
496 if (abs(yDelta) < iconRadius) // at the center we let the space for the icon
500 //offsetY = view()->viewHeight();
501 absOffset.move(0, listHeight());
505 // Let's attenuate the speed
506 yDelta /= speedReducer;
508 IntPoint scrollPoint(0, 0);
509 scrollPoint.setY(absOffset.y() + yDelta);
510 int newOffset = scrollToward(scrollPoint);
514 m_inAutoscroll = true;
515 selectElement().updateListBoxSelection(!selectElement().multiple());
516 m_inAutoscroll = false;
519 int RenderListBox::scrollToward(const IntPoint& destination)
521 // FIXME: This doesn't work correctly with transforms.
522 FloatPoint absPos = localToAbsolute();
523 IntSize positionOffset = roundedIntSize(destination - absPos);
525 int rows = numVisibleItems();
526 int offset = m_indexOffset;
528 if (positionOffset.height() < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1))
531 if (positionOffset.height() > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows))
532 return offset + rows - 1;
534 return listIndexAtOffset(positionOffset);
537 void RenderListBox::autoscroll(const IntPoint&)
539 IntPoint pos = frame().view()->windowToContents(frame().eventHandler().lastKnownMousePosition());
541 int endIndex = scrollToward(pos);
542 if (selectElement().isDisabledFormControl())
546 m_inAutoscroll = true;
548 if (!selectElement().multiple())
549 selectElement().setActiveSelectionAnchorIndex(endIndex);
551 selectElement().setActiveSelectionEndIndex(endIndex);
552 selectElement().updateListBoxSelection(!selectElement().multiple());
553 m_inAutoscroll = false;
557 void RenderListBox::stopAutoscroll()
559 if (selectElement().isDisabledFormControl())
562 selectElement().listBoxOnChange();
565 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
567 if (index < 0 || index >= numItems() || listIndexIsVisible(index))
571 if (index < m_indexOffset)
574 newOffset = index - numVisibleItems() + 1;
576 scrollToOffsetWithoutAnimation(VerticalScrollbar, newOffset);
581 bool RenderListBox::listIndexIsVisible(int index)
583 return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
586 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Element**, RenderBox*, const IntPoint&)
588 return ScrollableArea::scroll(direction, granularity, multiplier);
591 bool RenderListBox::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Element**)
593 return ScrollableArea::scroll(logicalToPhysical(direction, style().isHorizontalWritingMode(), style().isFlippedBlocksWritingMode()), granularity, multiplier);
596 void RenderListBox::valueChanged(unsigned listIndex)
598 selectElement().setSelectedIndex(selectElement().listToOptionIndex(listIndex));
599 selectElement().dispatchFormControlChangeEvent();
602 int RenderListBox::scrollSize(ScrollbarOrientation orientation) const
604 return ((orientation == VerticalScrollbar) && m_vBar) ? (m_vBar->totalSize() - m_vBar->visibleSize()) : 0;
607 int RenderListBox::scrollOffset(ScrollbarOrientation) const
609 return m_indexOffset;
612 ScrollPosition RenderListBox::minimumScrollPosition() const
617 ScrollPosition RenderListBox::maximumScrollPosition() const
619 return { 0, numItems() - numVisibleItems() };
622 void RenderListBox::setScrollOffset(const ScrollOffset& offset)
624 scrollTo(offset.y());
627 void RenderListBox::scrollTo(int newOffset)
629 if (newOffset == m_indexOffset)
632 m_indexOffset = newOffset;
634 document().eventQueue().enqueueOrDispatchScrollEvent(selectElement());
637 LayoutUnit RenderListBox::itemHeight() const
639 return style().fontMetrics().height() + rowSpacing;
642 int RenderListBox::verticalScrollbarWidth() const
644 return m_vBar ? m_vBar->occupiedWidth() : 0;
647 // FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's
648 // how the control currently paints.
649 int RenderListBox::scrollWidth() const
651 // There is no horizontal scrolling allowed.
652 return roundToInt(clientWidth());
655 int RenderListBox::scrollHeight() const
657 return roundToInt(std::max(clientHeight(), listHeight()));
660 int RenderListBox::scrollLeft() const
665 void RenderListBox::setScrollLeft(int)
669 int RenderListBox::scrollTop() const
671 return m_indexOffset * itemHeight();
674 static void setupWheelEventTestTrigger(RenderListBox& renderer, Frame* frame)
679 Page* page = frame->page();
680 if (!page || !page->expectsWheelEventTriggers())
683 renderer.scrollAnimator().setWheelEventTestTrigger(page->testTrigger());
686 void RenderListBox::setScrollTop(int newTop)
688 // Determine an index and scroll to it.
689 int index = newTop / itemHeight();
690 if (index < 0 || index >= numItems() || index == m_indexOffset)
692 setupWheelEventTestTrigger(*this, document().frame());
693 scrollToOffsetWithoutAnimation(VerticalScrollbar, index);
696 bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
698 if (!RenderBlockFlow::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction))
700 const Vector<HTMLElement*>& listItems = selectElement().listItems();
701 int size = numItems();
702 LayoutPoint adjustedLocation = accumulatedOffset + location();
704 for (int i = 0; i < size; ++i) {
705 if (itemBoundingBoxRect(adjustedLocation, i).contains(locationInContainer.point())) {
706 if (Element* node = listItems[i]) {
707 result.setInnerNode(node);
708 if (!result.innerNonSharedNode())
709 result.setInnerNonSharedNode(node);
710 result.setLocalPoint(locationInContainer.point() - toLayoutSize(adjustedLocation));
719 LayoutRect RenderListBox::controlClipRect(const LayoutPoint& additionalOffset) const
721 LayoutRect clipRect = contentBoxRect();
722 clipRect.moveBy(additionalOffset);
726 bool RenderListBox::isActive() const
728 Page* page = frame().page();
729 return page && page->focusController().isActive();
732 void RenderListBox::invalidateScrollbarRect(Scrollbar& scrollbar, const IntRect& rect)
734 IntRect scrollRect = rect;
735 scrollRect.move(width() - borderRight() - scrollbar.width(), borderTop());
736 repaintRectangle(scrollRect);
739 IntRect RenderListBox::convertFromScrollbarToContainingView(const Scrollbar& scrollbar, const IntRect& scrollbarRect) const
741 IntRect rect = scrollbarRect;
742 int scrollbarLeft = width() - borderRight() - scrollbar.width();
743 int scrollbarTop = borderTop();
744 rect.move(scrollbarLeft, scrollbarTop);
745 return view().frameView().convertFromRendererToContainingView(this, rect);
748 IntRect RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar& scrollbar, const IntRect& parentRect) const
750 IntRect rect = view().frameView().convertFromContainingViewToRenderer(this, parentRect);
751 int scrollbarLeft = width() - borderRight() - scrollbar.width();
752 int scrollbarTop = borderTop();
753 rect.move(-scrollbarLeft, -scrollbarTop);
757 IntPoint RenderListBox::convertFromScrollbarToContainingView(const Scrollbar& scrollbar, const IntPoint& scrollbarPoint) const
759 IntPoint point = scrollbarPoint;
760 int scrollbarLeft = width() - borderRight() - scrollbar.width();
761 int scrollbarTop = borderTop();
762 point.move(scrollbarLeft, scrollbarTop);
763 return view().frameView().convertFromRendererToContainingView(this, point);
766 IntPoint RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar& scrollbar, const IntPoint& parentPoint) const
768 IntPoint point = view().frameView().convertFromContainingViewToRenderer(this, parentPoint);
769 int scrollbarLeft = width() - borderRight() - scrollbar.width();
770 int scrollbarTop = borderTop();
771 point.move(-scrollbarLeft, -scrollbarTop);
775 IntSize RenderListBox::contentsSize() const
777 return IntSize(scrollWidth(), scrollHeight());
780 IntPoint RenderListBox::lastKnownMousePosition() const
782 return view().frameView().lastKnownMousePosition();
785 bool RenderListBox::isHandlingWheelEvent() const
787 return view().frameView().isHandlingWheelEvent();
790 bool RenderListBox::shouldSuspendScrollAnimations() const
792 return view().frameView().shouldSuspendScrollAnimations();
795 bool RenderListBox::forceUpdateScrollbarsOnMainThreadForPerformanceTesting() const
797 Page* page = frame().page();
798 return page && page->settings().forceUpdateScrollbarsOnMainThreadForPerformanceTesting();
801 ScrollableArea* RenderListBox::enclosingScrollableArea() const
803 // FIXME: Return a RenderLayer that's scrollable.
807 bool RenderListBox::isScrollableOrRubberbandable()
812 bool RenderListBox::hasScrollableOrRubberbandableAncestor()
814 return enclosingLayer() && enclosingLayer()->hasScrollableOrRubberbandableAncestor();
817 IntRect RenderListBox::scrollableAreaBoundingBox(bool*) const
819 return absoluteBoundingBoxRect();
822 bool RenderListBox::usesMockScrollAnimator() const
824 return Settings::usesMockScrollAnimator();
827 void RenderListBox::logMockScrollAnimatorMessage(const String& message) const
829 document().addConsoleMessage(MessageSource::Other, MessageLevel::Debug, "RenderListBox: " + message);
832 PassRefPtr<Scrollbar> RenderListBox::createScrollbar()
834 RefPtr<Scrollbar> widget;
835 bool hasCustomScrollbarStyle = style().hasPseudoStyle(SCROLLBAR);
836 if (hasCustomScrollbarStyle)
837 widget = RenderScrollbar::createCustomScrollbar(*this, VerticalScrollbar, &selectElement());
839 widget = Scrollbar::createNativeScrollbar(*this, VerticalScrollbar, theme().scrollbarControlSizeForPart(ListboxPart));
840 didAddScrollbar(widget.get(), VerticalScrollbar);
841 if (Page* page = frame().page()) {
842 if (page->expectsWheelEventTriggers())
843 scrollAnimator().setWheelEventTestTrigger(page->testTrigger());
846 view().frameView().addChild(widget.get());
847 return widget.release();
850 void RenderListBox::destroyScrollbar()
855 if (!m_vBar->isCustomScrollbar())
856 ScrollableArea::willRemoveScrollbar(m_vBar.get(), VerticalScrollbar);
857 m_vBar->removeFromParent();
861 void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar)
863 if (hasScrollbar == (m_vBar != nullptr))
867 m_vBar = createScrollbar();
872 m_vBar->styleChanged();
874 // Force an update since we know the scrollbars have changed things.
875 #if ENABLE(DASHBOARD_SUPPORT)
876 if (document().hasAnnotatedRegions())
877 document().setAnnotatedRegionsDirty(true);
881 bool RenderListBox::scrolledToTop() const
883 Scrollbar* vbar = verticalScrollbar();
887 return vbar->value() <= 0;
890 bool RenderListBox::scrolledToBottom() const
892 Scrollbar* vbar = verticalScrollbar();
896 return vbar->value() >= vbar->maximum();
899 bool RenderListBox::scrolledToLeft() const
901 // We do not scroll horizontally in a select element, so always report
902 // that we are at the full extent of the scroll.
906 bool RenderListBox::scrolledToRight() const
908 // We do not scroll horizontally in a select element, so always report
909 // that we are at the full extent of the scroll.
913 } // namespace WebCore