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