Fix the issue that Web Audio test case fails on PR3.
[framework/web/webkit-efl.git] / Source / WebCore / rendering / RenderMenuList.cpp
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 "RenderBR.h"
43 #include "RenderScrollbar.h"
44 #include "RenderTheme.h"
45 #include "Settings.h"
46 #include "StyleResolver.h"
47 #include "TextRun.h"
48 #include <math.h>
49
50 using namespace std;
51
52 namespace WebCore {
53
54 using namespace HTMLNames;
55
56 RenderMenuList::RenderMenuList(Element* element)
57     : RenderDeprecatedFlexibleBox(element)
58     , m_buttonText(0)
59     , m_innerBlock(0)
60     , m_optionsChanged(true)
61     , m_optionsWidth(0)
62     , m_lastActiveIndex(-1)
63     , m_popupIsVisible(false)
64 {
65     ASSERT(element);
66     ASSERT(element->isHTMLElement());
67     ASSERT(element->hasTagName(HTMLNames::selectTag));
68 }
69
70 RenderMenuList::~RenderMenuList()
71 {
72     if (m_popup)
73         m_popup->disconnectClient();
74     m_popup = 0;
75 }
76
77 void RenderMenuList::createInnerBlock()
78 {
79     if (m_innerBlock) {
80         ASSERT(firstChild() == m_innerBlock);
81         ASSERT(!m_innerBlock->nextSibling());
82         return;
83     }
84
85     // Create an anonymous block.
86     ASSERT(!firstChild());
87     m_innerBlock = createAnonymousBlock();
88     adjustInnerStyle();
89     RenderDeprecatedFlexibleBox::addChild(m_innerBlock);
90 }
91
92 void RenderMenuList::adjustInnerStyle()
93 {
94     RenderStyle* innerStyle = m_innerBlock->style();
95     innerStyle->setBoxFlex(1);
96     
97     innerStyle->setPaddingLeft(Length(theme()->popupInternalPaddingLeft(style()), Fixed));
98     innerStyle->setPaddingRight(Length(theme()->popupInternalPaddingRight(style()), Fixed));
99     innerStyle->setPaddingTop(Length(theme()->popupInternalPaddingTop(style()), Fixed));
100     innerStyle->setPaddingBottom(Length(theme()->popupInternalPaddingBottom(style()), Fixed));
101
102     if (document()->page()->chrome()->selectItemWritingDirectionIsNatural()) {
103         // Items in the popup will not respect the CSS text-align and direction properties,
104         // so we must adjust our own style to match.
105         innerStyle->setTextAlign(LEFT);
106         TextDirection direction = (m_buttonText && m_buttonText->text()->defaultWritingDirection() == WTF::Unicode::RightToLeft) ? RTL : LTR;
107         innerStyle->setDirection(direction);
108     } else if (m_optionStyle && document()->page()->chrome()->selectItemAlignmentFollowsMenuWritingDirection()) {
109         if ((m_optionStyle->direction() != innerStyle->direction() || m_optionStyle->unicodeBidi() != innerStyle->unicodeBidi()))
110             m_innerBlock->setNeedsLayoutAndPrefWidthsRecalc();
111         innerStyle->setTextAlign(style()->isLeftToRightDirection() ? LEFT : RIGHT);
112         innerStyle->setDirection(m_optionStyle->direction());
113         innerStyle->setUnicodeBidi(m_optionStyle->unicodeBidi());
114     }
115 }
116
117 void RenderMenuList::addChild(RenderObject* newChild, RenderObject* beforeChild)
118 {
119     createInnerBlock();
120     m_innerBlock->addChild(newChild, beforeChild);
121     ASSERT(m_innerBlock == firstChild());
122
123     if (AXObjectCache::accessibilityEnabled())
124         document()->axObjectCache()->childrenChanged(this);
125 }
126
127 void RenderMenuList::removeChild(RenderObject* oldChild)
128 {
129     if (oldChild == m_innerBlock || !m_innerBlock) {
130         RenderDeprecatedFlexibleBox::removeChild(oldChild);
131         m_innerBlock = 0;
132     } else
133         m_innerBlock->removeChild(oldChild);
134 }
135
136 void RenderMenuList::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
137 {
138     RenderBlock::styleDidChange(diff, oldStyle);
139
140     if (m_buttonText)
141         m_buttonText->setStyle(style());
142     if (m_innerBlock) // RenderBlock handled updating the anonymous block's style.
143         adjustInnerStyle();
144
145     bool fontChanged = !oldStyle || oldStyle->font() != style()->font();
146     if (fontChanged)
147         updateOptionsWidth();
148 }
149
150 void RenderMenuList::updateOptionsWidth()
151 {
152     float maxOptionWidth = 0;
153     const Vector<HTMLElement*>& listItems = toHTMLSelectElement(node())->listItems();
154     int size = listItems.size();    
155     FontCachePurgePreventer fontCachePurgePreventer;
156
157     for (int i = 0; i < size; ++i) {
158         HTMLElement* element = listItems[i];
159         if (!element->hasTagName(optionTag))
160             continue;
161
162         String text = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
163         applyTextTransform(style(), text, ' ');
164         if (theme()->popupOptionSupportsTextIndent()) {
165             // Add in the option's text indent.  We can't calculate percentage values for now.
166             float optionWidth = 0;
167             if (RenderStyle* optionStyle = element->renderStyle())
168                 optionWidth += minimumValueForLength(optionStyle->textIndent(), 0, view());
169             if (!text.isEmpty())
170                 optionWidth += style()->font().width(text);
171             maxOptionWidth = max(maxOptionWidth, optionWidth);
172         } else if (!text.isEmpty())
173             maxOptionWidth = max(maxOptionWidth, style()->font().width(text));
174     }
175
176     int width = static_cast<int>(ceilf(maxOptionWidth));
177     if (m_optionsWidth == width)
178         return;
179
180     m_optionsWidth = width;
181     if (parent())
182         setNeedsLayoutAndPrefWidthsRecalc();
183 }
184
185 void RenderMenuList::updateFromElement()
186 {
187     if (m_optionsChanged) {
188         updateOptionsWidth();
189         m_optionsChanged = false;
190     }
191
192     if (m_popupIsVisible)
193         m_popup->updateFromElement();
194     else
195         setTextFromOption(toHTMLSelectElement(node())->selectedIndex());
196 }
197
198 void RenderMenuList::setTextFromOption(int optionIndex)
199 {
200     HTMLSelectElement* select = toHTMLSelectElement(node());
201     const Vector<HTMLElement*>& listItems = select->listItems();
202     int size = listItems.size();
203
204     int i = select->optionToListIndex(optionIndex);
205     String text = "";
206     if (i >= 0 && i < size) {
207         Element* element = listItems[i];
208         if (element->hasTagName(optionTag)) {
209             text = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
210             m_optionStyle = element->renderStyle();
211         }
212     }
213
214     setText(text.stripWhiteSpace());
215     didUpdateActiveOption(optionIndex);
216 }
217
218 void RenderMenuList::setText(const String& s)
219 {
220     if (s.isEmpty()) {
221         if (!m_buttonText || !m_buttonText->isBR()) {
222             if (m_buttonText)
223                 m_buttonText->destroy();
224             m_buttonText = new (renderArena()) RenderBR(document());
225             m_buttonText->setStyle(style());
226             addChild(m_buttonText);
227         }
228     } else {
229         if (m_buttonText && !m_buttonText->isBR())
230             m_buttonText->setText(s.impl(), true);
231         else {
232             if (m_buttonText)
233                 m_buttonText->destroy();
234             m_buttonText = new (renderArena()) RenderText(document(), s.impl());
235             m_buttonText->setStyle(style());
236             addChild(m_buttonText);
237         }
238         adjustInnerStyle();
239     }
240 }
241
242 String RenderMenuList::text() const
243 {
244     return m_buttonText ? m_buttonText->text() : 0;
245 }
246
247 LayoutRect RenderMenuList::controlClipRect(const LayoutPoint& additionalOffset) const
248 {
249     // Clip to the intersection of the content box and the content box for the inner box
250     // This will leave room for the arrows which sit in the inner box padding,
251     // and if the inner box ever spills out of the outer box, that will get clipped too.
252     LayoutRect outerBox(additionalOffset.x() + borderLeft() + paddingLeft(), 
253                    additionalOffset.y() + borderTop() + paddingTop(),
254                    contentWidth(), 
255                    contentHeight());
256     
257     LayoutRect innerBox(additionalOffset.x() + m_innerBlock->x() + m_innerBlock->paddingLeft(), 
258                    additionalOffset.y() + m_innerBlock->y() + m_innerBlock->paddingTop(),
259                    m_innerBlock->contentWidth(), 
260                    m_innerBlock->contentHeight());
261
262     return intersection(outerBox, innerBox);
263 }
264
265 void RenderMenuList::computePreferredLogicalWidths()
266 {
267     m_minPreferredLogicalWidth = 0;
268     m_maxPreferredLogicalWidth = 0;
269     
270     if (style()->width().isFixed() && style()->width().value() > 0)
271         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
272     else
273         m_maxPreferredLogicalWidth = max(m_optionsWidth, theme()->minimumMenuListSize(style())) + m_innerBlock->paddingLeft() + m_innerBlock->paddingRight();
274
275     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
276         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
277         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
278     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
279         m_minPreferredLogicalWidth = 0;
280     else
281         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
282
283     if (style()->maxWidth().isFixed()) {
284         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
285         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
286     }
287
288     LayoutUnit toAdd = borderAndPaddingWidth();
289     m_minPreferredLogicalWidth += toAdd;
290     m_maxPreferredLogicalWidth += toAdd;
291
292     setPreferredLogicalWidthsDirty(false);
293 }
294
295 void RenderMenuList::showPopup()
296 {
297     if (m_popupIsVisible)
298         return;
299
300     if (document()->page()->chrome()->hasOpenedPopup())
301         return;
302
303     // Create m_innerBlock here so it ends up as the first child.
304     // This is important because otherwise we might try to create m_innerBlock
305     // inside the showPopup call and it would fail.
306     createInnerBlock();
307     if (!m_popup)
308         m_popup = document()->page()->chrome()->createPopupMenu(this);
309     m_popupIsVisible = true;
310
311     // Compute the top left taking transforms into account, but use
312     // the actual width of the element to size the popup.
313     FloatPoint absTopLeft = localToAbsolute(FloatPoint(), false, true);
314     IntRect absBounds = absoluteBoundingBoxRectIgnoringTransforms();
315     absBounds.setLocation(roundedIntPoint(absTopLeft));
316     HTMLSelectElement* select = toHTMLSelectElement(node());
317     m_popup->show(absBounds, document()->view(), select->optionToListIndex(select->selectedIndex()));
318 }
319
320 void RenderMenuList::hidePopup()
321 {
322     if (m_popup)
323         m_popup->hide();
324 }
325
326 void RenderMenuList::valueChanged(unsigned listIndex, bool fireOnChange)
327 {
328     // Check to ensure a page navigation has not occurred while
329     // the popup was up.
330     Document* doc = static_cast<Element*>(node())->document();
331     if (!doc || doc != doc->frame()->document())
332         return;
333     
334     HTMLSelectElement* select = toHTMLSelectElement(node());
335     select->optionSelectedByUser(select->listToOptionIndex(listIndex), fireOnChange);
336 }
337
338 void RenderMenuList::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
339 {
340     toHTMLSelectElement(node())->listBoxSelectItem(listIndex, allowMultiplySelections, shift, fireOnChangeNow);
341 }
342
343 bool RenderMenuList::multiple() const
344 {
345     return toHTMLSelectElement(node())->multiple();
346 }
347
348 void RenderMenuList::didSetSelectedIndex(int listIndex)
349 {
350     didUpdateActiveOption(toHTMLSelectElement(node())->listToOptionIndex(listIndex));
351 }
352
353 void RenderMenuList::didUpdateActiveOption(int optionIndex)
354 {
355     if (!AXObjectCache::accessibilityEnabled())
356         return;
357
358     if (m_lastActiveIndex == optionIndex)
359         return;
360     m_lastActiveIndex = optionIndex;
361
362     HTMLSelectElement* select = toHTMLSelectElement(node());
363     int listIndex = select->optionToListIndex(optionIndex);
364     if (listIndex < 0 || listIndex >= static_cast<int>(select->listItems().size()))
365         return;
366
367     ASSERT(select->listItems()[listIndex]);
368
369     if (AccessibilityMenuList* menuList = static_cast<AccessibilityMenuList*>(document()->axObjectCache()->get(this)))
370         menuList->didUpdateActiveOption(optionIndex);
371 }
372
373 String RenderMenuList::itemText(unsigned listIndex) const
374 {
375     HTMLSelectElement* select = toHTMLSelectElement(node());
376     const Vector<HTMLElement*>& listItems = select->listItems();
377     if (listIndex >= listItems.size())
378         return String();
379
380     String itemString;
381     Element* element = listItems[listIndex];
382     if (element->hasTagName(optgroupTag))
383         itemString = static_cast<const HTMLOptGroupElement*>(element)->groupLabelText();
384     else if (element->hasTagName(optionTag))
385         itemString = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
386
387     applyTextTransform(style(), itemString, ' ');
388     return itemString;
389 }
390
391 String RenderMenuList::itemLabel(unsigned) const
392 {
393     return String();
394 }
395
396 String RenderMenuList::itemIcon(unsigned) const
397 {
398     return String();
399 }
400
401 String RenderMenuList::itemAccessibilityText(unsigned listIndex) const
402 {
403     // Allow the accessible name be changed if necessary.
404     const Vector<HTMLElement*>& listItems = toHTMLSelectElement(node())->listItems();
405     if (listIndex >= listItems.size())
406         return String();
407     return listItems[listIndex]->fastGetAttribute(aria_labelAttr);
408 }
409     
410 String RenderMenuList::itemToolTip(unsigned listIndex) const
411 {
412     const Vector<HTMLElement*>& listItems = toHTMLSelectElement(node())->listItems();
413     if (listIndex >= listItems.size())
414         return String();
415     return listItems[listIndex]->title();
416 }
417
418 bool RenderMenuList::itemIsEnabled(unsigned listIndex) const
419 {
420     const Vector<HTMLElement*>& listItems = toHTMLSelectElement(node())->listItems();
421     if (listIndex >= listItems.size())
422         return false;
423     HTMLElement* element = listItems[listIndex];
424     if (!element->hasTagName(optionTag))
425         return false;
426
427     bool groupEnabled = true;
428     if (Element* parentElement = element->parentElement()) {
429         if (parentElement->hasTagName(optgroupTag))
430             groupEnabled = !static_cast<HTMLOptGroupElement*>(parentElement)->disabled();
431     }
432     if (!groupEnabled)
433         return false;
434
435     return element->isEnabledFormControl();
436 }
437
438 PopupMenuStyle RenderMenuList::itemStyle(unsigned listIndex) const
439 {
440     const Vector<HTMLElement*>& listItems = toHTMLSelectElement(node())->listItems();
441     if (listIndex >= listItems.size()) {
442         // If we are making an out of bounds access, then we want to use the style
443         // of a different option element (index 0). However, if there isn't an option element
444         // before at index 0, we fall back to the menu's style.
445         if (!listIndex)
446             return menuStyle();
447
448         // Try to retrieve the style of an option element we know exists (index 0).
449         listIndex = 0;
450     }
451     HTMLElement* element = listItems[listIndex];
452     
453     RenderStyle* style = element->renderStyle() ? element->renderStyle() : element->computedStyle();
454     return style ? PopupMenuStyle(style->visitedDependentColor(CSSPropertyColor), itemBackgroundColor(listIndex), style->font(), style->visibility() == VISIBLE,
455         style->display() == NONE, style->textIndent(), style->direction(), isOverride(style->unicodeBidi())) : menuStyle();
456 }
457
458 Color RenderMenuList::itemBackgroundColor(unsigned listIndex) const
459 {
460     const Vector<HTMLElement*>& listItems = toHTMLSelectElement(node())->listItems();
461     if (listIndex >= listItems.size())
462         return style()->visitedDependentColor(CSSPropertyBackgroundColor);
463     HTMLElement* element = listItems[listIndex];
464
465     Color backgroundColor;
466     if (element->renderStyle())
467         backgroundColor = element->renderStyle()->visitedDependentColor(CSSPropertyBackgroundColor);
468     // If the item has an opaque background color, return that.
469     if (!backgroundColor.hasAlpha())
470         return backgroundColor;
471
472     // Otherwise, the item's background is overlayed on top of the menu background.
473     backgroundColor = style()->visitedDependentColor(CSSPropertyBackgroundColor).blend(backgroundColor);
474     if (!backgroundColor.hasAlpha())
475         return backgroundColor;
476
477     // If the menu background is not opaque, then add an opaque white background behind.
478     return Color(Color::white).blend(backgroundColor);
479 }
480
481 PopupMenuStyle RenderMenuList::menuStyle() const
482 {
483     RenderStyle* s = m_innerBlock ? m_innerBlock->style() : style();
484     return PopupMenuStyle(s->visitedDependentColor(CSSPropertyColor), s->visitedDependentColor(CSSPropertyBackgroundColor), s->font(), s->visibility() == VISIBLE,
485         s->display() == NONE, s->textIndent(), style()->direction(), isOverride(style()->unicodeBidi()));
486 }
487
488 HostWindow* RenderMenuList::hostWindow() const
489 {
490     return document()->view()->hostWindow();
491 }
492
493 PassRefPtr<Scrollbar> RenderMenuList::createScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, ScrollbarControlSize controlSize)
494 {
495     RefPtr<Scrollbar> widget;
496     bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR);
497     if (hasCustomScrollbarStyle)
498         widget = RenderScrollbar::createCustomScrollbar(scrollableArea, orientation, this->node());
499     else
500         widget = Scrollbar::createNativeScrollbar(scrollableArea, orientation, controlSize);
501     return widget.release();
502 }
503
504 int RenderMenuList::clientInsetLeft() const
505 {
506     return 0;
507 }
508
509 int RenderMenuList::clientInsetRight() const
510 {
511     return 0;
512 }
513
514 LayoutUnit RenderMenuList::clientPaddingLeft() const
515 {
516     return paddingLeft() + m_innerBlock->paddingLeft();
517 }
518
519 const int endOfLinePadding = 2;
520 LayoutUnit RenderMenuList::clientPaddingRight() const
521 {
522     if (style()->appearance() == MenulistPart || style()->appearance() == MenulistButtonPart) {
523         // For these appearance values, the theme applies padding to leave room for the
524         // drop-down button. But leaving room for the button inside the popup menu itself
525         // looks strange, so we return a small default padding to avoid having a large empty
526         // space appear on the side of the popup menu.
527         return endOfLinePadding;
528     }
529
530     // If the appearance isn't MenulistPart, then the select is styled (non-native), so
531     // we want to return the user specified padding.
532     return paddingRight() + m_innerBlock->paddingRight();
533 }
534
535 int RenderMenuList::listSize() const
536 {
537     return toHTMLSelectElement(node())->listItems().size();
538 }
539
540 int RenderMenuList::selectedIndex() const
541 {
542     HTMLSelectElement* select = toHTMLSelectElement(node());
543     return select->optionToListIndex(select->selectedIndex());
544 }
545
546 void RenderMenuList::popupDidHide()
547 {
548     m_popupIsVisible = false;
549 }
550
551 bool RenderMenuList::itemIsSeparator(unsigned listIndex) const
552 {
553     const Vector<HTMLElement*>& listItems = toHTMLSelectElement(node())->listItems();
554     return listIndex < listItems.size() && listItems[listIndex]->hasTagName(hrTag);
555 }
556
557 bool RenderMenuList::itemIsLabel(unsigned listIndex) const
558 {
559     const Vector<HTMLElement*>& listItems = toHTMLSelectElement(node())->listItems();
560     return listIndex < listItems.size() && listItems[listIndex]->hasTagName(optgroupTag);
561 }
562
563 bool RenderMenuList::itemIsSelected(unsigned listIndex) const
564 {
565     const Vector<HTMLElement*>& listItems = toHTMLSelectElement(node())->listItems();
566     if (listIndex >= listItems.size())
567         return false;
568     HTMLElement* element = listItems[listIndex];
569     return element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected();
570 }
571
572 void RenderMenuList::setTextFromItem(unsigned listIndex)
573 {
574     setTextFromOption(toHTMLSelectElement(node())->listToOptionIndex(listIndex));
575 }
576
577 FontSelector* RenderMenuList::fontSelector() const
578 {
579     return document()->styleResolver()->fontSelector();
580 }
581
582 }