• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 // NOTE: This implementation is very similar to the implementation of popups in WebCore::PopupMenuWin.
27 // We should try and factor out the common bits and share them.
28 
29 #include "config.h"
30 #include "WebPopupMenuProxyWin.h"
31 
32 #include "NativeWebMouseEvent.h"
33 #include "WebView.h"
34 #include <WebCore/WebCoreInstanceHandle.h>
35 #include <WebCore/ScrollbarTheme.h>
36 #include <WebCore/BitmapInfo.h>
37 #include <WebCore/PlatformMouseEvent.h>
38 #include <windowsx.h>
39 
40 using namespace WebCore;
41 using namespace std;
42 
43 namespace WebKit {
44 
45 static const LPCWSTR kWebKit2WebPopupMenuProxyWindowClassName = L"WebKit2WebPopupMenuProxyWindowClass";
46 
47 static const int defaultAnimationDuration = 200;
48 static const int maxPopupHeight = 320;
49 static const int popupWindowBorderWidth = 1;
50 static const int separatorPadding = 4;
51 static const int separatorHeight = 1;
52 
53 // This is used from within our custom message pump when we want to send a
54 // message to the web view and not have our message stolen and sent to
55 // the popup window.
56 static const UINT WM_HOST_WINDOW_FIRST = WM_USER;
57 static const UINT WM_HOST_WINDOW_CHAR = WM_USER + WM_CHAR;
58 static const UINT WM_HOST_WINDOW_MOUSEMOVE = WM_USER + WM_MOUSEMOVE;
59 
isASCIIPrintable(unsigned c)60 static inline bool isASCIIPrintable(unsigned c)
61 {
62     return c >= 0x20 && c <= 0x7E;
63 }
64 
translatePoint(LPARAM & lParam,HWND from,HWND to)65 static void translatePoint(LPARAM& lParam, HWND from, HWND to)
66 {
67     POINT pt;
68     pt.x = static_cast<short>(GET_X_LPARAM(lParam));
69     pt.y = static_cast<short>(GET_Y_LPARAM(lParam));
70     ::MapWindowPoints(from, to, &pt, 1);
71     lParam = MAKELPARAM(pt.x, pt.y);
72 }
73 
WebPopupMenuProxyWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)74 LRESULT CALLBACK WebPopupMenuProxyWin::WebPopupMenuProxyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
75 {
76     LONG_PTR longPtr = ::GetWindowLongPtr(hWnd, 0);
77 
78     if (WebPopupMenuProxyWin* popupMenuProxy = reinterpret_cast<WebPopupMenuProxyWin*>(longPtr))
79         return popupMenuProxy->wndProc(hWnd, message, wParam, lParam);
80 
81     if (message == WM_CREATE) {
82         LPCREATESTRUCT createStruct = reinterpret_cast<LPCREATESTRUCT>(lParam);
83 
84         // Associate the WebView with the window.
85         ::SetWindowLongPtr(hWnd, 0, (LONG_PTR)createStruct->lpCreateParams);
86         return 0;
87     }
88 
89     return ::DefWindowProc(hWnd, message, wParam, lParam);
90 }
91 
wndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)92 LRESULT WebPopupMenuProxyWin::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
93 {
94     LRESULT lResult = 0;
95     bool handled = true;
96 
97     switch (message) {
98         case WM_MOUSEACTIVATE:
99             lResult = onMouseActivate(hWnd, message, wParam, lParam, handled);
100             break;
101         case WM_SIZE:
102             lResult = onSize(hWnd, message, wParam, lParam, handled);
103             break;
104         case WM_KEYDOWN:
105             lResult = onKeyDown(hWnd, message, wParam, lParam, handled);
106             break;
107         case WM_CHAR:
108             lResult = onChar(hWnd, message, wParam, lParam, handled);
109             break;
110         case WM_MOUSEMOVE:
111             lResult = onMouseMove(hWnd, message, wParam, lParam, handled);
112             break;
113         case WM_LBUTTONDOWN:
114             lResult = onLButtonDown(hWnd, message, wParam, lParam, handled);
115             break;
116         case WM_LBUTTONUP:
117             lResult = onLButtonUp(hWnd, message, wParam, lParam, handled);
118             break;
119         case WM_MOUSEWHEEL:
120             lResult = onMouseWheel(hWnd, message, wParam, lParam, handled);
121             break;
122         case WM_PAINT:
123             lResult = onPaint(hWnd, message, wParam, lParam, handled);
124             break;
125         case WM_PRINTCLIENT:
126             lResult = onPrintClient(hWnd, message, wParam, lParam, handled);
127             break;
128         default:
129             handled = false;
130             break;
131     }
132 
133     if (!handled)
134         lResult = ::DefWindowProc(hWnd, message, wParam, lParam);
135 
136     return lResult;
137 }
138 
registerWindowClass()139 bool WebPopupMenuProxyWin::registerWindowClass()
140 {
141     static bool haveRegisteredWindowClass = false;
142     if (haveRegisteredWindowClass)
143         return true;
144     haveRegisteredWindowClass = true;
145 
146     WNDCLASSEX wcex;
147     wcex.cbSize         = sizeof(WNDCLASSEX);
148     wcex.style          = CS_DROPSHADOW;
149     wcex.lpfnWndProc    = WebPopupMenuProxyWin::WebPopupMenuProxyWndProc;
150     wcex.cbClsExtra     = 0;
151     wcex.cbWndExtra     = sizeof(WebPopupMenuProxyWin*);
152     wcex.hInstance      = instanceHandle();
153     wcex.hIcon          = 0;
154     wcex.hCursor        = ::LoadCursor(0, IDC_ARROW);
155     wcex.hbrBackground  = 0;
156     wcex.lpszMenuName   = 0;
157     wcex.lpszClassName  = kWebKit2WebPopupMenuProxyWindowClassName;
158     wcex.hIconSm        = 0;
159 
160     return !!::RegisterClassEx(&wcex);
161 }
162 
WebPopupMenuProxyWin(WebView * webView,WebPopupMenuProxy::Client * client)163 WebPopupMenuProxyWin::WebPopupMenuProxyWin(WebView* webView, WebPopupMenuProxy::Client* client)
164     : WebPopupMenuProxy(client)
165     , m_webView(webView)
166     , m_newSelectedIndex(0)
167     , m_popup(0)
168     , m_DC(0)
169     , m_bmp(0)
170     , m_itemHeight(0)
171     , m_scrollOffset(0)
172     , m_wheelDelta(0)
173     , m_focusedIndex(0)
174     , m_wasClicked(false)
175     , m_scrollbarCapturingMouse(false)
176     , m_showPopup(false)
177 {
178 }
179 
~WebPopupMenuProxyWin()180 WebPopupMenuProxyWin::~WebPopupMenuProxyWin()
181 {
182     if (m_bmp)
183         ::DeleteObject(m_bmp);
184     if (m_DC)
185         ::DeleteDC(m_DC);
186     if (m_popup)
187         ::DestroyWindow(m_popup);
188     if (m_scrollbar)
189         m_scrollbar->setParent(0);
190 }
191 
showPopupMenu(const IntRect & rect,TextDirection,double,const Vector<WebPopupItem> & items,const PlatformPopupMenuData & data,int32_t selectedIndex)192 void WebPopupMenuProxyWin::showPopupMenu(const IntRect& rect, TextDirection, double, const Vector<WebPopupItem>& items, const PlatformPopupMenuData& data, int32_t selectedIndex)
193 {
194     m_items = items;
195     m_data = data;
196     m_newSelectedIndex = selectedIndex;
197 
198     calculatePositionAndSize(rect);
199     if (clientRect().isEmpty())
200         return;
201 
202     HWND hostWindow = m_webView->window();
203 
204     if (!m_scrollbar && visibleItems() < m_items.size()) {
205         m_scrollbar = Scrollbar::createNativeScrollbar(this, VerticalScrollbar, SmallScrollbar);
206         m_scrollbar->styleChanged();
207     }
208 
209     if (!m_popup) {
210         registerWindowClass();
211 
212         DWORD exStyle = WS_EX_LTRREADING;
213 
214         m_popup = ::CreateWindowEx(exStyle, kWebKit2WebPopupMenuProxyWindowClassName, TEXT("PopupMenu"),
215             WS_POPUP | WS_BORDER,
216             m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(),
217             hostWindow, 0, instanceHandle(), this);
218 
219         if (!m_popup)
220             return;
221     }
222 
223     BOOL shouldAnimate = FALSE;
224     ::SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &shouldAnimate, 0);
225 
226     if (shouldAnimate) {
227         RECT viewRect = {0};
228         ::GetWindowRect(hostWindow, &viewRect);
229 
230         if (!::IsRectEmpty(&viewRect)) {
231             // Popups should slide into view away from the <select> box
232             // NOTE: This may have to change for Vista
233             DWORD slideDirection = (m_windowRect.y() < viewRect.top + rect.location().y()) ? AW_VER_NEGATIVE : AW_VER_POSITIVE;
234 
235             ::AnimateWindow(m_popup, defaultAnimationDuration, AW_SLIDE | slideDirection);
236         }
237     } else
238         ::ShowWindow(m_popup, SW_SHOWNOACTIVATE);
239 
240 
241     int index = selectedIndex;
242     if (index >= 0)
243         setFocusedIndex(index);
244 
245     m_showPopup = true;
246 
247     // Protect the popup menu in case its owner is destroyed while we're running the message pump.
248     RefPtr<WebPopupMenuProxyWin> protect(this);
249 
250     ::SetCapture(hostWindow);
251 
252     MSG msg;
253     HWND activeWindow;
254 
255     while (::GetMessage(&msg, 0, 0, 0)) {
256         switch (msg.message) {
257         case WM_HOST_WINDOW_MOUSEMOVE:
258         case WM_HOST_WINDOW_CHAR:
259             if (msg.hwnd == m_popup) {
260                 // This message should be sent to the host window.
261                 msg.hwnd = hostWindow;
262                 msg.message -= WM_HOST_WINDOW_FIRST;
263             }
264             break;
265 
266         // Steal mouse messages.
267         case WM_NCMOUSEMOVE:
268         case WM_NCLBUTTONDOWN:
269         case WM_NCLBUTTONUP:
270         case WM_NCLBUTTONDBLCLK:
271         case WM_NCRBUTTONDOWN:
272         case WM_NCRBUTTONUP:
273         case WM_NCRBUTTONDBLCLK:
274         case WM_NCMBUTTONDOWN:
275         case WM_NCMBUTTONUP:
276         case WM_NCMBUTTONDBLCLK:
277         case WM_MOUSEWHEEL:
278             msg.hwnd = m_popup;
279             break;
280 
281         // These mouse messages use client coordinates so we need to convert them.
282         case WM_MOUSEMOVE:
283         case WM_LBUTTONDOWN:
284         case WM_LBUTTONUP:
285         case WM_LBUTTONDBLCLK:
286         case WM_RBUTTONDOWN:
287         case WM_RBUTTONUP:
288         case WM_RBUTTONDBLCLK:
289         case WM_MBUTTONDOWN:
290         case WM_MBUTTONUP:
291         case WM_MBUTTONDBLCLK: {
292             // Translate the coordinate.
293             translatePoint(msg.lParam, msg.hwnd, m_popup);
294             msg.hwnd = m_popup;
295             break;
296         }
297 
298         // Steal all keyboard messages.
299         case WM_KEYDOWN:
300         case WM_KEYUP:
301         case WM_CHAR:
302         case WM_DEADCHAR:
303         case WM_SYSKEYUP:
304         case WM_SYSCHAR:
305         case WM_SYSDEADCHAR:
306             msg.hwnd = m_popup;
307             break;
308         }
309 
310         ::TranslateMessage(&msg);
311         ::DispatchMessage(&msg);
312 
313         if (!m_showPopup)
314             break;
315         activeWindow = ::GetActiveWindow();
316         if (activeWindow != hostWindow && !::IsChild(activeWindow, hostWindow))
317             break;
318         if (::GetCapture() != hostWindow)
319             break;
320     }
321 
322     if (::GetCapture() == hostWindow)
323         ::ReleaseCapture();
324 
325     m_showPopup = false;
326     ::ShowWindow(m_popup, SW_HIDE);
327 
328     if (!m_client)
329         return;
330 
331     m_client->valueChangedForPopupMenu(this, m_newSelectedIndex);
332 
333     // <https://bugs.webkit.org/show_bug.cgi?id=57904> In order to properly call the onClick()
334     // handler on a <select> element, we need to fake a mouse up event in the main window.
335     // The main window already received the mouse down, which showed this popup, but upon
336     // selection of an item the mouse up gets eaten by the popup menu. So we take the mouse down
337     // event, change the message type to a mouse up event, and post that in the message queue.
338     // Thus, we are virtually clicking at the
339     // same location where the mouse down event occurred. This allows the hit test to select
340     // the correct element, and thereby call the onClick() JS handler.
341     if (!m_client->currentlyProcessedMouseDownEvent())
342         return;
343 
344     const MSG* initiatingWinEvent = m_client->currentlyProcessedMouseDownEvent()->nativeEvent();
345     MSG fakeEvent = *initiatingWinEvent;
346     fakeEvent.message = WM_LBUTTONUP;
347     ::PostMessage(fakeEvent.hwnd, fakeEvent.message, fakeEvent.wParam, fakeEvent.lParam);
348 }
349 
hidePopupMenu()350 void WebPopupMenuProxyWin::hidePopupMenu()
351 {
352     if (!m_showPopup)
353         return;
354     m_showPopup = false;
355 
356     ::ShowWindow(m_popup, SW_HIDE);
357 
358     // Post a WM_NULL message to wake up the message pump if necessary.
359     ::PostMessage(m_popup, WM_NULL, 0, 0);
360 }
361 
calculatePositionAndSize(const IntRect & rect)362 void WebPopupMenuProxyWin::calculatePositionAndSize(const IntRect& rect)
363 {
364     // Convert the rect (which is in view cooridates) into screen coordinates.
365     IntRect rectInScreenCoords = rect;
366     POINT location(rectInScreenCoords .location());
367     if (!::ClientToScreen(m_webView->window(), &location))
368         return;
369     rectInScreenCoords.setLocation(location);
370 
371     int itemCount = m_items.size();
372     m_itemHeight = m_data.m_itemHeight;
373 
374     int naturalHeight = m_itemHeight * itemCount;
375     int popupHeight = min(maxPopupHeight, naturalHeight);
376 
377     // The popup should show an integral number of items (i.e. no partial items should be visible)
378     popupHeight -= popupHeight % m_itemHeight;
379 
380     // Next determine its width
381     int popupWidth = m_data.m_popupWidth;
382 
383     if (naturalHeight > maxPopupHeight) {
384         // We need room for a scrollbar
385         popupWidth += ScrollbarTheme::nativeTheme()->scrollbarThickness(SmallScrollbar);
386     }
387 
388     popupHeight += 2 * popupWindowBorderWidth;
389 
390     // The popup should be at least as wide as the control on the page
391     popupWidth = max(rectInScreenCoords.width() - m_data.m_clientInsetLeft - m_data.m_clientInsetRight, popupWidth);
392 
393     // Always left-align items in the popup.  This matches popup menus on the mac.
394     int popupX = rectInScreenCoords.x() + m_data.m_clientInsetLeft;
395 
396     IntRect popupRect(popupX, rectInScreenCoords.maxY(), popupWidth, popupHeight);
397 
398     // The popup needs to stay within the bounds of the screen and not overlap any toolbars
399     HMONITOR monitor = ::MonitorFromWindow(m_webView->window(), MONITOR_DEFAULTTOPRIMARY);
400     MONITORINFOEX monitorInfo;
401     monitorInfo.cbSize = sizeof(MONITORINFOEX);
402     ::GetMonitorInfo(monitor, &monitorInfo);
403     FloatRect screen = monitorInfo.rcWork;
404 
405     // Check that we don't go off the screen vertically
406     if (popupRect.maxY() > screen.height()) {
407         // The popup will go off the screen, so try placing it above the client
408         if (rectInScreenCoords.y() - popupRect.height() < 0) {
409             // The popup won't fit above, either, so place it whereever's bigger and resize it to fit
410             if ((rectInScreenCoords.y() + rectInScreenCoords.height() / 2) < (screen.height() / 2)) {
411                 // Below is bigger
412                 popupRect.setHeight(screen.height() - popupRect.y());
413             } else {
414                 // Above is bigger
415                 popupRect.setY(0);
416                 popupRect.setHeight(rectInScreenCoords.y());
417             }
418         } else {
419             // The popup fits above, so reposition it
420             popupRect.setY(rectInScreenCoords.y() - popupRect.height());
421         }
422     }
423 
424     // Check that we don't go off the screen horizontally
425     if (popupRect.x() < screen.x()) {
426         popupRect.setWidth(popupRect.width() - (screen.x() - popupRect.x()));
427         popupRect.setX(screen.x());
428     }
429 
430     m_windowRect = popupRect;
431 }
432 
clientRect() const433 IntRect WebPopupMenuProxyWin::clientRect() const
434 {
435     IntRect clientRect = m_windowRect;
436     clientRect.inflate(-popupWindowBorderWidth);
437     clientRect.setLocation(IntPoint(0, 0));
438     return clientRect;
439 }
440 
invalidateItem(int index)441 void WebPopupMenuProxyWin::invalidateItem(int index)
442 {
443     if (!m_popup)
444         return;
445 
446     IntRect damageRect(clientRect());
447     damageRect.setY(m_itemHeight * (index - m_scrollOffset));
448     damageRect.setHeight(m_itemHeight);
449     if (m_scrollbar)
450         damageRect.setWidth(damageRect.width() - m_scrollbar->frameRect().width());
451 
452     RECT r = damageRect;
453     ::InvalidateRect(m_popup, &r, TRUE);
454 }
455 
scrollSize(ScrollbarOrientation orientation) const456 int WebPopupMenuProxyWin::scrollSize(ScrollbarOrientation orientation) const
457 {
458     return ((orientation == VerticalScrollbar) && m_scrollbar) ? (m_scrollbar->totalSize() - m_scrollbar->visibleSize()) : 0;
459 }
460 
scrollPosition(Scrollbar *) const461 int WebPopupMenuProxyWin::scrollPosition(Scrollbar*) const
462 {
463     return m_scrollOffset;
464 }
465 
setScrollOffset(const IntPoint & offset)466 void WebPopupMenuProxyWin::setScrollOffset(const IntPoint& offset)
467 {
468     scrollTo(offset.y());
469 }
470 
scrollTo(int offset)471 void WebPopupMenuProxyWin::scrollTo(int offset)
472 {
473     ASSERT(m_scrollbar);
474 
475     if (!m_popup)
476         return;
477 
478     if (m_scrollOffset == offset)
479         return;
480 
481     int scrolledLines = m_scrollOffset - offset;
482     m_scrollOffset = offset;
483 
484     UINT flags = SW_INVALIDATE;
485 
486 #ifdef CAN_SET_SMOOTH_SCROLLING_DURATION
487     BOOL shouldSmoothScroll = FALSE;
488     ::SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &shouldSmoothScroll, 0);
489     if (shouldSmoothScroll)
490         flags |= MAKEWORD(SW_SMOOTHSCROLL, smoothScrollAnimationDuration);
491 #endif
492 
493     IntRect listRect = clientRect();
494     if (m_scrollbar)
495         listRect.setWidth(listRect.width() - m_scrollbar->frameRect().width());
496     RECT r = listRect;
497     ::ScrollWindowEx(m_popup, 0, scrolledLines * m_itemHeight, &r, 0, 0, 0, flags);
498     if (m_scrollbar) {
499         r = m_scrollbar->frameRect();
500         ::InvalidateRect(m_popup, &r, TRUE);
501     }
502     ::UpdateWindow(m_popup);
503 }
504 
invalidateScrollbarRect(Scrollbar * scrollbar,const IntRect & rect)505 void WebPopupMenuProxyWin::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
506 {
507     IntRect scrollRect = rect;
508     scrollRect.move(scrollbar->x(), scrollbar->y());
509     RECT r = scrollRect;
510     ::InvalidateRect(m_popup, &r, false);
511 }
512 
513 // Message pump messages.
514 
onMouseActivate(HWND hWnd,UINT message,WPARAM,LPARAM,bool & handled)515 LRESULT WebPopupMenuProxyWin::onMouseActivate(HWND hWnd, UINT message, WPARAM, LPARAM, bool& handled)
516 {
517     handled = true;
518     return MA_NOACTIVATE;
519 }
520 
onSize(HWND hWnd,UINT message,WPARAM,LPARAM lParam,bool & handled)521 LRESULT WebPopupMenuProxyWin::onSize(HWND hWnd, UINT message, WPARAM, LPARAM lParam, bool& handled)
522 {
523     handled = true;
524     if (!scrollbar())
525         return 0;
526 
527     IntSize size(LOWORD(lParam), HIWORD(lParam));
528     scrollbar()->setFrameRect(IntRect(size.width() - scrollbar()->width(), 0, scrollbar()->width(), size.height()));
529 
530     int visibleItems = this->visibleItems();
531     scrollbar()->setEnabled(visibleItems < m_items.size());
532     scrollbar()->setSteps(1, max(1, visibleItems - 1));
533     scrollbar()->setProportion(visibleItems, m_items.size());
534     return 0;
535 }
536 
onKeyDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam,bool & handled)537 LRESULT WebPopupMenuProxyWin::onKeyDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
538 {
539     handled = true;
540 
541     LRESULT lResult = 0;
542     switch (LOWORD(wParam)) {
543     case VK_DOWN:
544     case VK_RIGHT:
545         down();
546         break;
547     case VK_UP:
548     case VK_LEFT:
549         up();
550         break;
551     case VK_HOME:
552         focusFirst();
553         break;
554     case VK_END:
555         focusLast();
556         break;
557     case VK_PRIOR:
558         if (focusedIndex() != scrollOffset()) {
559             // Set the selection to the first visible item
560             int firstVisibleItem = scrollOffset();
561             up(focusedIndex() - firstVisibleItem);
562         } else {
563             // The first visible item is selected, so move the selection back one page
564             up(visibleItems());
565         }
566         break;
567     case VK_NEXT: {
568         int lastVisibleItem = scrollOffset() + visibleItems() - 1;
569         if (focusedIndex() != lastVisibleItem) {
570             // Set the selection to the last visible item
571             down(lastVisibleItem - focusedIndex());
572         } else {
573             // The last visible item is selected, so move the selection forward one page
574             down(visibleItems());
575         }
576         break;
577     }
578     case VK_TAB:
579         ::SendMessage(m_webView->window(), message, wParam, lParam);
580         hide();
581         break;
582     case VK_ESCAPE:
583         hide();
584         break;
585     default:
586         if (isASCIIPrintable(wParam)) {
587             // Send the keydown to the WebView so it can be used for type-to-select.
588             // Since we know that the virtual key is ASCII printable, it's OK to convert this to
589             // a WM_CHAR message. (We don't want to call TranslateMessage because that will post a
590             // WM_CHAR message that will be stolen and redirected to the popup HWND.
591             ::PostMessage(m_popup, WM_HOST_WINDOW_CHAR, wParam, lParam);
592         } else
593             lResult = 1;
594         break;
595     }
596 
597     return lResult;
598 }
599 
onChar(HWND hWnd,UINT message,WPARAM wParam,LPARAM,bool & handled)600 LRESULT WebPopupMenuProxyWin::onChar(HWND hWnd, UINT message, WPARAM wParam, LPARAM, bool& handled)
601 {
602     handled = true;
603 
604     LRESULT lResult = 0;
605     int index;
606     switch (wParam) {
607     case 0x0D:   // Enter/Return
608         hide();
609         index = focusedIndex();
610         ASSERT(index >= 0);
611         // FIXME: Do we need to send back the index right away?
612         m_newSelectedIndex = index;
613         break;
614     case 0x1B:   // Escape
615         hide();
616         break;
617     case 0x09:   // TAB
618     case 0x08:   // Backspace
619     case 0x0A:   // Linefeed
620     default:     // Character
621         lResult = 1;
622         break;
623     }
624 
625     return lResult;
626 }
627 
onMouseMove(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam,bool & handled)628 LRESULT WebPopupMenuProxyWin::onMouseMove(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
629 {
630     handled = true;
631 
632     IntPoint mousePoint(MAKEPOINTS(lParam));
633     if (scrollbar()) {
634         IntRect scrollBarRect = scrollbar()->frameRect();
635         if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) {
636             // Put the point into coordinates relative to the scroll bar
637             mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
638             PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
639             scrollbar()->mouseMoved(event);
640             return 0;
641         }
642     }
643 
644     BOOL shouldHotTrack = FALSE;
645     ::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &shouldHotTrack, 0);
646 
647     RECT bounds;
648     ::GetClientRect(m_popup, &bounds);
649     if (!::PtInRect(&bounds, mousePoint) && !(wParam & MK_LBUTTON)) {
650         // When the mouse is not inside the popup menu and the left button isn't down, just
651         // repost the message to the web view.
652 
653         // Translate the coordinate.
654         translatePoint(lParam, m_popup, m_webView->window());
655 
656         ::PostMessage(m_popup, WM_HOST_WINDOW_MOUSEMOVE, wParam, lParam);
657         return 0;
658     }
659 
660     if ((shouldHotTrack || wParam & MK_LBUTTON) && ::PtInRect(&bounds, mousePoint))
661         setFocusedIndex(listIndexAtPoint(mousePoint), true);
662 
663     return 0;
664 }
665 
onLButtonDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam,bool & handled)666 LRESULT WebPopupMenuProxyWin::onLButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
667 {
668     handled = true;
669 
670     IntPoint mousePoint(MAKEPOINTS(lParam));
671     if (scrollbar()) {
672         IntRect scrollBarRect = scrollbar()->frameRect();
673         if (scrollBarRect.contains(mousePoint)) {
674             // Put the point into coordinates relative to the scroll bar
675             mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
676             PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
677             scrollbar()->mouseDown(event);
678             setScrollbarCapturingMouse(true);
679             return 0;
680         }
681     }
682 
683     // If the mouse is inside the window, update the focused index. Otherwise,
684     // hide the popup.
685     RECT bounds;
686     ::GetClientRect(m_popup, &bounds);
687     if (::PtInRect(&bounds, mousePoint))
688         setFocusedIndex(listIndexAtPoint(mousePoint), true);
689     else
690         hide();
691 
692     return 0;
693 }
694 
695 
onLButtonUp(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam,bool & handled)696 LRESULT WebPopupMenuProxyWin::onLButtonUp(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
697 {
698     handled = true;
699 
700     IntPoint mousePoint(MAKEPOINTS(lParam));
701     if (scrollbar()) {
702         IntRect scrollBarRect = scrollbar()->frameRect();
703         if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) {
704             setScrollbarCapturingMouse(false);
705             // Put the point into coordinates relative to the scroll bar
706             mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
707             PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
708             scrollbar()->mouseUp();
709             // FIXME: This is a hack to work around Scrollbar not invalidating correctly when it doesn't have a parent widget
710             RECT r = scrollBarRect;
711             ::InvalidateRect(m_popup, &r, TRUE);
712             return 0;
713         }
714     }
715     // Only hide the popup if the mouse is inside the popup window.
716     RECT bounds;
717     ::GetClientRect(m_popup, &bounds);
718     if (::PtInRect(&bounds, mousePoint)) {
719         hide();
720         int index = focusedIndex();
721         if (index >= 0) {
722             // FIXME: Do we need to send back the index right away?
723              m_newSelectedIndex = index;
724         }
725     }
726 
727     return 0;
728 }
729 
onMouseWheel(HWND hWnd,UINT message,WPARAM wParam,LPARAM,bool & handled)730 LRESULT WebPopupMenuProxyWin::onMouseWheel(HWND hWnd, UINT message, WPARAM wParam, LPARAM, bool& handled)
731 {
732     handled = true;
733 
734     if (!scrollbar())
735         return 0;
736 
737     int i = 0;
738     for (incrementWheelDelta(GET_WHEEL_DELTA_WPARAM(wParam)); abs(wheelDelta()) >= WHEEL_DELTA; reduceWheelDelta(WHEEL_DELTA)) {
739         if (wheelDelta() > 0)
740             ++i;
741         else
742             --i;
743     }
744 
745     ScrollableArea::scroll(i > 0 ? ScrollUp : ScrollDown, ScrollByLine, abs(i));
746     return 0;
747 }
748 
onPaint(HWND hWnd,UINT message,WPARAM,LPARAM,bool & handled)749 LRESULT WebPopupMenuProxyWin::onPaint(HWND hWnd, UINT message, WPARAM, LPARAM, bool& handled)
750 {
751     handled = true;
752 
753     PAINTSTRUCT paintStruct;
754     ::BeginPaint(m_popup, &paintStruct);
755     paint(paintStruct.rcPaint, paintStruct.hdc);
756     ::EndPaint(m_popup, &paintStruct);
757 
758     return 0;
759 }
760 
onPrintClient(HWND hWnd,UINT,WPARAM wParam,LPARAM,bool & handled)761 LRESULT WebPopupMenuProxyWin::onPrintClient(HWND hWnd, UINT, WPARAM wParam, LPARAM, bool& handled)
762 {
763     handled = true;
764 
765     HDC hdc = reinterpret_cast<HDC>(wParam);
766     paint(clientRect(), hdc);
767 
768     return 0;
769 }
770 
down(unsigned lines)771 bool WebPopupMenuProxyWin::down(unsigned lines)
772 {
773     int size = m_items.size();
774 
775     int lastSelectableIndex, selectedListIndex;
776     lastSelectableIndex = selectedListIndex = focusedIndex();
777     for (int i = selectedListIndex + 1; i >= 0 && i < size; ++i) {
778         if (m_items[i].m_isEnabled) {
779             lastSelectableIndex = i;
780             if (i >= selectedListIndex + (int)lines)
781                 break;
782         }
783     }
784 
785     return setFocusedIndex(lastSelectableIndex);
786 }
787 
up(unsigned lines)788 bool WebPopupMenuProxyWin::up(unsigned lines)
789 {
790     int size = m_items.size();
791 
792     int lastSelectableIndex, selectedListIndex;
793     lastSelectableIndex = selectedListIndex = focusedIndex();
794     for (int i = selectedListIndex - 1; i >= 0 && i < size; --i) {
795         if (m_items[i].m_isEnabled) {
796             lastSelectableIndex = i;
797             if (i <= selectedListIndex - (int)lines)
798                 break;
799         }
800     }
801 
802     return setFocusedIndex(lastSelectableIndex);
803 }
804 
paint(const IntRect & damageRect,HDC hdc)805 void WebPopupMenuProxyWin::paint(const IntRect& damageRect, HDC hdc)
806 {
807     if (!m_popup)
808         return;
809 
810     if (!m_DC) {
811         m_DC = ::CreateCompatibleDC(::GetDC(m_popup));
812         if (!m_DC)
813             return;
814     }
815 
816     if (m_bmp) {
817         bool keepBitmap = false;
818         BITMAP bitmap;
819         if (::GetObject(m_bmp, sizeof(bitmap), &bitmap))
820             keepBitmap = bitmap.bmWidth == clientRect().width() && bitmap.bmHeight == clientRect().height();
821         if (!keepBitmap) {
822             ::DeleteObject(m_bmp);
823             m_bmp = 0;
824         }
825     }
826 
827     if (!m_bmp) {
828         BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(clientRect().size());
829         void* pixels = 0;
830         m_bmp = ::CreateDIBSection(m_DC, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0);
831         if (!m_bmp)
832             return;
833         ::SelectObject(m_DC, m_bmp);
834     }
835 
836     GraphicsContext context(m_DC);
837 
838     IntRect translatedDamageRect = damageRect;
839     translatedDamageRect.move(IntSize(0, m_scrollOffset * m_itemHeight));
840     m_data.m_notSelectedBackingStore->paint(context, damageRect.location(), translatedDamageRect);
841 
842     IntRect selectedIndexRectInBackingStore(0, focusedIndex() * m_itemHeight, m_data.m_selectedBackingStore->size().width(), m_itemHeight);
843     IntPoint selectedIndexDstPoint = selectedIndexRectInBackingStore.location();
844     selectedIndexDstPoint.move(0, -m_scrollOffset * m_itemHeight);
845 
846     m_data.m_selectedBackingStore->paint(context, selectedIndexDstPoint, selectedIndexRectInBackingStore);
847 
848     if (m_scrollbar)
849         m_scrollbar->paint(&context, damageRect);
850 
851     HDC localDC = hdc ? hdc : ::GetDC(m_popup);
852 
853     ::BitBlt(localDC, damageRect.x(), damageRect.y(), damageRect.width(), damageRect.height(), m_DC, damageRect.x(), damageRect.y(), SRCCOPY);
854 
855     if (!hdc)
856         ::ReleaseDC(m_popup, localDC);
857 }
858 
setFocusedIndex(int i,bool hotTracking)859 bool WebPopupMenuProxyWin::setFocusedIndex(int i, bool hotTracking)
860 {
861     if (i < 0 || i >= m_items.size() || i == focusedIndex())
862         return false;
863 
864     if (!m_items[i].m_isEnabled)
865         return false;
866 
867     invalidateItem(focusedIndex());
868     invalidateItem(i);
869 
870     m_focusedIndex = i;
871 
872     if (!hotTracking) {
873         if (m_client)
874             m_client->setTextFromItemForPopupMenu(this, i);
875     }
876 
877     if (!scrollToRevealSelection())
878         ::UpdateWindow(m_popup);
879 
880     return true;
881 }
882 
visibleItems() const883 int WebPopupMenuProxyWin::visibleItems() const
884 {
885     return clientRect().height() / m_itemHeight;
886 }
887 
listIndexAtPoint(const IntPoint & point) const888 int WebPopupMenuProxyWin::listIndexAtPoint(const IntPoint& point) const
889 {
890     return m_scrollOffset + point.y() / m_itemHeight;
891 }
892 
focusedIndex() const893 int WebPopupMenuProxyWin::focusedIndex() const
894 {
895     return m_focusedIndex;
896 }
897 
focusFirst()898 void WebPopupMenuProxyWin::focusFirst()
899 {
900     int size = m_items.size();
901 
902     for (int i = 0; i < size; ++i) {
903         if (m_items[i].m_isEnabled) {
904             setFocusedIndex(i);
905             break;
906         }
907     }
908 }
909 
focusLast()910 void WebPopupMenuProxyWin::focusLast()
911 {
912     int size = m_items.size();
913 
914     for (int i = size - 1; i > 0; --i) {
915         if (m_items[i].m_isEnabled) {
916             setFocusedIndex(i);
917             break;
918         }
919     }
920 }
921 
922 
incrementWheelDelta(int delta)923 void WebPopupMenuProxyWin::incrementWheelDelta(int delta)
924 {
925     m_wheelDelta += delta;
926 }
927 
reduceWheelDelta(int delta)928 void WebPopupMenuProxyWin::reduceWheelDelta(int delta)
929 {
930     ASSERT(delta >= 0);
931     ASSERT(delta <= abs(m_wheelDelta));
932 
933     if (m_wheelDelta > 0)
934         m_wheelDelta -= delta;
935     else if (m_wheelDelta < 0)
936         m_wheelDelta += delta;
937     else
938         return;
939 }
940 
scrollToRevealSelection()941 bool WebPopupMenuProxyWin::scrollToRevealSelection()
942 {
943     if (!m_scrollbar)
944         return false;
945 
946     int index = focusedIndex();
947 
948     if (index < m_scrollOffset) {
949         ScrollableArea::scrollToYOffsetWithoutAnimation(index);
950         return true;
951     }
952 
953     if (index >= m_scrollOffset + visibleItems()) {
954         ScrollableArea::scrollToYOffsetWithoutAnimation(index - visibleItems() + 1);
955         return true;
956     }
957 
958     return false;
959 }
960 
961 } // namespace WebKit
962