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 | |
55 | namespace WebCore { |
56 | |
57 | using namespace HTMLNames; |
58 | |
59 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderMenuList); |
60 | |
61 | #if PLATFORM(IOS_FAMILY) |
62 | static 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 | |
76 | 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 | |
86 | RenderMenuList::() |
87 | { |
88 | // Do not add any code here. Add it to willBeDestroyed() instead. |
89 | } |
90 | |
91 | void RenderMenuList::() |
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 | |
102 | void RenderMenuList::(RenderBlock& innerRenderer) |
103 | { |
104 | ASSERT(!m_innerBlock.get()); |
105 | m_innerBlock = makeWeakPtr(innerRenderer); |
106 | adjustInnerStyle(); |
107 | } |
108 | |
109 | void RenderMenuList::() |
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 | |
163 | HTMLSelectElement& RenderMenuList::() const |
164 | { |
165 | return downcast<HTMLSelectElement>(nodeForNonAnonymous()); |
166 | } |
167 | |
168 | void RenderMenuList::(RenderObject& child, RenderObject*) |
169 | { |
170 | if (AXObjectCache* cache = document().existingAXObjectCache()) |
171 | cache->childrenChanged(this, &child); |
172 | } |
173 | |
174 | void RenderMenuList::(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 | |
188 | void RenderMenuList::() |
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 | |
228 | void RenderMenuList::() |
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 | |
243 | void RenderMenuList::(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 | |
271 | void RenderMenuList::(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 | |
290 | String RenderMenuList::() const |
291 | { |
292 | return m_buttonText ? m_buttonText->text() : String(); |
293 | } |
294 | |
295 | LayoutRect RenderMenuList::(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 | |
313 | void RenderMenuList::(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 | |
320 | void RenderMenuList::() |
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) |
348 | NO_RETURN_DUE_TO_ASSERT |
349 | void RenderMenuList::showPopup() |
350 | { |
351 | ASSERT_NOT_REACHED(); |
352 | } |
353 | #else |
354 | void RenderMenuList::() |
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 | |
373 | void RenderMenuList::() |
374 | { |
375 | #if !PLATFORM(IOS_FAMILY) |
376 | if (m_popup) |
377 | m_popup->hide(); |
378 | #endif |
379 | } |
380 | |
381 | void RenderMenuList::(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 | |
391 | void RenderMenuList::(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow) |
392 | { |
393 | selectElement().listBoxSelectItem(listIndex, allowMultiplySelections, shift, fireOnChangeNow); |
394 | } |
395 | |
396 | bool RenderMenuList::() const |
397 | { |
398 | return selectElement().multiple(); |
399 | } |
400 | |
401 | void RenderMenuList::(int listIndex) |
402 | { |
403 | didUpdateActiveOption(selectElement().listToOptionIndex(listIndex)); |
404 | } |
405 | |
406 | void RenderMenuList::(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* = downcast<AccessibilityMenuList>(axCache->get(this))) |
424 | menuList->didUpdateActiveOption(optionIndex); |
425 | } |
426 | |
427 | String RenderMenuList::(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 | |
443 | String RenderMenuList::(unsigned) const |
444 | { |
445 | return String(); |
446 | } |
447 | |
448 | String RenderMenuList::(unsigned) const |
449 | { |
450 | return String(); |
451 | } |
452 | |
453 | String RenderMenuList::(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 | |
462 | String RenderMenuList::(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 | |
470 | bool RenderMenuList::(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 | |
490 | PopupMenuStyle RenderMenuList::(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 | |
515 | void RenderMenuList::(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 | |
544 | PopupMenuStyle RenderMenuList::() 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 | |
555 | HostWindow* RenderMenuList::() const |
556 | { |
557 | return view().frameView().hostWindow(); |
558 | } |
559 | |
560 | Ref<Scrollbar> RenderMenuList::(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 | |
568 | int RenderMenuList::() const |
569 | { |
570 | return 0; |
571 | } |
572 | |
573 | int RenderMenuList::() const |
574 | { |
575 | return 0; |
576 | } |
577 | |
578 | const int endOfLinePadding = 2; |
579 | |
580 | LayoutUnit RenderMenuList::() 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 | |
594 | LayoutUnit RenderMenuList::() 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 | |
602 | int RenderMenuList::() const |
603 | { |
604 | return selectElement().listItems().size(); |
605 | } |
606 | |
607 | int RenderMenuList::() const |
608 | { |
609 | return selectElement().optionToListIndex(selectElement().selectedIndex()); |
610 | } |
611 | |
612 | void RenderMenuList::() |
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 | |
620 | bool RenderMenuList::(unsigned listIndex) const |
621 | { |
622 | const Vector<HTMLElement*>& listItems = selectElement().listItems(); |
623 | return listIndex < listItems.size() && listItems[listIndex]->hasTagName(hrTag); |
624 | } |
625 | |
626 | bool RenderMenuList::(unsigned listIndex) const |
627 | { |
628 | const Vector<HTMLElement*>& listItems = selectElement().listItems(); |
629 | return listIndex < listItems.size() && is<HTMLOptGroupElement>(*listItems[listIndex]); |
630 | } |
631 | |
632 | bool RenderMenuList::(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 | |
641 | void RenderMenuList::(unsigned listIndex) |
642 | { |
643 | setTextFromOption(selectElement().listToOptionIndex(listIndex)); |
644 | } |
645 | |
646 | FontSelector* RenderMenuList::() const |
647 | { |
648 | return &document().fontSelector(); |
649 | } |
650 | |
651 | } |
652 | |