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
48 using namespace WebCore;
49 using namespace WebKit;
50
51 namespace {
52
53 class TestPopupMenuClient : public PopupMenuClient {
54 public:
55 // Item at index 0 is selected by default.
TestPopupMenuClient()56 TestPopupMenuClient() : m_selectIndex(0) { }
~TestPopupMenuClient()57 virtual ~TestPopupMenuClient() {}
valueChanged(unsigned listIndex,bool fireEvents=true)58 virtual void valueChanged(unsigned listIndex, bool fireEvents = true)
59 {
60 m_selectIndex = listIndex;
61 }
selectionChanged(unsigned,bool)62 virtual void selectionChanged(unsigned, bool) {}
selectionCleared()63 virtual void selectionCleared() {}
64
itemText(unsigned listIndex) const65 virtual String itemText(unsigned listIndex) const
66 {
67 String str("Item ");
68 str.append(String::number(listIndex));
69 return str;
70 }
itemLabel(unsigned) const71 virtual String itemLabel(unsigned) const { return String(); }
itemIcon(unsigned) const72 virtual String itemIcon(unsigned) const { return String(); }
itemToolTip(unsigned listIndex) const73 virtual String itemToolTip(unsigned listIndex) const { return itemText(listIndex); }
itemAccessibilityText(unsigned listIndex) const74 virtual String itemAccessibilityText(unsigned listIndex) const { return itemText(listIndex); }
itemIsEnabled(unsigned listIndex) const75 virtual bool itemIsEnabled(unsigned listIndex) const { return true; }
itemStyle(unsigned listIndex) const76 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 }
menuStyle() const81 virtual PopupMenuStyle menuStyle() const { return itemStyle(0); }
clientInsetLeft() const82 virtual int clientInsetLeft() const { return 0; }
clientInsetRight() const83 virtual int clientInsetRight() const { return 0; }
clientPaddingLeft() const84 virtual int clientPaddingLeft() const { return 0; }
clientPaddingRight() const85 virtual int clientPaddingRight() const { return 0; }
listSize() const86 virtual int listSize() const { return 10; }
selectedIndex() const87 virtual int selectedIndex() const { return m_selectIndex; }
popupDidHide()88 virtual void popupDidHide() { }
itemIsSeparator(unsigned listIndex) const89 virtual bool itemIsSeparator(unsigned listIndex) const { return false; }
itemIsLabel(unsigned listIndex) const90 virtual bool itemIsLabel(unsigned listIndex) const { return false; }
itemIsSelected(unsigned listIndex) const91 virtual bool itemIsSelected(unsigned listIndex) const { return listIndex == m_selectIndex; }
shouldPopOver() const92 virtual bool shouldPopOver() const { return false; }
valueShouldChangeOnHotTrack() const93 virtual bool valueShouldChangeOnHotTrack() const { return false; }
setTextFromItem(unsigned listIndex)94 virtual void setTextFromItem(unsigned listIndex) { }
95
fontSelector() const96 virtual FontSelector* fontSelector() const { return 0; }
hostWindow() const97 virtual HostWindow* hostWindow() const { return 0; }
98
createScrollbar(ScrollableArea *,ScrollbarOrientation,ScrollbarControlSize)99 virtual PassRefPtr<Scrollbar> createScrollbar(ScrollableArea*, ScrollbarOrientation, ScrollbarControlSize) { return 0; }
100
101 private:
102 unsigned m_selectIndex;
103 };
104
105 class TestWebWidgetClient : public WebWidgetClient {
106 public:
~TestWebWidgetClient()107 ~TestWebWidgetClient() { }
108 };
109
110 class TestWebPopupMenuImpl : public WebPopupMenuImpl {
111 public:
create(WebWidgetClient * client)112 static PassRefPtr<TestWebPopupMenuImpl> create(WebWidgetClient* client)
113 {
114 return adoptRef(new TestWebPopupMenuImpl(client));
115 }
116
~TestWebPopupMenuImpl()117 ~TestWebPopupMenuImpl() { }
118
119 private:
TestWebPopupMenuImpl(WebWidgetClient * client)120 TestWebPopupMenuImpl(WebWidgetClient* client) : WebPopupMenuImpl(client) { }
121 };
122
123 class TestWebWidget : public WebWidget {
124 public:
~TestWebWidget()125 virtual ~TestWebWidget() { }
close()126 virtual void close() { }
size()127 virtual WebSize size() { return WebSize(100, 100); }
resize(const WebSize &)128 virtual void resize(const WebSize&) { }
layout()129 virtual void layout() { }
paint(WebCanvas *,const WebRect &)130 virtual void paint(WebCanvas*, const WebRect&) { }
themeChanged()131 virtual void themeChanged() { }
composite(bool finish)132 virtual void composite(bool finish) { }
handleInputEvent(const WebInputEvent &)133 virtual bool handleInputEvent(const WebInputEvent&) { return true; }
mouseCaptureLost()134 virtual void mouseCaptureLost() { }
setFocus(bool)135 virtual void setFocus(bool) { }
setComposition(const WebString & text,const WebVector<WebCompositionUnderline> & underlines,int selectionStart,int selectionEnd)136 virtual bool setComposition(
137 const WebString& text,
138 const WebVector<WebCompositionUnderline>& underlines,
139 int selectionStart,
140 int selectionEnd) { return true; }
confirmComposition()141 virtual bool confirmComposition() { return true; }
confirmComposition(const WebString & text)142 virtual bool confirmComposition(const WebString& text) { return true; }
textInputType()143 virtual WebTextInputType textInputType() { return WebKit::WebTextInputTypeNone; }
caretOrSelectionBounds()144 virtual WebRect caretOrSelectionBounds() { return WebRect(); }
selectionRange(WebPoint & start,WebPoint & end) const145 virtual bool selectionRange(WebPoint& start, WebPoint& end) const { return false; }
setTextDirection(WebTextDirection)146 virtual void setTextDirection(WebTextDirection) { }
147 };
148
149 class TestWebViewClient : public WebViewClient {
150 public:
TestWebViewClient()151 TestWebViewClient() : m_webPopupMenu(TestWebPopupMenuImpl::create(&m_webWidgetClient)) { }
~TestWebViewClient()152 ~TestWebViewClient() { }
153
createPopupMenu(WebPopupType)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).
screenInfo()158 virtual WebScreenInfo screenInfo()
159 {
160 WebScreenInfo screenInfo;
161 screenInfo.availableRect.height = 2000;
162 screenInfo.availableRect.width = 2000;
163 return screenInfo;
164 }
165
166 private:
167 TestWebWidgetClient m_webWidgetClient;
168 RefPtr<TestWebPopupMenuImpl> m_webPopupMenu;
169 };
170
171 class TestWebFrameClient : public WebFrameClient {
172 public:
~TestWebFrameClient()173 ~TestWebFrameClient() { }
174 };
175
176 class SelectPopupMenuTest : public testing::Test {
177 public:
SelectPopupMenuTest()178 SelectPopupMenuTest()
179 {
180 }
181
182 protected:
SetUp()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
TearDown()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.
popupOpen() const197 bool popupOpen() const { return m_webView->selectPopup(); }
198
selectedIndex() const199 int selectedIndex() const { return m_popupMenuClient.selectedIndex(); }
200
showPopup()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
hidePopup()209 void hidePopup()
210 {
211 m_popupMenu->hide();
212 EXPECT_FALSE(popupOpen());
213 }
214
simulateKeyDownEvent(int keyCode)215 void simulateKeyDownEvent(int keyCode)
216 {
217 simulateKeyEvent(WebInputEvent::RawKeyDown, keyCode);
218 }
219
simulateKeyUpEvent(int keyCode)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.
simulateKeyEvent(WebInputEvent::Type eventType,int keyCode)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.
simulateLeftMouseDownEvent(const IntPoint & point)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 }
simulateLeftMouseUpEvent(const IntPoint & point)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
249 protected:
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.
TEST_F(SelectPopupMenuTest,ShowThenHide)259 TEST_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.
TEST_F(SelectPopupMenuTest,ShowThenDelete)269 TEST_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.
TEST_F(SelectPopupMenuTest,ShowThenLoseFocus)276 TEST_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.
TEST_F(SelectPopupMenuTest,ShowThenPressESC)287 TEST_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.
TEST_F(SelectPopupMenuTest,SelectWithKeys)296 TEST_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.
TEST_F(SelectPopupMenuTest,ClickItem)325 TEST_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.
TEST_F(SelectPopupMenuTest,MouseOverItemClickOutside)342 TEST_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.
TEST_F(SelectPopupMenuTest,SelectItemWithKeyboardItemClickOutside)363 TEST_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