root/android_webview/javatests/src/org/chromium/android_webview/test/WebKitHitTestTest.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. setUp
  2. tearDown
  3. setServerResponseAndLoad
  4. fullPageLink
  5. simulateTouchCenterOfWebViewOnUiThread
  6. simulateTabDownUpOnUiThread
  7. simulateInput
  8. stringEquals
  9. pollForHitTestDataOnUiThread
  10. pollForHrefAndImageSrcOnUiThread
  11. srcAnchorTypeTestBody
  12. Feature
  13. testSrcAnchorType
  14. Feature
  15. testSrcAnchorTypeByFocus
  16. blankHrefTestBody
  17. Feature
  18. testSrcAnchorTypeBlankHref
  19. Feature
  20. testSrcAnchorTypeBlankHrefByFocus
  21. srcAnchorTypeRelativeUrlTestBody
  22. Feature
  23. testSrcAnchorTypeRelativeUrl
  24. Feature
  25. testSrcAnchorTypeRelativeUrlByFocus
  26. srcEmailTypeTestBody
  27. Feature
  28. testSrcEmailType
  29. Feature
  30. testSrcEmailTypeByFocus
  31. srcGeoTypeTestBody
  32. Feature
  33. testSrcGeoType
  34. Feature
  35. testSrcGeoTypeByFocus
  36. srcPhoneTypeTestBody
  37. Feature
  38. testSrcPhoneType
  39. Feature
  40. testSrcPhoneTypeByFocus
  41. srcImgeAnchorTypeTestBody
  42. Feature
  43. testSrcImgeAnchorType
  44. Feature
  45. testSrcImgeAnchorTypeByFocus
  46. srcImgeAnchorTypeRelativeUrlTestBody
  47. Feature
  48. testSrcImgeAnchorTypeRelativeUrl
  49. Feature
  50. testSrcImgeAnchorTypeRelativeUrlByFocus
  51. Feature
  52. testImgeType
  53. editTextTypeTestBody
  54. Feature
  55. testEditTextType
  56. Feature
  57. testEditTextTypeByFocus
  58. unknownTypeJavascriptSchemeTestBody
  59. Feature
  60. testUnknownTypeJavascriptScheme
  61. Feature
  62. testUnknownTypeJavascriptSchemeByFocus
  63. Feature
  64. testUnknownTypeUnrecognizedNode
  65. Feature
  66. testUnfocusedNodeAndTouchRace

// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.android_webview.test;

import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.webkit.WebView.HitTestResult;

import org.chromium.android_webview.AwContents;
import org.chromium.android_webview.test.util.CommonResources;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Feature;
import org.chromium.net.test.util.TestWebServer;

import java.util.concurrent.Callable;

/**
 * Test for getHitTestResult, requestFocusNodeHref, and requestImageRef methods
 */
public class WebKitHitTestTest extends AwTestBase {
    private TestAwContentsClient mContentsClient;
    private AwTestContainerView mTestView;
    private AwContents mAwContents;
    private TestWebServer mWebServer;

    private static String HREF = "http://foo/";
    private static String ANCHOR_TEXT = "anchor text";

    @Override
    public void setUp() throws Exception {
        super.setUp();
        mContentsClient = new TestAwContentsClient();
        mTestView = createAwTestContainerViewOnMainSync(mContentsClient);
        mAwContents = mTestView.getAwContents();
        mWebServer = new TestWebServer(false);
    }

    @Override
    public void tearDown() throws Exception {
        if (mWebServer != null) {
            mWebServer.shutdown();
        }
        super.tearDown();
    }

    private void setServerResponseAndLoad(String response) throws Throwable {
        String url = mWebServer.setResponse("/hittest.html", response, null);
        loadUrlSync(mAwContents,
                    mContentsClient.getOnPageFinishedHelper(),
                    url);
    }

    private static String fullPageLink(String href, String anchorText) {
        return CommonResources.makeHtmlPageFrom("", "<a class=\"full_view\" href=\"" +
                href + "\" " + "onclick=\"return false;\">" + anchorText + "</a>");
    }

    private void simulateTouchCenterOfWebViewOnUiThread() throws Throwable {
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                long eventTime = SystemClock.uptimeMillis();
                float x = (float) (mTestView.getRight() - mTestView.getLeft()) / 2;
                float y = (float) (mTestView.getBottom() - mTestView.getTop()) / 2;
                mAwContents.onTouchEvent(MotionEvent.obtain(
                        eventTime, eventTime, MotionEvent.ACTION_DOWN,
                        x, y, 0));
                mAwContents.onTouchEvent(MotionEvent.obtain(
                        eventTime, eventTime, MotionEvent.ACTION_UP,
                        x, y, 0));
            }
        });
    }

    private void simulateTabDownUpOnUiThread() throws Throwable {
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                mAwContents.getContentViewCore().dispatchKeyEvent(
                        new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB));
                mAwContents.getContentViewCore().dispatchKeyEvent(
                        new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TAB));
            }
        });
    }

    private void simulateInput(boolean byTouch) throws Throwable {
        // Send a touch click event if byTouch is true. Otherwise, send a TAB
        // key event to change the focused element of the page.
        if (byTouch) {
            simulateTouchCenterOfWebViewOnUiThread();
        } else {
            simulateTabDownUpOnUiThread();
        }
    }

    private static boolean stringEquals(String a, String b) {
        return a == null ? b == null : a.equals(b);
    }

    private void pollForHitTestDataOnUiThread(
            final int expectedType, final String expectedExtra) throws Throwable {
        pollOnUiThread(new Callable<Boolean>() {
            @Override
            public Boolean call() {
                AwContents.HitTestData data = mAwContents.getLastHitTestResult();
                return expectedType == data.hitTestResultType &&
                       stringEquals(expectedExtra, data.hitTestResultExtraData);
            }
        });
    }

    private void pollForHrefAndImageSrcOnUiThread(
            final String expectedHref,
            final String expectedAnchorText,
            final String expectedImageSrc) throws Throwable {
        pollOnUiThread(new Callable<Boolean>() {
            @Override
            public Boolean call() {
                AwContents.HitTestData data = mAwContents.getLastHitTestResult();
                return stringEquals(expectedHref, data.href) &&
                       stringEquals(expectedAnchorText, data.anchorText) &&
                       stringEquals(expectedImageSrc, data.imgSrc);
            }
        });

        Handler dummyHandler = new Handler();
        final Message focusNodeHrefMsg = dummyHandler.obtainMessage();
        final Message imageRefMsg = dummyHandler.obtainMessage();
        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
            @Override
            public void run() {
                mAwContents.requestFocusNodeHref(focusNodeHrefMsg);
                mAwContents.requestImageRef(imageRefMsg);
            }
        });
        assertEquals(expectedHref, focusNodeHrefMsg.getData().getString("url"));
        assertEquals(expectedAnchorText, focusNodeHrefMsg.getData().getString("title"));
        assertEquals(expectedImageSrc, focusNodeHrefMsg.getData().getString("src"));
        assertEquals(expectedImageSrc, imageRefMsg.getData().getString("url"));
    }

    private void srcAnchorTypeTestBody(boolean byTouch) throws Throwable {
        String page = fullPageLink(HREF, ANCHOR_TEXT);
        setServerResponseAndLoad(page);
        simulateInput(byTouch);
        pollForHitTestDataOnUiThread(HitTestResult.SRC_ANCHOR_TYPE, HREF);
        pollForHrefAndImageSrcOnUiThread(HREF, ANCHOR_TEXT, null);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcAnchorType() throws Throwable {
        srcAnchorTypeTestBody(true);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcAnchorTypeByFocus() throws Throwable {
        srcAnchorTypeTestBody(false);
    }

    private void blankHrefTestBody(boolean byTouch) throws Throwable {
        String fullPath = mWebServer.getResponseUrl("/hittest.html");
        String page = fullPageLink("", ANCHOR_TEXT);
        setServerResponseAndLoad(page);
        simulateInput(byTouch);
        pollForHitTestDataOnUiThread(HitTestResult.SRC_ANCHOR_TYPE, fullPath);
        pollForHrefAndImageSrcOnUiThread(fullPath, ANCHOR_TEXT, null);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcAnchorTypeBlankHref() throws Throwable {
        blankHrefTestBody(true);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcAnchorTypeBlankHrefByFocus() throws Throwable {
        blankHrefTestBody(false);
    }

    private void srcAnchorTypeRelativeUrlTestBody(boolean byTouch) throws Throwable {
        String relPath = "/foo.html";
        String fullPath = mWebServer.getResponseUrl(relPath);
        String page = fullPageLink(relPath, ANCHOR_TEXT);
        setServerResponseAndLoad(page);
        simulateInput(byTouch);
        pollForHitTestDataOnUiThread(HitTestResult.SRC_ANCHOR_TYPE, fullPath);
        pollForHrefAndImageSrcOnUiThread(fullPath, ANCHOR_TEXT, null);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcAnchorTypeRelativeUrl() throws Throwable {
        srcAnchorTypeRelativeUrlTestBody(true);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcAnchorTypeRelativeUrlByFocus() throws Throwable {
        srcAnchorTypeRelativeUrlTestBody(false);
    }

    private void srcEmailTypeTestBody(boolean byTouch) throws Throwable {
        String email = "foo@bar.com";
        String prefix = "mailto:";
        String page = fullPageLink(prefix + email, ANCHOR_TEXT);
        setServerResponseAndLoad(page);
        simulateInput(byTouch);
        pollForHitTestDataOnUiThread(HitTestResult.EMAIL_TYPE, email);
        pollForHrefAndImageSrcOnUiThread(prefix + email, ANCHOR_TEXT, null);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcEmailType() throws Throwable {
        srcEmailTypeTestBody(true);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcEmailTypeByFocus() throws Throwable {
        srcEmailTypeTestBody(false);
    }

    private void srcGeoTypeTestBody(boolean byTouch) throws Throwable {
        String location = "Jilin";
        String prefix = "geo:0,0?q=";
        String page = fullPageLink(prefix + location, ANCHOR_TEXT);
        setServerResponseAndLoad(page);
        simulateInput(byTouch);
        pollForHitTestDataOnUiThread(HitTestResult.GEO_TYPE, location);
        pollForHrefAndImageSrcOnUiThread(prefix + location, ANCHOR_TEXT, null);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcGeoType() throws Throwable {
        srcGeoTypeTestBody(true);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcGeoTypeByFocus() throws Throwable {
        srcGeoTypeTestBody(false);
    }

    private void srcPhoneTypeTestBody(boolean byTouch) throws Throwable {
        String phone_num = "%2B1234567890";
        String expected_phone_num = "+1234567890";
        String prefix = "tel:";
        String page = fullPageLink("tel:" + phone_num, ANCHOR_TEXT);
        setServerResponseAndLoad(page);
        simulateInput(byTouch);
        pollForHitTestDataOnUiThread(HitTestResult.PHONE_TYPE, expected_phone_num);
        pollForHrefAndImageSrcOnUiThread(prefix + phone_num, ANCHOR_TEXT, null);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcPhoneType() throws Throwable {
        srcPhoneTypeTestBody(true);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcPhoneTypeByFocus() throws Throwable {
        srcPhoneTypeTestBody(false);
    }

    private void srcImgeAnchorTypeTestBody(boolean byTouch) throws Throwable {
        String fullImageSrc = "http://foo.bar/nonexistent.jpg";
        String page = CommonResources.makeHtmlPageFrom("", "<a class=\"full_view\" href=\"" +
                HREF + "\"onclick=\"return false;\"><img class=\"full_view\" src=\"" +
                fullImageSrc + "\"></a>");
        setServerResponseAndLoad(page);
        simulateInput(byTouch);
        pollForHitTestDataOnUiThread(HitTestResult.SRC_IMAGE_ANCHOR_TYPE, fullImageSrc);
        pollForHrefAndImageSrcOnUiThread(HREF, null, fullImageSrc);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcImgeAnchorType() throws Throwable {
        srcImgeAnchorTypeTestBody(true);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcImgeAnchorTypeByFocus() throws Throwable {
        srcImgeAnchorTypeTestBody(false);
    }

    private void srcImgeAnchorTypeRelativeUrlTestBody(boolean byTouch) throws Throwable {
        String relImageSrc = "/nonexistent.jpg";
        String fullImageSrc = mWebServer.getResponseUrl(relImageSrc);
        String relPath = "/foo.html";
        String fullPath = mWebServer.getResponseUrl(relPath);
        String page = CommonResources.makeHtmlPageFrom("", "<a class=\"full_view\" href=\"" +
                relPath + "\"onclick=\"return false;\"><img class=\"full_view\" src=\"" +
                relImageSrc + "\"></a>");
        setServerResponseAndLoad(page);
        simulateInput(byTouch);
        pollForHitTestDataOnUiThread(HitTestResult.SRC_IMAGE_ANCHOR_TYPE, fullImageSrc);
        pollForHrefAndImageSrcOnUiThread(fullPath, null, fullImageSrc);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcImgeAnchorTypeRelativeUrl() throws Throwable {
        srcImgeAnchorTypeRelativeUrlTestBody(true);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testSrcImgeAnchorTypeRelativeUrlByFocus() throws Throwable {
        srcImgeAnchorTypeRelativeUrlTestBody(false);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testImgeType() throws Throwable {
        String relImageSrc = "/nonexistent2.jpg";
        String fullImageSrc = mWebServer.getResponseUrl(relImageSrc);
        String page = CommonResources.makeHtmlPageFrom("",
                "<img class=\"full_view\" src=\"" + relImageSrc + "\">");
        setServerResponseAndLoad(page);
        simulateTouchCenterOfWebViewOnUiThread();
        pollForHitTestDataOnUiThread(HitTestResult.IMAGE_TYPE, fullImageSrc);
        pollForHrefAndImageSrcOnUiThread(null, null, fullImageSrc);
    }

    private void editTextTypeTestBody(boolean byTouch) throws Throwable {
        String page = CommonResources.makeHtmlPageFrom("",
                "<form><input class=\"full_view\" type=\"text\" name=\"test\"></form>");
        setServerResponseAndLoad(page);
        simulateInput(byTouch);
        pollForHitTestDataOnUiThread(HitTestResult.EDIT_TEXT_TYPE, null);
        pollForHrefAndImageSrcOnUiThread(null, null, null);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testEditTextType() throws Throwable {
        editTextTypeTestBody(true);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testEditTextTypeByFocus() throws Throwable {
        editTextTypeTestBody(false);
    }

    public void unknownTypeJavascriptSchemeTestBody(boolean byTouch) throws Throwable {
        // Per documentation, javascript urls are special.
        String javascript = "javascript:alert('foo');";
        String page = fullPageLink(javascript, ANCHOR_TEXT);
        setServerResponseAndLoad(page);
        simulateInput(byTouch);
        pollForHrefAndImageSrcOnUiThread(javascript, ANCHOR_TEXT, null);
        pollForHitTestDataOnUiThread(HitTestResult.UNKNOWN_TYPE, null);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testUnknownTypeJavascriptScheme() throws Throwable {
        unknownTypeJavascriptSchemeTestBody(true);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testUnknownTypeJavascriptSchemeByFocus() throws Throwable {
        unknownTypeJavascriptSchemeTestBody(false);
    }

    @SmallTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testUnknownTypeUnrecognizedNode() throws Throwable {
        // Since UNKNOWN_TYPE is the default, hit test another type first for
        // this test to be valid.
        testSrcAnchorType();

        final String title = "UNKNOWN_TYPE title";

        String page = CommonResources.makeHtmlPageFrom(
                "<title>" + title + "</title>",
                "<div class=\"full_view\">div text</div>");
        setServerResponseAndLoad(page);

        // Wait for the new page to be loaded before trying hit test.
        pollOnUiThread(new Callable<Boolean>() {
            @Override
            public Boolean call() {
                return mAwContents.getContentViewCore().getTitle().equals(title);
            }
        });
        simulateTouchCenterOfWebViewOnUiThread();
        pollForHitTestDataOnUiThread(HitTestResult.UNKNOWN_TYPE, null);
    }

    @LargeTest
    @Feature({"AndroidWebView", "WebKitHitTest"})
    public void testUnfocusedNodeAndTouchRace() throws Throwable {
        // Test when the touch and focus paths racing with setting different
        // results.

        String relImageSrc = "/nonexistent3.jpg";
        String fullImageSrc = mWebServer.getResponseUrl(relImageSrc);
        String html = CommonResources.makeHtmlPageFrom(
                "<meta name=\"viewport\" content=\"width=device-width,height=device-height\" />" +
                        "<style type=\"text/css\">" +
                        ".full_width { width:100%; position:absolute; }" +
                        "</style>",
                        "<form><input class=\"full_width\" style=\"height:25%;\" " +
                        "type=\"text\" name=\"test\"></form>" +
                        "<img class=\"full_width\" style=\"height:50%;top:25%;\" " +
                        "src=\"" + relImageSrc + "\">");
        setServerResponseAndLoad(html);

        // Focus on input element and check the hit test results.
        simulateTabDownUpOnUiThread();
        pollForHitTestDataOnUiThread(HitTestResult.EDIT_TEXT_TYPE, null);
        pollForHrefAndImageSrcOnUiThread(null, null, null);

        // Touch image. Now the focus based hit test path will try to null out
        // the results and the touch based path will update with the result of
        // the image.
        simulateTouchCenterOfWebViewOnUiThread();

        // Make sure the result of image sticks.
        for (int i = 0; i < 2; ++i) {
            Thread.sleep(500);
            pollForHitTestDataOnUiThread(HitTestResult.IMAGE_TYPE, fullImageSrc);
            pollForHrefAndImageSrcOnUiThread(null, null, fullImageSrc);
        }
    }
}

/* [<][>][^][v][top][bottom][index][help] */