Home | History | Annotate | Download | only in tests
      1 /*
      2  * Copyright (C) 2013 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 "core/dom/ClientRect.h"
     34 #include "core/dom/ClientRectList.h"
     35 #include "core/dom/Document.h"
     36 #include "core/dom/Element.h"
     37 #include "core/dom/StaticNodeList.h"
     38 #include "core/dom/shadow/ShadowRoot.h"
     39 #include "core/frame/FrameView.h"
     40 #include "core/frame/LocalFrame.h"
     41 #include "core/page/EventHandler.h"
     42 #include "core/rendering/HitTestResult.h"
     43 #include "core/rendering/RenderTreeAsText.h"
     44 #include "core/testing/URLTestHelpers.h"
     45 #include "public/platform/Platform.h"
     46 #include "public/platform/WebUnitTestSupport.h"
     47 #include "public/web/WebDocument.h"
     48 #include "public/web/WebFrame.h"
     49 #include "public/web/WebHitTestResult.h"
     50 #include "public/web/WebInputEvent.h"
     51 #include "public/web/WebTouchAction.h"
     52 #include "public/web/WebView.h"
     53 #include "public/web/WebViewClient.h"
     54 #include "public/web/WebWidgetClient.h"
     55 #include "web/WebViewImpl.h"
     56 #include "web/tests/FrameTestHelpers.h"
     57 
     58 #include <gtest/gtest.h>
     59 
     60 using namespace blink;
     61 using blink::FrameTestHelpers::runPendingTasks;
     62 
     63 namespace {
     64 
     65 class TouchActionTrackingWebViewClient : public FrameTestHelpers::TestWebViewClient {
     66 public:
     67     TouchActionTrackingWebViewClient() :
     68         m_actionSetCount(0),
     69         m_action(WebTouchActionAuto)
     70     {
     71     }
     72 
     73     // WebWidgetClient methods
     74     virtual void setTouchAction(WebTouchAction touchAction)
     75     {
     76         m_actionSetCount++;
     77         m_action = touchAction;
     78     }
     79 
     80     // Local methods
     81     void reset()
     82     {
     83         m_actionSetCount = 0;
     84         m_action = WebTouchActionAuto;
     85     }
     86 
     87     int touchActionSetCount()
     88     {
     89         return m_actionSetCount;
     90     }
     91 
     92     WebTouchAction lastTouchAction()
     93     {
     94         return m_action;
     95     }
     96 
     97 private:
     98     int m_actionSetCount;
     99     WebTouchAction m_action;
    100 };
    101 
    102 const int kfakeTouchId = 7;
    103 
    104 class TouchActionTest : public testing::Test {
    105 public:
    106     TouchActionTest()
    107         : m_baseURL("http://www.test.com/")
    108     {
    109         URLTestHelpers::registerMockedURLFromBaseURL(WebString::fromUTF8(m_baseURL), "touch-action-tests.css");
    110         URLTestHelpers::registerMockedURLFromBaseURL(WebString::fromUTF8(m_baseURL), "touch-action-tests.js");
    111     }
    112 
    113     virtual void TearDown()
    114     {
    115         Platform::current()->unitTestSupport()->unregisterAllMockedURLs();
    116     }
    117 
    118 protected:
    119     void runTouchActionTest(std::string file);
    120     void runShadowDOMTest(std::string file);
    121     void sendTouchEvent(WebView*, WebInputEvent::Type, IntPoint clientPoint);
    122     WebView* setupTest(std::string file, TouchActionTrackingWebViewClient&);
    123     void runTestOnTree(ContainerNode* root, WebView*, TouchActionTrackingWebViewClient&);
    124 
    125     std::string m_baseURL;
    126     FrameTestHelpers::WebViewHelper m_webViewHelper;
    127 };
    128 
    129 void TouchActionTest::runTouchActionTest(std::string file)
    130 {
    131     TouchActionTrackingWebViewClient client;
    132 
    133     // runTouchActionTest() loads a document in a frame, setting up a
    134     // nested message loop. Should any Oilpan GC happen while it is in
    135     // effect, the implicit assumption that we're outside any event
    136     // loop (=> there being no pointers on the stack needing scanning)
    137     // when that GC strikes will no longer hold.
    138     //
    139     // To ensure that the references on the stack are also traced, we
    140     // turn them into persistent, stack allocated references. This
    141     // workaround is sufficient to handle this artificial test
    142     // scenario.
    143     WebView* webView = setupTest(file, client);
    144 
    145     RefPtrWillBePersistent<Document> document = static_cast<PassRefPtrWillBeRawPtr<Document> >(webView->mainFrame()->document());
    146     runTestOnTree(document.get(), webView, client);
    147 
    148     m_webViewHelper.reset(); // Explicitly reset to break dependency on locally scoped client.
    149 }
    150 
    151 void TouchActionTest::runShadowDOMTest(std::string file)
    152 {
    153     TouchActionTrackingWebViewClient client;
    154 
    155     WebView* webView = setupTest(file, client);
    156 
    157     TrackExceptionState es;
    158 
    159     // Oilpan: see runTouchActionTest() comment why these are persistent references.
    160     RefPtrWillBePersistent<Document> document = static_cast<PassRefPtrWillBeRawPtr<Document> >(webView->mainFrame()->document());
    161     RefPtrWillBePersistent<StaticElementList> hostNodes = document->querySelectorAll("[shadow-host]", es);
    162     ASSERT_FALSE(es.hadException());
    163     ASSERT_GE(hostNodes->length(), 1u);
    164 
    165     for (unsigned index = 0; index < hostNodes->length(); index++) {
    166         ShadowRoot* shadowRoot = hostNodes->item(index)->shadowRoot();
    167         runTestOnTree(shadowRoot, webView, client);
    168     }
    169 
    170     // Projections show up in the main document.
    171     runTestOnTree(document.get(), webView, client);
    172 
    173     m_webViewHelper.reset(); // Explicitly reset to break dependency on locally scoped client.
    174 }
    175 
    176 WebView* TouchActionTest::setupTest(std::string file, TouchActionTrackingWebViewClient& client)
    177 {
    178     URLTestHelpers::registerMockedURLFromBaseURL(WebString::fromUTF8(m_baseURL), WebString::fromUTF8(file));
    179     // Note that JavaScript must be enabled for shadow DOM tests.
    180     WebView* webView = m_webViewHelper.initializeAndLoad(m_baseURL + file, true, 0, &client);
    181 
    182     // Lock page scale factor to avoid zooming out to contents size.
    183     m_webViewHelper.webView()->setPageScaleFactorLimits(1, 1);
    184 
    185     // Set size to enable hit testing, and avoid line wrapping for consistency with browser.
    186     webView->resize(WebSize(800, 1200));
    187 
    188     // Scroll to verify the code properly transforms windows to client co-ords.
    189     const int kScrollOffset = 100;
    190     RefPtrWillBeRawPtr<Document> document = static_cast<PassRefPtrWillBeRawPtr<Document> >(webView->mainFrame()->document());
    191     document->frame()->view()->setScrollOffset(IntPoint(0, kScrollOffset));
    192 
    193     return webView;
    194 }
    195 
    196 void TouchActionTest::runTestOnTree(ContainerNode* root, WebView* webView, TouchActionTrackingWebViewClient& client)
    197 {
    198     // Find all elements to test the touch-action of in the document.
    199     TrackExceptionState es;
    200 
    201     // Oilpan: see runTouchActionTest() comment why these are persistent references.
    202     RefPtrWillBePersistent<StaticElementList> elements = root->querySelectorAll("[expected-action]", es);
    203     ASSERT_FALSE(es.hadException());
    204 
    205     for (unsigned index = 0; index < elements->length(); index++) {
    206         Element* element = elements->item(index);
    207         element->scrollIntoViewIfNeeded();
    208 
    209         std::string failureContext("Test case: ");
    210         if (element->hasID()) {
    211             failureContext.append(element->getIdAttribute().ascii().data());
    212         } else if (element->firstChild()) {
    213             failureContext.append("\"");
    214             failureContext.append(element->firstChild()->textContent(false).stripWhiteSpace().ascii().data());
    215             failureContext.append("\"");
    216         } else {
    217             failureContext += "<missing ID>";
    218         }
    219 
    220         // Run each test three times at different positions in the element.
    221         // Note that we don't want the bounding box because our tests sometimes have elements with
    222         // multiple border boxes with other elements in between. Use the first border box (which
    223         // we can easily visualize in a browser for debugging).
    224         RefPtrWillBePersistent<ClientRectList> rects = element->getClientRects();
    225         ASSERT_GE(rects->length(), 0u) << failureContext;
    226         RefPtrWillBePersistent<ClientRect> r = rects->item(0);
    227         FloatRect clientFloatRect = FloatRect(r->left(), r->top(), r->width(), r->height());
    228         IntRect clientRect =  enclosedIntRect(clientFloatRect);
    229         for (int locIdx = 0; locIdx < 3; locIdx++) {
    230             IntPoint clientPoint;
    231             std::stringstream contextStream;
    232             contextStream << failureContext << " (";
    233             switch (locIdx) {
    234             case 0:
    235                 clientPoint = clientRect.center();
    236                 contextStream << "center";
    237                 break;
    238             case 1:
    239                 clientPoint = clientRect.location();
    240                 contextStream << "top-left";
    241                 break;
    242             case 2:
    243                 clientPoint = clientRect.maxXMaxYCorner();
    244                 clientPoint.move(-1, -1);
    245                 contextStream << "bottom-right";
    246                 break;
    247             default:
    248                 FAIL() << "Invalid location index.";
    249             }
    250             contextStream << "=" << clientPoint.x() << "," << clientPoint.y() << ").";
    251             std::string failureContextPos = contextStream.str();
    252 
    253             LocalFrame* frame = root->document().frame();
    254             FrameView* frameView = frame->view();
    255             IntRect visibleRect = frameView->windowClipRect();
    256             ASSERT_TRUE(visibleRect.contains(clientPoint)) << failureContextPos
    257                 << " Test point not contained in visible area: " << visibleRect.x() << "," << visibleRect.y()
    258                 << "-" << visibleRect.maxX() << "," << visibleRect.maxY();
    259 
    260             // First validate that a hit test at this point will really hit the element
    261             // we intended. This is the easiest way for a test to be broken, but has nothing really
    262             // to do with touch action.
    263             // Note that we can't use WebView's hit test API because it doesn't look into shadow DOM.
    264             IntPoint docPoint(frameView->windowToContents(clientPoint));
    265             HitTestResult result = frame->eventHandler().hitTestResultAtPoint(docPoint, HitTestRequest::ReadOnly | HitTestRequest::Active);
    266             ASSERT_EQ(element, result.innerElement()) << "Unexpected hit test result " << failureContextPos
    267                 << "  Got element: \"" << result.innerElement()->outerHTML().stripWhiteSpace().left(80).ascii().data() << "\""
    268                 << std::endl << "Document render tree:" << std::endl << externalRepresentation(root->document().frame()).utf8().data();
    269 
    270             // Now send the touch event and check any touch action result.
    271             sendTouchEvent(webView, WebInputEvent::TouchStart, clientPoint);
    272 
    273             AtomicString expectedAction = element->getAttribute("expected-action");
    274             if (expectedAction == "auto") {
    275                 // Auto is the default - no action set.
    276                 EXPECT_EQ(0, client.touchActionSetCount()) << failureContextPos;
    277                 EXPECT_EQ(WebTouchActionAuto, client.lastTouchAction()) << failureContextPos;
    278             } else {
    279                 // Should have received exactly one touch action.
    280                 EXPECT_EQ(1, client.touchActionSetCount()) << failureContextPos;
    281                 if (client.touchActionSetCount()) {
    282                     if (expectedAction == "none") {
    283                         EXPECT_EQ(WebTouchActionNone, client.lastTouchAction()) << failureContextPos;
    284                     } else if (expectedAction == "pan-x") {
    285                         EXPECT_EQ(WebTouchActionPanX, client.lastTouchAction()) << failureContextPos;
    286                     } else if (expectedAction == "pan-y") {
    287                         EXPECT_EQ(WebTouchActionPanY, client.lastTouchAction()) << failureContextPos;
    288                     } else if (expectedAction == "pan-x-y") {
    289                         EXPECT_EQ((WebTouchActionPanX | WebTouchActionPanY), client.lastTouchAction()) << failureContextPos;
    290                     } else if (expectedAction == "manipulation") {
    291                         EXPECT_EQ((WebTouchActionPanX | WebTouchActionPanY | WebTouchActionPinchZoom), client.lastTouchAction()) << failureContextPos;
    292                     } else {
    293                         FAIL() << "Unrecognized expected-action \"" << expectedAction.ascii().data()
    294                             << "\" " << failureContextPos;
    295                     }
    296                 }
    297             }
    298 
    299             // Reset webview touch state.
    300             client.reset();
    301             sendTouchEvent(webView, WebInputEvent::TouchCancel, clientPoint);
    302             EXPECT_EQ(0, client.touchActionSetCount());
    303         }
    304     }
    305 }
    306 void TouchActionTest::sendTouchEvent(WebView* webView, WebInputEvent::Type type, IntPoint clientPoint)
    307 {
    308     ASSERT_TRUE(type == WebInputEvent::TouchStart || type == WebInputEvent::TouchCancel);
    309 
    310     WebTouchEvent webTouchEvent;
    311     webTouchEvent.type = type;
    312     if (type == WebInputEvent::TouchCancel)
    313         webTouchEvent.cancelable = false;
    314     webTouchEvent.touchesLength = 1;
    315     webTouchEvent.touches[0].state = (type == WebInputEvent::TouchStart ?
    316         WebTouchPoint::StatePressed :
    317         WebTouchPoint::StateCancelled);
    318     webTouchEvent.touches[0].id = kfakeTouchId;
    319     webTouchEvent.touches[0].screenPosition.x = clientPoint.x();
    320     webTouchEvent.touches[0].screenPosition.y = clientPoint.y();
    321     webTouchEvent.touches[0].position.x = clientPoint.x();
    322     webTouchEvent.touches[0].position.y = clientPoint.y();
    323     webTouchEvent.touches[0].radiusX = 10;
    324     webTouchEvent.touches[0].radiusY = 10;
    325 
    326     webView->handleInputEvent(webTouchEvent);
    327     runPendingTasks();
    328 }
    329 
    330 // crbug.com/411038
    331 TEST_F(TouchActionTest, DISABLED_Simple)
    332 {
    333     runTouchActionTest("touch-action-simple.html");
    334 }
    335 
    336 TEST_F(TouchActionTest, Overflow)
    337 {
    338     runTouchActionTest("touch-action-overflow.html");
    339 }
    340 
    341 TEST_F(TouchActionTest, ShadowDOM)
    342 {
    343     runShadowDOMTest("touch-action-shadow-dom.html");
    344 }
    345 
    346 TEST_F(TouchActionTest, Pan)
    347 {
    348     runTouchActionTest("touch-action-pan.html");
    349 }
    350 
    351 }
    352