2 * Copyright (C) 2006, 2007, 2008, 2011 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 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.
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 "core/rendering/RenderListBox.h"
34 #include "HTMLNames.h"
35 #include "core/accessibility/AXObjectCache.h"
36 #include "core/css/CSSFontSelector.h"
37 #include "core/css/resolver/StyleResolver.h"
38 #include "core/dom/Document.h"
39 #include "core/dom/NodeRenderStyle.h"
40 #include "core/editing/FrameSelection.h"
41 #include "core/frame/FrameView.h"
42 #include "core/frame/LocalFrame.h"
43 #include "core/html/HTMLOptGroupElement.h"
44 #include "core/html/HTMLOptionElement.h"
45 #include "core/html/HTMLSelectElement.h"
46 #include "core/page/EventHandler.h"
47 #include "core/page/FocusController.h"
48 #include "core/page/Page.h"
49 #include "core/page/SpatialNavigation.h"
50 #include "core/rendering/HitTestResult.h"
51 #include "core/rendering/LayoutRectRecorder.h"
52 #include "core/rendering/PaintInfo.h"
53 #include "core/rendering/RenderScrollbar.h"
54 #include "core/rendering/RenderText.h"
55 #include "core/rendering/RenderTheme.h"
56 #include "core/rendering/RenderView.h"
57 #include "platform/fonts/FontCache.h"
58 #include "platform/graphics/GraphicsContext.h"
59 #include "platform/scroll/Scrollbar.h"
60 #include "platform/text/BidiTextRun.h"
66 using namespace HTMLNames;
68 const int rowSpacing = 1;
70 const int optionsSpacingHorizontal = 2;
72 // The minSize constant was originally defined to render scrollbars correctly.
73 // This might vary for different platforms.
74 const int minSize = 4;
76 // Default size when the multiple attribute is present but size attribute is absent.
77 const int defaultSize = 4;
79 // FIXME: This hardcoded baselineAdjustment is what we used to do for the old
80 // widget, but I'm not sure this is right for the new control.
81 const int baselineAdjustment = 7;
83 RenderListBox::RenderListBox(Element* element)
84 : RenderBlockFlow(element)
85 , m_optionsChanged(true)
86 , m_scrollToRevealSelectionAfterLayout(true)
87 , m_inAutoscroll(false)
92 ASSERT(element->isHTMLElement());
93 ASSERT(isHTMLSelectElement(element));
95 if (FrameView* frameView = frame()->view())
96 frameView->addScrollableArea(this);
99 RenderListBox::~RenderListBox()
101 setHasVerticalScrollbar(false);
103 if (FrameView* frameView = frame()->view())
104 frameView->removeScrollableArea(this);
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
111 return object->isAnonymous() && !object->isRenderFullScreen();
114 inline HTMLSelectElement* RenderListBox::selectElement() const
116 return toHTMLSelectElement(node());
119 void RenderListBox::updateFromElement()
121 FontCachePurgePreventer fontCachePurgePreventer;
123 if (m_optionsChanged) {
124 const Vector<HTMLElement*>& listItems = selectElement()->listItems();
125 int size = numItems();
128 for (int i = 0; i < size; ++i) {
129 HTMLElement* element = listItems[i];
131 Font itemFont = style()->font();
132 if (isHTMLOptionElement(*element)) {
133 text = toHTMLOptionElement(*element).textIndentedToRespectGroupLabel();
134 } else if (isHTMLOptGroupElement(*element)) {
135 text = toHTMLOptGroupElement(*element).groupLabelText();
136 FontDescription d = itemFont.fontDescription();
137 d.setWeight(d.bolderWeight());
139 itemFont.update(document().styleEngine()->fontSelector());
142 if (!text.isEmpty()) {
143 applyTextTransform(style(), text, ' ');
145 bool hasStrongDirectionality;
146 TextDirection direction = determineDirectionality(text, hasStrongDirectionality);
147 TextRun textRun = constructTextRun(this, itemFont, text, style(), TextRun::AllowTrailingExpansion);
148 if (hasStrongDirectionality)
149 textRun.setDirection(direction);
150 textRun.disableRoundingHacks();
151 float textWidth = itemFont.width(textRun);
152 width = max(width, textWidth);
155 m_optionsWidth = static_cast<int>(ceilf(width));
156 m_optionsChanged = false;
158 setHasVerticalScrollbar(true);
160 setNeedsLayoutAndPrefWidthsRecalc();
164 void RenderListBox::selectionChanged()
167 if (!m_inAutoscroll) {
168 if (m_optionsChanged || needsLayout())
169 m_scrollToRevealSelectionAfterLayout = true;
171 scrollToRevealSelection();
174 if (AXObjectCache* cache = document().existingAXObjectCache())
175 cache->selectedChildrenChanged(this);
178 void RenderListBox::layout()
180 LayoutRectRecorder recorder(*this);
181 RenderBlockFlow::layout();
184 bool enabled = numVisibleItems() < numItems();
185 m_vBar->setEnabled(enabled);
186 m_vBar->setProportion(numVisibleItems(), numItems());
188 scrollToOffsetWithoutAnimation(VerticalScrollbar, 0);
193 if (m_scrollToRevealSelectionAfterLayout) {
194 LayoutStateDisabler layoutStateDisabler(*this);
195 scrollToRevealSelection();
199 void RenderListBox::scrollToRevealSelection()
201 HTMLSelectElement* select = selectElement();
203 m_scrollToRevealSelectionAfterLayout = false;
205 int firstIndex = select->activeSelectionStartListIndex();
206 if (firstIndex >= 0 && !listIndexIsVisible(select->activeSelectionEndListIndex()))
207 scrollToRevealElementAtListIndex(firstIndex);
210 void RenderListBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
212 maxLogicalWidth = m_optionsWidth + 2 * optionsSpacingHorizontal;
214 maxLogicalWidth += verticalScrollbarWidth();
215 if (!style()->width().isPercent())
216 minLogicalWidth = maxLogicalWidth;
219 void RenderListBox::computePreferredLogicalWidths()
221 ASSERT(!m_optionsChanged);
223 m_minPreferredLogicalWidth = 0;
224 m_maxPreferredLogicalWidth = 0;
226 if (style()->width().isFixed() && style()->width().value() > 0)
227 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style()->width().value());
229 computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
231 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
232 m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value()));
233 m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value()));
236 if (style()->maxWidth().isFixed()) {
237 m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value()));
238 m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value()));
241 LayoutUnit toAdd = borderAndPaddingWidth();
242 m_minPreferredLogicalWidth += toAdd;
243 m_maxPreferredLogicalWidth += toAdd;
245 clearPreferredLogicalWidthsDirty();
248 int RenderListBox::size() const
250 int specifiedSize = selectElement()->size();
251 if (specifiedSize > 1)
252 return max(minSize, specifiedSize);
257 int RenderListBox::numVisibleItems() const
259 // Only count fully visible rows. But don't return 0 even if only part of a row shows.
260 return max<int>(1, (contentHeight() + rowSpacing) / itemHeight());
263 int RenderListBox::numItems() const
265 return selectElement()->listItems().size();
268 LayoutUnit RenderListBox::listHeight() const
270 return itemHeight() * numItems() - rowSpacing;
273 void RenderListBox::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
275 LayoutUnit height = itemHeight() * size() - rowSpacing + borderAndPaddingHeight();
276 RenderBox::computeLogicalHeight(height, logicalTop, computedValues);
279 int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const
281 return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode) - baselineAdjustment;
284 LayoutRect RenderListBox::itemBoundingBoxRect(const LayoutPoint& additionalOffset, int index)
286 // For RTL, items start after the left-side vertical scrollbar.
287 int scrollbarOffset = style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft() ? verticalScrollbarWidth() : 0;
288 return LayoutRect(additionalOffset.x() + borderLeft() + paddingLeft() + scrollbarOffset,
289 additionalOffset.y() + borderTop() + paddingTop() + itemHeight() * (index - m_indexOffset),
290 contentWidth(), itemHeight());
293 void RenderListBox::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
295 if (style()->visibility() != VISIBLE)
298 int listItemsSize = numItems();
300 if (paintInfo.phase == PaintPhaseForeground) {
301 int index = m_indexOffset;
302 while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
303 paintItemForeground(paintInfo, paintOffset, index);
308 // Paint the children.
309 RenderBlockFlow::paintObject(paintInfo, paintOffset);
311 switch (paintInfo.phase) {
312 // Depending on whether we have overlay scrollbars they
313 // get rendered in the foreground or background phases
314 case PaintPhaseForeground:
315 if (m_vBar->isOverlayScrollbar())
316 paintScrollbar(paintInfo, paintOffset);
318 case PaintPhaseBlockBackground:
319 if (!m_vBar->isOverlayScrollbar())
320 paintScrollbar(paintInfo, paintOffset);
322 case PaintPhaseChildBlockBackground:
323 case PaintPhaseChildBlockBackgrounds: {
324 int index = m_indexOffset;
325 while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
326 paintItemBackground(paintInfo, paintOffset, index);
336 void RenderListBox::addFocusRingRects(Vector<IntRect>& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject* paintContainer)
338 if (!isSpatialNavigationEnabled(frame()))
339 return RenderBlockFlow::addFocusRingRects(rects, additionalOffset, paintContainer);
341 HTMLSelectElement* select = selectElement();
343 // Focus the last selected item.
344 int selectedItem = select->activeSelectionEndListIndex();
345 if (selectedItem >= 0) {
346 rects.append(pixelSnappedIntRect(itemBoundingBoxRect(additionalOffset, selectedItem)));
350 // No selected items, find the first non-disabled item.
351 int size = numItems();
352 const Vector<HTMLElement*>& listItems = select->listItems();
353 for (int i = 0; i < size; ++i) {
354 HTMLElement* element = listItems[i];
355 if (isHTMLOptionElement(*element) && !element->isDisabledFormControl()) {
356 rects.append(pixelSnappedIntRect(itemBoundingBoxRect(additionalOffset, i)));
362 int RenderListBox::scrollbarLeft() const
364 int scrollbarLeft = 0;
365 if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
366 scrollbarLeft = borderLeft();
368 scrollbarLeft = width() - borderRight() - verticalScrollbarWidth();
369 return scrollbarLeft;
372 void RenderListBox::paintScrollbar(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
375 IntRect scrollRect = pixelSnappedIntRect(paintOffset.x() + scrollbarLeft(),
376 paintOffset.y() + borderTop(),
377 verticalScrollbarWidth(),
378 height() - (borderTop() + borderBottom()));
379 m_vBar->setFrameRect(scrollRect);
380 m_vBar->paint(paintInfo.context, paintInfo.rect);
384 static LayoutSize itemOffsetForAlignment(TextRun textRun, RenderStyle* itemStyle, Font itemFont, LayoutRect itemBoudingBox)
386 ETextAlign actualAlignment = itemStyle->textAlign();
387 // FIXME: Firefox doesn't respect JUSTIFY. Should we?
388 // FIXME: Handle TAEND here
389 if (actualAlignment == TASTART || actualAlignment == JUSTIFY)
390 actualAlignment = itemStyle->isLeftToRightDirection() ? LEFT : RIGHT;
392 LayoutSize offset = LayoutSize(0, itemFont.fontMetrics().ascent());
393 if (actualAlignment == RIGHT || actualAlignment == WEBKIT_RIGHT) {
394 float textWidth = itemFont.width(textRun);
395 offset.setWidth(itemBoudingBox.width() - textWidth - optionsSpacingHorizontal);
396 } else if (actualAlignment == CENTER || actualAlignment == WEBKIT_CENTER) {
397 float textWidth = itemFont.width(textRun);
398 offset.setWidth((itemBoudingBox.width() - textWidth) / 2);
400 offset.setWidth(optionsSpacingHorizontal);
404 void RenderListBox::paintItemForeground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
406 FontCachePurgePreventer fontCachePurgePreventer;
408 HTMLSelectElement* select = selectElement();
410 const Vector<HTMLElement*>& listItems = select->listItems();
411 HTMLElement* element = listItems[listIndex];
413 RenderStyle* itemStyle = element->renderStyle();
417 if (itemStyle->visibility() == HIDDEN)
421 bool isOptionElement = isHTMLOptionElement(*element);
423 itemText = toHTMLOptionElement(*element).textIndentedToRespectGroupLabel();
424 else if (isHTMLOptGroupElement(*element))
425 itemText = toHTMLOptGroupElement(*element).groupLabelText();
426 applyTextTransform(style(), itemText, ' ');
428 Color textColor = element->renderStyle() ? resolveColor(element->renderStyle(), CSSPropertyColor) : resolveColor(CSSPropertyColor);
429 if (isOptionElement && ((toHTMLOptionElement(*element).selected() && select->suggestedIndex() < 0) || listIndex == select->suggestedIndex())) {
430 if (frame()->selection().isFocusedAndActive() && document().focusedElement() == node())
431 textColor = RenderTheme::theme().activeListBoxSelectionForegroundColor();
432 // Honor the foreground color for disabled items
433 else if (!element->isDisabledFormControl() && !select->isDisabledFormControl())
434 textColor = RenderTheme::theme().inactiveListBoxSelectionForegroundColor();
437 paintInfo.context->setFillColor(textColor);
439 TextRun textRun(itemText, 0, 0, TextRun::AllowTrailingExpansion, itemStyle->direction(), isOverride(itemStyle->unicodeBidi()), true, TextRun::NoRounding);
440 Font itemFont = style()->font();
441 LayoutRect r = itemBoundingBoxRect(paintOffset, listIndex);
442 r.move(itemOffsetForAlignment(textRun, itemStyle, itemFont, r));
444 if (isHTMLOptGroupElement(*element)) {
445 FontDescription d = itemFont.fontDescription();
446 d.setWeight(d.bolderWeight());
448 itemFont.update(document().styleEngine()->fontSelector());
451 // Draw the item text
452 TextRunPaintInfo textRunPaintInfo(textRun);
453 textRunPaintInfo.bounds = r;
454 paintInfo.context->drawBidiText(itemFont, textRunPaintInfo, roundedIntPoint(r.location()));
457 void RenderListBox::paintItemBackground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
459 const Vector<HTMLElement*>& listItems = selectElement()->listItems();
460 HTMLElement* element = listItems[listIndex];
463 if (isHTMLOptionElement(*element) && ((toHTMLOptionElement(*element).selected() && selectElement()->suggestedIndex() < 0) || listIndex == selectElement()->suggestedIndex())) {
464 if (frame()->selection().isFocusedAndActive() && document().focusedElement() == node())
465 backColor = RenderTheme::theme().activeListBoxSelectionBackgroundColor();
467 backColor = RenderTheme::theme().inactiveListBoxSelectionBackgroundColor();
469 backColor = element->renderStyle() ? resolveColor(element->renderStyle(), CSSPropertyBackgroundColor) : resolveColor(CSSPropertyBackgroundColor);
472 // Draw the background for this list box item
473 if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDEN) {
474 LayoutRect itemRect = itemBoundingBoxRect(paintOffset, listIndex);
475 itemRect.intersect(controlClipRect(paintOffset));
476 paintInfo.context->fillRect(pixelSnappedIntRect(itemRect), backColor);
480 bool RenderListBox::isPointInOverflowControl(HitTestResult& result, const LayoutPoint& locationInContainer, const LayoutPoint& accumulatedOffset)
482 if (!m_vBar || !m_vBar->shouldParticipateInHitTesting())
485 LayoutRect vertRect(accumulatedOffset.x() + scrollbarLeft(),
486 accumulatedOffset.y() + borderTop(),
487 verticalScrollbarWidth(),
488 height() - borderTop() - borderBottom());
490 if (vertRect.contains(locationInContainer)) {
491 result.setScrollbar(m_vBar.get());
497 int RenderListBox::listIndexAtOffset(const LayoutSize& offset)
502 if (offset.height() < borderTop() + paddingTop() || offset.height() > height() - paddingBottom() - borderBottom())
505 int scrollbarWidth = verticalScrollbarWidth();
506 int rightScrollbarOffset = style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft() ? scrollbarWidth : 0;
507 int leftScrollbarOffset = style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft() ? 0 : scrollbarWidth;
508 if (offset.width() < borderLeft() + paddingLeft() + rightScrollbarOffset
509 || offset.width() > width() - borderRight() - paddingRight() - leftScrollbarOffset)
512 int newOffset = (offset.height() - borderTop() - paddingTop()) / itemHeight() + m_indexOffset;
513 return newOffset < numItems() ? newOffset : -1;
516 void RenderListBox::panScroll(const IntPoint& panStartMousePosition)
518 const int maxSpeed = 20;
519 const int iconRadius = 7;
520 const int speedReducer = 4;
522 // FIXME: This doesn't work correctly with transforms.
523 FloatPoint absOffset = localToAbsolute();
525 IntPoint lastKnownMousePosition = frame()->eventHandler().lastKnownMousePosition();
526 // 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
527 static IntPoint previousMousePosition;
528 if (lastKnownMousePosition.y() < 0)
529 lastKnownMousePosition = previousMousePosition;
531 previousMousePosition = lastKnownMousePosition;
533 int yDelta = lastKnownMousePosition.y() - panStartMousePosition.y();
535 // If the point is too far from the center we limit the speed
536 yDelta = max<int>(min<int>(yDelta, maxSpeed), -maxSpeed);
538 if (abs(yDelta) < iconRadius) // at the center we let the space for the icon
542 absOffset.move(0, listHeight().toFloat());
546 // Let's attenuate the speed
547 yDelta /= speedReducer;
549 IntPoint scrollPoint(0, 0);
550 scrollPoint.setY(absOffset.y() + yDelta);
551 int newOffset = scrollToward(scrollPoint);
555 m_inAutoscroll = true;
556 HTMLSelectElement* select = selectElement();
557 select->updateListBoxSelection(!select->multiple());
558 m_inAutoscroll = false;
561 int RenderListBox::scrollToward(const IntPoint& destination)
563 // FIXME: This doesn't work correctly with transforms.
564 FloatPoint absPos = localToAbsolute();
565 IntSize positionOffset = roundedIntSize(destination - absPos);
567 int rows = numVisibleItems();
568 int offset = m_indexOffset;
570 if (positionOffset.height() < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1))
573 if (positionOffset.height() > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows))
574 return offset + rows - 1;
576 return listIndexAtOffset(positionOffset);
579 void RenderListBox::autoscroll(const IntPoint&)
581 IntPoint pos = frame()->view()->windowToContents(frame()->eventHandler().lastKnownMousePosition());
583 int endIndex = scrollToward(pos);
584 if (selectElement()->isDisabledFormControl())
588 HTMLSelectElement* select = selectElement();
589 m_inAutoscroll = true;
591 if (!select->multiple())
592 select->setActiveSelectionAnchorIndex(endIndex);
594 select->setActiveSelectionEndIndex(endIndex);
595 select->updateListBoxSelection(!select->multiple());
596 m_inAutoscroll = false;
600 void RenderListBox::stopAutoscroll()
602 if (selectElement()->isDisabledFormControl())
605 selectElement()->listBoxOnChange();
608 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
610 if (index < 0 || index >= numItems() || listIndexIsVisible(index))
614 if (index < m_indexOffset)
617 newOffset = index - numVisibleItems() + 1;
619 scrollToOffsetWithoutAnimation(VerticalScrollbar, newOffset);
624 bool RenderListBox::listIndexIsVisible(int index)
626 return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
629 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
631 return ScrollableArea::scroll(direction, granularity, multiplier);
634 int RenderListBox::scrollSize(ScrollbarOrientation orientation) const
636 return orientation == VerticalScrollbar ? (numItems() - numVisibleItems()) : 0;
639 IntPoint RenderListBox::scrollPosition() const
641 return IntPoint(0, m_indexOffset);
644 void RenderListBox::setScrollOffset(const IntPoint& offset)
646 scrollTo(offset.y());
649 void RenderListBox::scrollTo(int newOffset)
651 if (newOffset == m_indexOffset)
654 m_indexOffset = newOffset;
656 if (RuntimeEnabledFeatures::repaintAfterLayoutEnabled() && frameView()->isInPerformLayout())
657 setShouldDoFullRepaintAfterLayout(true);
661 node()->document().enqueueScrollEventForNode(node());
664 LayoutUnit RenderListBox::itemHeight() const
666 return style()->fontMetrics().height() + rowSpacing;
669 int RenderListBox::verticalScrollbarWidth() const
671 return m_vBar && !m_vBar->isOverlayScrollbar() ? m_vBar->width() : 0;
674 // FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's
675 // how the control currently paints.
676 int RenderListBox::scrollWidth() const
678 // There is no horizontal scrolling allowed.
679 return pixelSnappedClientWidth();
682 int RenderListBox::scrollHeight() const
684 return max(pixelSnappedClientHeight(), roundToInt(listHeight()));
687 int RenderListBox::scrollLeft() const
692 void RenderListBox::setScrollLeft(int)
696 int RenderListBox::scrollTop() const
698 return m_indexOffset * itemHeight();
701 void RenderListBox::setScrollTop(int newTop)
703 // Determine an index and scroll to it.
704 int index = newTop / itemHeight();
705 if (index < 0 || index >= numItems() || index == m_indexOffset)
708 scrollToOffsetWithoutAnimation(VerticalScrollbar, index);
711 bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
713 if (!RenderBlockFlow::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction))
715 const Vector<HTMLElement*>& listItems = selectElement()->listItems();
716 int size = numItems();
717 LayoutPoint adjustedLocation = accumulatedOffset + location();
719 for (int i = 0; i < size; ++i) {
720 if (itemBoundingBoxRect(adjustedLocation, i).contains(locationInContainer.point())) {
721 if (Element* node = listItems[i]) {
722 result.setInnerNode(node);
723 if (!result.innerNonSharedNode())
724 result.setInnerNonSharedNode(node);
725 result.setLocalPoint(locationInContainer.point() - toLayoutSize(adjustedLocation));
734 LayoutRect RenderListBox::controlClipRect(const LayoutPoint& additionalOffset) const
736 LayoutRect clipRect = contentBoxRect();
737 if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
738 clipRect.moveBy(additionalOffset + LayoutPoint(verticalScrollbarWidth(), 0));
740 clipRect.moveBy(additionalOffset);
744 bool RenderListBox::isActive() const
746 Page* page = frame()->page();
747 return page && page->focusController().isActive();
750 void RenderListBox::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
752 IntRect scrollRect = rect;
753 if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
754 scrollRect.move(borderLeft(), borderTop());
756 scrollRect.move(width() - borderRight() - scrollbar->width(), borderTop());
758 if (RuntimeEnabledFeatures::repaintAfterLayoutEnabled() && frameView()->isInPerformLayout()) {
759 m_verticalBarDamage = scrollRect;
760 m_hasVerticalBarDamage = true;
762 repaintRectangle(scrollRect);
766 void RenderListBox::repaintScrollbarIfNeeded()
768 if (!hasVerticalBarDamage())
770 repaintRectangle(verticalBarDamage());
772 resetScrollbarDamage();
775 IntRect RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
777 RenderView* view = this->view();
779 return scrollbarRect;
781 IntRect rect = scrollbarRect;
783 int scrollbarTop = borderTop();
784 rect.move(scrollbarLeft(), scrollbarTop);
786 return view->frameView()->convertFromRenderer(this, rect);
789 IntRect RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
791 RenderView* view = this->view();
795 IntRect rect = view->frameView()->convertToRenderer(this, parentRect);
797 int scrollbarTop = borderTop();
798 rect.move(-scrollbarLeft(), -scrollbarTop);
802 IntPoint RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
804 RenderView* view = this->view();
806 return scrollbarPoint;
808 IntPoint point = scrollbarPoint;
810 int scrollbarTop = borderTop();
811 point.move(scrollbarLeft(), scrollbarTop);
813 return view->frameView()->convertFromRenderer(this, point);
816 IntPoint RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
818 RenderView* view = this->view();
822 IntPoint point = view->frameView()->convertToRenderer(this, parentPoint);
824 int scrollbarTop = borderTop();
825 point.move(-scrollbarLeft(), -scrollbarTop);
829 IntSize RenderListBox::contentsSize() const
831 return IntSize(scrollWidth(), scrollHeight());
834 int RenderListBox::visibleHeight() const
839 int RenderListBox::visibleWidth() const
844 IntPoint RenderListBox::lastKnownMousePosition() const
846 RenderView* view = this->view();
849 return view->frameView()->lastKnownMousePosition();
852 bool RenderListBox::shouldSuspendScrollAnimations() const
854 RenderView* view = this->view();
857 return view->frameView()->shouldSuspendScrollAnimations();
860 bool RenderListBox::scrollbarsCanBeActive() const
862 RenderView* view = this->view();
865 return view->frameView()->scrollbarsCanBeActive();
868 IntPoint RenderListBox::minimumScrollPosition() const
873 IntPoint RenderListBox::maximumScrollPosition() const
875 return IntPoint(0, std::max(numItems() - numVisibleItems(), 0));
878 bool RenderListBox::userInputScrollable(ScrollbarOrientation orientation) const
880 return orientation == VerticalScrollbar;
883 bool RenderListBox::shouldPlaceVerticalScrollbarOnLeft() const
888 int RenderListBox::lineStep(ScrollbarOrientation) const
893 int RenderListBox::pageStep(ScrollbarOrientation orientation) const
895 return max(1, numVisibleItems() - 1);
898 float RenderListBox::pixelStep(ScrollbarOrientation) const
900 return 1.0f / itemHeight();
903 IntRect RenderListBox::scrollableAreaBoundingBox() const
905 return absoluteBoundingBoxRect();
908 PassRefPtr<Scrollbar> RenderListBox::createScrollbar()
910 RefPtr<Scrollbar> widget;
911 bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR);
912 if (hasCustomScrollbarStyle)
913 widget = RenderScrollbar::createCustomScrollbar(this, VerticalScrollbar, this->node());
915 widget = Scrollbar::create(this, VerticalScrollbar, RenderTheme::theme().scrollbarControlSizeForPart(ListboxPart));
916 didAddScrollbar(widget.get(), VerticalScrollbar);
918 document().view()->addChild(widget.get());
919 return widget.release();
922 void RenderListBox::destroyScrollbar()
927 if (!m_vBar->isCustomScrollbar())
928 ScrollableArea::willRemoveScrollbar(m_vBar.get(), VerticalScrollbar);
929 m_vBar->removeFromParent();
930 m_vBar->disconnectFromScrollableArea();
934 void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar)
936 if (hasScrollbar == (m_vBar != 0))
940 m_vBar = createScrollbar();
945 m_vBar->styleChanged();
947 // Force an update since we know the scrollbars have changed things.
948 if (document().hasAnnotatedRegions())
949 document().setAnnotatedRegionsDirty(true);
952 } // namespace WebCore