1/*
2 * Copyright (C) 2010 Google 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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32
33#include <gtest/gtest.h>
34
35#include "Color.h"
36#include "KeyboardCodes.h"
37#include "PopupMenu.h"
38#include "PopupMenuClient.h"
39#include "PopupMenuChromium.h"
40#include "WebFrameClient.h"
41#include "WebFrameImpl.h"
42#include "WebInputEvent.h"
43#include "WebPopupMenuImpl.h"
44#include "WebScreenInfo.h"
45#include "WebViewClient.h"
46#include "WebViewImpl.h"
47
48using namespace WebCore;
49using namespace WebKit;
50
51namespace {
52
53class TestPopupMenuClient : public PopupMenuClient {
54public:
55    // Item at index 0 is selected by default.
56    TestPopupMenuClient() : m_selectIndex(0) { }
57    virtual ~TestPopupMenuClient() {}
58    virtual void valueChanged(unsigned listIndex, bool fireEvents = true)
59    {
60        m_selectIndex = listIndex;
61    }
62    virtual void selectionChanged(unsigned, bool) {}
63    virtual void selectionCleared() {}
64
65    virtual String itemText(unsigned listIndex) const
66    {
67        String str("Item ");
68        str.append(String::number(listIndex));
69        return str;
70    }
71    virtual String itemLabel(unsigned) const { return String(); }
72    virtual String itemIcon(unsigned) const { return String(); }
73    virtual String itemToolTip(unsigned listIndex) const { return itemText(listIndex); }
74    virtual String itemAccessibilityText(unsigned listIndex) const { return itemText(listIndex); }
75    virtual bool itemIsEnabled(unsigned listIndex) const { return true; }
76    virtual PopupMenuStyle itemStyle(unsigned listIndex) const
77    {
78        Font font(FontPlatformData(12.0, false, false), false);
79        return PopupMenuStyle(Color::black, Color::white, font, true, false, Length(), TextDirection(), false /* has text direction override */);
80    }
81    virtual PopupMenuStyle menuStyle() const { return itemStyle(0); }
82    virtual int clientInsetLeft() const { return 0; }
83    virtual int clientInsetRight() const { return 0; }
84    virtual int clientPaddingLeft() const { return 0; }
85    virtual int clientPaddingRight() const { return 0; }
86    virtual int listSize() const { return 10; }
87    virtual int selectedIndex() const { return m_selectIndex; }
88    virtual void popupDidHide() { }
89    virtual bool itemIsSeparator(unsigned listIndex) const { return false; }
90    virtual bool itemIsLabel(unsigned listIndex) const { return false; }
91    virtual bool itemIsSelected(unsigned listIndex) const { return listIndex == m_selectIndex; }
92    virtual bool shouldPopOver() const { return false; }
93    virtual bool valueShouldChangeOnHotTrack() const { return false; }
94    virtual void setTextFromItem(unsigned listIndex) { }
95
96    virtual FontSelector* fontSelector() const { return 0; }
97    virtual HostWindow* hostWindow() const { return 0; }
98
99    virtual PassRefPtr<Scrollbar> createScrollbar(ScrollableArea*, ScrollbarOrientation, ScrollbarControlSize) { return 0; }
100
101private:
102    unsigned m_selectIndex;
103};
104
105class TestWebWidgetClient : public WebWidgetClient {
106public:
107    ~TestWebWidgetClient() { }
108};
109
110class TestWebPopupMenuImpl : public WebPopupMenuImpl {
111public:
112    static PassRefPtr<TestWebPopupMenuImpl> create(WebWidgetClient* client)
113    {
114        return adoptRef(new TestWebPopupMenuImpl(client));
115    }
116
117    ~TestWebPopupMenuImpl() { }
118
119private:
120    TestWebPopupMenuImpl(WebWidgetClient* client) : WebPopupMenuImpl(client) { }
121};
122
123class TestWebWidget : public WebWidget {
124public:
125    virtual ~TestWebWidget() { }
126    virtual void close() { }
127    virtual WebSize size() { return WebSize(100, 100); }
128    virtual void resize(const WebSize&) { }
129    virtual void layout() { }
130    virtual void paint(WebCanvas*, const WebRect&) { }
131    virtual void themeChanged() { }
132    virtual void composite(bool finish) { }
133    virtual bool handleInputEvent(const WebInputEvent&) { return true; }
134    virtual void mouseCaptureLost() { }
135    virtual void setFocus(bool) { }
136    virtual bool setComposition(
137        const WebString& text,
138        const WebVector<WebCompositionUnderline>& underlines,
139        int selectionStart,
140        int selectionEnd) { return true; }
141    virtual bool confirmComposition() { return true; }
142    virtual bool confirmComposition(const WebString& text) { return true; }
143    virtual WebTextInputType textInputType() { return WebKit::WebTextInputTypeNone; }
144    virtual WebRect caretOrSelectionBounds() { return WebRect(); }
145    virtual bool selectionRange(WebPoint& start, WebPoint& end) const { return false; }
146    virtual void setTextDirection(WebTextDirection) { }
147};
148
149class TestWebViewClient : public WebViewClient {
150public:
151    TestWebViewClient() : m_webPopupMenu(TestWebPopupMenuImpl::create(&m_webWidgetClient)) { }
152    ~TestWebViewClient() { }
153
154    virtual WebWidget* createPopupMenu(WebPopupType) { return m_webPopupMenu.get(); }
155
156    // We need to override this so that the popup menu size is not 0
157    // (the layout code checks to see if the popup fits on the screen).
158    virtual WebScreenInfo screenInfo()
159    {
160        WebScreenInfo screenInfo;
161        screenInfo.availableRect.height = 2000;
162        screenInfo.availableRect.width = 2000;
163        return screenInfo;
164    }
165
166private:
167    TestWebWidgetClient m_webWidgetClient;
168    RefPtr<TestWebPopupMenuImpl> m_webPopupMenu;
169};
170
171class TestWebFrameClient : public WebFrameClient {
172public:
173    ~TestWebFrameClient() { }
174};
175
176class SelectPopupMenuTest : public testing::Test {
177public:
178    SelectPopupMenuTest()
179    {
180    }
181
182protected:
183    virtual void SetUp()
184    {
185        m_webView = static_cast<WebViewImpl*>(WebView::create(&m_webviewClient));
186        m_webView->initializeMainFrame(&m_webFrameClient);
187        m_popupMenu = adoptRef(new PopupMenuChromium(&m_popupMenuClient));
188    }
189
190    virtual void TearDown()
191    {
192        m_popupMenu = 0;
193        m_webView->close();
194    }
195
196    // Returns true if there currently is a select popup in the WebView.
197    bool popupOpen() const { return m_webView->selectPopup(); }
198
199    int selectedIndex() const { return m_popupMenuClient.selectedIndex(); }
200
201    void showPopup()
202    {
203        m_popupMenu->show(IntRect(0, 0, 100, 100),
204            static_cast<WebFrameImpl*>(m_webView->mainFrame())->frameView(), 0);
205        ASSERT_TRUE(popupOpen());
206        EXPECT_TRUE(m_webView->selectPopup()->popupType() == PopupContainer::Select);
207    }
208
209    void hidePopup()
210    {
211        m_popupMenu->hide();
212        EXPECT_FALSE(popupOpen());
213    }
214
215    void simulateKeyDownEvent(int keyCode)
216    {
217        simulateKeyEvent(WebInputEvent::RawKeyDown, keyCode);
218    }
219
220    void simulateKeyUpEvent(int keyCode)
221    {
222        simulateKeyEvent(WebInputEvent::KeyUp, keyCode);
223    }
224
225    // Simulates a key event on the WebView.
226    // The WebView forwards the event to the select popup if one is open.
227    void simulateKeyEvent(WebInputEvent::Type eventType, int keyCode)
228    {
229        WebKeyboardEvent keyEvent;
230        keyEvent.windowsKeyCode = keyCode;
231        keyEvent.type = eventType;
232        m_webView->handleInputEvent(keyEvent);
233    }
234
235    // Simulates a mouse event on the select popup.
236    void simulateLeftMouseDownEvent(const IntPoint& point)
237    {
238        PlatformMouseEvent mouseEvent(point, point, LeftButton, MouseEventPressed,
239                                      1, false, false, false, false, 0);
240        m_webView->selectPopup()->handleMouseDownEvent(mouseEvent);
241    }
242    void simulateLeftMouseUpEvent(const IntPoint& point)
243    {
244        PlatformMouseEvent mouseEvent(point, point, LeftButton, MouseEventReleased,
245                                      1, false, false, false, false, 0);
246        m_webView->selectPopup()->handleMouseReleaseEvent(mouseEvent);
247    }
248
249protected:
250    TestWebViewClient m_webviewClient;
251    WebViewImpl* m_webView;
252    TestWebFrameClient m_webFrameClient;
253    TestPopupMenuClient m_popupMenuClient;
254    RefPtr<PopupMenu> m_popupMenu;
255};
256
257// Tests that show/hide and repeats.  Select popups are reused in web pages when
258// they are reopened, that what this is testing.
259TEST_F(SelectPopupMenuTest, ShowThenHide)
260{
261    for (int i = 0; i < 3; i++) {
262        showPopup();
263        hidePopup();
264    }
265}
266
267// Tests that showing a select popup and deleting it does not cause problem.
268// This happens in real-life if a page navigates while a select popup is showing.
269TEST_F(SelectPopupMenuTest, ShowThenDelete)
270{
271    showPopup();
272    // Nothing else to do, TearDown() deletes the popup.
273}
274
275// Tests that losing focus closes the select popup.
276TEST_F(SelectPopupMenuTest, ShowThenLoseFocus)
277{
278    showPopup();
279    // Simulate losing focus.
280    m_webView->setFocus(false);
281
282    // Popup should have closed.
283    EXPECT_FALSE(popupOpen());
284}
285
286// Tests that pressing ESC closes the popup.
287TEST_F(SelectPopupMenuTest, ShowThenPressESC)
288{
289    showPopup();
290    simulateKeyDownEvent(VKEY_ESCAPE);
291    // Popup should have closed.
292    EXPECT_FALSE(popupOpen());
293}
294
295// Tests selecting an item with the arrows and enter/esc/tab.
296TEST_F(SelectPopupMenuTest, SelectWithKeys)
297{
298    showPopup();
299    // Simulate selecting the 2nd item by pressing Down, Down, enter.
300    simulateKeyDownEvent(VKEY_DOWN);
301    simulateKeyDownEvent(VKEY_DOWN);
302    simulateKeyDownEvent(VKEY_RETURN);
303
304    // Popup should have closed.
305    EXPECT_TRUE(!popupOpen());
306    EXPECT_EQ(2, selectedIndex());
307
308    // It should work as well with ESC.
309    showPopup();
310    simulateKeyDownEvent(VKEY_DOWN);
311    simulateKeyDownEvent(VKEY_ESCAPE);
312    EXPECT_FALSE(popupOpen());
313    EXPECT_EQ(3, selectedIndex());
314
315    // It should work as well with TAB.
316    showPopup();
317    simulateKeyDownEvent(VKEY_DOWN);
318    simulateKeyDownEvent(VKEY_TAB);
319    EXPECT_FALSE(popupOpen());
320    EXPECT_EQ(4, selectedIndex());
321}
322
323// Tests that selecting an item with the mouse does select the item and close
324// the popup.
325TEST_F(SelectPopupMenuTest, ClickItem)
326{
327    showPopup();
328
329    // Y of 18 to be on the item at index 1 (12 font plus border and more to be safe).
330    IntPoint row1Point(2, 18);
331    // Simulate a click down/up on the first item.
332    simulateLeftMouseDownEvent(row1Point);
333    simulateLeftMouseUpEvent(row1Point);
334
335    // Popup should have closed and the item at index 1 selected.
336    EXPECT_FALSE(popupOpen());
337    EXPECT_EQ(1, selectedIndex());
338}
339
340// Tests that moving the mouse over an item and then clicking outside the select popup
341// leaves the seleted item unchanged.
342TEST_F(SelectPopupMenuTest, MouseOverItemClickOutside)
343{
344    showPopup();
345
346    // Y of 18 to be on the item at index 1 (12 font plus border and more to be safe).
347    IntPoint row1Point(2, 18);
348    // Simulate the mouse moving over the first item.
349    PlatformMouseEvent mouseEvent(row1Point, row1Point, NoButton, MouseEventMoved,
350                                  1, false, false, false, false, 0);
351    m_webView->selectPopup()->handleMouseMoveEvent(mouseEvent);
352
353    // Click outside the popup.
354    simulateLeftMouseDownEvent(IntPoint(1000, 1000));
355
356    // Popup should have closed and item 0 should still be selected.
357    EXPECT_FALSE(popupOpen());
358    EXPECT_EQ(0, selectedIndex());
359}
360
361// Tests that selecting an item with the keyboard and then clicking outside the select
362// popup does select that item.
363TEST_F(SelectPopupMenuTest, SelectItemWithKeyboardItemClickOutside)
364{
365    showPopup();
366
367    // Simulate selecting the 2nd item by pressing Down, Down.
368    simulateKeyDownEvent(VKEY_DOWN);
369    simulateKeyDownEvent(VKEY_DOWN);
370
371    // Click outside the popup.
372    simulateLeftMouseDownEvent(IntPoint(1000, 1000));
373
374    // Popup should have closed and the item should have been selected.
375    EXPECT_FALSE(popupOpen());
376    EXPECT_EQ(2, selectedIndex());
377}
378
379} // namespace
380