2369a9ab13e30fd05d15989dc320635b6ff68a33
[WebKit.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 TimeStamp kTypeAheadTimeoutMs = 1000;
77
78 // The settings used for the drop down menu.
79 // This is the delegate used if none is provided.
80 static const PopupContainerSettings dropDownSettings = {
81     true,   // setTextOnIndexChange
82     true,   // acceptOnAbandon
83     false,  // loopSelectionNavigation
84     false,  // restrictWidthOfListBox
85     // display item text in its first strong directional character's directionality.
86     PopupContainerSettings::FirstStrongDirectionalCharacterDirection,
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, int popupInitialY)
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     layout();
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 this::x() for location because RTL position is considered
353         // in layout().
354         widgetRect = chromeClient->windowToScreen(IntRect(x(), popupInitialY, targetSize.width(), targetSize.height()));
355         if (widgetRect.maxY() > static_cast<int>(screen.maxY())) {
356             if (widgetRect.y() - widgetRect.height() - targetControlHeight > 0) {
357                 // There is enough room to open upwards.
358                 widgetRect.move(0, -(widgetRect.height() + targetControlHeight));
359             } else {
360                 // Figure whether upwards or downwards has more room and set the
361                 // maximum number of items.
362                 int spaceAbove = widgetRect.y() - targetControlHeight;
363                 int spaceBelow = screen.maxY() - widgetRect.y();
364                 if (spaceAbove > spaceBelow)
365                     m_listBox->setMaxHeight(spaceAbove);
366                 else
367                     m_listBox->setMaxHeight(spaceBelow);
368                 layout();
369                 // Our size has changed, recompute the widgetRect.
370                 widgetRect = chromeClient->windowToScreen(frameRect());
371                 // And move upwards if necessary.
372                 if (spaceAbove > spaceBelow)
373                     widgetRect.move(0, -(widgetRect.height() + targetControlHeight));
374             }
375         }
376     }
377     return widgetRect;
378 }
379
380 void PopupContainer::showPopup(FrameView* view)
381 {
382     m_frameView = view;
383
384     ChromeClientChromium* chromeClient = chromeClientChromium();
385     if (chromeClient) {
386         IntRect popupRect = frameRect();
387         chromeClient->popupOpened(this, layoutAndCalculateWidgetRect(popupRect.height(), popupRect.y()), false);
388         m_popupOpen = true;
389     }
390
391     if (!m_listBox->parent())
392         addChild(m_listBox.get());
393
394     // Enable scrollbars after the listbox is inserted into the hierarchy,
395     // so it has a proper WidgetClient.
396     m_listBox->setVerticalScrollbarMode(ScrollbarAuto);
397
398     m_listBox->scrollToRevealSelection();
399
400     invalidate();
401 }
402
403 void PopupContainer::showExternal(const IntRect& rect, FrameView* v, int index)
404 {
405     if (!listBox())
406         return;
407
408     listBox()->setBaseWidth(rect.width());
409     listBox()->updateFromElement();
410
411     if (listBox()->numItems() < 1) {
412         hidePopup();
413         return;
414     }
415
416     // Adjust the popup position to account for scrolling.
417     IntPoint location = v->contentsToWindow(rect.location());
418     IntRect popupRect(location, rect.size());
419
420     // Get the ChromeClient and pass it the popup menu's listbox data.
421     m_frameView = v;
422     chromeClientChromium()->popupOpened(this, popupRect, true);
423
424     // The popup sends its "closed" notification through its parent. Set the
425     // parent, even though external popups have no real on-screen widget but a
426     // native menu (see |PopupListBox::hidePopup()|);
427     if (!m_listBox->parent())
428         addChild(m_listBox.get());
429 }
430
431 void PopupContainer::hidePopup()
432 {
433     listBox()->hidePopup();
434 }
435
436 void PopupContainer::notifyPopupHidden()
437 {
438     if (!m_popupOpen)
439         return;
440     m_popupOpen = false;
441     chromeClientChromium()->popupClosed(this);
442 }
443
444 void PopupContainer::layout()
445 {
446     m_listBox->layout();
447
448     // Place the listbox within our border.
449     m_listBox->move(kBorderSize, kBorderSize);
450
451     // popupWidth is the width of <select> element. Record it before resize frame.
452     int popupWidth = frameRect().width();
453     // Size ourselves to contain listbox + border.
454     int listBoxWidth = m_listBox->width() + kBorderSize * 2;
455     resize(listBoxWidth, m_listBox->height() + kBorderSize * 2);
456
457     // Adjust the starting x-axis for RTL dropdown. For RTL dropdown, the right edge
458     // of dropdown box should be aligned with the right edge of <select> element box,
459     // and the dropdown box should be expanded to left if more space needed.
460     PopupMenuClient* popupClient = m_listBox->m_popupClient;
461     if (popupClient) {
462         bool rightAligned = m_listBox->m_popupClient->menuStyle().textDirection() == RTL;
463         if (rightAligned)
464             move(x() + popupWidth - listBoxWidth, y());
465     }
466     invalidate();
467 }
468
469 bool PopupContainer::handleMouseDownEvent(const PlatformMouseEvent& event)
470 {
471     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
472     return m_listBox->handleMouseDownEvent(
473         constructRelativeMouseEvent(event, this, m_listBox.get()));
474 }
475
476 bool PopupContainer::handleMouseMoveEvent(const PlatformMouseEvent& event)
477 {
478     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
479     return m_listBox->handleMouseMoveEvent(
480         constructRelativeMouseEvent(event, this, m_listBox.get()));
481 }
482
483 bool PopupContainer::handleMouseReleaseEvent(const PlatformMouseEvent& event)
484 {
485     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
486     return m_listBox->handleMouseReleaseEvent(
487         constructRelativeMouseEvent(event, this, m_listBox.get()));
488 }
489
490 bool PopupContainer::handleWheelEvent(const PlatformWheelEvent& event)
491 {
492     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
493     return m_listBox->handleWheelEvent(
494         constructRelativeWheelEvent(event, this, m_listBox.get()));
495 }
496
497 bool PopupContainer::handleKeyEvent(const PlatformKeyboardEvent& event)
498 {
499     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
500     return m_listBox->handleKeyEvent(event);
501 }
502
503 void PopupContainer::hide()
504 {
505     m_listBox->abandon();
506 }
507
508 void PopupContainer::paint(GraphicsContext* gc, const IntRect& rect)
509 {
510     // adjust coords for scrolled frame
511     IntRect r = intersection(rect, frameRect());
512     int tx = x();
513     int ty = y();
514
515     r.move(-tx, -ty);
516
517     gc->translate(static_cast<float>(tx), static_cast<float>(ty));
518     m_listBox->paint(gc, r);
519     gc->translate(-static_cast<float>(tx), -static_cast<float>(ty));
520
521     paintBorder(gc, rect);
522 }
523
524 void PopupContainer::paintBorder(GraphicsContext* gc, const IntRect& rect)
525 {
526     // FIXME: Where do we get the border color from?
527     Color borderColor(127, 157, 185);
528
529     gc->setStrokeStyle(NoStroke);
530     gc->setFillColor(borderColor, ColorSpaceDeviceRGB);
531
532     int tx = x();
533     int ty = y();
534
535     // top, left, bottom, right
536     gc->drawRect(IntRect(tx, ty, width(), kBorderSize));
537     gc->drawRect(IntRect(tx, ty, kBorderSize, height()));
538     gc->drawRect(IntRect(tx, ty + height() - kBorderSize, width(), kBorderSize));
539     gc->drawRect(IntRect(tx + width() - kBorderSize, ty, kBorderSize, height()));
540 }
541
542 bool PopupContainer::isInterestedInEventForKey(int keyCode)
543 {
544     return m_listBox->isInterestedInEventForKey(keyCode);
545 }
546
547 ChromeClientChromium* PopupContainer::chromeClientChromium()
548 {
549     return static_cast<ChromeClientChromium*>(m_frameView->frame()->page()->chrome()->client());
550 }
551
552 void PopupContainer::show(const IntRect& r, FrameView* v, int index)
553 {
554     // The rect is the size of the select box. It's usually larger than we need.
555     // subtract border size so that usually the container will be displayed
556     // exactly the same width as the select box.
557     listBox()->setBaseWidth(max(r.width() - kBorderSize * 2, 0));
558
559     listBox()->updateFromElement();
560
561     // We set the selected item in updateFromElement(), and disregard the
562     // index passed into this function (same as Webkit's PopupMenuWin.cpp)
563     // FIXME: make sure this is correct, and add an assertion.
564     // ASSERT(popupWindow(popup)->listBox()->selectedIndex() == index);
565
566     // Convert point to main window coords.
567     IntPoint location = v->contentsToWindow(r.location());
568
569     // Move it below the select widget.
570     location.move(0, r.height());
571
572     setFrameRect(IntRect(location, r.size()));
573     showPopup(v);
574 }
575
576 void PopupContainer::refresh(const IntRect& targetControlRect)
577 {
578     IntPoint location = m_frameView->contentsToWindow(targetControlRect.location());
579     // Move it below the select widget.
580     location.move(0, targetControlRect.height());
581
582     listBox()->updateFromElement();
583     // Store the original height to check if we need to request the location.
584     int originalHeight = height();
585     IntRect widgetRect = layoutAndCalculateWidgetRect(targetControlRect.height(), location.y());
586     if (originalHeight != widgetRect.height())
587         setFrameRect(widgetRect);
588
589     invalidate();
590 }
591
592 int PopupContainer::selectedIndex() const
593 {
594     return m_listBox->selectedIndex();
595 }
596
597 int PopupContainer::menuItemHeight() const
598 {
599     return m_listBox->getRowHeight(0);
600 }
601
602 int PopupContainer::menuItemFontSize() const
603 {
604     return m_listBox->getRowFont(0).size();
605 }
606
607 PopupMenuStyle PopupContainer::menuStyle() const
608 {
609     return m_listBox->m_popupClient->menuStyle();
610 }
611
612 const WTF::Vector<PopupItem*>& PopupContainer:: popupData() const
613 {
614     return m_listBox->items();
615 }
616
617 ///////////////////////////////////////////////////////////////////////////////
618 // PopupListBox implementation
619
620 bool PopupListBox::handleMouseDownEvent(const PlatformMouseEvent& event)
621 {
622     Scrollbar* scrollbar = scrollbarAtPoint(event.pos());
623     if (scrollbar) {
624         m_capturingScrollbar = scrollbar;
625         m_capturingScrollbar->mouseDown(event);
626         return true;
627     }
628
629     if (!isPointInBounds(event.pos()))
630         abandon();
631
632     return true;
633 }
634
635 bool PopupListBox::handleMouseMoveEvent(const PlatformMouseEvent& event)
636 {
637     if (m_capturingScrollbar) {
638         m_capturingScrollbar->mouseMoved(event);
639         return true;
640     }
641
642     Scrollbar* scrollbar = scrollbarAtPoint(event.pos());
643     if (m_lastScrollbarUnderMouse != scrollbar) {
644         // Send mouse exited to the old scrollbar.
645         if (m_lastScrollbarUnderMouse)
646             m_lastScrollbarUnderMouse->mouseExited();
647         m_lastScrollbarUnderMouse = scrollbar;
648     }
649
650     if (scrollbar) {
651         scrollbar->mouseMoved(event);
652         return true;
653     }
654
655     if (!isPointInBounds(event.pos()))
656         return false;
657
658     selectIndex(pointToRowIndex(event.pos()));
659     return true;
660 }
661
662 bool PopupListBox::handleMouseReleaseEvent(const PlatformMouseEvent& event)
663 {
664     if (m_capturingScrollbar) {
665         m_capturingScrollbar->mouseUp();
666         m_capturingScrollbar = 0;
667         return true;
668     }
669
670     if (!isPointInBounds(event.pos()))
671         return true;
672
673     acceptIndex(pointToRowIndex(event.pos()));
674     return true;
675 }
676
677 bool PopupListBox::handleWheelEvent(const PlatformWheelEvent& event)
678 {
679     if (!isPointInBounds(event.pos())) {
680         abandon();
681         return true;
682     }
683
684     // Pass it off to the scroll view.
685     // Sadly, WebCore devs don't understand the whole "const" thing.
686     wheelEvent(const_cast<PlatformWheelEvent&>(event));
687     return true;
688 }
689
690 // Should be kept in sync with handleKeyEvent().
691 bool PopupListBox::isInterestedInEventForKey(int keyCode)
692 {
693     switch (keyCode) {
694     case VKEY_ESCAPE:
695     case VKEY_RETURN:
696     case VKEY_UP:
697     case VKEY_DOWN:
698     case VKEY_PRIOR:
699     case VKEY_NEXT:
700     case VKEY_HOME:
701     case VKEY_END:
702     case VKEY_TAB:
703         return true;
704     default:
705         return false;
706     }
707 }
708
709 static bool isCharacterTypeEvent(const PlatformKeyboardEvent& event)
710 {
711     // Check whether the event is a character-typed event or not.
712     // We use RawKeyDown/Char/KeyUp event scheme on all platforms,
713     // so PlatformKeyboardEvent::Char (not RawKeyDown) type event
714     // is considered as character type event.
715     return event.type() == PlatformKeyboardEvent::Char;
716 }
717
718 bool PopupListBox::handleKeyEvent(const PlatformKeyboardEvent& event)
719 {
720     if (event.type() == PlatformKeyboardEvent::KeyUp)
721         return true;
722
723     if (numItems() == 0 && event.windowsVirtualKeyCode() != VKEY_ESCAPE)
724         return true;
725
726     switch (event.windowsVirtualKeyCode()) {
727     case VKEY_ESCAPE:
728         abandon();  // may delete this
729         return true;
730     case VKEY_RETURN:
731         if (m_selectedIndex == -1)  {
732             hidePopup();
733             // Don't eat the enter if nothing is selected.
734             return false;
735         }
736         acceptIndex(m_selectedIndex);  // may delete this
737         return true;
738     case VKEY_UP:
739         selectPreviousRow();
740         break;
741     case VKEY_DOWN:
742         selectNextRow();
743         break;
744     case VKEY_PRIOR:
745         adjustSelectedIndex(-m_visibleRows);
746         break;
747     case VKEY_NEXT:
748         adjustSelectedIndex(m_visibleRows);
749         break;
750     case VKEY_HOME:
751         adjustSelectedIndex(-m_selectedIndex);
752         break;
753     case VKEY_END:
754         adjustSelectedIndex(m_items.size());
755         break;
756     default:
757         if (!event.ctrlKey() && !event.altKey() && !event.metaKey()
758             && isPrintableChar(event.windowsVirtualKeyCode())
759             && isCharacterTypeEvent(event))
760             typeAheadFind(event);
761         break;
762     }
763
764     if (m_originalIndex != m_selectedIndex) {
765         // Keyboard events should update the selection immediately (but we don't
766         // want to fire the onchange event until the popup is closed, to match
767         // IE).  We change the original index so we revert to that when the
768         // popup is closed.
769         if (m_settings.acceptOnAbandon)
770             m_acceptedIndexOnAbandon = m_selectedIndex;
771
772         setOriginalIndex(m_selectedIndex);
773         if (m_settings.setTextOnIndexChange)
774             m_popupClient->setTextFromItem(m_selectedIndex);
775     }
776     if (event.windowsVirtualKeyCode() == VKEY_TAB) {
777         // TAB is a special case as it should select the current item if any and
778         // advance focus.
779         if (m_selectedIndex >= 0) {
780             acceptIndex(m_selectedIndex); // May delete us.
781             // Return false so the TAB key event is propagated to the page.
782             return false;
783         }
784         // Call abandon() so we honor m_acceptedIndexOnAbandon if set.
785         abandon();
786         // Return false so the TAB key event is propagated to the page.
787         return false;
788     }
789
790     return true;
791 }
792
793 HostWindow* PopupListBox::hostWindow() const
794 {
795     // Our parent is the root ScrollView, so it is the one that has a
796     // HostWindow.  FrameView::hostWindow() works similarly.
797     return parent() ? parent()->hostWindow() : 0;
798 }
799
800 // From HTMLSelectElement.cpp
801 static String stripLeadingWhiteSpace(const String& string)
802 {
803     int length = string.length();
804     int i;
805     for (i = 0; i < length; ++i)
806         if (string[i] != noBreakSpace
807             && (string[i] <= 0x7F ? !isspace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
808             break;
809
810     return string.substring(i, length - i);
811 }
812
813 // From HTMLSelectElement.cpp, with modifications
814 void PopupListBox::typeAheadFind(const PlatformKeyboardEvent& event)
815 {
816     TimeStamp now = static_cast<TimeStamp>(currentTime() * 1000.0f);
817     TimeStamp delta = now - m_lastCharTime;
818
819     // Reset the time when user types in a character. The time gap between
820     // last character and the current character is used to indicate whether
821     // user typed in a string or just a character as the search prefix.
822     m_lastCharTime = now;
823
824     UChar c = event.windowsVirtualKeyCode();
825
826     String prefix;
827     int searchStartOffset = 1;
828     if (delta > kTypeAheadTimeoutMs) {
829         m_typedString = prefix = String(&c, 1);
830         m_repeatingChar = c;
831     } else {
832         m_typedString.append(c);
833
834         if (c == m_repeatingChar)
835             // The user is likely trying to cycle through all the items starting with this character, so just search on the character
836             prefix = String(&c, 1);
837         else {
838             m_repeatingChar = 0;
839             prefix = m_typedString;
840             searchStartOffset = 0;
841         }
842     }
843
844     // Compute a case-folded copy of the prefix string before beginning the search for
845     // a matching element. This code uses foldCase to work around the fact that
846     // String::startWith does not fold non-ASCII characters. This code can be changed
847     // to use startWith once that is fixed.
848     String prefixWithCaseFolded(prefix.foldCase());
849     int itemCount = numItems();
850     int index = (max(0, m_selectedIndex) + searchStartOffset) % itemCount;
851     for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) {
852         if (!isSelectableItem(index))
853             continue;
854
855         if (stripLeadingWhiteSpace(m_items[index]->label).foldCase().startsWith(prefixWithCaseFolded)) {
856             selectIndex(index);
857             return;
858         }
859     }
860 }
861
862 void PopupListBox::paint(GraphicsContext* gc, const IntRect& rect)
863 {
864     // adjust coords for scrolled frame
865     IntRect r = intersection(rect, frameRect());
866     int tx = x() - scrollX();
867     int ty = y() - scrollY();
868
869     r.move(-tx, -ty);
870
871     // set clip rect to match revised damage rect
872     gc->save();
873     gc->translate(static_cast<float>(tx), static_cast<float>(ty));
874     gc->clip(r);
875
876     // FIXME: Can we optimize scrolling to not require repainting the entire
877     // window?  Should we?
878     for (int i = 0; i < numItems(); ++i)
879         paintRow(gc, r, i);
880
881     // Special case for an empty popup.
882     if (numItems() == 0)
883         gc->fillRect(r, Color::white, ColorSpaceDeviceRGB);
884
885     gc->restore();
886
887     ScrollView::paint(gc, rect);
888 }
889
890 static const int separatorPadding = 4;
891 static const int separatorHeight = 1;
892
893 void PopupListBox::paintRow(GraphicsContext* gc, const IntRect& rect, int rowIndex)
894 {
895     // This code is based largely on RenderListBox::paint* methods.
896
897     IntRect rowRect = getRowBounds(rowIndex);
898     if (!rowRect.intersects(rect))
899         return;
900
901     PopupMenuStyle style = m_popupClient->itemStyle(rowIndex);
902
903     // Paint background
904     Color backColor, textColor, labelColor;
905     if (rowIndex == m_selectedIndex) {
906         backColor = RenderTheme::defaultTheme()->activeListBoxSelectionBackgroundColor();
907         textColor = RenderTheme::defaultTheme()->activeListBoxSelectionForegroundColor();
908         labelColor = textColor;
909     } else {
910         backColor = style.backgroundColor();
911         textColor = style.foregroundColor();
912         // FIXME: for now the label color is hard-coded. It should be added to
913         // the PopupMenuStyle.
914         labelColor = Color(115, 115, 115);
915     }
916
917     // If we have a transparent background, make sure it has a color to blend
918     // against.
919     if (backColor.hasAlpha())
920         gc->fillRect(rowRect, Color::white, ColorSpaceDeviceRGB);
921
922     gc->fillRect(rowRect, backColor, ColorSpaceDeviceRGB);
923
924     if (m_popupClient->itemIsSeparator(rowIndex)) {
925         IntRect separatorRect(
926             rowRect.x() + separatorPadding,
927             rowRect.y() + (rowRect.height() - separatorHeight) / 2,
928             rowRect.width() - 2 * separatorPadding, separatorHeight);
929         gc->fillRect(separatorRect, textColor, ColorSpaceDeviceRGB);
930         return;
931     }
932
933     if (!style.isVisible())
934         return;
935
936     gc->setFillColor(textColor, ColorSpaceDeviceRGB);
937
938     Font itemFont = getRowFont(rowIndex);
939     // FIXME: http://crbug.com/19872 We should get the padding of individual option
940     // elements.  This probably implies changes to PopupMenuClient.
941     bool rightAligned = m_popupClient->menuStyle().textDirection() == RTL;
942     int textX = 0;
943     int maxWidth = 0;
944     if (rightAligned)
945         maxWidth = rowRect.width() - max(0, m_popupClient->clientPaddingRight() - m_popupClient->clientInsetRight());
946     else {
947         textX = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft());
948         maxWidth = rowRect.width() - textX;
949     }
950     // Prepare text to be drawn.
951     String itemText = m_popupClient->itemText(rowIndex);
952     String itemLabel = m_popupClient->itemLabel(rowIndex);
953     String itemIcon = m_popupClient->itemIcon(rowIndex);
954     if (m_settings.restrictWidthOfListBox) { // Truncate strings to fit in.
955         // FIXME: We should leftTruncate for the rtl case.
956         // StringTruncator::leftTruncate would have to be implemented.
957         String str = StringTruncator::rightTruncate(itemText, maxWidth, itemFont);
958         if (str != itemText) {
959             itemText = str;
960             // Don't display the label or icon, we already don't have enough room for the item text.
961             itemLabel = "";
962             itemIcon = "";
963         } else if (!itemLabel.isEmpty()) {
964             int availableWidth = maxWidth - kTextToLabelPadding -
965                 StringTruncator::width(itemText, itemFont);
966             itemLabel = StringTruncator::rightTruncate(itemLabel, availableWidth, itemFont);
967         }
968     }
969
970     // Prepare the directionality to draw text.
971     bool rtl = false;
972     if (m_settings.itemTextDirectionalityHint == PopupContainerSettings::DOMElementDirection)
973         rtl = style.textDirection() == RTL;
974     else if (m_settings.itemTextDirectionalityHint ==
975              PopupContainerSettings::FirstStrongDirectionalCharacterDirection)
976         rtl = itemText.defaultWritingDirection() == WTF::Unicode::RightToLeft;
977     TextRun textRun(itemText.characters(), itemText.length(), false, 0, 0, TextRun::AllowTrailingExpansion, rtl);
978     // If the text is right-to-left, make it right-aligned by adjusting its
979     // beginning position.
980     if (rightAligned)
981         textX += maxWidth - itemFont.width(textRun);
982
983     // Draw the item text.
984     int textY = rowRect.y() + itemFont.fontMetrics().ascent() + (rowRect.height() - itemFont.fontMetrics().height()) / 2;
985     gc->drawBidiText(itemFont, textRun, IntPoint(textX, textY));
986
987     // We are using the left padding as the right padding includes room for the scroll-bar which
988     // does not show in this case.
989     int rightPadding = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft());
990     int remainingWidth = rowRect.width() - rightPadding;
991
992     // Draw the icon if applicable.
993     RefPtr<Image> image(Image::loadPlatformResource(itemIcon.utf8().data()));
994     if (image && !image->isNull()) {
995         IntRect imageRect = image->rect();
996         remainingWidth -= (imageRect.width() + kLabelToIconPadding);
997         imageRect.setX(rowRect.width() - rightPadding - imageRect.width());
998         imageRect.setY(rowRect.y() + (rowRect.height() - imageRect.height()) / 2);
999         gc->drawImage(image.get(), ColorSpaceDeviceRGB, imageRect);
1000     }
1001
1002     // Draw the the label if applicable.
1003     if (itemLabel.isEmpty())
1004         return;
1005     TextRun labelTextRun(itemLabel.characters(), itemLabel.length(), false, 0, 0, TextRun::AllowTrailingExpansion, rtl);
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     String icon = m_popupClient->itemIcon(index);
1122     RefPtr<Image> image(Image::loadPlatformResource(icon.utf8().data()));
1123
1124     int fontHeight = getRowFont(index).fontMetrics().height();
1125     int iconHeight = (image && !image->isNull()) ? image->rect().height() : 0;
1126
1127     return max(fontHeight, iconHeight);
1128 }
1129
1130 IntRect PopupListBox::getRowBounds(int index)
1131 {
1132     if (index < 0)
1133         return IntRect(0, 0, visibleWidth(), getRowHeight(index));
1134
1135     return IntRect(0, m_items[index]->yOffset, visibleWidth(), getRowHeight(index));
1136 }
1137
1138 void PopupListBox::invalidateRow(int index)
1139 {
1140     if (index < 0)
1141         return;
1142
1143     // Invalidate in the window contents, as FramelessScrollView::invalidateRect
1144     // paints in the window coordinates.
1145     invalidateRect(contentsToWindow(getRowBounds(index)));
1146 }
1147
1148 void PopupListBox::scrollToRevealRow(int index)
1149 {
1150     if (index < 0)
1151         return;
1152
1153     IntRect rowRect = getRowBounds(index);
1154
1155     if (rowRect.y() < scrollY()) {
1156         // Row is above current scroll position, scroll up.
1157         ScrollView::setScrollPosition(IntPoint(0, rowRect.y()));
1158     } else if (rowRect.maxY() > scrollY() + visibleHeight()) {
1159         // Row is below current scroll position, scroll down.
1160         ScrollView::setScrollPosition(IntPoint(0, rowRect.maxY() - visibleHeight()));
1161     }
1162 }
1163
1164 bool PopupListBox::isSelectableItem(int index)
1165 {
1166     ASSERT(index >= 0 && index < numItems());
1167     return m_items[index]->type == PopupItem::TypeOption && m_popupClient->itemIsEnabled(index);
1168 }
1169
1170 void PopupListBox::clearSelection()
1171 {
1172     if (m_selectedIndex != -1) {
1173         invalidateRow(m_selectedIndex);
1174         m_selectedIndex = -1;
1175         m_popupClient->selectionCleared();
1176     }
1177 }
1178
1179 void PopupListBox::selectNextRow()
1180 {
1181     if (!m_settings.loopSelectionNavigation || m_selectedIndex != numItems() - 1) {
1182         adjustSelectedIndex(1);
1183         return;
1184     }
1185
1186     // We are moving past the last item, no row should be selected.
1187     clearSelection();
1188 }
1189
1190 void PopupListBox::selectPreviousRow()
1191 {
1192     if (!m_settings.loopSelectionNavigation || m_selectedIndex > 0) {
1193         adjustSelectedIndex(-1);
1194         return;
1195     }
1196
1197     if (m_selectedIndex == 0) {
1198         // We are moving past the first item, clear the selection.
1199         clearSelection();
1200         return;
1201     }
1202
1203     // No row is selected, jump to the last item.
1204     selectIndex(numItems() - 1);
1205     scrollToRevealSelection();
1206 }
1207
1208 void PopupListBox::adjustSelectedIndex(int delta)
1209 {
1210     int targetIndex = m_selectedIndex + delta;
1211     targetIndex = min(max(targetIndex, 0), numItems() - 1);
1212     if (!isSelectableItem(targetIndex)) {
1213         // We didn't land on an option.  Try to find one.
1214         // We try to select the closest index to target, prioritizing any in
1215         // the range [current, target].
1216
1217         int dir = delta > 0 ? 1 : -1;
1218         int testIndex = m_selectedIndex;
1219         int bestIndex = m_selectedIndex;
1220         bool passedTarget = false;
1221         while (testIndex >= 0 && testIndex < numItems()) {
1222             if (isSelectableItem(testIndex))
1223                 bestIndex = testIndex;
1224             if (testIndex == targetIndex)
1225                 passedTarget = true;
1226             if (passedTarget && bestIndex != m_selectedIndex)
1227                 break;
1228
1229             testIndex += dir;
1230         }
1231
1232         // Pick the best index, which may mean we don't change.
1233         targetIndex = bestIndex;
1234     }
1235
1236     // Select the new index, and ensure its visible.  We do this regardless of
1237     // whether the selection changed to ensure keyboard events always bring the
1238     // selection into view.
1239     selectIndex(targetIndex);
1240     scrollToRevealSelection();
1241 }
1242
1243 void PopupListBox::hidePopup()
1244 {
1245     if (parent()) {
1246         PopupContainer* container = static_cast<PopupContainer*>(parent());
1247         if (container->client())
1248             container->client()->popupClosed(container);
1249         container->notifyPopupHidden();
1250     }
1251
1252     if (m_popupClient)
1253         m_popupClient->popupDidHide();
1254 }
1255
1256 void PopupListBox::updateFromElement()
1257 {
1258     clear();
1259
1260     int size = m_popupClient->listSize();
1261     for (int i = 0; i < size; ++i) {
1262         PopupItem::Type type;
1263         if (m_popupClient->itemIsSeparator(i))
1264             type = PopupItem::TypeSeparator;
1265         else if (m_popupClient->itemIsLabel(i))
1266             type = PopupItem::TypeGroup;
1267         else
1268             type = PopupItem::TypeOption;
1269         m_items.append(new PopupItem(m_popupClient->itemText(i), type));
1270         m_items[i]->enabled = isSelectableItem(i);
1271     }
1272
1273     m_selectedIndex = m_popupClient->selectedIndex();
1274     setOriginalIndex(m_selectedIndex);
1275
1276     layout();
1277 }
1278
1279 void PopupListBox::layout()
1280 {
1281     // Size our child items.
1282     int baseWidth = 0;
1283     int paddingWidth = 0;
1284     int y = 0;
1285     for (int i = 0; i < numItems(); ++i) {
1286         // Place the item vertically.
1287         m_items[i]->yOffset = y;
1288         if (m_popupClient->itemStyle(i).isDisplayNone())
1289             continue;
1290         y += getRowHeight(i);
1291
1292         // Ensure the popup is wide enough to fit this item.
1293         Font itemFont = getRowFont(i);
1294         String text = m_popupClient->itemText(i);
1295         String label = m_popupClient->itemLabel(i);
1296         String icon = m_popupClient->itemIcon(i);
1297         RefPtr<Image> iconImage(Image::loadPlatformResource(icon.utf8().data()));
1298         int width = 0;
1299         if (!text.isEmpty())
1300             width = itemFont.width(TextRun(text));
1301         if (!label.isEmpty()) {
1302             if (width > 0)
1303                 width += kTextToLabelPadding;
1304             width += itemFont.width(TextRun(label));
1305         }
1306         if (iconImage && !iconImage->isNull()) {
1307             if (width > 0)
1308                 width += kLabelToIconPadding;
1309             width += iconImage->rect().width();
1310         }
1311
1312         baseWidth = max(baseWidth, width);
1313         // FIXME: http://b/1210481 We should get the padding of individual option elements.
1314         paddingWidth = max(paddingWidth,
1315             m_popupClient->clientPaddingLeft() + m_popupClient->clientPaddingRight());
1316     }
1317
1318     // Calculate scroll bar width.
1319     int windowHeight = 0;
1320
1321 #if OS(DARWIN)
1322     // Set the popup's window to contain all available items on Mac only, which
1323     // uses native controls that manage their own scrolling. This allows hit
1324     // testing to work when selecting items in popups that have more menu entries
1325     // than the maximum window size.
1326     m_visibleRows = numItems();
1327 #else
1328     m_visibleRows = min(numItems(), kMaxVisibleRows);
1329 #endif
1330
1331     for (int i = 0; i < m_visibleRows; ++i) {
1332         int rowHeight = getRowHeight(i);
1333
1334 #if !OS(DARWIN)
1335         // Only clip the window height for non-Mac platforms.
1336         if (windowHeight + rowHeight > m_maxHeight) {
1337             m_visibleRows = i;
1338             break;
1339         }
1340 #endif
1341
1342         windowHeight += rowHeight;
1343     }
1344
1345     // Set our widget and scrollable contents sizes.
1346     int scrollbarWidth = 0;
1347     if (m_visibleRows < numItems())
1348         scrollbarWidth = ScrollbarTheme::nativeTheme()->scrollbarThickness();
1349
1350     int windowWidth;
1351     int contentWidth;
1352     if (m_settings.restrictWidthOfListBox) {
1353         windowWidth = m_baseWidth;
1354         contentWidth = m_baseWidth - scrollbarWidth;
1355     } else {
1356         windowWidth = baseWidth + scrollbarWidth + paddingWidth;
1357         contentWidth = baseWidth + paddingWidth;
1358
1359         if (windowWidth < m_baseWidth) {
1360             windowWidth = m_baseWidth;
1361             contentWidth = m_baseWidth - scrollbarWidth;
1362         } else
1363             m_baseWidth = baseWidth;
1364     }
1365
1366     resize(windowWidth, windowHeight);
1367     setContentsSize(IntSize(contentWidth, getRowBounds(numItems() - 1).maxY()));
1368
1369     if (hostWindow())
1370         scrollToRevealSelection();
1371
1372     invalidate();
1373 }
1374
1375 void PopupListBox::clear()
1376 {
1377     for (Vector<PopupItem*>::iterator it = m_items.begin(); it != m_items.end(); ++it)
1378         delete *it;
1379     m_items.clear();
1380 }
1381
1382 bool PopupListBox::isPointInBounds(const IntPoint& point)
1383 {
1384     return numItems() != 0 && IntRect(0, 0, width(), height()).contains(point);
1385 }
1386
1387 ///////////////////////////////////////////////////////////////////////////////
1388 // PopupMenuChromium implementation
1389 //
1390 // Note: you cannot add methods to this class, since it is defined above the
1391 //       portability layer. To access methods and properties on the
1392 //       popup widgets, use |popupWindow| above.
1393
1394 PopupMenuChromium::PopupMenuChromium(PopupMenuClient* client)
1395     : m_popupClient(client)
1396 {
1397 }
1398
1399 PopupMenuChromium::~PopupMenuChromium()
1400 {
1401     // When the PopupMenuChromium is destroyed, the client could already have been
1402     // deleted.
1403     if (p.popup)
1404         p.popup->listBox()->disconnectClient();
1405     hide();
1406 }
1407
1408 // The Mac Chromium implementation relies on external control (a Cocoa control)
1409 // to display, handle the input tracking and menu item selection for the popup.
1410 // Windows and Linux Chromium let our WebKit port handle the display, while
1411 // another process manages the popup window and input handling.
1412 void PopupMenuChromium::show(const IntRect& r, FrameView* v, int index)
1413 {
1414     if (!p.popup)
1415         p.popup = PopupContainer::create(client(), PopupContainer::Select, dropDownSettings);
1416 #if OS(DARWIN)
1417     p.popup->showExternal(r, v, index);
1418 #else
1419     p.popup->show(r, v, index);
1420 #endif
1421 }
1422
1423 void PopupMenuChromium::hide()
1424 {
1425     if (p.popup)
1426         p.popup->hide();
1427 }
1428
1429 void PopupMenuChromium::updateFromElement()
1430 {
1431     p.popup->listBox()->updateFromElement();
1432 }
1433
1434
1435 void PopupMenuChromium::disconnectClient()
1436 {
1437     m_popupClient = 0;
1438 }
1439
1440 } // namespace WebCore