Home | History | Annotate | Download | only in tests
      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 "URLTestHelpers.h"
     36 #include "WebDocument.h"
     37 #include "WebElement.h"
     38 #include "WebFrame.h"
     39 #include "WebFrameClient.h"
     40 #include "WebFrameImpl.h"
     41 #include "WebInputEvent.h"
     42 #include "WebPopupMenuImpl.h"
     43 #include "WebScreenInfo.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/html/HTMLSelectElement.h"
     50 #include "core/page/EventHandler.h"
     51 #include "core/page/FrameView.h"
     52 #include "RuntimeEnabledFeatures.h"
     53 #include "core/platform/PlatformMouseEvent.h"
     54 #include "core/platform/PopupMenu.h"
     55 #include "core/platform/PopupMenuClient.h"
     56 #include "core/platform/chromium/KeyboardCodes.h"
     57 #include "core/platform/graphics/Color.h"
     58 #include "v8.h"
     59 #include <gtest/gtest.h>
     60 #include "public/platform/Platform.h"
     61 #include "public/platform/WebString.h"
     62 #include "public/platform/WebUnitTestSupport.h"
     63 #include "public/platform/WebURL.h"
     64 #include "public/platform/WebURLRequest.h"
     65 #include "public/platform/WebURLResponse.h"
     66 
     67 using namespace WebCore;
     68 using namespace WebKit;
     69 using WebKit::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 = static_cast<WebViewImpl*>(WebView::create(&m_webviewClient));
    189         m_webView->initializeMainFrame(&m_webFrameClient);
    190         m_popupMenu = adoptRef(new PopupMenuChromium(*static_cast<WebFrameImpl*>(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(static_cast<WebFrameImpl*>(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(static_cast<WebFrameImpl*>(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(static_cast<WebFrameImpl*>(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(static_cast<WebFrameImpl*>(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