1/* 2 * This file is part of the select element renderer in WebCore. 3 * 4 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 5 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. 6 * 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 * 23 */ 24 25#include "config.h" 26#include "RenderMenuList.h" 27 28#include "AXObjectCache.h" 29#include "AccessibilityMenuList.h" 30#include "CSSFontSelector.h" 31#include "Chrome.h" 32#include "FontCache.h" 33#include "Frame.h" 34#include "FrameView.h" 35#include "HTMLNames.h" 36#include "HTMLOptionElement.h" 37#include "HTMLOptGroupElement.h" 38#include "HTMLSelectElement.h" 39#include "NodeRenderStyle.h" 40#include "Page.h" 41#include "PopupMenu.h" 42#include "RenderScrollbar.h" 43#include "RenderText.h" 44#include "RenderTheme.h" 45#include "RenderView.h" 46#include "Settings.h" 47#include "StyleResolver.h" 48#include "TextRun.h" 49#include <math.h> 50 51#if PLATFORM(IOS) 52#include "LocalizedStrings.h" 53#endif 54 55namespace WebCore { 56 57using namespace HTMLNames; 58 59#if PLATFORM(IOS) 60static size_t selectedOptionCount(const RenderMenuList& renderMenuList) 61{ 62 const Vector<HTMLElement*>& listItems = renderMenuList.selectElement().listItems(); 63 size_t numberOfItems = listItems.size(); 64 65 size_t count = 0; 66 for (size_t i = 0; i < numberOfItems; ++i) { 67 if (listItems[i]->hasTagName(optionTag) && toHTMLOptionElement(listItems[i])->selected()) 68 ++count; 69 } 70 return count; 71} 72#endif 73 74RenderMenuList::RenderMenuList(HTMLSelectElement& element, PassRef<RenderStyle> style) 75 : RenderFlexibleBox(element, WTF::move(style)) 76 , m_buttonText(nullptr) 77 , m_innerBlock(nullptr) 78 , m_needsOptionsWidthUpdate(true) 79 , m_optionsWidth(0) 80 , m_lastActiveIndex(-1) 81#if !PLATFORM(IOS) 82 , m_popupIsVisible(false) 83#endif 84{ 85} 86 87RenderMenuList::~RenderMenuList() 88{ 89#if !PLATFORM(IOS) 90 if (m_popup) 91 m_popup->disconnectClient(); 92 m_popup = 0; 93#endif 94} 95 96void RenderMenuList::createInnerBlock() 97{ 98 if (m_innerBlock) { 99 ASSERT(firstChild() == m_innerBlock); 100 ASSERT(!m_innerBlock->nextSibling()); 101 return; 102 } 103 104 // Create an anonymous block. 105 ASSERT(!firstChild()); 106 m_innerBlock = createAnonymousBlock(); 107 adjustInnerStyle(); 108 RenderFlexibleBox::addChild(m_innerBlock); 109} 110 111void RenderMenuList::adjustInnerStyle() 112{ 113 RenderStyle& innerStyle = m_innerBlock->style(); 114 innerStyle.setFlexGrow(1); 115 innerStyle.setFlexShrink(1); 116 // min-width: 0; is needed for correct shrinking. 117 // FIXME: Remove this line when https://bugs.webkit.org/show_bug.cgi?id=111790 is fixed. 118 innerStyle.setMinWidth(Length(0, Fixed)); 119 // Use margin:auto instead of align-items:center to get safe centering, i.e. 120 // when the content overflows, treat it the same as align-items: flex-start. 121 // But we only do that for the cases where html.css would otherwise use center. 122 if (style().alignItems() == AlignCenter) { 123 innerStyle.setMarginTop(Length()); 124 innerStyle.setMarginBottom(Length()); 125 innerStyle.setAlignSelf(AlignFlexStart); 126 } 127 128 innerStyle.setPaddingLeft(Length(theme().popupInternalPaddingLeft(style()), Fixed)); 129 innerStyle.setPaddingRight(Length(theme().popupInternalPaddingRight(style()), Fixed)); 130 innerStyle.setPaddingTop(Length(theme().popupInternalPaddingTop(style()), Fixed)); 131 innerStyle.setPaddingBottom(Length(theme().popupInternalPaddingBottom(style()), Fixed)); 132 133 if (document().page()->chrome().selectItemWritingDirectionIsNatural()) { 134 // Items in the popup will not respect the CSS text-align and direction properties, 135 // so we must adjust our own style to match. 136 innerStyle.setTextAlign(LEFT); 137 TextDirection direction = (m_buttonText && m_buttonText->text()->defaultWritingDirection() == U_RIGHT_TO_LEFT) ? RTL : LTR; 138 innerStyle.setDirection(direction); 139#if PLATFORM(IOS) 140 } else if (document().page()->chrome().selectItemAlignmentFollowsMenuWritingDirection()) { 141 innerStyle.setTextAlign(style().direction() == LTR ? LEFT : RIGHT); 142 TextDirection direction; 143 EUnicodeBidi unicodeBidi; 144 if (multiple() && selectedOptionCount(*this) != 1) { 145 direction = (m_buttonText && m_buttonText->text()->defaultWritingDirection() == U_RIGHT_TO_LEFT) ? RTL : LTR; 146 unicodeBidi = UBNormal; 147 } else if (m_optionStyle) { 148 direction = m_optionStyle->direction(); 149 unicodeBidi = m_optionStyle->unicodeBidi(); 150 } else { 151 direction = style().direction(); 152 unicodeBidi = style().unicodeBidi(); 153 } 154 155 innerStyle.setDirection(direction); 156 innerStyle.setUnicodeBidi(unicodeBidi); 157 } 158#else 159 } else if (m_optionStyle && document().page()->chrome().selectItemAlignmentFollowsMenuWritingDirection()) { 160 if ((m_optionStyle->direction() != innerStyle.direction() || m_optionStyle->unicodeBidi() != innerStyle.unicodeBidi())) 161 m_innerBlock->setNeedsLayoutAndPrefWidthsRecalc(); 162 innerStyle.setTextAlign(style().isLeftToRightDirection() ? LEFT : RIGHT); 163 innerStyle.setDirection(m_optionStyle->direction()); 164 innerStyle.setUnicodeBidi(m_optionStyle->unicodeBidi()); 165 } 166#endif // !PLATFORM(IOS) 167} 168 169HTMLSelectElement& RenderMenuList::selectElement() const 170{ 171 return toHTMLSelectElement(nodeForNonAnonymous()); 172} 173 174void RenderMenuList::addChild(RenderObject* newChild, RenderObject* beforeChild) 175{ 176 createInnerBlock(); 177 m_innerBlock->addChild(newChild, beforeChild); 178 ASSERT(m_innerBlock == firstChild()); 179 180 if (AXObjectCache* cache = document().existingAXObjectCache()) 181 cache->childrenChanged(this, newChild); 182} 183 184RenderObject* RenderMenuList::removeChild(RenderObject& oldChild) 185{ 186 if (&oldChild == m_innerBlock || !m_innerBlock) { 187 RenderObject* next = RenderFlexibleBox::removeChild(oldChild); 188 m_innerBlock = 0; 189 return next; 190 } else 191 return m_innerBlock->removeChild(oldChild); 192} 193 194void RenderMenuList::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 195{ 196 RenderBlock::styleDidChange(diff, oldStyle); 197 198 if (m_innerBlock) // RenderBlock handled updating the anonymous block's style. 199 adjustInnerStyle(); 200 201 bool fontChanged = !oldStyle || oldStyle->font() != style().font(); 202 if (fontChanged) { 203 updateOptionsWidth(); 204 m_needsOptionsWidthUpdate = false; 205 } 206} 207 208void RenderMenuList::updateOptionsWidth() 209{ 210 float maxOptionWidth = 0; 211 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 212 int size = listItems.size(); 213 FontCachePurgePreventer fontCachePurgePreventer; 214 215 for (int i = 0; i < size; ++i) { 216 HTMLElement* element = listItems[i]; 217 if (!isHTMLOptionElement(element)) 218 continue; 219 220 String text = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel(); 221 applyTextTransform(style(), text, ' '); 222 if (theme().popupOptionSupportsTextIndent()) { 223 // Add in the option's text indent. We can't calculate percentage values for now. 224 float optionWidth = 0; 225 if (RenderStyle* optionStyle = element->renderStyle()) 226 optionWidth += minimumValueForLength(optionStyle->textIndent(), 0); 227 if (!text.isEmpty()) { 228 const Font& font = style().font(); 229 TextRun run = RenderBlock::constructTextRun(this, font, text, style(), TextRun::AllowTrailingExpansion | TextRun::ForbidLeadingExpansion, DefaultTextRunFlags); 230 optionWidth += font.width(run); 231 } 232 maxOptionWidth = std::max(maxOptionWidth, optionWidth); 233 } else if (!text.isEmpty()) { 234 const Font& font = style().font(); 235 TextRun run = RenderBlock::constructTextRun(this, font, text, style(), TextRun::AllowTrailingExpansion | TextRun::ForbidLeadingExpansion, DefaultTextRunFlags); 236 maxOptionWidth = std::max(maxOptionWidth, font.width(run)); 237 } 238 } 239 240 int width = static_cast<int>(ceilf(maxOptionWidth)); 241 if (m_optionsWidth == width) 242 return; 243 244 m_optionsWidth = width; 245 if (parent()) 246 setNeedsLayoutAndPrefWidthsRecalc(); 247} 248 249void RenderMenuList::updateFromElement() 250{ 251 if (m_needsOptionsWidthUpdate) { 252 updateOptionsWidth(); 253 m_needsOptionsWidthUpdate = false; 254 } 255 256#if !PLATFORM(IOS) 257 if (m_popupIsVisible) 258 m_popup->updateFromElement(); 259 else 260#endif 261 setTextFromOption(selectElement().selectedIndex()); 262} 263 264void RenderMenuList::setTextFromOption(int optionIndex) 265{ 266 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 267 int size = listItems.size(); 268 269 int i = selectElement().optionToListIndex(optionIndex); 270 String text = emptyString(); 271 if (i >= 0 && i < size) { 272 Element* element = listItems[i]; 273 if (isHTMLOptionElement(element)) { 274 text = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel(); 275 m_optionStyle = element->renderStyle(); 276 } 277 } 278 279#if PLATFORM(IOS) 280 if (multiple()) { 281 size_t count = selectedOptionCount(*this); 282 if (count != 1) 283 text = htmlSelectMultipleItems(count); 284 } 285#endif 286 287 setText(text.stripWhiteSpace()); 288 didUpdateActiveOption(optionIndex); 289} 290 291void RenderMenuList::setText(const String& s) 292{ 293 String textToUse = s.isEmpty() ? String(ASCIILiteral("\n")) : s; 294 295 if (m_buttonText) 296 m_buttonText->setText(textToUse.impl(), true); 297 else { 298 m_buttonText = new RenderText(document(), textToUse); 299 addChild(m_buttonText); 300 } 301 adjustInnerStyle(); 302} 303 304String RenderMenuList::text() const 305{ 306 return m_buttonText ? m_buttonText->text() : String(); 307} 308 309LayoutRect RenderMenuList::controlClipRect(const LayoutPoint& additionalOffset) const 310{ 311 // Clip to the intersection of the content box and the content box for the inner box 312 // This will leave room for the arrows which sit in the inner box padding, 313 // and if the inner box ever spills out of the outer box, that will get clipped too. 314 LayoutRect outerBox(additionalOffset.x() + borderLeft() + paddingLeft(), 315 additionalOffset.y() + borderTop() + paddingTop(), 316 contentWidth(), 317 contentHeight()); 318 319 LayoutRect innerBox(additionalOffset.x() + m_innerBlock->x() + m_innerBlock->paddingLeft(), 320 additionalOffset.y() + m_innerBlock->y() + m_innerBlock->paddingTop(), 321 m_innerBlock->contentWidth(), 322 m_innerBlock->contentHeight()); 323 324 return intersection(outerBox, innerBox); 325} 326 327void RenderMenuList::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const 328{ 329 maxLogicalWidth = std::max(m_optionsWidth, theme().minimumMenuListSize(style())) + m_innerBlock->paddingLeft() + m_innerBlock->paddingRight(); 330 if (!style().width().isPercent()) 331 minLogicalWidth = maxLogicalWidth; 332} 333 334void RenderMenuList::computePreferredLogicalWidths() 335{ 336 m_minPreferredLogicalWidth = 0; 337 m_maxPreferredLogicalWidth = 0; 338 339 if (style().width().isFixed() && style().width().value() > 0) 340 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style().width().value()); 341 else 342 computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth); 343 344 if (style().minWidth().isFixed() && style().minWidth().value() > 0) { 345 m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value())); 346 m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value())); 347 } 348 349 if (style().maxWidth().isFixed()) { 350 m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value())); 351 m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value())); 352 } 353 354 LayoutUnit toAdd = horizontalBorderAndPaddingExtent(); 355 m_minPreferredLogicalWidth += toAdd; 356 m_maxPreferredLogicalWidth += toAdd; 357 358 setPreferredLogicalWidthsDirty(false); 359} 360 361#if PLATFORM(IOS) 362NO_RETURN_DUE_TO_ASSERT 363void RenderMenuList::showPopup() 364{ 365 ASSERT_NOT_REACHED(); 366} 367#else 368void RenderMenuList::showPopup() 369{ 370 if (m_popupIsVisible) 371 return; 372 373 if (document().page()->chrome().hasOpenedPopup()) 374 return; 375 376 // Create m_innerBlock here so it ends up as the first child. 377 // This is important because otherwise we might try to create m_innerBlock 378 // inside the showPopup call and it would fail. 379 createInnerBlock(); 380 if (!m_popup) 381 m_popup = document().page()->chrome().createPopupMenu(this); 382 m_popupIsVisible = true; 383 384 // Compute the top left taking transforms into account, but use 385 // the actual width of the element to size the popup. 386 FloatPoint absTopLeft = localToAbsolute(FloatPoint(), UseTransforms); 387 IntRect absBounds = absoluteBoundingBoxRectIgnoringTransforms(); 388 absBounds.setLocation(roundedIntPoint(absTopLeft)); 389 m_popup->show(absBounds, &view().frameView(), selectElement().optionToListIndex(selectElement().selectedIndex())); 390} 391#endif 392 393void RenderMenuList::hidePopup() 394{ 395#if !PLATFORM(IOS) 396 if (m_popup) 397 m_popup->hide(); 398#endif 399} 400 401void RenderMenuList::valueChanged(unsigned listIndex, bool fireOnChange) 402{ 403 // Check to ensure a page navigation has not occurred while 404 // the popup was up. 405 if (&document() != document().frame()->document()) 406 return; 407 408 selectElement().optionSelectedByUser(selectElement().listToOptionIndex(listIndex), fireOnChange); 409} 410 411void RenderMenuList::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow) 412{ 413 selectElement().listBoxSelectItem(listIndex, allowMultiplySelections, shift, fireOnChangeNow); 414} 415 416bool RenderMenuList::multiple() const 417{ 418 return selectElement().multiple(); 419} 420 421void RenderMenuList::didSetSelectedIndex(int listIndex) 422{ 423 didUpdateActiveOption(selectElement().listToOptionIndex(listIndex)); 424} 425 426void RenderMenuList::didUpdateActiveOption(int optionIndex) 427{ 428 if (!AXObjectCache::accessibilityEnabled() || !document().existingAXObjectCache()) 429 return; 430 431 if (m_lastActiveIndex == optionIndex) 432 return; 433 m_lastActiveIndex = optionIndex; 434 435 int listIndex = selectElement().optionToListIndex(optionIndex); 436 if (listIndex < 0 || listIndex >= static_cast<int>(selectElement().listItems().size())) 437 return; 438 439 if (AXObjectCache* cache = document().existingAXObjectCache()) { 440 if (AccessibilityMenuList* menuList = toAccessibilityMenuList(cache->get(this))) 441 menuList->didUpdateActiveOption(optionIndex); 442 } 443} 444 445String RenderMenuList::itemText(unsigned listIndex) const 446{ 447 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 448 if (listIndex >= listItems.size()) 449 return String(); 450 451 String itemString; 452 Element* element = listItems[listIndex]; 453 if (isHTMLOptGroupElement(element)) 454 itemString = toHTMLOptGroupElement(element)->groupLabelText(); 455 else if (isHTMLOptionElement(element)) 456 itemString = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel(); 457 458 applyTextTransform(style(), itemString, ' '); 459 return itemString; 460} 461 462String RenderMenuList::itemLabel(unsigned) const 463{ 464 return String(); 465} 466 467String RenderMenuList::itemIcon(unsigned) const 468{ 469 return String(); 470} 471 472String RenderMenuList::itemAccessibilityText(unsigned listIndex) const 473{ 474 // Allow the accessible name be changed if necessary. 475 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 476 if (listIndex >= listItems.size()) 477 return String(); 478 return listItems[listIndex]->fastGetAttribute(aria_labelAttr); 479} 480 481String RenderMenuList::itemToolTip(unsigned listIndex) const 482{ 483 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 484 if (listIndex >= listItems.size()) 485 return String(); 486 return listItems[listIndex]->title(); 487} 488 489bool RenderMenuList::itemIsEnabled(unsigned listIndex) const 490{ 491 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 492 if (listIndex >= listItems.size()) 493 return false; 494 HTMLElement* element = listItems[listIndex]; 495 if (!isHTMLOptionElement(element)) 496 return false; 497 498 bool groupEnabled = true; 499 if (Element* parentElement = element->parentElement()) { 500 if (isHTMLOptGroupElement(parentElement)) 501 groupEnabled = !parentElement->isDisabledFormControl(); 502 } 503 if (!groupEnabled) 504 return false; 505 506 return !element->isDisabledFormControl(); 507} 508 509PopupMenuStyle RenderMenuList::itemStyle(unsigned listIndex) const 510{ 511 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 512 if (listIndex >= listItems.size()) { 513 // If we are making an out of bounds access, then we want to use the style 514 // of a different option element (index 0). However, if there isn't an option element 515 // before at index 0, we fall back to the menu's style. 516 if (!listIndex) 517 return menuStyle(); 518 519 // Try to retrieve the style of an option element we know exists (index 0). 520 listIndex = 0; 521 } 522 HTMLElement* element = listItems[listIndex]; 523 524 Color itemBackgroundColor; 525 bool itemHasCustomBackgroundColor; 526 getItemBackgroundColor(listIndex, itemBackgroundColor, itemHasCustomBackgroundColor); 527 528 RenderStyle* style = element->renderStyle() ? element->renderStyle() : element->computedStyle(); 529 return style ? PopupMenuStyle(style->visitedDependentColor(CSSPropertyColor), itemBackgroundColor, style->font(), style->visibility() == VISIBLE, 530 style->display() == NONE, true, style->textIndent(), style->direction(), isOverride(style->unicodeBidi()), 531 itemHasCustomBackgroundColor ? PopupMenuStyle::CustomBackgroundColor : PopupMenuStyle::DefaultBackgroundColor) : menuStyle(); 532} 533 534void RenderMenuList::getItemBackgroundColor(unsigned listIndex, Color& itemBackgroundColor, bool& itemHasCustomBackgroundColor) const 535{ 536 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 537 if (listIndex >= listItems.size()) { 538 itemBackgroundColor = style().visitedDependentColor(CSSPropertyBackgroundColor); 539 itemHasCustomBackgroundColor = false; 540 return; 541 } 542 HTMLElement* element = listItems[listIndex]; 543 544 Color backgroundColor; 545 if (element->renderStyle()) 546 backgroundColor = element->renderStyle()->visitedDependentColor(CSSPropertyBackgroundColor); 547 itemHasCustomBackgroundColor = backgroundColor.isValid() && backgroundColor.alpha(); 548 // If the item has an opaque background color, return that. 549 if (!backgroundColor.hasAlpha()) { 550 itemBackgroundColor = backgroundColor; 551 return; 552 } 553 554 // Otherwise, the item's background is overlayed on top of the menu background. 555 backgroundColor = style().visitedDependentColor(CSSPropertyBackgroundColor).blend(backgroundColor); 556 if (!backgroundColor.hasAlpha()) { 557 itemBackgroundColor = backgroundColor; 558 return; 559 } 560 561 // If the menu background is not opaque, then add an opaque white background behind. 562 itemBackgroundColor = Color(Color::white).blend(backgroundColor); 563} 564 565PopupMenuStyle RenderMenuList::menuStyle() const 566{ 567 const RenderStyle& styleToUse = m_innerBlock ? m_innerBlock->style() : style(); 568 IntRect absBounds = absoluteBoundingBoxRectIgnoringTransforms(); 569 return PopupMenuStyle(styleToUse.visitedDependentColor(CSSPropertyColor), styleToUse.visitedDependentColor(CSSPropertyBackgroundColor), 570 styleToUse.font(), styleToUse.visibility() == VISIBLE, styleToUse.display() == NONE, style().hasAppearance(), styleToUse.textIndent(), 571 style().direction(), isOverride(style().unicodeBidi()), PopupMenuStyle::DefaultBackgroundColor, 572 PopupMenuStyle::SelectPopup, theme().popupMenuSize(styleToUse, absBounds)); 573} 574 575HostWindow* RenderMenuList::hostWindow() const 576{ 577 return view().frameView().hostWindow(); 578} 579 580PassRefPtr<Scrollbar> RenderMenuList::createScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, ScrollbarControlSize controlSize) 581{ 582 RefPtr<Scrollbar> widget; 583 bool hasCustomScrollbarStyle = style().hasPseudoStyle(SCROLLBAR); 584 if (hasCustomScrollbarStyle) 585 widget = RenderScrollbar::createCustomScrollbar(scrollableArea, orientation, &selectElement()); 586 else 587 widget = Scrollbar::createNativeScrollbar(scrollableArea, orientation, controlSize); 588 return widget.release(); 589} 590 591int RenderMenuList::clientInsetLeft() const 592{ 593 return 0; 594} 595 596int RenderMenuList::clientInsetRight() const 597{ 598 return 0; 599} 600 601LayoutUnit RenderMenuList::clientPaddingLeft() const 602{ 603 return paddingLeft() + m_innerBlock->paddingLeft(); 604} 605 606const int endOfLinePadding = 2; 607LayoutUnit RenderMenuList::clientPaddingRight() const 608{ 609 if (style().appearance() == MenulistPart || style().appearance() == MenulistButtonPart) { 610 // For these appearance values, the theme applies padding to leave room for the 611 // drop-down button. But leaving room for the button inside the popup menu itself 612 // looks strange, so we return a small default padding to avoid having a large empty 613 // space appear on the side of the popup menu. 614 return endOfLinePadding; 615 } 616 617 // If the appearance isn't MenulistPart, then the select is styled (non-native), so 618 // we want to return the user specified padding. 619 return paddingRight() + m_innerBlock->paddingRight(); 620} 621 622int RenderMenuList::listSize() const 623{ 624 return selectElement().listItems().size(); 625} 626 627int RenderMenuList::selectedIndex() const 628{ 629 return selectElement().optionToListIndex(selectElement().selectedIndex()); 630} 631 632void RenderMenuList::popupDidHide() 633{ 634#if !PLATFORM(IOS) 635 m_popupIsVisible = false; 636#endif 637} 638 639bool RenderMenuList::itemIsSeparator(unsigned listIndex) const 640{ 641 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 642 return listIndex < listItems.size() && listItems[listIndex]->hasTagName(hrTag); 643} 644 645bool RenderMenuList::itemIsLabel(unsigned listIndex) const 646{ 647 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 648 return listIndex < listItems.size() && isHTMLOptGroupElement(listItems[listIndex]); 649} 650 651bool RenderMenuList::itemIsSelected(unsigned listIndex) const 652{ 653 const Vector<HTMLElement*>& listItems = selectElement().listItems(); 654 if (listIndex >= listItems.size()) 655 return false; 656 HTMLElement* element = listItems[listIndex]; 657 return isHTMLOptionElement(element) && toHTMLOptionElement(element)->selected(); 658} 659 660void RenderMenuList::setTextFromItem(unsigned listIndex) 661{ 662 setTextFromOption(selectElement().listToOptionIndex(listIndex)); 663} 664 665FontSelector* RenderMenuList::fontSelector() const 666{ 667 return document().ensureStyleResolver().fontSelector(); 668} 669 670} 671