2 * Copyright (C) 2010 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 #include <gtest/gtest.h>
34 #include <webkit/support/webkit_support.h>
38 #include "FrameView.h"
39 #include "KeyboardCodes.h"
40 #include "PopupContainer.h"
41 #include "PopupMenu.h"
42 #include "PopupMenuClient.h"
43 #include "PopupMenuChromium.h"
44 #include "SelectElement.h"
45 #include "WebDocument.h"
46 #include "WebElement.h"
48 #include "WebFrameClient.h"
49 #include "WebFrameImpl.h"
50 #include "WebInputEvent.h"
51 #include "WebPopupMenuImpl.h"
52 #include "WebScreenInfo.h"
53 #include "WebSettings.h"
54 #include "WebString.h"
56 #include "WebURLRequest.h"
57 #include "WebURLResponse.h"
59 #include "WebViewClient.h"
60 #include "WebViewImpl.h"
63 using namespace WebCore;
64 using namespace WebKit;
68 class TestPopupMenuClient : public PopupMenuClient {
70 // Item at index 0 is selected by default.
71 TestPopupMenuClient() : m_selectIndex(0), m_node(0) { }
72 virtual ~TestPopupMenuClient() {}
73 virtual void valueChanged(unsigned listIndex, bool fireEvents = true)
75 m_selectIndex = listIndex;
77 SelectElement* select = toSelectElement(static_cast<Element*>(m_node));
78 select->setSelectedIndexByUser(select->listToOptionIndex(listIndex), true, fireEvents);
81 virtual void selectionChanged(unsigned, bool) {}
82 virtual void selectionCleared() {}
84 virtual String itemText(unsigned listIndex) const
87 str.append(String::number(listIndex));
90 virtual String itemLabel(unsigned) const { return String(); }
91 virtual String itemIcon(unsigned) const { return String(); }
92 virtual String itemToolTip(unsigned listIndex) const { return itemText(listIndex); }
93 virtual String itemAccessibilityText(unsigned listIndex) const { return itemText(listIndex); }
94 virtual bool itemIsEnabled(unsigned listIndex) const { return m_disabledIndexSet.find(listIndex) == m_disabledIndexSet.end(); }
95 virtual PopupMenuStyle itemStyle(unsigned listIndex) const
97 Font font(FontPlatformData(12.0, false, false), false);
98 return PopupMenuStyle(Color::black, Color::white, font, true, false, Length(), TextDirection(), false /* has text direction override */);
100 virtual PopupMenuStyle menuStyle() const { return itemStyle(0); }
101 virtual int clientInsetLeft() const { return 0; }
102 virtual int clientInsetRight() const { return 0; }
103 virtual int clientPaddingLeft() const { return 0; }
104 virtual int clientPaddingRight() const { return 0; }
105 virtual int listSize() const { return 10; }
106 virtual int selectedIndex() const { return m_selectIndex; }
107 virtual void popupDidHide() { }
108 virtual bool itemIsSeparator(unsigned listIndex) const { return false; }
109 virtual bool itemIsLabel(unsigned listIndex) const { return false; }
110 virtual bool itemIsSelected(unsigned listIndex) const { return listIndex == m_selectIndex; }
111 virtual bool shouldPopOver() const { return false; }
112 virtual bool valueShouldChangeOnHotTrack() const { return false; }
113 virtual void setTextFromItem(unsigned listIndex) { }
115 virtual FontSelector* fontSelector() const { return 0; }
116 virtual HostWindow* hostWindow() const { return 0; }
118 virtual PassRefPtr<Scrollbar> createScrollbar(ScrollableArea*, ScrollbarOrientation, ScrollbarControlSize) { return 0; }
120 void setDisabledIndex(unsigned index) { m_disabledIndexSet.insert(index); }
121 void setFocusedNode(Node* node) { m_node = node; }
124 unsigned m_selectIndex;
125 std::set<unsigned> m_disabledIndexSet;
129 class TestWebWidgetClient : public WebWidgetClient {
131 ~TestWebWidgetClient() { }
134 class TestWebPopupMenuImpl : public WebPopupMenuImpl {
136 static PassRefPtr<TestWebPopupMenuImpl> create(WebWidgetClient* client)
138 return adoptRef(new TestWebPopupMenuImpl(client));
141 ~TestWebPopupMenuImpl() { }
144 TestWebPopupMenuImpl(WebWidgetClient* client) : WebPopupMenuImpl(client) { }
147 class TestWebViewClient : public WebViewClient {
149 TestWebViewClient() : m_webPopupMenu(TestWebPopupMenuImpl::create(&m_webWidgetClient)) { }
150 ~TestWebViewClient() { }
152 virtual WebWidget* createPopupMenu(WebPopupType) { return m_webPopupMenu.get(); }
154 // We need to override this so that the popup menu size is not 0
155 // (the layout code checks to see if the popup fits on the screen).
156 virtual WebScreenInfo screenInfo()
158 WebScreenInfo screenInfo;
159 screenInfo.availableRect.height = 2000;
160 screenInfo.availableRect.width = 2000;
165 TestWebWidgetClient m_webWidgetClient;
166 RefPtr<TestWebPopupMenuImpl> m_webPopupMenu;
169 class TestWebFrameClient : public WebFrameClient {
171 ~TestWebFrameClient() { }
174 class SelectPopupMenuTest : public testing::Test {
176 SelectPopupMenuTest()
177 : baseURL("http://www.test.com/")
184 m_webView = static_cast<WebViewImpl*>(WebView::create(&m_webviewClient));
185 m_webView->initializeMainFrame(&m_webFrameClient);
186 m_popupMenu = adoptRef(new PopupMenuChromium(&m_popupMenuClient));
189 virtual void TearDown()
193 webkit_support::UnregisterAllMockedURLs();
196 // Returns true if there currently is a select popup in the WebView.
197 bool popupOpen() const { return m_webView->selectPopup(); }
199 int selectedIndex() const { return m_popupMenuClient.selectedIndex(); }
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);
212 EXPECT_FALSE(popupOpen());
215 void simulateKeyDownEvent(int keyCode)
217 simulateKeyEvent(WebInputEvent::RawKeyDown, keyCode);
220 void simulateKeyUpEvent(int keyCode)
222 simulateKeyEvent(WebInputEvent::KeyUp, keyCode);
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)
229 WebKeyboardEvent keyEvent;
230 keyEvent.windowsKeyCode = keyCode;
231 keyEvent.type = eventType;
232 m_webView->handleInputEvent(keyEvent);
235 // Simulates a mouse event on the select popup.
236 void simulateLeftMouseDownEvent(const IntPoint& point)
238 PlatformMouseEvent mouseEvent(point, point, LeftButton, MouseEventPressed,
239 1, false, false, false, false, 0);
240 m_webView->selectPopup()->handleMouseDownEvent(mouseEvent);
242 void simulateLeftMouseUpEvent(const IntPoint& point)
244 PlatformMouseEvent mouseEvent(point, point, LeftButton, MouseEventReleased,
245 1, false, false, false, false, 0);
246 m_webView->selectPopup()->handleMouseReleaseEvent(mouseEvent);
249 void registerMockedURLLoad(const std::string& fileName)
251 WebURLResponse response;
252 response.initialize();
253 response.setMIMEType("text/html");
255 std::string filePath = webkit_support::GetWebKitRootDir().utf8();
256 filePath += "/Source/WebKit/chromium/tests/data/popup/";
257 filePath += fileName;
259 webkit_support::RegisterMockedURL(WebURL(GURL(baseURL + fileName)), response, WebString::fromUTF8(filePath));
264 webkit_support::ServeAsynchronousMockedRequests();
267 void loadFrame(WebFrame* frame, const std::string& fileName)
269 WebURLRequest urlRequest;
270 urlRequest.initialize();
271 urlRequest.setURL(WebURL(GURL(baseURL + fileName)));
272 frame->loadRequest(urlRequest);
276 TestWebViewClient m_webviewClient;
277 WebViewImpl* m_webView;
278 TestWebFrameClient m_webFrameClient;
279 TestPopupMenuClient m_popupMenuClient;
280 RefPtr<PopupMenu> m_popupMenu;
284 // Tests that show/hide and repeats. Select popups are reused in web pages when
285 // they are reopened, that what this is testing.
286 TEST_F(SelectPopupMenuTest, ShowThenHide)
288 for (int i = 0; i < 3; i++) {
294 // Tests that showing a select popup and deleting it does not cause problem.
295 // This happens in real-life if a page navigates while a select popup is showing.
296 TEST_F(SelectPopupMenuTest, ShowThenDelete)
299 // Nothing else to do, TearDown() deletes the popup.
302 // Tests that losing focus closes the select popup.
303 TEST_F(SelectPopupMenuTest, ShowThenLoseFocus)
306 // Simulate losing focus.
307 m_webView->setFocus(false);
309 // Popup should have closed.
310 EXPECT_FALSE(popupOpen());
313 // Tests that pressing ESC closes the popup.
314 TEST_F(SelectPopupMenuTest, ShowThenPressESC)
317 simulateKeyDownEvent(VKEY_ESCAPE);
318 // Popup should have closed.
319 EXPECT_FALSE(popupOpen());
322 // Tests selecting an item with the arrows and enter/esc/tab.
323 TEST_F(SelectPopupMenuTest, SelectWithKeys)
326 // Simulate selecting the 2nd item by pressing Down, Down, enter.
327 simulateKeyDownEvent(VKEY_DOWN);
328 simulateKeyDownEvent(VKEY_DOWN);
329 simulateKeyDownEvent(VKEY_RETURN);
331 // Popup should have closed.
332 EXPECT_TRUE(!popupOpen());
333 EXPECT_EQ(2, selectedIndex());
335 // It should work as well with ESC.
337 simulateKeyDownEvent(VKEY_DOWN);
338 simulateKeyDownEvent(VKEY_ESCAPE);
339 EXPECT_FALSE(popupOpen());
340 EXPECT_EQ(3, selectedIndex());
342 // It should work as well with TAB.
344 simulateKeyDownEvent(VKEY_DOWN);
345 simulateKeyDownEvent(VKEY_TAB);
346 EXPECT_FALSE(popupOpen());
347 EXPECT_EQ(4, selectedIndex());
350 // Tests that selecting an item with the mouse does select the item and close
352 TEST_F(SelectPopupMenuTest, ClickItem)
356 // Y of 18 to be on the item at index 1 (12 font plus border and more to be safe).
357 IntPoint row1Point(2, 18);
358 // Simulate a click down/up on the first item.
359 simulateLeftMouseDownEvent(row1Point);
360 simulateLeftMouseUpEvent(row1Point);
362 // Popup should have closed and the item at index 1 selected.
363 EXPECT_FALSE(popupOpen());
364 EXPECT_EQ(1, selectedIndex());
367 // Tests that moving the mouse over an item and then clicking outside the select popup
368 // leaves the seleted item unchanged.
369 TEST_F(SelectPopupMenuTest, MouseOverItemClickOutside)
373 // Y of 18 to be on the item at index 1 (12 font plus border and more to be safe).
374 IntPoint row1Point(2, 18);
375 // Simulate the mouse moving over the first item.
376 PlatformMouseEvent mouseEvent(row1Point, row1Point, NoButton, MouseEventMoved,
377 1, false, false, false, false, 0);
378 m_webView->selectPopup()->handleMouseMoveEvent(mouseEvent);
380 // Click outside the popup.
381 simulateLeftMouseDownEvent(IntPoint(1000, 1000));
383 // Popup should have closed and item 0 should still be selected.
384 EXPECT_FALSE(popupOpen());
385 EXPECT_EQ(0, selectedIndex());
388 // Tests that selecting an item with the keyboard and then clicking outside the select
389 // popup does select that item.
390 TEST_F(SelectPopupMenuTest, SelectItemWithKeyboardItemClickOutside)
394 // Simulate selecting the 2nd item by pressing Down, Down.
395 simulateKeyDownEvent(VKEY_DOWN);
396 simulateKeyDownEvent(VKEY_DOWN);
398 // Click outside the popup.
399 simulateLeftMouseDownEvent(IntPoint(1000, 1000));
401 // Popup should have closed and the item should have been selected.
402 EXPECT_FALSE(popupOpen());
403 EXPECT_EQ(2, selectedIndex());
406 TEST_F(SelectPopupMenuTest, DISABLED_SelectItemEventFire)
408 registerMockedURLLoad("select_event.html");
409 m_webView->settings()->setJavaScriptEnabled(true);
410 loadFrame(m_webView->mainFrame(), "select_event.html");
413 m_popupMenuClient.setFocusedNode(static_cast<WebFrameImpl*>(m_webView->mainFrame())->frameView()->frame()->document()->focusedNode());
417 int menuHeight = m_webView->selectPopup()->menuItemHeight();
418 // menuHeight * 0.5 means the Y position on the item at index 0.
419 IntPoint row1Point(2, menuHeight * 0.5);
420 simulateLeftMouseDownEvent(row1Point);
421 simulateLeftMouseUpEvent(row1Point);
423 WebElement element = m_webView->mainFrame()->document().getElementById("message");
425 // mousedown event is held by select node, and we don't simulate the event for the node.
426 // So we can only see mouseup and click event.
427 EXPECT_STREQ("upclick", std::string(element.innerText().utf8()).c_str());
429 // Disable the item at index 1.
430 m_popupMenuClient.setDisabledIndex(1);
433 // menuHeight * 1.5 means the Y position on the item at index 1.
434 row1Point.setY(menuHeight * 1.5);
435 simulateLeftMouseDownEvent(row1Point);
436 simulateLeftMouseUpEvent(row1Point);
438 // The item at index 1 is disabled, so the text should not be changed.
439 EXPECT_STREQ("upclick", std::string(element.innerText().utf8()).c_str());
442 // menuHeight * 2.5 means the Y position on the item at index 2.
443 row1Point.setY(menuHeight * 2.5);
444 simulateLeftMouseDownEvent(row1Point);
445 simulateLeftMouseUpEvent(row1Point);
447 // The item is changed to the item at index 2, from index 0, so change event is fired.
448 EXPECT_STREQ("upclickchangeupclick", std::string(element.innerText().utf8()).c_str());
451 TEST_F(SelectPopupMenuTest, FLAKY_SelectItemKeyEvent)
453 registerMockedURLLoad("select_event.html");
454 m_webView->settings()->setJavaScriptEnabled(true);
455 loadFrame(m_webView->mainFrame(), "select_event.html");
458 m_popupMenuClient.setFocusedNode(static_cast<WebFrameImpl*>(m_webView->mainFrame())->frameView()->frame()->document()->focusedNode());
462 // Siumulate to choose the item at index 1 with keyboard.
463 simulateKeyDownEvent(VKEY_DOWN);
464 simulateKeyDownEvent(VKEY_DOWN);
465 simulateKeyDownEvent(VKEY_RETURN);
467 WebElement element = m_webView->mainFrame()->document().getElementById("message");
468 // We only can see change event but no other mouse related events.
469 EXPECT_STREQ("change", std::string(element.innerText().utf8()).c_str());
472 TEST_F(SelectPopupMenuTest, SelectItemRemoveSelectOnChange)
474 // Make sure no crash, even if select node is removed on 'change' event handler.
475 registerMockedURLLoad("select_event_remove_on_change.html");
476 m_webView->settings()->setJavaScriptEnabled(true);
477 loadFrame(m_webView->mainFrame(), "select_event_remove_on_change.html");
480 m_popupMenuClient.setFocusedNode(static_cast<WebFrameImpl*>(m_webView->mainFrame())->frameView()->frame()->document()->focusedNode());
484 int menuHeight = m_webView->selectPopup()->menuItemHeight();
485 // menuHeight * 1.5 means the Y position on the item at index 1.
486 IntPoint row1Point(2, menuHeight * 1.5);
487 simulateLeftMouseDownEvent(row1Point);
488 simulateLeftMouseUpEvent(row1Point);
490 WebElement element = m_webView->mainFrame()->document().getElementById("message");
491 EXPECT_STREQ("change", std::string(element.innerText().utf8()).c_str());
494 TEST_F(SelectPopupMenuTest, SelectItemRemoveSelectOnClick)
496 // Make sure no crash, even if select node is removed on 'click' event handler.
497 registerMockedURLLoad("select_event_remove_on_click.html");
498 m_webView->settings()->setJavaScriptEnabled(true);
499 loadFrame(m_webView->mainFrame(), "select_event_remove_on_click.html");
502 m_popupMenuClient.setFocusedNode(static_cast<WebFrameImpl*>(m_webView->mainFrame())->frameView()->frame()->document()->focusedNode());
506 int menuHeight = m_webView->selectPopup()->menuItemHeight();
507 // menuHeight * 1.5 means the Y position on the item at index 1.
508 IntPoint row1Point(2, menuHeight * 1.5);
509 simulateLeftMouseDownEvent(row1Point);
510 simulateLeftMouseUpEvent(row1Point);
512 WebElement element = m_webView->mainFrame()->document().getElementById("message");
513 EXPECT_STREQ("click", std::string(element.innerText().utf8()).c_str());