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 "PopupContainer.h" 34 #include "PopupMenuChromium.h" 35 #include "RuntimeEnabledFeatures.h" 36 #include "URLTestHelpers.h" 37 #include "WebDocument.h" 38 #include "WebElement.h" 39 #include "WebFrame.h" 40 #include "WebFrameClient.h" 41 #include "WebFrameImpl.h" 42 #include "WebInputEvent.h" 43 #include "WebPopupMenuImpl.h" 44 #include "WebSettings.h" 45 #include "WebView.h" 46 #include "WebViewClient.h" 47 #include "WebViewImpl.h" 48 #include "core/dom/Element.h" 49 #include "core/frame/FrameView.h" 50 #include "core/html/HTMLSelectElement.h" 51 #include "core/page/EventHandler.h" 52 #include "platform/KeyboardCodes.h" 53 #include "platform/PlatformMouseEvent.h" 54 #include "platform/PopupMenuClient.h" 55 #include "platform/PopupMenu.h" 56 #include "platform/graphics/Color.h" 57 #include "public/platform/Platform.h" 58 #include "public/platform/WebScreenInfo.h" 59 #include "public/platform/WebString.h" 60 #include "public/platform/WebURL.h" 61 #include "public/platform/WebURLRequest.h" 62 #include "public/platform/WebURLResponse.h" 63 #include "public/platform/WebUnitTestSupport.h" 64 #include "v8.h" 65 #include <gtest/gtest.h> 66 67 using namespace WebCore; 68 using namespace blink; 69 using blink::URLTestHelpers::toKURL; 70 71 namespace { 72 73 class TestPopupMenuClient : public PopupMenuClient { 74 public: 75 // Item at index 0 is selected by default. 76 TestPopupMenuClient() : m_selectIndex(0), m_node(0) { } 77 virtual ~TestPopupMenuClient() {} 78 virtual void valueChanged(unsigned listIndex, bool fireEvents = true) 79 { 80 m_selectIndex = listIndex; 81 if (m_node) { 82 HTMLSelectElement* select = toHTMLSelectElement(m_node); 83 select->optionSelectedByUser(select->listToOptionIndex(listIndex), fireEvents); 84 } 85 } 86 virtual void selectionChanged(unsigned, bool) {} 87 virtual void selectionCleared() {} 88 89 virtual String itemText(unsigned listIndex) const 90 { 91 String str("Item "); 92 str.append(String::number(listIndex)); 93 return str; 94 } 95 virtual String itemLabel(unsigned) const { return String(); } 96 virtual String itemIcon(unsigned) const { return String(); } 97 virtual String itemToolTip(unsigned listIndex) const { return itemText(listIndex); } 98 virtual String itemAccessibilityText(unsigned listIndex) const { return itemText(listIndex); } 99 virtual bool itemIsEnabled(unsigned listIndex) const { return m_disabledIndexSet.find(listIndex) == m_disabledIndexSet.end(); } 100 virtual PopupMenuStyle itemStyle(unsigned listIndex) const 101 { 102 Font font(FontPlatformData(12.0, false, false), false); 103 return PopupMenuStyle(Color::black, Color::white, font, true, false, Length(), TextDirection(), false /* has text direction override */); 104 } 105 virtual PopupMenuStyle menuStyle() const { return itemStyle(0); } 106 virtual int clientInsetLeft() const { return 0; } 107 virtual int clientInsetRight() const { return 0; } 108 virtual LayoutUnit clientPaddingLeft() const { return 0; } 109 virtual LayoutUnit clientPaddingRight() const { return 0; } 110 virtual int listSize() const { return 10; } 111 virtual int selectedIndex() const { return m_selectIndex; } 112 virtual void popupDidHide() { } 113 virtual bool itemIsSeparator(unsigned listIndex) const { return false; } 114 virtual bool itemIsLabel(unsigned listIndex) const { return false; } 115 virtual bool itemIsSelected(unsigned listIndex) const { return listIndex == m_selectIndex; } 116 virtual bool valueShouldChangeOnHotTrack() const { return false; } 117 virtual void setTextFromItem(unsigned listIndex) { } 118 119 virtual FontSelector* fontSelector() const { return 0; } 120 virtual HostWindow* hostWindow() const { return 0; } 121 122 virtual PassRefPtr<Scrollbar> createScrollbar(ScrollableArea*, ScrollbarOrientation, ScrollbarControlSize) { return 0; } 123 124 void setDisabledIndex(unsigned index) { m_disabledIndexSet.insert(index); } 125 void setFocusedNode(Node* node) { m_node = node; } 126 127 private: 128 unsigned m_selectIndex; 129 std::set<unsigned> m_disabledIndexSet; 130 Node* m_node; 131 }; 132 133 class TestWebWidgetClient : public WebWidgetClient { 134 public: 135 ~TestWebWidgetClient() { } 136 }; 137 138 class TestWebPopupMenuImpl : public WebPopupMenuImpl { 139 public: 140 static PassRefPtr<TestWebPopupMenuImpl> create(WebWidgetClient* client) 141 { 142 return adoptRef(new TestWebPopupMenuImpl(client)); 143 } 144 145 ~TestWebPopupMenuImpl() { } 146 147 private: 148 TestWebPopupMenuImpl(WebWidgetClient* client) : WebPopupMenuImpl(client) { } 149 }; 150 151 class TestWebViewClient : public WebViewClient { 152 public: 153 TestWebViewClient() : m_webPopupMenu(TestWebPopupMenuImpl::create(&m_webWidgetClient)) { } 154 ~TestWebViewClient() { } 155 156 virtual WebWidget* createPopupMenu(WebPopupType) { return m_webPopupMenu.get(); } 157 158 // We need to override this so that the popup menu size is not 0 159 // (the layout code checks to see if the popup fits on the screen). 160 virtual WebScreenInfo screenInfo() 161 { 162 WebScreenInfo screenInfo; 163 screenInfo.availableRect.height = 2000; 164 screenInfo.availableRect.width = 2000; 165 return screenInfo; 166 } 167 168 private: 169 TestWebWidgetClient m_webWidgetClient; 170 RefPtr<TestWebPopupMenuImpl> m_webPopupMenu; 171 }; 172 173 class TestWebFrameClient : public WebFrameClient { 174 public: 175 ~TestWebFrameClient() { } 176 }; 177 178 class SelectPopupMenuTest : public testing::Test { 179 public: 180 SelectPopupMenuTest() 181 : baseURL("http://www.test.com/") 182 { 183 } 184 185 protected: 186 virtual void SetUp() 187 { 188 m_webView = toWebViewImpl(WebView::create(&m_webviewClient)); 189 m_webView->initializeMainFrame(&m_webFrameClient); 190 m_popupMenu = adoptRef(new PopupMenuChromium(*toWebFrameImpl(m_webView->mainFrame())->frame(), &m_popupMenuClient)); 191 } 192 193 virtual void TearDown() 194 { 195 m_popupMenu = 0; 196 m_webView->close(); 197 Platform::current()->unitTestSupport()->unregisterAllMockedURLs(); 198 } 199 200 // Returns true if there currently is a select popup in the WebView. 201 bool popupOpen() const { return m_webView->selectPopup(); } 202 203 int selectedIndex() const { return m_popupMenuClient.selectedIndex(); } 204 205 void showPopup() 206 { 207 m_popupMenu->show(FloatQuad(FloatRect(0, 0, 100, 100)), IntSize(100, 100), 0); 208 ASSERT_TRUE(popupOpen()); 209 EXPECT_TRUE(m_webView->selectPopup()->popupType() == PopupContainer::Select); 210 } 211 212 void hidePopup() 213 { 214 m_popupMenu->hide(); 215 EXPECT_FALSE(popupOpen()); 216 } 217 218 void simulateKeyDownEvent(int keyCode) 219 { 220 simulateKeyEvent(WebInputEvent::RawKeyDown, keyCode); 221 } 222 223 void simulateKeyUpEvent(int keyCode) 224 { 225 simulateKeyEvent(WebInputEvent::KeyUp, keyCode); 226 } 227 228 // Simulates a key event on the WebView. 229 // The WebView forwards the event to the select popup if one is open. 230 void simulateKeyEvent(WebInputEvent::Type eventType, int keyCode) 231 { 232 WebKeyboardEvent keyEvent; 233 keyEvent.windowsKeyCode = keyCode; 234 keyEvent.type = eventType; 235 m_webView->handleInputEvent(keyEvent); 236 } 237 238 // Simulates a mouse event on the select popup. 239 void simulateLeftMouseDownEvent(const IntPoint& point) 240 { 241 PlatformMouseEvent mouseEvent(point, point, LeftButton, PlatformEvent::MousePressed, 242 1, false, false, false, false, 0); 243 m_webView->selectPopup()->handleMouseDownEvent(mouseEvent); 244 } 245 void simulateLeftMouseUpEvent(const IntPoint& point) 246 { 247 PlatformMouseEvent mouseEvent(point, point, LeftButton, PlatformEvent::MouseReleased, 248 1, false, false, false, false, 0); 249 m_webView->selectPopup()->handleMouseReleaseEvent(mouseEvent); 250 } 251 252 void registerMockedURLLoad(const std::string& fileName) 253 { 254 URLTestHelpers::registerMockedURLLoad(toKURL(baseURL + fileName), WebString::fromUTF8(fileName.c_str()), WebString::fromUTF8("popup/"), WebString::fromUTF8("text/html")); 255 } 256 257 void serveRequests() 258 { 259 Platform::current()->unitTestSupport()->serveAsynchronousMockedRequests(); 260 } 261 262 void loadFrame(WebFrame* frame, const std::string& fileName) 263 { 264 WebURLRequest urlRequest; 265 urlRequest.initialize(); 266 urlRequest.setURL(WebURL(toKURL(baseURL + fileName))); 267 frame->loadRequest(urlRequest); 268 } 269 270 protected: 271 TestWebViewClient m_webviewClient; 272 WebViewImpl* m_webView; 273 TestWebFrameClient m_webFrameClient; 274 TestPopupMenuClient m_popupMenuClient; 275 RefPtr<PopupMenu> m_popupMenu; 276 std::string baseURL; 277 }; 278 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) 282 { 283 for (int i = 0; i < 3; i++) { 284 showPopup(); 285 hidePopup(); 286 } 287 } 288 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) 292 { 293 showPopup(); 294 // Nothing else to do, TearDown() deletes the popup. 295 } 296 297 // Tests that losing focus closes the select popup. 298 TEST_F(SelectPopupMenuTest, ShowThenLoseFocus) 299 { 300 showPopup(); 301 // Simulate losing focus. 302 m_webView->setFocus(false); 303 304 // Popup should have closed. 305 EXPECT_FALSE(popupOpen()); 306 } 307 308 // Tests that pressing ESC closes the popup. 309 TEST_F(SelectPopupMenuTest, ShowThenPressESC) 310 { 311 showPopup(); 312 simulateKeyDownEvent(VKEY_ESCAPE); 313 // Popup should have closed. 314 EXPECT_FALSE(popupOpen()); 315 } 316 317 // Tests selecting an item with the arrows and enter/esc/tab. 318 TEST_F(SelectPopupMenuTest, SelectWithKeys) 319 { 320 showPopup(); 321 // Simulate selecting the 2nd item by pressing Down, Down, enter. 322 simulateKeyDownEvent(VKEY_DOWN); 323 simulateKeyDownEvent(VKEY_DOWN); 324 simulateKeyDownEvent(VKEY_RETURN); 325 326 // Popup should have closed. 327 EXPECT_TRUE(!popupOpen()); 328 EXPECT_EQ(2, selectedIndex()); 329 330 // It should work as well with ESC. 331 showPopup(); 332 simulateKeyDownEvent(VKEY_DOWN); 333 simulateKeyDownEvent(VKEY_ESCAPE); 334 EXPECT_FALSE(popupOpen()); 335 EXPECT_EQ(3, selectedIndex()); 336 337 // It should work as well with TAB. 338 showPopup(); 339 simulateKeyDownEvent(VKEY_DOWN); 340 simulateKeyDownEvent(VKEY_TAB); 341 EXPECT_FALSE(popupOpen()); 342 EXPECT_EQ(4, selectedIndex()); 343 } 344 345 // Tests that selecting an item with the mouse does select the item and close 346 // the popup. 347 TEST_F(SelectPopupMenuTest, ClickItem) 348 { 349 showPopup(); 350 351 int menuItemHeight = m_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); 357 358 // Popup should have closed and the item at index 1 selected. 359 EXPECT_FALSE(popupOpen()); 360 EXPECT_EQ(1, selectedIndex()); 361 } 362 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) 366 { 367 showPopup(); 368 369 int menuItemHeight = m_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 m_webView->selectPopup()->handleMouseMoveEvent(mouseEvent); 376 377 // Click outside the popup. 378 simulateLeftMouseDownEvent(IntPoint(1000, 1000)); 379 380 // Popup should have closed and item 0 should still be selected. 381 EXPECT_FALSE(popupOpen()); 382 EXPECT_EQ(0, selectedIndex()); 383 } 384 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) 388 { 389 showPopup(); 390 391 // Simulate selecting the 2nd item by pressing Down, Down. 392 simulateKeyDownEvent(VKEY_DOWN); 393 simulateKeyDownEvent(VKEY_DOWN); 394 395 // Click outside the popup. 396 simulateLeftMouseDownEvent(IntPoint(1000, 1000)); 397 398 // Popup should have closed and the item should have been selected. 399 EXPECT_FALSE(popupOpen()); 400 EXPECT_EQ(2, selectedIndex()); 401 } 402 403 TEST_F(SelectPopupMenuTest, DISABLED_SelectItemEventFire) 404 { 405 registerMockedURLLoad("select_event.html"); 406 m_webView->settings()->setJavaScriptEnabled(true); 407 loadFrame(m_webView->mainFrame(), "select_event.html"); 408 serveRequests(); 409 410 m_popupMenuClient.setFocusedNode(toWebFrameImpl(m_webView->mainFrame())->frameView()->frame().document()->focusedElement()); 411 412 showPopup(); 413 414 int menuItemHeight = m_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); 419 420 WebElement element = m_webView->mainFrame()->document().getElementById("message"); 421 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()); 425 426 // Disable the item at index 1. 427 m_popupMenuClient.setDisabledIndex(1); 428 429 showPopup(); 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); 434 435 // The item at index 1 is disabled, so the text should not be changed. 436 EXPECT_STREQ("upclick", element.innerText().utf8().data()); 437 438 showPopup(); 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); 443 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()); 446 } 447 448 TEST_F(SelectPopupMenuTest, FLAKY_SelectItemKeyEvent) 449 { 450 registerMockedURLLoad("select_event.html"); 451 m_webView->settings()->setJavaScriptEnabled(true); 452 loadFrame(m_webView->mainFrame(), "select_event.html"); 453 serveRequests(); 454 455 m_popupMenuClient.setFocusedNode(toWebFrameImpl(m_webView->mainFrame())->frameView()->frame().document()->focusedElement()); 456 457 showPopup(); 458 459 // Siumulate to choose the item at index 1 with keyboard. 460 simulateKeyDownEvent(VKEY_DOWN); 461 simulateKeyDownEvent(VKEY_DOWN); 462 simulateKeyDownEvent(VKEY_RETURN); 463 464 WebElement element = m_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()); 467 } 468 469 TEST_F(SelectPopupMenuTest, SelectItemRemoveSelectOnChange) 470 { 471 // Make sure no crash, even if select node is removed on 'change' event handler. 472 registerMockedURLLoad("select_event_remove_on_change.html"); 473 m_webView->settings()->setJavaScriptEnabled(true); 474 loadFrame(m_webView->mainFrame(), "select_event_remove_on_change.html"); 475 serveRequests(); 476 477 m_popupMenuClient.setFocusedNode(toWebFrameImpl(m_webView->mainFrame())->frameView()->frame().document()->focusedElement()); 478 479 showPopup(); 480 481 int menuItemHeight = m_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); 486 487 WebElement element = m_webView->mainFrame()->document().getElementById("message"); 488 EXPECT_STREQ("change", element.innerText().utf8().data()); 489 } 490 491 TEST_F(SelectPopupMenuTest, SelectItemRemoveSelectOnClick) 492 { 493 // Make sure no crash, even if select node is removed on 'click' event handler. 494 registerMockedURLLoad("select_event_remove_on_click.html"); 495 m_webView->settings()->setJavaScriptEnabled(true); 496 loadFrame(m_webView->mainFrame(), "select_event_remove_on_click.html"); 497 serveRequests(); 498 499 m_popupMenuClient.setFocusedNode(toWebFrameImpl(m_webView->mainFrame())->frameView()->frame().document()->focusedElement()); 500 501 showPopup(); 502 503 int menuItemHeight = m_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); 508 509 WebElement element = m_webView->mainFrame()->document().getElementById("message"); 510 EXPECT_STREQ("click", element.innerText().utf8().data()); 511 } 512 513 } // namespace 514