2011-04-19 Naoki Takano <takano.naoki@gmail.com>
[WebKit-https.git] / Source / WebCore / platform / chromium / PopupMenuChromium.cpp
1 /*
2  * Copyright (c) 2008, 2009, Google Inc. All rights reserved.
3  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "PopupMenuChromium.h"
34
35 #include "Chrome.h"
36 #include "ChromeClientChromium.h"
37 #include "Font.h"
38 #include "FontSelector.h"
39 #include "FrameView.h"
40 #include "Frame.h"
41 #include "FramelessScrollView.h"
42 #include "FramelessScrollViewClient.h"
43 #include "GraphicsContext.h"
44 #include "IntRect.h"
45 #include "KeyboardCodes.h"
46 #include "Page.h"
47 #include "PlatformKeyboardEvent.h"
48 #include "PlatformMouseEvent.h"
49 #include "PlatformScreen.h"
50 #include "PlatformWheelEvent.h"
51 #include "PopupMenuClient.h"
52 #include "RenderTheme.h"
53 #include "ScrollbarTheme.h"
54 #include "StringTruncator.h"
55 #include "SystemTime.h"
56 #include "TextRun.h"
57 #include "UserGestureIndicator.h"
58 #include <wtf/CurrentTime.h>
59 #include <wtf/unicode/CharacterNames.h>
60
61 using namespace WTF;
62 using namespace Unicode;
63
64 using std::min;
65 using std::max;
66
67 namespace WebCore {
68
69 typedef unsigned long long TimeStamp;
70
71 static const int kMaxVisibleRows = 20;
72 static const int kMaxHeight = 500;
73 static const int kBorderSize = 1;
74 static const int kTextToLabelPadding = 10;
75 static const int kLabelToIconPadding = 5;
76 static const int kMinEndOfLinePadding = 2;
77 static const TimeStamp kTypeAheadTimeoutMs = 1000;
78 static const int kLinePaddingHeight = 3; // Padding height put at the top and bottom of each line.
79
80 // The settings used for the drop down menu.
81 // This is the delegate used if none is provided.
82 static const PopupContainerSettings dropDownSettings = {
83     true, // setTextOnIndexChange
84     true, // acceptOnAbandon
85     false, // loopSelectionNavigation
86     false // restrictWidthOfListBox
87 };
88
89 // This class uses WebCore code to paint and handle events for a drop-down list
90 // box ("combobox" on Windows).
91 class PopupListBox : public FramelessScrollView {
92 public:
93     static PassRefPtr<PopupListBox> create(PopupMenuClient* client, const PopupContainerSettings& settings)
94     {
95         return adoptRef(new PopupListBox(client, settings));
96     }
97
98     // FramelessScrollView
99     virtual void paint(GraphicsContext*, const IntRect&);
100     virtual bool handleMouseDownEvent(const PlatformMouseEvent&);
101     virtual bool handleMouseMoveEvent(const PlatformMouseEvent&);
102     virtual bool handleMouseReleaseEvent(const PlatformMouseEvent&);
103     virtual bool handleWheelEvent(const PlatformWheelEvent&);
104     virtual bool handleKeyEvent(const PlatformKeyboardEvent&);
105
106     // ScrollView
107     virtual HostWindow* hostWindow() const;
108
109     // PopupListBox methods
110
111     // Hides the popup.
112     void hidePopup();
113
114     // Updates our internal list to match the client.
115     void updateFromElement();
116
117     // Frees any allocated resources used in a particular popup session.
118     void clear();
119
120     // Sets the index of the option that is displayed in the <select> widget in the page
121     void setOriginalIndex(int index);
122
123     // Gets the index of the item that the user is currently moused over or has selected with
124     // the keyboard. This is not the same as the original index, since the user has not yet
125     // accepted this input.
126     int selectedIndex() const { return m_selectedIndex; }
127
128     // Moves selection down/up the given number of items, scrolling if necessary.
129     // Positive is down.  The resulting index will be clamped to the range
130     // [0, numItems), and non-option items will be skipped.
131     void adjustSelectedIndex(int delta);
132
133     // Returns the number of items in the list.
134     int numItems() const { return static_cast<int>(m_items.size()); }
135
136     void setBaseWidth(int width) { m_baseWidth = width; }
137
138     // Computes the size of widget and children.
139     void layout();
140
141     // Returns whether the popup wants to process events for the passed key.
142     bool isInterestedInEventForKey(int keyCode);
143
144     // Gets the height of a row.
145     int getRowHeight(int index);
146
147     void setMaxHeight(int maxHeight) { m_maxHeight = maxHeight; }
148
149     void disconnectClient() { m_popupClient = 0; }
150
151     const Vector<PopupItem*>& items() const { return m_items; }
152
153 private:
154     friend class PopupContainer;
155     friend class RefCounted<PopupListBox>;
156
157     PopupListBox(PopupMenuClient* client, const PopupContainerSettings& settings)
158         : m_settings(settings)
159         , m_originalIndex(0)
160         , m_selectedIndex(0)
161         , m_acceptedIndexOnAbandon(-1)
162         , m_visibleRows(0)
163         , m_baseWidth(0)
164         , m_maxHeight(kMaxHeight)
165         , m_popupClient(client)
166         , m_repeatingChar(0)
167         , m_lastCharTime(0)
168     {
169         setScrollbarModes(ScrollbarAlwaysOff, ScrollbarAlwaysOff);
170     }
171
172     ~PopupListBox()
173     {
174         clear();
175     }
176
177     // Closes the popup
178     void abandon();
179
180     // Returns true if the selection can be changed to index.
181     // Disabled items, or labels cannot be selected.
182     bool isSelectableItem(int index);
183
184     // Select an index in the list, scrolling if necessary.
185     void selectIndex(int index);
186
187     // Accepts the selected index as the value to be displayed in the <select> widget on
188     // the web page, and closes the popup.
189     void acceptIndex(int index);
190
191     // Clears the selection (so no row appears selected).
192     void clearSelection();
193
194     // Scrolls to reveal the given index.
195     void scrollToRevealRow(int index);
196     void scrollToRevealSelection() { scrollToRevealRow(m_selectedIndex); }
197
198     // Invalidates the row at the given index.
199     void invalidateRow(int index);
200
201     // Get the bounds of a row.
202     IntRect getRowBounds(int index);
203
204     // Converts a point to an index of the row the point is over
205     int pointToRowIndex(const IntPoint&);
206
207     // Paint an individual row
208     void paintRow(GraphicsContext*, const IntRect&, int rowIndex);
209
210     // Test if the given point is within the bounds of the popup window.
211     bool isPointInBounds(const IntPoint&);
212
213     // Called when the user presses a text key.  Does a prefix-search of the items.
214     void typeAheadFind(const PlatformKeyboardEvent&);
215
216     // Returns the font to use for the given row
217     Font getRowFont(int index);
218
219     // Moves the selection down/up one item, taking care of looping back to the
220     // first/last element if m_loopSelectionNavigation is true.
221     void selectPreviousRow();
222     void selectNextRow();
223
224     // The settings that specify the behavior for this Popup window.
225     PopupContainerSettings m_settings;
226
227     // This is the index of the item marked as "selected" - i.e. displayed in the widget on the
228     // page.
229     int m_originalIndex;
230
231     // This is the index of the item that the user is hovered over or has selected using the
232     // keyboard in the list. They have not confirmed this selection by clicking or pressing
233     // enter yet however.
234     int m_selectedIndex;
235
236     // If >= 0, this is the index we should accept if the popup is "abandoned".
237     // This is used for keyboard navigation, where we want the
238     // selection to change immediately, and is only used if the settings
239     // acceptOnAbandon field is true.
240     int m_acceptedIndexOnAbandon;
241
242     // This is the number of rows visible in the popup. The maximum number visible at a time is
243     // defined as being kMaxVisibleRows. For a scrolled popup, this can be thought of as the
244     // page size in data units.
245     int m_visibleRows;
246
247     // Our suggested width, not including scrollbar.
248     int m_baseWidth;
249
250     // The maximum height we can be without being off-screen.
251     int m_maxHeight;
252
253     // A list of the options contained within the <select>
254     Vector<PopupItem*> m_items;
255
256     // The <select> PopupMenuClient that opened us.
257     PopupMenuClient* m_popupClient;
258
259     // The scrollbar which has mouse capture.  Mouse events go straight to this
260     // if non-NULL.
261     RefPtr<Scrollbar> m_capturingScrollbar;
262
263     // The last scrollbar that the mouse was over.  Used for mouseover highlights.
264     RefPtr<Scrollbar> m_lastScrollbarUnderMouse;
265
266     // The string the user has typed so far into the popup. Used for typeAheadFind.
267     String m_typedString;
268
269     // The char the user has hit repeatedly.  Used for typeAheadFind.
270     UChar m_repeatingChar;
271
272     // The last time the user hit a key.  Used for typeAheadFind.
273     TimeStamp m_lastCharTime;
274 };
275
276 static PlatformMouseEvent constructRelativeMouseEvent(const PlatformMouseEvent& e,
277                                                       FramelessScrollView* parent,
278                                                       FramelessScrollView* child)
279 {
280     IntPoint pos = parent->convertSelfToChild(child, e.pos());
281
282     // FIXME: This is a horrible hack since PlatformMouseEvent has no setters for x/y.
283     PlatformMouseEvent relativeEvent = e;
284     IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.pos());
285     relativePos.setX(pos.x());
286     relativePos.setY(pos.y());
287     return relativeEvent;
288 }
289
290 static PlatformWheelEvent constructRelativeWheelEvent(const PlatformWheelEvent& e,
291                                                       FramelessScrollView* parent,
292                                                       FramelessScrollView* child)
293 {
294     IntPoint pos = parent->convertSelfToChild(child, e.pos());
295
296     // FIXME: This is a horrible hack since PlatformWheelEvent has no setters for x/y.
297     PlatformWheelEvent relativeEvent = e;
298     IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.pos());
299     relativePos.setX(pos.x());
300     relativePos.setY(pos.y());
301     return relativeEvent;
302 }
303
304 ///////////////////////////////////////////////////////////////////////////////
305 // PopupContainer implementation
306
307 // static
308 PassRefPtr<PopupContainer> PopupContainer::create(PopupMenuClient* client,
309                                                   PopupType popupType,
310                                                   const PopupContainerSettings& settings)
311 {
312     return adoptRef(new PopupContainer(client, popupType, settings));
313 }
314
315 PopupContainer::PopupContainer(PopupMenuClient* client,
316                                PopupType popupType,
317                                const PopupContainerSettings& settings)
318     : m_listBox(PopupListBox::create(client, settings))
319     , m_settings(settings)
320     , m_popupType(popupType)
321     , m_popupOpen(false)
322 {
323     setScrollbarModes(ScrollbarAlwaysOff, ScrollbarAlwaysOff);
324 }
325
326 PopupContainer::~PopupContainer()
327 {
328     if (m_listBox && m_listBox->parent())
329         removeChild(m_listBox.get());
330 }
331
332 IntRect PopupContainer::layoutAndCalculateWidgetRect(int targetControlHeight, const IntPoint& popupInitialCoordinate)
333 {
334     // Reset the max height to its default value, it will be recomputed below
335     // if necessary.
336     m_listBox->setMaxHeight(kMaxHeight);
337
338     // Lay everything out to figure out our preferred size, then tell the view's
339     // WidgetClient about it.  It should assign us a client.
340     int rightOffset = layoutAndGetRightOffset();
341
342     // Assume m_listBox size is already calculated.
343     IntSize targetSize(m_listBox->width() + kBorderSize * 2,
344                        m_listBox->height() + kBorderSize * 2);
345
346     IntRect widgetRect;
347     ChromeClientChromium* chromeClient = chromeClientChromium();
348     if (chromeClient) {
349         // If the popup would extend past the bottom of the screen, open upwards
350         // instead.
351         FloatRect screen = screenAvailableRect(m_frameView.get());
352         // Use popupInitialCoordinate.x() + rightOffset because RTL position
353         // needs to be considered.
354         widgetRect = chromeClient->windowToScreen(IntRect(popupInitialCoordinate.x() + rightOffset, popupInitialCoordinate.y(), targetSize.width(), targetSize.height()));
355
356         // If we have multiple screens and the browser rect is in one screen, we have
357         // to clip the window width to the screen width.
358         FloatRect windowRect = chromeClient->windowRect();
359         if (windowRect.x() >= screen.x() && windowRect.maxX() <= screen.maxX()) {
360             if (m_listBox->m_popupClient->menuStyle().textDirection() == RTL && widgetRect.x() < screen.x()) {
361                 widgetRect.setWidth(widgetRect.maxX() - screen.x());
362                 widgetRect.setX(screen.x());
363             } else if (widgetRect.maxX() > screen.maxX())
364                 widgetRect.setWidth(screen.maxX() - widgetRect.x());
365         }
366
367         // Calculate Y axis size.
368         if (widgetRect.maxY() > static_cast<int>(screen.maxY())) {
369             if (widgetRect.y() - widgetRect.height() - targetControlHeight > 0) {
370                 // There is enough room to open upwards.
371                 widgetRect.move(0, -(widgetRect.height() + targetControlHeight));
372             } else {
373                 // Figure whether upwards or downwards has more room and set the
374                 // maximum number of items.
375                 int spaceAbove = widgetRect.y() - targetControlHeight;
376                 int spaceBelow = screen.maxY() - widgetRect.y();
377                 if (spaceAbove > spaceBelow)
378                     m_listBox->setMaxHeight(spaceAbove);
379                 else
380                     m_listBox->setMaxHeight(spaceBelow);
381                 layoutAndGetRightOffset();
382                 // Our height has changed, so recompute only Y axis of widgetRect.
383                 // We don't have to recompute X axis, so we only replace Y axis
384                 // in widgetRect.
385                 IntRect frameInScreen = chromeClient->windowToScreen(frameRect());
386                 widgetRect.setY(frameInScreen.y());
387                 widgetRect.setHeight(frameInScreen.height());
388                 // And move upwards if necessary.
389                 if (spaceAbove > spaceBelow)
390                     widgetRect.move(0, -(widgetRect.height() + targetControlHeight));
391             }
392         }
393     }
394     return widgetRect;
395 }
396
397 void PopupContainer::showPopup(FrameView* view)
398 {
399     m_frameView = view;
400
401     ChromeClientChromium* chromeClient = chromeClientChromium();
402     if (chromeClient) {
403         IntRect popupRect = frameRect();
404         chromeClient->popupOpened(this, layoutAndCalculateWidgetRect(popupRect.height(), popupRect.location()), false);
405         m_popupOpen = true;
406     }
407
408     if (!m_listBox->parent())
409         addChild(m_listBox.get());
410
411     // Enable scrollbars after the listbox is inserted into the hierarchy,
412     // so it has a proper WidgetClient.
413     m_listBox->setVerticalScrollbarMode(ScrollbarAuto);
414
415     m_listBox->scrollToRevealSelection();
416
417     invalidate();
418 }
419
420 void PopupContainer::hidePopup()
421 {
422     listBox()->hidePopup();
423 }
424
425 void PopupContainer::notifyPopupHidden()
426 {
427     if (!m_popupOpen)
428         return;
429     m_popupOpen = false;
430     chromeClientChromium()->popupClosed(this);
431 }
432
433 int PopupContainer::layoutAndGetRightOffset()
434 {
435     m_listBox->layout();
436
437     // Place the listbox within our border.
438     m_listBox->move(kBorderSize, kBorderSize);
439
440     // popupWidth is the width of <select> element. Record it before resize frame.
441     int popupWidth = frameRect().width();
442     // Size ourselves to contain listbox + border.
443     int listBoxWidth = m_listBox->width() + kBorderSize * 2;
444     resize(listBoxWidth, m_listBox->height() + kBorderSize * 2);
445
446     // Adjust the starting x-axis for RTL dropdown. For RTL dropdown, the right edge
447     // of dropdown box should be aligned with the right edge of <select> element box,
448     // and the dropdown box should be expanded to left if more space needed.
449     PopupMenuClient* popupClient = m_listBox->m_popupClient;
450     int rightOffset = 0;
451     if (popupClient) {
452         bool rightAligned = m_listBox->m_popupClient->menuStyle().textDirection() == RTL;
453         if (rightAligned)
454             rightOffset = popupWidth - listBoxWidth;
455     }
456     invalidate();
457
458     return rightOffset;
459 }
460
461 bool PopupContainer::handleMouseDownEvent(const PlatformMouseEvent& event)
462 {
463     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
464     return m_listBox->handleMouseDownEvent(
465         constructRelativeMouseEvent(event, this, m_listBox.get()));
466 }
467
468 bool PopupContainer::handleMouseMoveEvent(const PlatformMouseEvent& event)
469 {
470     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
471     return m_listBox->handleMouseMoveEvent(
472         constructRelativeMouseEvent(event, this, m_listBox.get()));
473 }
474
475 bool PopupContainer::handleMouseReleaseEvent(const PlatformMouseEvent& event)
476 {
477     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
478     return m_listBox->handleMouseReleaseEvent(
479         constructRelativeMouseEvent(event, this, m_listBox.get()));
480 }
481
482 bool PopupContainer::handleWheelEvent(const PlatformWheelEvent& event)
483 {
484     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
485     return m_listBox->handleWheelEvent(
486         constructRelativeWheelEvent(event, this, m_listBox.get()));
487 }
488
489 bool PopupContainer::handleKeyEvent(const PlatformKeyboardEvent& event)
490 {
491     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
492     return m_listBox->handleKeyEvent(event);
493 }
494
495 void PopupContainer::hide()
496 {
497     m_listBox->abandon();
498 }
499
500 void PopupContainer::paint(GraphicsContext* gc, const IntRect& rect)
501 {
502     // adjust coords for scrolled frame
503     IntRect r = intersection(rect, frameRect());
504     int tx = x();
505     int ty = y();
506
507     r.move(-tx, -ty);
508
509     gc->translate(static_cast<float>(tx), static_cast<float>(ty));
510     m_listBox->paint(gc, r);
511     gc->translate(-static_cast<float>(tx), -static_cast<float>(ty));
512
513     paintBorder(gc, rect);
514 }
515
516 void PopupContainer::paintBorder(GraphicsContext* gc, const IntRect& rect)
517 {
518     // FIXME: Where do we get the border color from?
519     Color borderColor(127, 157, 185);
520
521     gc->setStrokeStyle(NoStroke);
522     gc->setFillColor(borderColor, ColorSpaceDeviceRGB);
523
524     int tx = x();
525     int ty = y();
526
527     // top, left, bottom, right
528     gc->drawRect(IntRect(tx, ty, width(), kBorderSize));
529     gc->drawRect(IntRect(tx, ty, kBorderSize, height()));
530     gc->drawRect(IntRect(tx, ty + height() - kBorderSize, width(), kBorderSize));
531     gc->drawRect(IntRect(tx + width() - kBorderSize, ty, kBorderSize, height()));
532 }
533
534 bool PopupContainer::isInterestedInEventForKey(int keyCode)
535 {
536     return m_listBox->isInterestedInEventForKey(keyCode);
537 }
538
539 ChromeClientChromium* PopupContainer::chromeClientChromium()
540 {
541     return static_cast<ChromeClientChromium*>(m_frameView->frame()->page()->chrome()->client());
542 }
543
544 void PopupContainer::showInRect(const IntRect& r, FrameView* v, int index)
545 {
546     // The rect is the size of the select box. It's usually larger than we need.
547     // subtract border size so that usually the container will be displayed
548     // exactly the same width as the select box.
549     listBox()->setBaseWidth(max(r.width() - kBorderSize * 2, 0));
550
551     listBox()->updateFromElement();
552
553     // We set the selected item in updateFromElement(), and disregard the
554     // index passed into this function (same as Webkit's PopupMenuWin.cpp)
555     // FIXME: make sure this is correct, and add an assertion.
556     // ASSERT(popupWindow(popup)->listBox()->selectedIndex() == index);
557
558     // Convert point to main window coords.
559     IntPoint location = v->contentsToWindow(r.location());
560
561     // Move it below the select widget.
562     location.move(0, r.height());
563
564     setFrameRect(IntRect(location, r.size()));
565     showPopup(v);
566 }
567
568 void PopupContainer::refresh(const IntRect& targetControlRect)
569 {
570     IntPoint location = m_frameView->contentsToWindow(targetControlRect.location());
571     // Move it below the select widget.
572     location.move(0, targetControlRect.height());
573
574     listBox()->updateFromElement();
575     // Store the original size to check if we need to request the location.
576     IntSize originalSize = size();
577     IntRect widgetRect = layoutAndCalculateWidgetRect(targetControlRect.height(), location);
578     if (originalSize != widgetRect.size())
579         setFrameRect(widgetRect);
580
581     invalidate();
582 }
583
584 int PopupContainer::selectedIndex() const
585 {
586     return m_listBox->selectedIndex();
587 }
588
589 int PopupContainer::menuItemHeight() const
590 {
591     return m_listBox->getRowHeight(0);
592 }
593
594 int PopupContainer::menuItemFontSize() const
595 {
596     return m_listBox->getRowFont(0).size();
597 }
598
599 PopupMenuStyle PopupContainer::menuStyle() const
600 {
601     return m_listBox->m_popupClient->menuStyle();
602 }
603
604 const WTF::Vector<PopupItem*>& PopupContainer:: popupData() const
605 {
606     return m_listBox->items();
607 }
608
609 ///////////////////////////////////////////////////////////////////////////////
610 // PopupListBox implementation
611
612 bool PopupListBox::handleMouseDownEvent(const PlatformMouseEvent& event)
613 {
614     Scrollbar* scrollbar = scrollbarAtPoint(event.pos());
615     if (scrollbar) {
616         m_capturingScrollbar = scrollbar;
617         m_capturingScrollbar->mouseDown(event);
618         return true;
619     }
620
621     if (!isPointInBounds(event.pos()))
622         abandon();
623
624     return true;
625 }
626
627 bool PopupListBox::handleMouseMoveEvent(const PlatformMouseEvent& event)
628 {
629     if (m_capturingScrollbar) {
630         m_capturingScrollbar->mouseMoved(event);
631         return true;
632     }
633
634     Scrollbar* scrollbar = scrollbarAtPoint(event.pos());
635     if (m_lastScrollbarUnderMouse != scrollbar) {
636         // Send mouse exited to the old scrollbar.
637         if (m_lastScrollbarUnderMouse)
638             m_lastScrollbarUnderMouse->mouseExited();
639         m_lastScrollbarUnderMouse = scrollbar;
640     }
641
642     if (scrollbar) {
643         scrollbar->mouseMoved(event);
644         return true;
645     }
646
647     if (!isPointInBounds(event.pos()))
648         return false;
649
650     selectIndex(pointToRowIndex(event.pos()));
651     return true;
652 }
653
654 bool PopupListBox::handleMouseReleaseEvent(const PlatformMouseEvent& event)
655 {
656     if (m_capturingScrollbar) {
657         m_capturingScrollbar->mouseUp();
658         m_capturingScrollbar = 0;
659         return true;
660     }
661
662     if (!isPointInBounds(event.pos()))
663         return true;
664
665     acceptIndex(pointToRowIndex(event.pos()));
666     return true;
667 }
668
669 bool PopupListBox::handleWheelEvent(const PlatformWheelEvent& event)
670 {
671     if (!isPointInBounds(event.pos())) {
672         abandon();
673         return true;
674     }
675
676     // Pass it off to the scroll view.
677     // Sadly, WebCore devs don't understand the whole "const" thing.
678     wheelEvent(const_cast<PlatformWheelEvent&>(event));
679     return true;
680 }
681
682 // Should be kept in sync with handleKeyEvent().
683 bool PopupListBox::isInterestedInEventForKey(int keyCode)
684 {
685     switch (keyCode) {
686     case VKEY_ESCAPE:
687     case VKEY_RETURN:
688     case VKEY_UP:
689     case VKEY_DOWN:
690     case VKEY_PRIOR:
691     case VKEY_NEXT:
692     case VKEY_HOME:
693     case VKEY_END:
694     case VKEY_TAB:
695         return true;
696     default:
697         return false;
698     }
699 }
700
701 static bool isCharacterTypeEvent(const PlatformKeyboardEvent& event)
702 {
703     // Check whether the event is a character-typed event or not.
704     // We use RawKeyDown/Char/KeyUp event scheme on all platforms,
705     // so PlatformKeyboardEvent::Char (not RawKeyDown) type event
706     // is considered as character type event.
707     return event.type() == PlatformKeyboardEvent::Char;
708 }
709
710 bool PopupListBox::handleKeyEvent(const PlatformKeyboardEvent& event)
711 {
712     if (event.type() == PlatformKeyboardEvent::KeyUp)
713         return true;
714
715     if (numItems() == 0 && event.windowsVirtualKeyCode() != VKEY_ESCAPE)
716         return true;
717
718     switch (event.windowsVirtualKeyCode()) {
719     case VKEY_ESCAPE:
720         abandon();  // may delete this
721         return true;
722     case VKEY_RETURN:
723         if (m_selectedIndex == -1)  {
724             hidePopup();
725             // Don't eat the enter if nothing is selected.
726             return false;
727         }
728         acceptIndex(m_selectedIndex);  // may delete this
729         return true;
730     case VKEY_UP:
731         selectPreviousRow();
732         break;
733     case VKEY_DOWN:
734         selectNextRow();
735         break;
736     case VKEY_PRIOR:
737         adjustSelectedIndex(-m_visibleRows);
738         break;
739     case VKEY_NEXT:
740         adjustSelectedIndex(m_visibleRows);
741         break;
742     case VKEY_HOME:
743         adjustSelectedIndex(-m_selectedIndex);
744         break;
745     case VKEY_END:
746         adjustSelectedIndex(m_items.size());
747         break;
748     default:
749         if (!event.ctrlKey() && !event.altKey() && !event.metaKey()
750             && isPrintableChar(event.windowsVirtualKeyCode())
751             && isCharacterTypeEvent(event))
752             typeAheadFind(event);
753         break;
754     }
755
756     if (m_originalIndex != m_selectedIndex) {
757         // Keyboard events should update the selection immediately (but we don't
758         // want to fire the onchange event until the popup is closed, to match
759         // IE).  We change the original index so we revert to that when the
760         // popup is closed.
761         if (m_settings.acceptOnAbandon)
762             m_acceptedIndexOnAbandon = m_selectedIndex;
763
764         setOriginalIndex(m_selectedIndex);
765         if (m_settings.setTextOnIndexChange)
766             m_popupClient->setTextFromItem(m_selectedIndex);
767     }
768     if (event.windowsVirtualKeyCode() == VKEY_TAB) {
769         // TAB is a special case as it should select the current item if any and
770         // advance focus.
771         if (m_selectedIndex >= 0) {
772             acceptIndex(m_selectedIndex); // May delete us.
773             // Return false so the TAB key event is propagated to the page.
774             return false;
775         }
776         // Call abandon() so we honor m_acceptedIndexOnAbandon if set.
777         abandon();
778         // Return false so the TAB key event is propagated to the page.
779         return false;
780     }
781
782     return true;
783 }
784
785 HostWindow* PopupListBox::hostWindow() const
786 {
787     // Our parent is the root ScrollView, so it is the one that has a
788     // HostWindow.  FrameView::hostWindow() works similarly.
789     return parent() ? parent()->hostWindow() : 0;
790 }
791
792 // From HTMLSelectElement.cpp
793 static String stripLeadingWhiteSpace(const String& string)
794 {
795     int length = string.length();
796     int i;
797     for (i = 0; i < length; ++i)
798         if (string[i] != noBreakSpace
799             && (string[i] <= 0x7F ? !isspace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
800             break;
801
802     return string.substring(i, length - i);
803 }
804
805 // From HTMLSelectElement.cpp, with modifications
806 void PopupListBox::typeAheadFind(const PlatformKeyboardEvent& event)
807 {
808     TimeStamp now = static_cast<TimeStamp>(currentTime() * 1000.0f);
809     TimeStamp delta = now - m_lastCharTime;
810
811     // Reset the time when user types in a character. The time gap between
812     // last character and the current character is used to indicate whether
813     // user typed in a string or just a character as the search prefix.
814     m_lastCharTime = now;
815
816     UChar c = event.windowsVirtualKeyCode();
817
818     String prefix;
819     int searchStartOffset = 1;
820     if (delta > kTypeAheadTimeoutMs) {
821         m_typedString = prefix = String(&c, 1);
822         m_repeatingChar = c;
823     } else {
824         m_typedString.append(c);
825
826         if (c == m_repeatingChar)
827             // The user is likely trying to cycle through all the items starting with this character, so just search on the character
828             prefix = String(&c, 1);
829         else {
830             m_repeatingChar = 0;
831             prefix = m_typedString;
832             searchStartOffset = 0;
833         }
834     }
835
836     // Compute a case-folded copy of the prefix string before beginning the search for
837     // a matching element. This code uses foldCase to work around the fact that
838     // String::startWith does not fold non-ASCII characters. This code can be changed
839     // to use startWith once that is fixed.
840     String prefixWithCaseFolded(prefix.foldCase());
841     int itemCount = numItems();
842     int index = (max(0, m_selectedIndex) + searchStartOffset) % itemCount;
843     for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) {
844         if (!isSelectableItem(index))
845             continue;
846
847         if (stripLeadingWhiteSpace(m_items[index]->label).foldCase().startsWith(prefixWithCaseFolded)) {
848             selectIndex(index);
849             return;
850         }
851     }
852 }
853
854 void PopupListBox::paint(GraphicsContext* gc, const IntRect& rect)
855 {
856     // adjust coords for scrolled frame
857     IntRect r = intersection(rect, frameRect());
858     int tx = x() - scrollX();
859     int ty = y() - scrollY();
860
861     r.move(-tx, -ty);
862
863     // set clip rect to match revised damage rect
864     gc->save();
865     gc->translate(static_cast<float>(tx), static_cast<float>(ty));
866     gc->clip(r);
867
868     // FIXME: Can we optimize scrolling to not require repainting the entire
869     // window?  Should we?
870     for (int i = 0; i < numItems(); ++i)
871         paintRow(gc, r, i);
872
873     // Special case for an empty popup.
874     if (numItems() == 0)
875         gc->fillRect(r, Color::white, ColorSpaceDeviceRGB);
876
877     gc->restore();
878
879     ScrollView::paint(gc, rect);
880 }
881
882 static const int separatorPadding = 4;
883 static const int separatorHeight = 1;
884
885 void PopupListBox::paintRow(GraphicsContext* gc, const IntRect& rect, int rowIndex)
886 {
887     // This code is based largely on RenderListBox::paint* methods.
888
889     IntRect rowRect = getRowBounds(rowIndex);
890     if (!rowRect.intersects(rect))
891         return;
892
893     PopupMenuStyle style = m_popupClient->itemStyle(rowIndex);
894
895     // Paint background
896     Color backColor, textColor, labelColor;
897     if (rowIndex == m_selectedIndex) {
898         backColor = RenderTheme::defaultTheme()->activeListBoxSelectionBackgroundColor();
899         textColor = RenderTheme::defaultTheme()->activeListBoxSelectionForegroundColor();
900         labelColor = textColor;
901     } else {
902         backColor = style.backgroundColor();
903         textColor = style.foregroundColor();
904         // FIXME: for now the label color is hard-coded. It should be added to
905         // the PopupMenuStyle.
906         labelColor = Color(115, 115, 115);
907     }
908
909     // If we have a transparent background, make sure it has a color to blend
910     // against.
911     if (backColor.hasAlpha())
912         gc->fillRect(rowRect, Color::white, ColorSpaceDeviceRGB);
913
914     gc->fillRect(rowRect, backColor, ColorSpaceDeviceRGB);
915
916     // It doesn't look good but Autofill requires special style for separator.
917     // Autofill doesn't have padding and #dcdcdc color.
918     if (m_popupClient->itemIsSeparator(rowIndex)) {
919         int padding = style.menuType() == PopupMenuStyle::AutofillPopup ? 0 : separatorPadding;
920         IntRect separatorRect(
921             rowRect.x() + padding,
922             rowRect.y() + (rowRect.height() - separatorHeight) / 2,
923             rowRect.width() - 2 * padding, separatorHeight);
924         gc->fillRect(separatorRect, style.menuType() == PopupMenuStyle::AutofillPopup ? Color(0xdc, 0xdc, 0xdc) : textColor, ColorSpaceDeviceRGB);
925         return;
926     }
927
928     if (!style.isVisible())
929         return;
930
931     gc->setFillColor(textColor, ColorSpaceDeviceRGB);
932
933     Font itemFont = getRowFont(rowIndex);
934     // FIXME: http://crbug.com/19872 We should get the padding of individual option
935     // elements.  This probably implies changes to PopupMenuClient.
936     bool rightAligned = m_popupClient->menuStyle().textDirection() == RTL;
937     int textX = 0;
938     int maxWidth = 0;
939     if (rightAligned)
940         maxWidth = rowRect.width() - max(0, m_popupClient->clientPaddingRight() - m_popupClient->clientInsetRight());
941     else {
942         textX = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft());
943         maxWidth = rowRect.width() - textX;
944     }
945     // Prepare text to be drawn.
946     String itemText = m_popupClient->itemText(rowIndex);
947     String itemLabel = m_popupClient->itemLabel(rowIndex);
948     String itemIcon = m_popupClient->itemIcon(rowIndex);
949     if (m_settings.restrictWidthOfListBox) { // Truncate strings to fit in.
950         // FIXME: We should leftTruncate for the rtl case.
951         // StringTruncator::leftTruncate would have to be implemented.
952         String str = StringTruncator::rightTruncate(itemText, maxWidth, itemFont);
953         if (str != itemText) {
954             itemText = str;
955             // Don't display the label or icon, we already don't have enough room for the item text.
956             itemLabel = "";
957             itemIcon = "";
958         } else if (!itemLabel.isEmpty()) {
959             int availableWidth = maxWidth - kTextToLabelPadding -
960                 StringTruncator::width(itemText, itemFont);
961             itemLabel = StringTruncator::rightTruncate(itemLabel, availableWidth, itemFont);
962         }
963     }
964
965     // Prepare the directionality to draw text.
966     bool rtl = style.textDirection() == RTL;
967     TextRun textRun(itemText.characters(), itemText.length(), false, 0, 0, TextRun::AllowTrailingExpansion, rtl, style.hasTextDirectionOverride());
968     // If the text is right-to-left, make it right-aligned by adjusting its
969     // beginning position.
970     if (rightAligned)
971         textX += maxWidth - itemFont.width(textRun);
972
973     // Draw the item text.
974     int textY = rowRect.y() + itemFont.fontMetrics().ascent() + (rowRect.height() - itemFont.fontMetrics().height()) / 2;
975     gc->drawBidiText(itemFont, textRun, IntPoint(textX, textY));
976
977     // We are using the left padding as the right padding includes room for the scroll-bar which
978     // does not show in this case.
979     int rightPadding = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft());
980     int remainingWidth = rowRect.width() - rightPadding;
981
982     // Draw the icon if applicable.
983     RefPtr<Image> image(Image::loadPlatformResource(itemIcon.utf8().data()));
984     if (image && !image->isNull()) {
985         IntRect imageRect = image->rect();
986         remainingWidth -= (imageRect.width() + kLabelToIconPadding);
987         imageRect.setX(rowRect.width() - rightPadding - imageRect.width());
988         imageRect.setY(rowRect.y() + (rowRect.height() - imageRect.height()) / 2);
989         gc->drawImage(image.get(), ColorSpaceDeviceRGB, imageRect);
990     }
991
992     // Draw the the label if applicable.
993     if (itemLabel.isEmpty())
994         return;
995
996     // Autofill label is 0.9 smaller than regular font size.
997     if (style.menuType() == PopupMenuStyle::AutofillPopup) {
998         itemFont = m_popupClient->itemStyle(rowIndex).font();
999         FontDescription d = itemFont.fontDescription();
1000         d.setComputedSize(d.computedSize() * 0.9);
1001         itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
1002         itemFont.update(0);
1003     }
1004
1005     TextRun labelTextRun(itemLabel.characters(), itemLabel.length(), false, 0, 0, TextRun::AllowTrailingExpansion, rtl, style.hasTextDirectionOverride());
1006     if (rightAligned)
1007         textX = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft());
1008     else
1009         textX = remainingWidth - itemFont.width(labelTextRun);
1010
1011     gc->setFillColor(labelColor, ColorSpaceDeviceRGB);
1012     gc->drawBidiText(itemFont, labelTextRun, IntPoint(textX, textY));
1013 }
1014
1015 Font PopupListBox::getRowFont(int rowIndex)
1016 {
1017     Font itemFont = m_popupClient->itemStyle(rowIndex).font();
1018     if (m_popupClient->itemIsLabel(rowIndex)) {
1019         // Bold-ify labels (ie, an <optgroup> heading).
1020         FontDescription d = itemFont.fontDescription();
1021         d.setWeight(FontWeightBold);
1022         Font font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
1023         font.update(0);
1024         return font;
1025     }
1026
1027     return itemFont;
1028 }
1029
1030 void PopupListBox::abandon()
1031 {
1032     RefPtr<PopupListBox> keepAlive(this);
1033
1034     m_selectedIndex = m_originalIndex;
1035
1036     hidePopup();
1037
1038     if (m_acceptedIndexOnAbandon >= 0) {
1039         if (m_popupClient)
1040             m_popupClient->valueChanged(m_acceptedIndexOnAbandon);
1041         m_acceptedIndexOnAbandon = -1;
1042     }
1043 }
1044
1045 int PopupListBox::pointToRowIndex(const IntPoint& point)
1046 {
1047     int y = scrollY() + point.y();
1048
1049     // FIXME: binary search if perf matters.
1050     for (int i = 0; i < numItems(); ++i) {
1051         if (y < m_items[i]->yOffset)
1052             return i-1;
1053     }
1054
1055     // Last item?
1056     if (y < contentsHeight())
1057         return m_items.size()-1;
1058
1059     return -1;
1060 }
1061
1062 void PopupListBox::acceptIndex(int index)
1063 {
1064     // Clear m_acceptedIndexOnAbandon once user accepts the selected index.
1065     if (m_acceptedIndexOnAbandon >= 0)
1066         m_acceptedIndexOnAbandon = -1;
1067
1068     if (index >= numItems())
1069         return;
1070
1071     if (index < 0) {
1072         if (m_popupClient) {
1073             // Enter pressed with no selection, just close the popup.
1074             hidePopup();
1075         }
1076         return;
1077     }
1078
1079     if (isSelectableItem(index)) {
1080         RefPtr<PopupListBox> keepAlive(this);
1081
1082         // Hide ourselves first since valueChanged may have numerous side-effects.
1083         hidePopup();
1084
1085         // Tell the <select> PopupMenuClient what index was selected.
1086         m_popupClient->valueChanged(index);
1087     }
1088 }
1089
1090 void PopupListBox::selectIndex(int index)
1091 {
1092     if (index < 0 || index >= numItems())
1093         return;
1094
1095     bool isSelectable = isSelectableItem(index);
1096     if (index != m_selectedIndex && isSelectable) {
1097         invalidateRow(m_selectedIndex);
1098         m_selectedIndex = index;
1099         invalidateRow(m_selectedIndex);
1100
1101         scrollToRevealSelection();
1102         m_popupClient->selectionChanged(m_selectedIndex);
1103     } else if (!isSelectable) {
1104         clearSelection();
1105     }
1106 }
1107
1108 void PopupListBox::setOriginalIndex(int index)
1109 {
1110     m_originalIndex = m_selectedIndex = index;
1111 }
1112
1113 int PopupListBox::getRowHeight(int index)
1114 {
1115     if (index < 0)
1116         return 0;
1117
1118     if (m_popupClient->itemStyle(index).isDisplayNone())
1119         return 0;
1120
1121     // Separator row height is the same size as itself.
1122     if (m_popupClient->itemIsSeparator(index))
1123         return separatorHeight;
1124
1125     String icon = m_popupClient->itemIcon(index);
1126     RefPtr<Image> image(Image::loadPlatformResource(icon.utf8().data()));
1127
1128     int fontHeight = getRowFont(index).fontMetrics().height();
1129     int iconHeight = (image && !image->isNull()) ? image->rect().height() : 0;
1130
1131     int linePaddingHeight = m_popupClient->menuStyle().menuType() == PopupMenuStyle::AutofillPopup ? kLinePaddingHeight : 0;
1132     return max(fontHeight, iconHeight) + linePaddingHeight * 2;
1133 }
1134
1135 IntRect PopupListBox::getRowBounds(int index)
1136 {
1137     if (index < 0)
1138         return IntRect(0, 0, visibleWidth(), getRowHeight(index));
1139
1140     return IntRect(0, m_items[index]->yOffset, visibleWidth(), getRowHeight(index));
1141 }
1142
1143 void PopupListBox::invalidateRow(int index)
1144 {
1145     if (index < 0)
1146         return;
1147
1148     // Invalidate in the window contents, as FramelessScrollView::invalidateRect
1149     // paints in the window coordinates.
1150     invalidateRect(contentsToWindow(getRowBounds(index)));
1151 }
1152
1153 void PopupListBox::scrollToRevealRow(int index)
1154 {
1155     if (index < 0)
1156         return;
1157
1158     IntRect rowRect = getRowBounds(index);
1159
1160     if (rowRect.y() < scrollY()) {
1161         // Row is above current scroll position, scroll up.
1162         ScrollView::setScrollPosition(IntPoint(0, rowRect.y()));
1163     } else if (rowRect.maxY() > scrollY() + visibleHeight()) {
1164         // Row is below current scroll position, scroll down.
1165         ScrollView::setScrollPosition(IntPoint(0, rowRect.maxY() - visibleHeight()));
1166     }
1167 }
1168
1169 bool PopupListBox::isSelectableItem(int index)
1170 {
1171     ASSERT(index >= 0 && index < numItems());
1172     return m_items[index]->type == PopupItem::TypeOption && m_popupClient->itemIsEnabled(index);
1173 }
1174
1175 void PopupListBox::clearSelection()
1176 {
1177     if (m_selectedIndex != -1) {
1178         invalidateRow(m_selectedIndex);
1179         m_selectedIndex = -1;
1180         m_popupClient->selectionCleared();
1181     }
1182 }
1183
1184 void PopupListBox::selectNextRow()
1185 {
1186     if (!m_settings.loopSelectionNavigation || m_selectedIndex != numItems() - 1) {
1187         adjustSelectedIndex(1);
1188         return;
1189     }
1190
1191     // We are moving past the last item, no row should be selected.
1192     clearSelection();
1193 }
1194
1195 void PopupListBox::selectPreviousRow()
1196 {
1197     if (!m_settings.loopSelectionNavigation || m_selectedIndex > 0) {
1198         adjustSelectedIndex(-1);
1199         return;
1200     }
1201
1202     if (m_selectedIndex == 0) {
1203         // We are moving past the first item, clear the selection.
1204         clearSelection();
1205         return;
1206     }
1207
1208     // No row is selected, jump to the last item.
1209     selectIndex(numItems() - 1);
1210     scrollToRevealSelection();
1211 }
1212
1213 void PopupListBox::adjustSelectedIndex(int delta)
1214 {
1215     int targetIndex = m_selectedIndex + delta;
1216     targetIndex = min(max(targetIndex, 0), numItems() - 1);
1217     if (!isSelectableItem(targetIndex)) {
1218         // We didn't land on an option.  Try to find one.
1219         // We try to select the closest index to target, prioritizing any in
1220         // the range [current, target].
1221
1222         int dir = delta > 0 ? 1 : -1;
1223         int testIndex = m_selectedIndex;
1224         int bestIndex = m_selectedIndex;
1225         bool passedTarget = false;
1226         while (testIndex >= 0 && testIndex < numItems()) {
1227             if (isSelectableItem(testIndex))
1228                 bestIndex = testIndex;
1229             if (testIndex == targetIndex)
1230                 passedTarget = true;
1231             if (passedTarget && bestIndex != m_selectedIndex)
1232                 break;
1233
1234             testIndex += dir;
1235         }
1236
1237         // Pick the best index, which may mean we don't change.
1238         targetIndex = bestIndex;
1239     }
1240
1241     // Select the new index, and ensure its visible.  We do this regardless of
1242     // whether the selection changed to ensure keyboard events always bring the
1243     // selection into view.
1244     selectIndex(targetIndex);
1245     scrollToRevealSelection();
1246 }
1247
1248 void PopupListBox::hidePopup()
1249 {
1250     if (parent()) {
1251         PopupContainer* container = static_cast<PopupContainer*>(parent());
1252         if (container->client())
1253             container->client()->popupClosed(container);
1254         container->notifyPopupHidden();
1255     }
1256
1257     if (m_popupClient)
1258         m_popupClient->popupDidHide();
1259 }
1260
1261 void PopupListBox::updateFromElement()
1262 {
1263     clear();
1264
1265     int size = m_popupClient->listSize();
1266     for (int i = 0; i < size; ++i) {
1267         PopupItem::Type type;
1268         if (m_popupClient->itemIsSeparator(i))
1269             type = PopupItem::TypeSeparator;
1270         else if (m_popupClient->itemIsLabel(i))
1271             type = PopupItem::TypeGroup;
1272         else
1273             type = PopupItem::TypeOption;
1274         m_items.append(new PopupItem(m_popupClient->itemText(i), type));
1275         m_items[i]->enabled = isSelectableItem(i);
1276         PopupMenuStyle style = m_popupClient->itemStyle(i);
1277         m_items[i]->textDirection = style.textDirection();
1278         m_items[i]->hasTextDirectionOverride = style.hasTextDirectionOverride();
1279     }
1280
1281     m_selectedIndex = m_popupClient->selectedIndex();
1282     setOriginalIndex(m_selectedIndex);
1283
1284     layout();
1285 }
1286
1287 void PopupListBox::layout()
1288 {
1289     bool isRightAligned = m_popupClient->menuStyle().textDirection() == RTL;
1290     
1291     // Size our child items.
1292     int baseWidth = 0;
1293     int paddingWidth = 0;
1294     int lineEndPaddingWidth = 0;
1295     int y = 0;
1296     for (int i = 0; i < numItems(); ++i) {
1297         // Place the item vertically.
1298         m_items[i]->yOffset = y;
1299         if (m_popupClient->itemStyle(i).isDisplayNone())
1300             continue;
1301         y += getRowHeight(i);
1302
1303         // Ensure the popup is wide enough to fit this item.
1304         Font itemFont = getRowFont(i);
1305         String text = m_popupClient->itemText(i);
1306         String label = m_popupClient->itemLabel(i);
1307         String icon = m_popupClient->itemIcon(i);
1308         RefPtr<Image> iconImage(Image::loadPlatformResource(icon.utf8().data()));
1309         int width = 0;
1310         if (!text.isEmpty())
1311             width = itemFont.width(TextRun(text));
1312         if (!label.isEmpty()) {
1313             if (width > 0)
1314                 width += kTextToLabelPadding;
1315             width += itemFont.width(TextRun(label));
1316         }
1317         if (iconImage && !iconImage->isNull()) {
1318             if (width > 0)
1319                 width += kLabelToIconPadding;
1320             width += iconImage->rect().width();
1321         }
1322
1323         baseWidth = max(baseWidth, width);
1324         // FIXME: http://b/1210481 We should get the padding of individual option elements.
1325         paddingWidth = max(paddingWidth,
1326             m_popupClient->clientPaddingLeft() + m_popupClient->clientPaddingRight());
1327         lineEndPaddingWidth = max(lineEndPaddingWidth,
1328             isRightAligned ? m_popupClient->clientPaddingLeft() : m_popupClient->clientPaddingRight());
1329     }
1330
1331     // Calculate scroll bar width.
1332     int windowHeight = 0;
1333     m_visibleRows = min(numItems(), kMaxVisibleRows);
1334
1335     for (int i = 0; i < m_visibleRows; ++i) {
1336         int rowHeight = getRowHeight(i);
1337
1338         // Only clip the window height for non-Mac platforms.
1339         if (windowHeight + rowHeight > m_maxHeight) {
1340             m_visibleRows = i;
1341             break;
1342         }
1343
1344         windowHeight += rowHeight;
1345     }
1346
1347     // Set our widget and scrollable contents sizes.
1348     int scrollbarWidth = 0;
1349     if (m_visibleRows < numItems()) {
1350         scrollbarWidth = ScrollbarTheme::nativeTheme()->scrollbarThickness();
1351
1352         // Use kMinEndOfLinePadding when there is a scrollbar so that we use
1353         // as much as (lineEndPaddingWidth - kMinEndOfLinePadding) padding
1354         // space for scrollbar and allow user to use CSS padding to make the
1355         // popup listbox align with the select element.
1356         paddingWidth = paddingWidth - lineEndPaddingWidth + kMinEndOfLinePadding;
1357     }
1358
1359     int windowWidth;
1360     int contentWidth;
1361     if (m_settings.restrictWidthOfListBox) {
1362         windowWidth = m_baseWidth;
1363         contentWidth = m_baseWidth - scrollbarWidth;
1364     } else {
1365         windowWidth = baseWidth + scrollbarWidth + paddingWidth;
1366         contentWidth = baseWidth + paddingWidth;
1367
1368         if (windowWidth < m_baseWidth) {
1369             windowWidth = m_baseWidth;
1370             contentWidth = m_baseWidth - scrollbarWidth;
1371         } else
1372             m_baseWidth = baseWidth;
1373     }
1374
1375     resize(windowWidth, windowHeight);
1376     setContentsSize(IntSize(contentWidth, getRowBounds(numItems() - 1).maxY()));
1377
1378     if (hostWindow())
1379         scrollToRevealSelection();
1380
1381     invalidate();
1382 }
1383
1384 void PopupListBox::clear()
1385 {
1386     for (Vector<PopupItem*>::iterator it = m_items.begin(); it != m_items.end(); ++it)
1387         delete *it;
1388     m_items.clear();
1389 }
1390
1391 bool PopupListBox::isPointInBounds(const IntPoint& point)
1392 {
1393     return numItems() != 0 && IntRect(0, 0, width(), height()).contains(point);
1394 }
1395
1396 ///////////////////////////////////////////////////////////////////////////////
1397 // PopupMenuChromium implementation
1398 //
1399 // Note: you cannot add methods to this class, since it is defined above the
1400 //       portability layer. To access methods and properties on the
1401 //       popup widgets, use |popupWindow| above.
1402
1403 PopupMenuChromium::PopupMenuChromium(PopupMenuClient* client)
1404     : m_popupClient(client)
1405 {
1406 }
1407
1408 PopupMenuChromium::~PopupMenuChromium()
1409 {
1410     // When the PopupMenuChromium is destroyed, the client could already have been
1411     // deleted.
1412     if (p.popup)
1413         p.popup->listBox()->disconnectClient();
1414     hide();
1415 }
1416
1417 void PopupMenuChromium::show(const IntRect& r, FrameView* v, int index)
1418 {
1419     if (!p.popup)
1420         p.popup = PopupContainer::create(client(), PopupContainer::Select, dropDownSettings);
1421     p.popup->showInRect(r, v, index);
1422 }
1423
1424 void PopupMenuChromium::hide()
1425 {
1426     if (p.popup)
1427         p.popup->hide();
1428 }
1429
1430 void PopupMenuChromium::updateFromElement()
1431 {
1432     p.popup->listBox()->updateFromElement();
1433 }
1434
1435
1436 void PopupMenuChromium::disconnectClient()
1437 {
1438     m_popupClient = 0;
1439 }
1440
1441 } // namespace WebCore