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. 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 101 private: 102 unsigned m_selectIndex; 103 }; 104 105 class TestWebWidgetClient : public WebWidgetClient { 106 public: 107 ~TestWebWidgetClient() { } 108 }; 109 110 class TestWebPopupMenuImpl : public WebPopupMenuImpl { 111 public: 112 static PassRefPtr<TestWebPopupMenuImpl> create(WebWidgetClient* client) 113 { 114 return adoptRef(new TestWebPopupMenuImpl(client)); 115 } 116 117 ~TestWebPopupMenuImpl() { } 118 119 private: 120 TestWebPopupMenuImpl(WebWidgetClient* client) : WebPopupMenuImpl(client) { } 121 }; 122 123 class TestWebWidget : public WebWidget { 124 public: 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 149 class TestWebViewClient : public WebViewClient { 150 public: 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 166 private: 167 TestWebWidgetClient m_webWidgetClient; 168 RefPtr<TestWebPopupMenuImpl> m_webPopupMenu; 169 }; 170 171 class TestWebFrameClient : public WebFrameClient { 172 public: 173 ~TestWebFrameClient() { } 174 }; 175 176 class SelectPopupMenuTest : public testing::Test { 177 public: 178 SelectPopupMenuTest() 179 { 180 } 181 182 protected: 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 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. 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. 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. 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. 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. 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. 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. 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. 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