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, 2015 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 "Frame.h"
33#include "FrameView.h"
34#include "HTMLNames.h"
35#include "HTMLOptionElement.h"
36#include "HTMLOptGroupElement.h"
37#include "HTMLSelectElement.h"
38#include "NodeRenderStyle.h"
39#include "Page.h"
40#include "PopupMenu.h"
41#include "RenderScrollbar.h"
42#include "RenderText.h"
43#include "RenderTheme.h"
44#include "RenderTreeBuilder.h"
45#include "RenderView.h"
46#include "StyleResolver.h"
47#include "TextRun.h"
48#include <math.h>
49#include <wtf/IsoMallocInlines.h>
50
51#if PLATFORM(IOS_FAMILY)
52#include "LocalizedStrings.h"
53#endif
54
55namespace WebCore {
56
57using namespace HTMLNames;
58
59WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMenuList);
60
61#if PLATFORM(IOS_FAMILY)
62static size_t selectedOptionCount(const RenderMenuList& renderMenuList)
63{
64 const Vector<HTMLElement*>& listItems = renderMenuList.selectElement().listItems();
65 size_t numberOfItems = listItems.size();
66
67 size_t count = 0;
68 for (size_t i = 0; i < numberOfItems; ++i) {
69 if (is<HTMLOptionElement>(*listItems[i]) && downcast<HTMLOptionElement>(*listItems[i]).selected())
70 ++count;
71 }
72 return count;
73}
74#endif
75
76RenderMenuList::RenderMenuList(HTMLSelectElement& element, RenderStyle&& style)
77 : RenderFlexibleBox(element, WTFMove(style))
78 , m_needsOptionsWidthUpdate(true)
79 , m_optionsWidth(0)
80#if !PLATFORM(IOS_FAMILY)
81 , m_popupIsVisible(false)
82#endif
83{
84}
85
86RenderMenuList::~RenderMenuList()
87{
88 // Do not add any code here. Add it to willBeDestroyed() instead.
89}
90
91void RenderMenuList::willBeDestroyed()
92{
93#if !PLATFORM(IOS_FAMILY)
94 if (m_popup)
95 m_popup->disconnectClient();
96 m_popup = nullptr;
97#endif
98
99 RenderFlexibleBox::willBeDestroyed();
100}
101
102void RenderMenuList::setInnerRenderer(RenderBlock& innerRenderer)
103{
104 ASSERT(!m_innerBlock.get());
105 m_innerBlock = makeWeakPtr(innerRenderer);
106 adjustInnerStyle();
107}
108
109void RenderMenuList::adjustInnerStyle()
110{
111 auto& innerStyle = m_innerBlock->mutableStyle();
112 innerStyle.setFlexGrow(1);
113 innerStyle.setFlexShrink(1);
114 // min-width: 0; is needed for correct shrinking.
115 innerStyle.setMinWidth(Length(0, Fixed));
116 // Use margin:auto instead of align-items:center to get safe centering, i.e.
117 // when the content overflows, treat it the same as align-items: flex-start.
118 // But we only do that for the cases where html.css would otherwise use center.
119 if (style().alignItems().position() == ItemPosition::Center) {
120 innerStyle.setMarginTop(Length());
121 innerStyle.setMarginBottom(Length());
122 innerStyle.setAlignSelfPosition(ItemPosition::FlexStart);
123 }
124
125 innerStyle.setPaddingBox(theme().popupInternalPaddingBox(style()));
126
127 if (document().page()->chrome().selectItemWritingDirectionIsNatural()) {
128 // Items in the popup will not respect the CSS text-align and direction properties,
129 // so we must adjust our own style to match.
130 innerStyle.setTextAlign(TextAlignMode::Left);
131 TextDirection direction = (m_buttonText && m_buttonText->text().defaultWritingDirection() == U_RIGHT_TO_LEFT) ? TextDirection::RTL : TextDirection::LTR;
132 innerStyle.setDirection(direction);
133#if PLATFORM(IOS_FAMILY)
134 } else if (document().page()->chrome().selectItemAlignmentFollowsMenuWritingDirection()) {
135 innerStyle.setTextAlign(style().direction() == TextDirection::LTR ? TextAlignMode::Left : TextAlignMode::Right);
136 TextDirection direction;
137 EUnicodeBidi unicodeBidi;
138 if (multiple() && selectedOptionCount(*this) != 1) {
139 direction = (m_buttonText && m_buttonText->text().defaultWritingDirection() == U_RIGHT_TO_LEFT) ? TextDirection::RTL : TextDirection::LTR;
140 unicodeBidi = UBNormal;
141 } else if (m_optionStyle) {
142 direction = m_optionStyle->direction();
143 unicodeBidi = m_optionStyle->unicodeBidi();
144 } else {
145 direction = style().direction();
146 unicodeBidi = style().unicodeBidi();
147 }
148
149 innerStyle.setDirection(direction);
150 innerStyle.setUnicodeBidi(unicodeBidi);
151 }
152#else
153 } else if (m_optionStyle && document().page()->chrome().selectItemAlignmentFollowsMenuWritingDirection()) {
154 if ((m_optionStyle->direction() != innerStyle.direction() || m_optionStyle->unicodeBidi() != innerStyle.unicodeBidi()))
155 m_innerBlock->setNeedsLayoutAndPrefWidthsRecalc();
156 innerStyle.setTextAlign(style().isLeftToRightDirection() ? TextAlignMode::Left : TextAlignMode::Right);
157 innerStyle.setDirection(m_optionStyle->direction());
158 innerStyle.setUnicodeBidi(m_optionStyle->unicodeBidi());
159 }
160#endif // !PLATFORM(IOS_FAMILY)
161}
162
163HTMLSelectElement& RenderMenuList::selectElement() const
164{
165 return downcast<HTMLSelectElement>(nodeForNonAnonymous());
166}
167
168void RenderMenuList::didAttachChild(RenderObject& child, RenderObject*)
169{
170 if (AXObjectCache* cache = document().existingAXObjectCache())
171 cache->childrenChanged(this, &child);
172}
173
174void RenderMenuList::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
175{
176 RenderBlock::styleDidChange(diff, oldStyle);
177
178 if (m_innerBlock) // RenderBlock handled updating the anonymous block's style.
179 adjustInnerStyle();
180
181 bool fontChanged = !oldStyle || oldStyle->fontCascade() != style().fontCascade();
182 if (fontChanged) {
183 updateOptionsWidth();
184 m_needsOptionsWidthUpdate = false;
185 }
186}
187
188void RenderMenuList::updateOptionsWidth()
189{
190 float maxOptionWidth = 0;
191 const Vector<HTMLElement*>& listItems = selectElement().listItems();
192 int size = listItems.size();
193
194 for (int i = 0; i < size; ++i) {
195 HTMLElement* element = listItems[i];
196 if (!is<HTMLOptionElement>(*element))
197 continue;
198
199 String text = downcast<HTMLOptionElement>(*element).textIndentedToRespectGroupLabel();
200 text = applyTextTransform(style(), text, ' ');
201 if (theme().popupOptionSupportsTextIndent()) {
202 // Add in the option's text indent. We can't calculate percentage values for now.
203 float optionWidth = 0;
204 if (auto* optionStyle = element->computedStyle())
205 optionWidth += minimumValueForLength(optionStyle->textIndent(), 0);
206 if (!text.isEmpty()) {
207 const FontCascade& font = style().fontCascade();
208 TextRun run = RenderBlock::constructTextRun(text, style());
209 optionWidth += font.width(run);
210 }
211 maxOptionWidth = std::max(maxOptionWidth, optionWidth);
212 } else if (!text.isEmpty()) {
213 const FontCascade& font = style().fontCascade();
214 TextRun run = RenderBlock::constructTextRun(text, style());
215 maxOptionWidth = std::max(maxOptionWidth, font.width(run));
216 }
217 }
218
219 int width = static_cast<int>(ceilf(maxOptionWidth));
220 if (m_optionsWidth == width)
221 return;
222
223 m_optionsWidth = width;
224 if (parent())
225 setNeedsLayoutAndPrefWidthsRecalc();
226}
227
228void RenderMenuList::updateFromElement()
229{
230 if (m_needsOptionsWidthUpdate) {
231 updateOptionsWidth();
232 m_needsOptionsWidthUpdate = false;
233 }
234
235#if !PLATFORM(IOS_FAMILY)
236 if (m_popupIsVisible)
237 m_popup->updateFromElement();
238 else
239#endif
240 setTextFromOption(selectElement().selectedIndex());
241}
242
243void RenderMenuList::setTextFromOption(int optionIndex)
244{
245 const Vector<HTMLElement*>& listItems = selectElement().listItems();
246 int size = listItems.size();
247
248 int i = selectElement().optionToListIndex(optionIndex);
249 String text = emptyString();
250 if (i >= 0 && i < size) {
251 Element* element = listItems[i];
252 if (is<HTMLOptionElement>(*element)) {
253 text = downcast<HTMLOptionElement>(*element).textIndentedToRespectGroupLabel();
254 auto* style = element->computedStyle();
255 m_optionStyle = style ? RenderStyle::clonePtr(*style) : nullptr;
256 }
257 }
258
259#if PLATFORM(IOS_FAMILY)
260 if (multiple()) {
261 size_t count = selectedOptionCount(*this);
262 if (count != 1)
263 text = htmlSelectMultipleItems(count);
264 }
265#endif
266
267 setText(text.stripWhiteSpace());
268 didUpdateActiveOption(optionIndex);
269}
270
271void RenderMenuList::setText(const String& s)
272{
273 String textToUse = s.isEmpty() ? "\n"_str : s;
274
275 if (m_buttonText)
276 m_buttonText->setText(textToUse.impl(), true);
277 else {
278 auto newButtonText = createRenderer<RenderText>(document(), textToUse);
279 m_buttonText = makeWeakPtr(*newButtonText);
280 // FIXME: This mutation should go through the normal RenderTreeBuilder path.
281 if (RenderTreeBuilder::current())
282 RenderTreeBuilder::current()->attach(*this, WTFMove(newButtonText));
283 else
284 RenderTreeBuilder(*document().renderView()).attach(*this, WTFMove(newButtonText));
285 }
286
287 adjustInnerStyle();
288}
289
290String RenderMenuList::text() const
291{
292 return m_buttonText ? m_buttonText->text() : String();
293}
294
295LayoutRect RenderMenuList::controlClipRect(const LayoutPoint& additionalOffset) const
296{
297 // Clip to the intersection of the content box and the content box for the inner box
298 // This will leave room for the arrows which sit in the inner box padding,
299 // and if the inner box ever spills out of the outer box, that will get clipped too.
300 LayoutRect outerBox(additionalOffset.x() + borderLeft() + paddingLeft(),
301 additionalOffset.y() + borderTop() + paddingTop(),
302 contentWidth(),
303 contentHeight());
304
305 LayoutRect innerBox(additionalOffset.x() + m_innerBlock->x() + m_innerBlock->paddingLeft(),
306 additionalOffset.y() + m_innerBlock->y() + m_innerBlock->paddingTop(),
307 m_innerBlock->contentWidth(),
308 m_innerBlock->contentHeight());
309
310 return intersection(outerBox, innerBox);
311}
312
313void RenderMenuList::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
314{
315 maxLogicalWidth = std::max(m_optionsWidth, theme().minimumMenuListSize(style())) + m_innerBlock->paddingLeft() + m_innerBlock->paddingRight();
316 if (!style().width().isPercentOrCalculated())
317 minLogicalWidth = maxLogicalWidth;
318}
319
320void RenderMenuList::computePreferredLogicalWidths()
321{
322 m_minPreferredLogicalWidth = 0;
323 m_maxPreferredLogicalWidth = 0;
324
325 if (style().width().isFixed() && style().width().value() > 0)
326 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style().width().value());
327 else
328 computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
329
330 if (style().minWidth().isFixed() && style().minWidth().value() > 0) {
331 m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
332 m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().minWidth().value()));
333 }
334
335 if (style().maxWidth().isFixed()) {
336 m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
337 m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style().maxWidth().value()));
338 }
339
340 LayoutUnit toAdd = horizontalBorderAndPaddingExtent();
341 m_minPreferredLogicalWidth += toAdd;
342 m_maxPreferredLogicalWidth += toAdd;
343
344 setPreferredLogicalWidthsDirty(false);
345}
346
347#if PLATFORM(IOS_FAMILY)
348NO_RETURN_DUE_TO_ASSERT
349void RenderMenuList::showPopup()
350{
351 ASSERT_NOT_REACHED();
352}
353#else
354void RenderMenuList::showPopup()
355{
356 if (m_popupIsVisible)
357 return;
358
359 ASSERT(m_innerBlock);
360 if (!m_popup)
361 m_popup = document().page()->chrome().createPopupMenu(*this);
362 m_popupIsVisible = true;
363
364 // Compute the top left taking transforms into account, but use
365 // the actual width of the element to size the popup.
366 FloatPoint absTopLeft = localToAbsolute(FloatPoint(), UseTransforms);
367 IntRect absBounds = absoluteBoundingBoxRectIgnoringTransforms();
368 absBounds.setLocation(roundedIntPoint(absTopLeft));
369 m_popup->show(absBounds, &view().frameView(), selectElement().optionToListIndex(selectElement().selectedIndex()));
370}
371#endif
372
373void RenderMenuList::hidePopup()
374{
375#if !PLATFORM(IOS_FAMILY)
376 if (m_popup)
377 m_popup->hide();
378#endif
379}
380
381void RenderMenuList::valueChanged(unsigned listIndex, bool fireOnChange)
382{
383 // Check to ensure a page navigation has not occurred while
384 // the popup was up.
385 if (&document() != document().frame()->document())
386 return;
387
388 selectElement().optionSelectedByUser(selectElement().listToOptionIndex(listIndex), fireOnChange);
389}
390
391void RenderMenuList::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
392{
393 selectElement().listBoxSelectItem(listIndex, allowMultiplySelections, shift, fireOnChangeNow);
394}
395
396bool RenderMenuList::multiple() const
397{
398 return selectElement().multiple();
399}
400
401void RenderMenuList::didSetSelectedIndex(int listIndex)
402{
403 didUpdateActiveOption(selectElement().listToOptionIndex(listIndex));
404}
405
406void RenderMenuList::didUpdateActiveOption(int optionIndex)
407{
408 if (!AXObjectCache::accessibilityEnabled())
409 return;
410
411 auto* axCache = document().existingAXObjectCache();
412 if (!axCache)
413 return;
414
415 if (m_lastActiveIndex == optionIndex)
416 return;
417 m_lastActiveIndex = optionIndex;
418
419 int listIndex = selectElement().optionToListIndex(optionIndex);
420 if (listIndex < 0 || listIndex >= static_cast<int>(selectElement().listItems().size()))
421 return;
422
423 if (auto* menuList = downcast<AccessibilityMenuList>(axCache->get(this)))
424 menuList->didUpdateActiveOption(optionIndex);
425}
426
427String RenderMenuList::itemText(unsigned listIndex) const
428{
429 auto& listItems = selectElement().listItems();
430 if (listIndex >= listItems.size())
431 return String();
432
433 String itemString;
434 auto& element = *listItems[listIndex];
435 if (is<HTMLOptGroupElement>(element))
436 itemString = downcast<HTMLOptGroupElement>(element).groupLabelText();
437 else if (is<HTMLOptionElement>(element))
438 itemString = downcast<HTMLOptionElement>(element).textIndentedToRespectGroupLabel();
439
440 return applyTextTransform(style(), itemString, ' ');
441}
442
443String RenderMenuList::itemLabel(unsigned) const
444{
445 return String();
446}
447
448String RenderMenuList::itemIcon(unsigned) const
449{
450 return String();
451}
452
453String RenderMenuList::itemAccessibilityText(unsigned listIndex) const
454{
455 // Allow the accessible name be changed if necessary.
456 const Vector<HTMLElement*>& listItems = selectElement().listItems();
457 if (listIndex >= listItems.size())
458 return String();
459 return listItems[listIndex]->attributeWithoutSynchronization(aria_labelAttr);
460}
461
462String RenderMenuList::itemToolTip(unsigned listIndex) const
463{
464 const Vector<HTMLElement*>& listItems = selectElement().listItems();
465 if (listIndex >= listItems.size())
466 return String();
467 return listItems[listIndex]->title();
468}
469
470bool RenderMenuList::itemIsEnabled(unsigned listIndex) const
471{
472 const Vector<HTMLElement*>& listItems = selectElement().listItems();
473 if (listIndex >= listItems.size())
474 return false;
475 HTMLElement* element = listItems[listIndex];
476 if (!is<HTMLOptionElement>(*element))
477 return false;
478
479 bool groupEnabled = true;
480 if (Element* parentElement = element->parentElement()) {
481 if (is<HTMLOptGroupElement>(*parentElement))
482 groupEnabled = !parentElement->isDisabledFormControl();
483 }
484 if (!groupEnabled)
485 return false;
486
487 return !element->isDisabledFormControl();
488}
489
490PopupMenuStyle RenderMenuList::itemStyle(unsigned listIndex) const
491{
492 const Vector<HTMLElement*>& listItems = selectElement().listItems();
493 if (listIndex >= listItems.size()) {
494 // If we are making an out of bounds access, then we want to use the style
495 // of a different option element (index 0). However, if there isn't an option element
496 // before at index 0, we fall back to the menu's style.
497 if (!listIndex)
498 return menuStyle();
499
500 // Try to retrieve the style of an option element we know exists (index 0).
501 listIndex = 0;
502 }
503 HTMLElement* element = listItems[listIndex];
504
505 Color itemBackgroundColor;
506 bool itemHasCustomBackgroundColor;
507 getItemBackgroundColor(listIndex, itemBackgroundColor, itemHasCustomBackgroundColor);
508
509 auto& style = *element->computedStyle();
510 return PopupMenuStyle(style.visitedDependentColorWithColorFilter(CSSPropertyColor), itemBackgroundColor, style.fontCascade(), style.visibility() == Visibility::Visible,
511 style.display() == DisplayType::None, true, style.textIndent(), style.direction(), isOverride(style.unicodeBidi()),
512 itemHasCustomBackgroundColor ? PopupMenuStyle::CustomBackgroundColor : PopupMenuStyle::DefaultBackgroundColor);
513}
514
515void RenderMenuList::getItemBackgroundColor(unsigned listIndex, Color& itemBackgroundColor, bool& itemHasCustomBackgroundColor) const
516{
517 const Vector<HTMLElement*>& listItems = selectElement().listItems();
518 if (listIndex >= listItems.size()) {
519 itemBackgroundColor = style().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
520 itemHasCustomBackgroundColor = false;
521 return;
522 }
523 HTMLElement* element = listItems[listIndex];
524
525 Color backgroundColor = element->computedStyle()->visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
526 itemHasCustomBackgroundColor = backgroundColor.isValid() && backgroundColor.isVisible();
527 // If the item has an opaque background color, return that.
528 if (backgroundColor.isOpaque()) {
529 itemBackgroundColor = backgroundColor;
530 return;
531 }
532
533 // Otherwise, the item's background is overlayed on top of the menu background.
534 backgroundColor = style().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor).blend(backgroundColor);
535 if (backgroundColor.isOpaque()) {
536 itemBackgroundColor = backgroundColor;
537 return;
538 }
539
540 // If the menu background is not opaque, then add an opaque white background behind.
541 itemBackgroundColor = Color(Color::white).blend(backgroundColor);
542}
543
544PopupMenuStyle RenderMenuList::menuStyle() const
545{
546 const RenderStyle& styleToUse = m_innerBlock ? m_innerBlock->style() : style();
547 IntRect absBounds = absoluteBoundingBoxRectIgnoringTransforms();
548 return PopupMenuStyle(styleToUse.visitedDependentColorWithColorFilter(CSSPropertyColor), styleToUse.visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor),
549 styleToUse.fontCascade(), styleToUse.visibility() == Visibility::Visible, styleToUse.display() == DisplayType::None,
550 style().hasAppearance() && style().appearance() == MenulistPart, styleToUse.textIndent(),
551 style().direction(), isOverride(style().unicodeBidi()), PopupMenuStyle::DefaultBackgroundColor,
552 PopupMenuStyle::SelectPopup, theme().popupMenuSize(styleToUse, absBounds));
553}
554
555HostWindow* RenderMenuList::hostWindow() const
556{
557 return view().frameView().hostWindow();
558}
559
560Ref<Scrollbar> RenderMenuList::createScrollbar(ScrollableArea& scrollableArea, ScrollbarOrientation orientation, ScrollbarControlSize controlSize)
561{
562 bool hasCustomScrollbarStyle = style().hasPseudoStyle(PseudoId::Scrollbar);
563 if (hasCustomScrollbarStyle)
564 return RenderScrollbar::createCustomScrollbar(scrollableArea, orientation, &selectElement());
565 return Scrollbar::createNativeScrollbar(scrollableArea, orientation, controlSize);
566}
567
568int RenderMenuList::clientInsetLeft() const
569{
570 return 0;
571}
572
573int RenderMenuList::clientInsetRight() const
574{
575 return 0;
576}
577
578const int endOfLinePadding = 2;
579
580LayoutUnit RenderMenuList::clientPaddingLeft() const
581{
582 if ((style().appearance() == MenulistPart || style().appearance() == MenulistButtonPart) && style().direction() == TextDirection::RTL) {
583 // For these appearance values, the theme applies padding to leave room for the
584 // drop-down button. But leaving room for the button inside the popup menu itself
585 // looks strange, so we return a small default padding to avoid having a large empty
586 // space appear on the side of the popup menu.
587 return endOfLinePadding;
588 }
589 // If the appearance isn't MenulistPart, then the select is styled (non-native), so
590 // we want to return the user specified padding.
591 return paddingLeft() + m_innerBlock->paddingLeft();
592}
593
594LayoutUnit RenderMenuList::clientPaddingRight() const
595{
596 if ((style().appearance() == MenulistPart || style().appearance() == MenulistButtonPart) && style().direction() == TextDirection::LTR)
597 return endOfLinePadding;
598
599 return paddingRight() + m_innerBlock->paddingRight();
600}
601
602int RenderMenuList::listSize() const
603{
604 return selectElement().listItems().size();
605}
606
607int RenderMenuList::selectedIndex() const
608{
609 return selectElement().optionToListIndex(selectElement().selectedIndex());
610}
611
612void RenderMenuList::popupDidHide()
613{
614#if !PLATFORM(IOS_FAMILY)
615 // PopupMenuMac::show in WebKitLegacy can call this callback even when popup had already been dismissed.
616 m_popupIsVisible = false;
617#endif
618}
619
620bool RenderMenuList::itemIsSeparator(unsigned listIndex) const
621{
622 const Vector<HTMLElement*>& listItems = selectElement().listItems();
623 return listIndex < listItems.size() && listItems[listIndex]->hasTagName(hrTag);
624}
625
626bool RenderMenuList::itemIsLabel(unsigned listIndex) const
627{
628 const Vector<HTMLElement*>& listItems = selectElement().listItems();
629 return listIndex < listItems.size() && is<HTMLOptGroupElement>(*listItems[listIndex]);
630}
631
632bool RenderMenuList::itemIsSelected(unsigned listIndex) const
633{
634 const Vector<HTMLElement*>& listItems = selectElement().listItems();
635 if (listIndex >= listItems.size())
636 return false;
637 HTMLElement* element = listItems[listIndex];
638 return is<HTMLOptionElement>(*element) && downcast<HTMLOptionElement>(*element).selected();
639}
640
641void RenderMenuList::setTextFromItem(unsigned listIndex)
642{
643 setTextFromOption(selectElement().listToOptionIndex(listIndex));
644}
645
646FontSelector* RenderMenuList::fontSelector() const
647{
648 return &document().fontSelector();
649}
650
651}
652