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 "FrameTestHelpers.h"
34 #include "PopupContainer.h"
35 #include "PopupMenuChromium.h"
36 #include "RuntimeEnabledFeatures.h"
37 #include "URLTestHelpers.h"
38 #include "WebDocument.h"
39 #include "WebElement.h"
41 #include "WebFrameClient.h"
42 #include "WebFrameImpl.h"
43 #include "WebInputEvent.h"
44 #include "WebPopupMenuImpl.h"
45 #include "WebSettings.h"
47 #include "WebViewClient.h"
48 #include "WebViewImpl.h"
49 #include "core/dom/Element.h"
50 #include "core/frame/FrameView.h"
51 #include "core/html/HTMLSelectElement.h"
52 #include "core/page/EventHandler.h"
53 #include "platform/KeyboardCodes.h"
54 #include "platform/PlatformMouseEvent.h"
55 #include "platform/PopupMenuClient.h"
56 #include "platform/PopupMenu.h"
57 #include "platform/graphics/Color.h"
58 #include "public/platform/Platform.h"
59 #include "public/platform/WebScreenInfo.h"
60 #include "public/platform/WebString.h"
61 #include "public/platform/WebURL.h"
62 #include "public/platform/WebURLRequest.h"
63 #include "public/platform/WebURLResponse.h"
64 #include "public/platform/WebUnitTestSupport.h"
66 #include <gtest/gtest.h>
68 using namespace WebCore;
69 using namespace blink;
70 using blink::URLTestHelpers::toKURL;
74 class TestPopupMenuClient : public PopupMenuClient {
76 // Item at index 0 is selected by default.
77 TestPopupMenuClient() : m_selectIndex(0), m_node(0) { }
78 virtual ~TestPopupMenuClient() {}
79 virtual void valueChanged(unsigned listIndex, bool fireEvents = true)
81 m_selectIndex = listIndex;
83 HTMLSelectElement* select = toHTMLSelectElement(m_node);
84 select->optionSelectedByUser(select->listToOptionIndex(listIndex), fireEvents);
87 virtual void selectionChanged(unsigned, bool) {}
88 virtual void selectionCleared() {}
90 virtual String itemText(unsigned listIndex) const
93 str.append(String::number(listIndex));
96 virtual String itemLabel(unsigned) const { return String(); }
97 virtual String itemIcon(unsigned) const { return String(); }
98 virtual String itemToolTip(unsigned listIndex) const { return itemText(listIndex); }
99 virtual String itemAccessibilityText(unsigned listIndex) const { return itemText(listIndex); }
100 virtual bool itemIsEnabled(unsigned listIndex) const { return m_disabledIndexSet.find(listIndex) == m_disabledIndexSet.end(); }
101 virtual PopupMenuStyle itemStyle(unsigned listIndex) const
103 FontDescription fontDescription;
104 fontDescription.setComputedSize(12.0);
105 Font font(fontDescription);
107 return PopupMenuStyle(Color::black, Color::white, font, true, false, Length(), TextDirection(), false /* has text direction override */);
109 virtual PopupMenuStyle menuStyle() const { return itemStyle(0); }
110 virtual int clientInsetLeft() const { return 0; }
111 virtual int clientInsetRight() const { return 0; }
112 virtual LayoutUnit clientPaddingLeft() const { return 0; }
113 virtual LayoutUnit clientPaddingRight() const { return 0; }
114 virtual int listSize() const { return 10; }
115 virtual int selectedIndex() const { return m_selectIndex; }
116 virtual void popupDidHide() { }
117 virtual bool itemIsSeparator(unsigned listIndex) const { return false; }
118 virtual bool itemIsLabel(unsigned listIndex) const { return false; }
119 virtual bool itemIsSelected(unsigned listIndex) const { return listIndex == m_selectIndex; }
120 virtual bool valueShouldChangeOnHotTrack() const { return false; }
121 virtual void setTextFromItem(unsigned listIndex) { }
123 virtual FontSelector* fontSelector() const { return 0; }
124 virtual HostWindow* hostWindow() const { return 0; }
126 virtual PassRefPtr<Scrollbar> createScrollbar(ScrollableArea*, ScrollbarOrientation, ScrollbarControlSize) { return 0; }
128 void setDisabledIndex(unsigned index) { m_disabledIndexSet.insert(index); }
129 void setFocusedNode(Node* node) { m_node = node; }
132 unsigned m_selectIndex;
133 std::set<unsigned> m_disabledIndexSet;
137 class TestWebWidgetClient : public WebWidgetClient {
139 ~TestWebWidgetClient() { }
142 class TestWebPopupMenuImpl : public WebPopupMenuImpl {
144 static PassRefPtr<TestWebPopupMenuImpl> create(WebWidgetClient* client)
146 return adoptRef(new TestWebPopupMenuImpl(client));
149 ~TestWebPopupMenuImpl() { }
152 TestWebPopupMenuImpl(WebWidgetClient* client) : WebPopupMenuImpl(client) { }
155 class TestWebViewClient : public WebViewClient {
157 TestWebViewClient() : m_webPopupMenu(TestWebPopupMenuImpl::create(&m_webWidgetClient)) { }
158 ~TestWebViewClient() { }
160 virtual WebWidget* createPopupMenu(WebPopupType) { return m_webPopupMenu.get(); }
162 // We need to override this so that the popup menu size is not 0
163 // (the layout code checks to see if the popup fits on the screen).
164 virtual WebScreenInfo screenInfo()
166 WebScreenInfo screenInfo;
167 screenInfo.availableRect.height = 2000;
168 screenInfo.availableRect.width = 2000;
173 TestWebWidgetClient m_webWidgetClient;
174 RefPtr<TestWebPopupMenuImpl> m_webPopupMenu;
177 class SelectPopupMenuTest : public testing::Test {
179 SelectPopupMenuTest()
180 : baseURL("http://www.test.com/")
187 m_helper.initialize(false, 0, &m_webviewClient);
188 m_popupMenu = adoptRef(new PopupMenuChromium(*mainFrame()->frame(), &m_popupMenuClient));
191 virtual void TearDown()
194 Platform::current()->unitTestSupport()->unregisterAllMockedURLs();
197 // Returns true if there currently is a select popup in the WebView.
198 bool popupOpen() const { return webView()->selectPopup(); }
200 int selectedIndex() const { return m_popupMenuClient.selectedIndex(); }
204 m_popupMenu->show(FloatQuad(FloatRect(0, 0, 100, 100)), IntSize(100, 100), 0);
205 ASSERT_TRUE(popupOpen());
211 EXPECT_FALSE(popupOpen());
214 void simulateKeyDownEvent(int keyCode)
216 simulateKeyEvent(WebInputEvent::RawKeyDown, keyCode);
219 void simulateKeyUpEvent(int keyCode)
221 simulateKeyEvent(WebInputEvent::KeyUp, keyCode);
224 // Simulates a key event on the WebView.
225 // The WebView forwards the event to the select popup if one is open.
226 void simulateKeyEvent(WebInputEvent::Type eventType, int keyCode)
228 WebKeyboardEvent keyEvent;
229 keyEvent.windowsKeyCode = keyCode;
230 keyEvent.type = eventType;
231 webView()->handleInputEvent(keyEvent);
234 // Simulates a mouse event on the select popup.
235 void simulateLeftMouseDownEvent(const IntPoint& point)
237 PlatformMouseEvent mouseEvent(point, point, LeftButton, PlatformEvent::MousePressed,
238 1, false, false, false, false, 0);
239 webView()->selectPopup()->handleMouseDownEvent(mouseEvent);
241 void simulateLeftMouseUpEvent(const IntPoint& point)
243 PlatformMouseEvent mouseEvent(point, point, LeftButton, PlatformEvent::MouseReleased,
244 1, false, false, false, false, 0);
245 webView()->selectPopup()->handleMouseReleaseEvent(mouseEvent);
248 void registerMockedURLLoad(const std::string& fileName)
250 URLTestHelpers::registerMockedURLLoad(toKURL(baseURL + fileName), WebString::fromUTF8(fileName.c_str()), WebString::fromUTF8("popup/"), WebString::fromUTF8("text/html"));
255 Platform::current()->unitTestSupport()->serveAsynchronousMockedRequests();
258 void loadFrame(WebFrame* frame, const std::string& fileName)
260 WebURLRequest urlRequest;
261 urlRequest.initialize();
262 urlRequest.setURL(WebURL(toKURL(baseURL + fileName)));
263 frame->loadRequest(urlRequest);
266 WebViewImpl* webView() const { return m_helper.webViewImpl(); }
267 WebFrameImpl* mainFrame() const { return m_helper.webViewImpl()->mainFrameImpl(); }
270 TestWebViewClient m_webviewClient;
271 TestPopupMenuClient m_popupMenuClient;
272 RefPtr<PopupMenu> m_popupMenu;
276 FrameTestHelpers::WebViewHelper m_helper;
279 // Tests that show/hide and repeats. Select popups are reused in web pages when
280 // they are reopened, that what this is testing.
281 TEST_F(SelectPopupMenuTest, ShowThenHide)
283 for (int i = 0; i < 3; i++) {
289 // Tests that showing a select popup and deleting it does not cause problem.
290 // This happens in real-life if a page navigates while a select popup is showing.
291 TEST_F(SelectPopupMenuTest, ShowThenDelete)
294 // Nothing else to do, TearDown() deletes the popup.
297 // Tests that losing focus closes the select popup.
298 TEST_F(SelectPopupMenuTest, ShowThenLoseFocus)
301 // Simulate losing focus.
302 webView()->setFocus(false);
304 // Popup should have closed.
305 EXPECT_FALSE(popupOpen());
308 // Tests that pressing ESC closes the popup.
309 TEST_F(SelectPopupMenuTest, ShowThenPressESC)
312 simulateKeyDownEvent(VKEY_ESCAPE);
313 // Popup should have closed.
314 EXPECT_FALSE(popupOpen());
317 // Tests selecting an item with the arrows and enter/esc/tab.
318 TEST_F(SelectPopupMenuTest, SelectWithKeys)
321 // Simulate selecting the 2nd item by pressing Down, Down, enter.
322 simulateKeyDownEvent(VKEY_DOWN);
323 simulateKeyDownEvent(VKEY_DOWN);
324 simulateKeyDownEvent(VKEY_RETURN);
326 // Popup should have closed.
327 EXPECT_TRUE(!popupOpen());
328 EXPECT_EQ(2, selectedIndex());
330 // It should work as well with ESC.
332 simulateKeyDownEvent(VKEY_DOWN);
333 simulateKeyDownEvent(VKEY_ESCAPE);
334 EXPECT_FALSE(popupOpen());
335 EXPECT_EQ(3, selectedIndex());
337 // It should work as well with TAB.
339 simulateKeyDownEvent(VKEY_DOWN);
340 simulateKeyDownEvent(VKEY_TAB);
341 EXPECT_FALSE(popupOpen());
342 EXPECT_EQ(4, selectedIndex());
345 // Tests that selecting an item with the mouse does select the item and close
347 TEST_F(SelectPopupMenuTest, ClickItem)
351 int menuItemHeight = webView()->selectPopup()->menuItemHeight();
352 // menuItemHeight * 1.5 means the Y position on the item at index 1.
353 IntPoint row1Point(2, menuItemHeight * 1.5);
354 // Simulate a click down/up on the first item.
355 simulateLeftMouseDownEvent(row1Point);
356 simulateLeftMouseUpEvent(row1Point);
358 // Popup should have closed and the item at index 1 selected.
359 EXPECT_FALSE(popupOpen());
360 EXPECT_EQ(1, selectedIndex());
363 // Tests that moving the mouse over an item and then clicking outside the select popup
364 // leaves the seleted item unchanged.
365 TEST_F(SelectPopupMenuTest, MouseOverItemClickOutside)
369 int menuItemHeight = webView()->selectPopup()->menuItemHeight();
370 // menuItemHeight * 1.5 means the Y position on the item at index 1.
371 IntPoint row1Point(2, menuItemHeight * 1.5);
372 // Simulate the mouse moving over the first item.
373 PlatformMouseEvent mouseEvent(row1Point, row1Point, NoButton, PlatformEvent::MouseMoved,
374 1, false, false, false, false, 0);
375 webView()->selectPopup()->handleMouseMoveEvent(mouseEvent);
377 // Click outside the popup.
378 simulateLeftMouseDownEvent(IntPoint(1000, 1000));
380 // Popup should have closed and item 0 should still be selected.
381 EXPECT_FALSE(popupOpen());
382 EXPECT_EQ(0, selectedIndex());
385 // Tests that selecting an item with the keyboard and then clicking outside the select
386 // popup does select that item.
387 TEST_F(SelectPopupMenuTest, SelectItemWithKeyboardItemClickOutside)
391 // Simulate selecting the 2nd item by pressing Down, Down.
392 simulateKeyDownEvent(VKEY_DOWN);
393 simulateKeyDownEvent(VKEY_DOWN);
395 // Click outside the popup.
396 simulateLeftMouseDownEvent(IntPoint(1000, 1000));
398 // Popup should have closed and the item should have been selected.
399 EXPECT_FALSE(popupOpen());
400 EXPECT_EQ(2, selectedIndex());
403 TEST_F(SelectPopupMenuTest, DISABLED_SelectItemEventFire)
405 registerMockedURLLoad("select_event.html");
406 webView()->settings()->setJavaScriptEnabled(true);
407 loadFrame(mainFrame(), "select_event.html");
410 m_popupMenuClient.setFocusedNode(mainFrame()->frame()->document()->focusedElement());
414 int menuItemHeight = webView()->selectPopup()->menuItemHeight();
415 // menuItemHeight * 0.5 means the Y position on the item at index 0.
416 IntPoint row1Point(2, menuItemHeight * 0.5);
417 simulateLeftMouseDownEvent(row1Point);
418 simulateLeftMouseUpEvent(row1Point);
420 WebElement element = webView()->mainFrame()->document().getElementById("message");
422 // mousedown event is held by select node, and we don't simulate the event for the node.
423 // So we can only see mouseup and click event.
424 EXPECT_STREQ("upclick", element.innerText().utf8().data());
426 // Disable the item at index 1.
427 m_popupMenuClient.setDisabledIndex(1);
430 // menuItemHeight * 1.5 means the Y position on the item at index 1.
431 row1Point.setY(menuItemHeight * 1.5);
432 simulateLeftMouseDownEvent(row1Point);
433 simulateLeftMouseUpEvent(row1Point);
435 // The item at index 1 is disabled, so the text should not be changed.
436 EXPECT_STREQ("upclick", element.innerText().utf8().data());
439 // menuItemHeight * 2.5 means the Y position on the item at index 2.
440 row1Point.setY(menuItemHeight * 2.5);
441 simulateLeftMouseDownEvent(row1Point);
442 simulateLeftMouseUpEvent(row1Point);
444 // The item is changed to the item at index 2, from index 0, so change event is fired.
445 EXPECT_STREQ("upclickchangeupclick", element.innerText().utf8().data());
448 TEST_F(SelectPopupMenuTest, FLAKY_SelectItemKeyEvent)
450 registerMockedURLLoad("select_event.html");
451 webView()->settings()->setJavaScriptEnabled(true);
452 loadFrame(mainFrame(), "select_event.html");
455 m_popupMenuClient.setFocusedNode(mainFrame()->frame()->document()->focusedElement());
459 // Siumulate to choose the item at index 1 with keyboard.
460 simulateKeyDownEvent(VKEY_DOWN);
461 simulateKeyDownEvent(VKEY_DOWN);
462 simulateKeyDownEvent(VKEY_RETURN);
464 WebElement element = webView()->mainFrame()->document().getElementById("message");
465 // We only can see change event but no other mouse related events.
466 EXPECT_STREQ("change", element.innerText().utf8().data());
469 TEST_F(SelectPopupMenuTest, SelectItemRemoveSelectOnChange)
471 // Make sure no crash, even if select node is removed on 'change' event handler.
472 registerMockedURLLoad("select_event_remove_on_change.html");
473 webView()->settings()->setJavaScriptEnabled(true);
474 loadFrame(mainFrame(), "select_event_remove_on_change.html");
477 m_popupMenuClient.setFocusedNode(mainFrame()->frame()->document()->focusedElement());
481 int menuItemHeight = webView()->selectPopup()->menuItemHeight();
482 // menuItemHeight * 1.5 means the Y position on the item at index 1.
483 IntPoint row1Point(2, menuItemHeight * 1.5);
484 simulateLeftMouseDownEvent(row1Point);
485 simulateLeftMouseUpEvent(row1Point);
487 WebElement element = webView()->mainFrame()->document().getElementById("message");
488 EXPECT_STREQ("change", element.innerText().utf8().data());
491 TEST_F(SelectPopupMenuTest, SelectItemRemoveSelectOnClick)
493 // Make sure no crash, even if select node is removed on 'click' event handler.
494 registerMockedURLLoad("select_event_remove_on_click.html");
495 webView()->settings()->setJavaScriptEnabled(true);
496 loadFrame(mainFrame(), "select_event_remove_on_click.html");
499 m_popupMenuClient.setFocusedNode(mainFrame()->frame()->document()->focusedElement());
503 int menuItemHeight = webView()->selectPopup()->menuItemHeight();
504 // menuItemHeight * 1.5 means the Y position on the item at index 1.
505 IntPoint row1Point(2, menuItemHeight * 1.5);
506 simulateLeftMouseDownEvent(row1Point);
507 simulateLeftMouseUpEvent(row1Point);
509 WebElement element = webView()->mainFrame()->document().getElementById("message");
510 EXPECT_STREQ("click", element.innerText().utf8().data());