/* * Copyright (C) 2006-2018 Apple Inc. All rights reserved. * 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "RenderListBox.h" #include "AXObjectCache.h" #include "CSSFontSelector.h" #include "DeprecatedGlobalSettings.h" #include "Document.h" #include "EventHandler.h" #include "FocusController.h" #include "Frame.h" #include "FrameSelection.h" #include "FrameView.h" #include "GraphicsContext.h" #include "HTMLNames.h" #include "HTMLOptionElement.h" #include "HTMLOptGroupElement.h" #include "HTMLSelectElement.h" #include "HitTestResult.h" #include "NodeRenderStyle.h" #include "Page.h" #include "PaintInfo.h" #include "RenderLayer.h" #include "RenderLayerScrollableArea.h" #include "RenderLayoutState.h" #include "RenderScrollbar.h" #include "RenderText.h" #include "RenderTheme.h" #include "RenderView.h" #include "ScrollAnimator.h" #include "Scrollbar.h" #include "ScrollbarTheme.h" #include "Settings.h" #include "SpatialNavigation.h" #include "StyleResolver.h" #include "StyleTreeResolver.h" #include "WheelEventTestMonitor.h" #include #include #include namespace WebCore { using namespace HTMLNames; WTF_MAKE_ISO_ALLOCATED_IMPL(RenderListBox); const int rowSpacing = 1; const int optionsSpacingHorizontal = 2; // The minSize constant was originally defined to render scrollbars correctly. // This might vary for different platforms. const int minSize = 4; // Default size when the multiple attribute is present but size attribute is absent. const int defaultSize = 4; // FIXME: This hardcoded baselineAdjustment is what we used to do for the old // widget, but I'm not sure this is right for the new control. const int baselineAdjustment = 7; RenderListBox::RenderListBox(HTMLSelectElement& element, RenderStyle&& style) : RenderBlockFlow(element, WTFMove(style)) , m_optionsChanged(true) , m_scrollToRevealSelectionAfterLayout(false) , m_inAutoscroll(false) , m_optionsWidth(0) , m_indexOffset(0) { view().frameView().addScrollableArea(this); } RenderListBox::~RenderListBox() { // Do not add any code here. Add it to willBeDestroyed() instead. } void RenderListBox::willBeDestroyed() { setHasVerticalScrollbar(false); view().frameView().removeScrollableArea(this); RenderBlockFlow::willBeDestroyed(); } HTMLSelectElement& RenderListBox::selectElement() const { return downcast(nodeForNonAnonymous()); } static FontCascade bolder(Document& document, const FontCascade& font) { auto description = font.fontDescription(); description.setWeight(description.bolderWeight()); auto result = FontCascade { WTFMove(description), font.letterSpacing(), font.wordSpacing() }; result.update(&document.fontSelector()); return result; } void RenderListBox::updateFromElement() { if (m_optionsChanged) { float width = 0; auto& normalFont = style().fontCascade(); std::optional boldFont; for (auto* element : selectElement().listItems()) { String text; WTF::Function selectFont = [&normalFont] () -> const FontCascade& { return normalFont; }; if (is(*element)) text = downcast(*element).textIndentedToRespectGroupLabel(); else if (is(*element)) { text = downcast(*element).groupLabelText(); selectFont = [this, &normalFont, &boldFont] () -> const FontCascade& { if (!boldFont) boldFont = bolder(document(), normalFont); return boldFont.value(); }; } if (text.isEmpty()) continue; text = applyTextTransform(style(), text, ' '); auto textRun = constructTextRun(text, style(), AllowRightExpansion); width = std::max(width, selectFont().width(textRun)); } // FIXME: Is ceiling right here, or should we be doing some kind of rounding instead? m_optionsWidth = static_cast(std::ceil(width)); m_optionsChanged = false; setHasVerticalScrollbar(true); computeFirstIndexesVisibleInPaddingTopBottomAreas(); setNeedsLayoutAndPrefWidthsRecalc(); } } void RenderListBox::selectionChanged() { repaint(); if (!m_inAutoscroll) { if (m_optionsChanged || needsLayout()) m_scrollToRevealSelectionAfterLayout = true; else scrollToRevealSelection(); } if (AXObjectCache* cache = document().existingAXObjectCache()) cache->deferSelectedChildrenChangedIfNeeded(selectElement()); } void RenderListBox::layout() { StackStats::LayoutCheckPoint layoutCheckPoint; RenderBlockFlow::layout(); if (m_vBar) { bool enabled = numVisibleItems() < numItems(); m_vBar->setEnabled(enabled); m_vBar->setSteps(1, std::max(1, numVisibleItems() - 1), itemHeight()); m_vBar->setProportion(numVisibleItems(), numItems()); if (!enabled) { scrollToOffsetWithoutAnimation(VerticalScrollbar, 0); m_indexOffset = 0; } } if (m_scrollToRevealSelectionAfterLayout) { LayoutStateDisabler layoutStateDisabler(view().frameView().layoutContext()); scrollToRevealSelection(); } } void RenderListBox::scrollToRevealSelection() { m_scrollToRevealSelectionAfterLayout = false; int firstIndex = selectElement().activeSelectionStartListIndex(); if (firstIndex >= 0 && !listIndexIsVisible(selectElement().activeSelectionEndListIndex())) scrollToRevealElementAtListIndex(firstIndex); } void RenderListBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const { maxLogicalWidth = shouldApplySizeContainment(*this) ? 2 * optionsSpacingHorizontal : m_optionsWidth + 2 * optionsSpacingHorizontal; if (m_vBar) maxLogicalWidth += m_vBar->width(); if (!style().width().isPercentOrCalculated()) minLogicalWidth = maxLogicalWidth; } void RenderListBox::computePreferredLogicalWidths() { // Nested style recal do not fire post recal callbacks. see webkit.org/b/153767 ASSERT(!m_optionsChanged || Style::postResolutionCallbacksAreSuspended()); m_minPreferredLogicalWidth = 0; m_maxPreferredLogicalWidth = 0; if (style().width().isFixed() && style().width().value() > 0) m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style().width()); else computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); RenderBox::computePreferredLogicalWidths(style().minWidth(), style().maxWidth(), horizontalBorderAndPaddingExtent()); setPreferredLogicalWidthsDirty(false); } int RenderListBox::size() const { int specifiedSize = selectElement().size(); if (specifiedSize > 1) return std::max(minSize, specifiedSize); return defaultSize; } int RenderListBox::numVisibleItems(ConsiderPadding considerPadding) const { // Only count fully visible rows. But don't return 0 even if only part of a row shows. int visibleItemsExcludingPadding = std::max(1, (contentHeight() + rowSpacing) / itemHeight()); if (considerPadding == ConsiderPadding::No) return visibleItemsExcludingPadding; return numberOfVisibleItemsInPaddingTop() + visibleItemsExcludingPadding + numberOfVisibleItemsInPaddingBottom(); } int RenderListBox::numItems() const { return selectElement().listItems().size(); } LayoutUnit RenderListBox::listHeight() const { return itemHeight() * numItems() - rowSpacing; } RenderBox::LogicalExtentComputedValues RenderListBox::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop) const { LayoutUnit height = itemHeight() * size() - rowSpacing; cacheIntrinsicContentLogicalHeightForFlexItem(height); height += verticalBorderAndPaddingExtent(); return RenderBox::computeLogicalHeight(height, logicalTop); } LayoutUnit RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const { auto baseline = RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode); if (!shouldApplyLayoutContainment(*this)) baseline -= baselineAdjustment; return baseline; } LayoutRect RenderListBox::itemBoundingBoxRect(const LayoutPoint& additionalOffset, int index) { LayoutUnit x = additionalOffset.x() + borderLeft() + paddingLeft(); if (shouldPlaceVerticalScrollbarOnLeft() && m_vBar) x += m_vBar->occupiedWidth(); LayoutUnit y = additionalOffset.y() + borderTop() + paddingTop() + itemHeight() * (index - m_indexOffset); return LayoutRect(x, y, contentWidth(), itemHeight()); } void RenderListBox::paintItem(PaintInfo& paintInfo, const LayoutPoint& paintOffset, const PaintFunction& paintFunction) { int listItemsSize = numItems(); int firstVisibleItem = m_indexOfFirstVisibleItemInsidePaddingTopArea.value_or(m_indexOffset); int endIndex = firstVisibleItem + numVisibleItems(ConsiderPadding::Yes); for (int i = firstVisibleItem; i < listItemsSize && i < endIndex; ++i) paintFunction(paintInfo, paintOffset, i); } void RenderListBox::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (style().visibility() != Visibility::Visible) return; if (paintInfo.phase == PaintPhase::Foreground) { paintItem(paintInfo, paintOffset, [this](PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listItemIndex) { paintItemForeground(paintInfo, paintOffset, listItemIndex); }); } // Paint the children. RenderBlockFlow::paintObject(paintInfo, paintOffset); switch (paintInfo.phase) { // Depending on whether we have overlay scrollbars they // get rendered in the foreground or background phases case PaintPhase::Foreground: if (m_vBar->isOverlayScrollbar()) paintScrollbar(paintInfo, paintOffset); break; case PaintPhase::BlockBackground: if (!m_vBar->isOverlayScrollbar()) paintScrollbar(paintInfo, paintOffset); break; case PaintPhase::ChildBlockBackground: case PaintPhase::ChildBlockBackgrounds: { paintItem(paintInfo, paintOffset, [this](PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listItemIndex) { paintItemBackground(paintInfo, paintOffset, listItemIndex); }); break; } default: break; } } void RenderListBox::addFocusRingRects(Vector& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject* paintContainer) { if (!selectElement().allowsNonContiguousSelection()) return RenderBlockFlow::addFocusRingRects(rects, additionalOffset, paintContainer); // Focus the last selected item. int selectedItem = selectElement().activeSelectionEndListIndex(); if (selectedItem >= 0) { rects.append(snappedIntRect(itemBoundingBoxRect(additionalOffset, selectedItem))); return; } // No selected items, find the first non-disabled item. int size = numItems(); const Vector& listItems = selectElement().listItems(); for (int i = 0; i < size; ++i) { HTMLElement* element = listItems[i]; if (is(*element) && !element->isDisabledFormControl()) { selectElement().setActiveSelectionEndIndex(i); rects.append(itemBoundingBoxRect(additionalOffset, i)); return; } } } void RenderListBox::paintScrollbar(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (!m_vBar) return; LayoutUnit left = paintOffset.x() + (shouldPlaceVerticalScrollbarOnLeft() ? borderLeft() : width() - borderRight() - m_vBar->width()); LayoutUnit top = paintOffset.y() + borderTop(); LayoutUnit width = m_vBar->width(); LayoutUnit height = this->height() - (borderTop() + borderBottom()); IntRect scrollRect = snappedIntRect(left, top, width, height); m_vBar->setFrameRect(scrollRect); m_vBar->paint(paintInfo.context(), snappedIntRect(paintInfo.rect)); } static LayoutSize itemOffsetForAlignment(TextRun textRun, const RenderStyle* itemStyle, FontCascade itemFont, LayoutRect itemBoudingBox) { TextAlignMode actualAlignment = itemStyle->textAlign(); // FIXME: Firefox doesn't respect TextAlignMode::Justify. Should we? // FIXME: Handle TextAlignMode::End here if (actualAlignment == TextAlignMode::Start || actualAlignment == TextAlignMode::Justify) actualAlignment = itemStyle->isLeftToRightDirection() ? TextAlignMode::Left : TextAlignMode::Right; LayoutSize offset = LayoutSize(0, itemFont.fontMetrics().ascent()); if (actualAlignment == TextAlignMode::Right || actualAlignment == TextAlignMode::WebKitRight) { float textWidth = itemFont.width(textRun); offset.setWidth(itemBoudingBox.width() - textWidth - optionsSpacingHorizontal); } else if (actualAlignment == TextAlignMode::Center || actualAlignment == TextAlignMode::WebKitCenter) { float textWidth = itemFont.width(textRun); offset.setWidth((itemBoudingBox.width() - textWidth) / 2); } else offset.setWidth(optionsSpacingHorizontal); return offset; } void RenderListBox::paintItemForeground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex) { const Vector& listItems = selectElement().listItems(); HTMLElement* listItemElement = listItems[listIndex]; auto& itemStyle = *listItemElement->computedStyle(); if (itemStyle.visibility() == Visibility::Hidden) return; String itemText; bool isOptionElement = is(*listItemElement); if (isOptionElement) itemText = downcast(*listItemElement).textIndentedToRespectGroupLabel(); else if (is(*listItemElement)) itemText = downcast(*listItemElement).groupLabelText(); itemText = applyTextTransform(style(), itemText, ' '); if (itemText.isNull()) return; Color textColor = itemStyle.visitedDependentColorWithColorFilter(CSSPropertyColor); if (isOptionElement && downcast(*listItemElement).selected()) { if (frame().selection().isFocusedAndActive() && document().focusedElement() == &selectElement()) textColor = theme().activeListBoxSelectionForegroundColor(styleColorOptions()); // Honor the foreground color for disabled items else if (!listItemElement->isDisabledFormControl() && !selectElement().isDisabledFormControl()) textColor = theme().inactiveListBoxSelectionForegroundColor(styleColorOptions()); } paintInfo.context().setFillColor(textColor); TextRun textRun(itemText, 0, 0, AllowRightExpansion, itemStyle.direction(), isOverride(itemStyle.unicodeBidi()), true); FontCascade itemFont = style().fontCascade(); LayoutRect r = itemBoundingBoxRect(paintOffset, listIndex); r.move(itemOffsetForAlignment(textRun, &itemStyle, itemFont, r)); if (is(*listItemElement)) { auto description = itemFont.fontDescription(); description.setWeight(description.bolderWeight()); itemFont = FontCascade(WTFMove(description), itemFont.letterSpacing(), itemFont.wordSpacing()); itemFont.update(&document().fontSelector()); } // Draw the item text paintInfo.context().drawBidiText(itemFont, textRun, roundedIntPoint(r.location())); } void RenderListBox::paintItemBackground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex) { const Vector& listItems = selectElement().listItems(); HTMLElement* listItemElement = listItems[listIndex]; auto& itemStyle = *listItemElement->computedStyle(); Color backColor; if (is(*listItemElement) && downcast(*listItemElement).selected()) { if (frame().selection().isFocusedAndActive() && document().focusedElement() == &selectElement()) backColor = theme().activeListBoxSelectionBackgroundColor(styleColorOptions()); else backColor = theme().inactiveListBoxSelectionBackgroundColor(styleColorOptions()); } else backColor = itemStyle.visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor); // Draw the background for this list box item if (itemStyle.visibility() == Visibility::Hidden) return; LayoutRect itemRect = itemBoundingBoxRect(paintOffset, listIndex); itemRect.intersect(controlClipRect(paintOffset)); paintInfo.context().fillRect(snappedIntRect(itemRect), backColor); } bool RenderListBox::isPointInOverflowControl(HitTestResult& result, const LayoutPoint& locationInContainer, const LayoutPoint& accumulatedOffset) { if (!m_vBar || !m_vBar->shouldParticipateInHitTesting()) return false; LayoutUnit x = accumulatedOffset.x() + (shouldPlaceVerticalScrollbarOnLeft() ? borderLeft() : width() - borderRight() - m_vBar->width()); LayoutUnit y = accumulatedOffset.y() + borderTop(); LayoutUnit width = m_vBar->width(); LayoutUnit height = this->height() - borderTop() - borderBottom(); LayoutRect vertRect(x, y, width, height); if (!vertRect.contains(locationInContainer)) return false; result.setScrollbar(m_vBar.get()); return true; } int RenderListBox::listIndexAtOffset(const LayoutSize& offset) { if (!numItems()) return -1; if (offset.height() < borderTop() || offset.height() > height() - borderBottom()) return -1; int scrollbarWidth = m_vBar ? m_vBar->width() : 0; if (shouldPlaceVerticalScrollbarOnLeft() && (offset.width() < borderLeft() + paddingLeft() + scrollbarWidth || offset.width() > width() - borderRight() - paddingRight())) return -1; if (!shouldPlaceVerticalScrollbarOnLeft() && (offset.width() < borderLeft() + paddingLeft() || offset.width() > width() - borderRight() - paddingRight() - scrollbarWidth)) return -1; int newOffset = (offset.height() - borderTop() - paddingTop()) / itemHeight() + m_indexOffset; return newOffset < numItems() ? newOffset : -1; } void RenderListBox::panScroll(const IntPoint& panStartMousePosition) { const int maxSpeed = 20; const int iconRadius = 7; const int speedReducer = 4; // FIXME: This doesn't work correctly with transforms. FloatPoint absOffset = localToAbsolute(); IntPoint lastKnownMousePosition = frame().eventHandler().lastKnownMousePosition(); // 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 static IntPoint previousMousePosition; if (lastKnownMousePosition.y() < 0) lastKnownMousePosition = previousMousePosition; else previousMousePosition = lastKnownMousePosition; int yDelta = lastKnownMousePosition.y() - panStartMousePosition.y(); // If the point is too far from the center we limit the speed yDelta = std::max(std::min(yDelta, maxSpeed), -maxSpeed); if (abs(yDelta) < iconRadius) // at the center we let the space for the icon return; if (yDelta > 0) //offsetY = view()->viewHeight(); absOffset.move(0, listHeight()); else if (yDelta < 0) yDelta--; // Let's attenuate the speed yDelta /= speedReducer; IntPoint scrollPoint(0, 0); scrollPoint.setY(absOffset.y() + yDelta); int newOffset = scrollToward(scrollPoint); if (newOffset < 0) return; m_inAutoscroll = true; selectElement().updateListBoxSelection(!selectElement().multiple()); m_inAutoscroll = false; } int RenderListBox::scrollToward(const IntPoint& destination) { // FIXME: This doesn't work correctly with transforms. FloatPoint absPos = localToAbsolute(); IntSize positionOffset = roundedIntSize(destination - absPos); int rows = numVisibleItems(); int offset = m_indexOffset; if (positionOffset.height() < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1)) return offset - 1; if (positionOffset.height() > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows)) return offset + rows - 1; return listIndexAtOffset(positionOffset); } void RenderListBox::autoscroll(const IntPoint&) { IntPoint pos = frame().view()->windowToContents(frame().eventHandler().lastKnownMousePosition()); int endIndex = scrollToward(pos); if (selectElement().isDisabledFormControl()) return; if (endIndex >= 0) { m_inAutoscroll = true; if (!selectElement().multiple()) selectElement().setActiveSelectionAnchorIndex(endIndex); selectElement().setActiveSelectionEndIndex(endIndex); selectElement().updateListBoxSelection(!selectElement().multiple()); m_inAutoscroll = false; } } void RenderListBox::stopAutoscroll() { if (selectElement().isDisabledFormControl()) return; selectElement().listBoxOnChange(); } bool RenderListBox::scrollToRevealElementAtListIndex(int index) { if (index < 0 || index >= numItems() || listIndexIsVisible(index)) return false; int newOffset; if (index < m_indexOffset) newOffset = index; else newOffset = index - numVisibleItems() + 1; scrollToOffsetWithoutAnimation(VerticalScrollbar, newOffset); return true; } bool RenderListBox::listIndexIsVisible(int index) { int firstIndex = m_indexOfFirstVisibleItemInsidePaddingTopArea.value_or(m_indexOffset); int endIndex = m_indexOfFirstVisibleItemInsidePaddingBottomArea ? m_indexOfFirstVisibleItemInsidePaddingBottomArea.value() + numberOfVisibleItemsInPaddingBottom() : m_indexOffset + numVisibleItems(); return index >= firstIndex && index < endIndex; } bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Element**, RenderBox*, const IntPoint&) { return ScrollableArea::scroll(direction, granularity, multiplier); } bool RenderListBox::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Element**) { return ScrollableArea::scroll(logicalToPhysical(direction, style().isHorizontalWritingMode(), style().isFlippedBlocksWritingMode()), granularity, multiplier); } void RenderListBox::valueChanged(unsigned listIndex) { selectElement().setSelectedIndex(selectElement().listToOptionIndex(listIndex)); selectElement().dispatchFormControlChangeEvent(); } ScrollPosition RenderListBox::scrollPosition() const { return { 0, m_indexOffset }; } ScrollPosition RenderListBox::minimumScrollPosition() const { return { 0, 0 }; } ScrollPosition RenderListBox::maximumScrollPosition() const { return { 0, numItems() - numVisibleItems() }; } void RenderListBox::setScrollOffset(const ScrollOffset& offset) { scrollTo(offset.y()); } int RenderListBox::maximumNumberOfItemsThatFitInPaddingBottomArea() const { return paddingBottom() / itemHeight(); } int RenderListBox::numberOfVisibleItemsInPaddingTop() const { if (!m_indexOfFirstVisibleItemInsidePaddingTopArea) return 0; return m_indexOffset - m_indexOfFirstVisibleItemInsidePaddingTopArea.value(); } int RenderListBox::numberOfVisibleItemsInPaddingBottom() const { if (!m_indexOfFirstVisibleItemInsidePaddingBottomArea) return 0; return std::min(maximumNumberOfItemsThatFitInPaddingBottomArea(), numItems() - m_indexOffset - numVisibleItems()); } void RenderListBox::computeFirstIndexesVisibleInPaddingTopBottomAreas() { m_indexOfFirstVisibleItemInsidePaddingTopArea = std::nullopt; m_indexOfFirstVisibleItemInsidePaddingBottomArea = std::nullopt; int maximumNumberOfItemsThatFitInPaddingTopArea = paddingTop() / itemHeight(); if (maximumNumberOfItemsThatFitInPaddingTopArea) { if (m_indexOffset) m_indexOfFirstVisibleItemInsidePaddingTopArea = std::max(0, m_indexOffset - maximumNumberOfItemsThatFitInPaddingTopArea); } if (maximumNumberOfItemsThatFitInPaddingBottomArea()) { if (numItems() > (m_indexOffset + numVisibleItems())) m_indexOfFirstVisibleItemInsidePaddingBottomArea = m_indexOffset + numVisibleItems(); } } void RenderListBox::scrollTo(int newOffset) { if (newOffset == m_indexOffset) return; m_indexOffset = newOffset; computeFirstIndexesVisibleInPaddingTopBottomAreas(); repaint(); document().addPendingScrollEventTarget(selectElement()); } LayoutUnit RenderListBox::itemHeight() const { return style().fontMetrics().height() + rowSpacing; } int RenderListBox::verticalScrollbarWidth() const { return m_vBar ? m_vBar->occupiedWidth() : 0; } // FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's // how the control currently paints. int RenderListBox::scrollWidth() const { // There is no horizontal scrolling allowed. return roundToInt(clientWidth()); } int RenderListBox::scrollHeight() const { return roundToInt(std::max(clientHeight(), listHeight())); } int RenderListBox::scrollLeft() const { return 0; } void RenderListBox::setScrollLeft(int, const ScrollPositionChangeOptions&) { } int RenderListBox::scrollTop() const { return m_indexOffset * itemHeight(); } static void setupWheelEventTestMonitor(RenderListBox& renderer) { if (!renderer.page().isMonitoringWheelEvents()) return; renderer.scrollAnimator().setWheelEventTestMonitor(renderer.page().wheelEventTestMonitor()); } void RenderListBox::setScrollTop(int newTop, const ScrollPositionChangeOptions&) { // Determine an index and scroll to it. int index = newTop / itemHeight(); if (index < 0 || index >= numItems() || index == m_indexOffset) return; setupWheelEventTestMonitor(*this); scrollToOffsetWithoutAnimation(VerticalScrollbar, index); } bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) { if (!RenderBlockFlow::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction)) return false; const Vector& listItems = selectElement().listItems(); int size = numItems(); LayoutPoint adjustedLocation = accumulatedOffset + location(); for (int i = 0; i < size; ++i) { if (!itemBoundingBoxRect(adjustedLocation, i).contains(locationInContainer.point())) continue; if (Element* node = listItems[i]) { result.setInnerNode(node); if (!result.innerNonSharedNode()) result.setInnerNonSharedNode(node); result.setLocalPoint(locationInContainer.point() - toLayoutSize(adjustedLocation)); break; } } return true; } LayoutRect RenderListBox::controlClipRect(const LayoutPoint& additionalOffset) const { // Clip against the padding box, to give