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