1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.android_webview.test;
7 import android.os.Handler;
8 import android.os.Message;
9 import android.test.suitebuilder.annotation.LargeTest;
10 import android.test.suitebuilder.annotation.SmallTest;
11 import android.view.KeyEvent;
12 import android.webkit.WebView.HitTestResult;
14 import org.chromium.android_webview.AwContents;
15 import org.chromium.android_webview.test.util.AwTestTouchUtils;
16 import org.chromium.android_webview.test.util.CommonResources;
17 import org.chromium.base.ThreadUtils;
18 import org.chromium.base.test.util.Feature;
19 import org.chromium.net.test.util.TestWebServer;
21 import java.util.concurrent.Callable;
24 * Test for getHitTestResult, requestFocusNodeHref, and requestImageRef methods
26 public class WebKitHitTestTest extends AwTestBase {
27 private TestAwContentsClient mContentsClient;
28 private AwTestContainerView mTestView;
29 private AwContents mAwContents;
30 private TestWebServer mWebServer;
32 private static final String HREF = "http://foo/";
33 private static final String ANCHOR_TEXT = "anchor text";
36 public void setUp() throws Exception {
38 mContentsClient = new TestAwContentsClient();
39 mTestView = createAwTestContainerViewOnMainSync(mContentsClient);
40 mAwContents = mTestView.getAwContents();
41 mWebServer = TestWebServer.start();
45 public void tearDown() throws Exception {
46 if (mWebServer != null) {
47 mWebServer.shutdown();
52 private void setServerResponseAndLoad(String response) throws Throwable {
53 String url = mWebServer.setResponse("/hittest.html", response, null);
54 loadUrlSync(mAwContents,
55 mContentsClient.getOnPageFinishedHelper(),
59 private static String fullPageLink(String href, String anchorText) {
60 return CommonResources.makeHtmlPageFrom("", "<a class=\"full_view\" href=\"" +
61 href + "\" " + "onclick=\"return false;\">" + anchorText + "</a>");
64 private void simulateTabDownUpOnUiThread() throws Throwable {
65 runTestOnUiThread(new Runnable() {
68 mAwContents.getContentViewCore().dispatchKeyEvent(
69 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB));
70 mAwContents.getContentViewCore().dispatchKeyEvent(
71 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TAB));
76 private void simulateInput(boolean byTouch) throws Throwable {
77 // Send a touch click event if byTouch is true. Otherwise, send a TAB
78 // key event to change the focused element of the page.
80 AwTestTouchUtils.simulateTouchCenterOfView(mTestView);
82 simulateTabDownUpOnUiThread();
86 private static boolean stringEquals(String a, String b) {
87 return a == null ? b == null : a.equals(b);
90 private void pollForHitTestDataOnUiThread(
91 final int expectedType, final String expectedExtra) throws Throwable {
92 pollOnUiThread(new Callable<Boolean>() {
94 public Boolean call() {
95 AwContents.HitTestData data = mAwContents.getLastHitTestResult();
96 return expectedType == data.hitTestResultType &&
97 stringEquals(expectedExtra, data.hitTestResultExtraData);
102 private void pollForHrefAndImageSrcOnUiThread(
103 final String expectedHref,
104 final String expectedAnchorText,
105 final String expectedImageSrc) throws Throwable {
106 pollOnUiThread(new Callable<Boolean>() {
108 public Boolean call() {
109 AwContents.HitTestData data = mAwContents.getLastHitTestResult();
110 return stringEquals(expectedHref, data.href) &&
111 stringEquals(expectedAnchorText, data.anchorText) &&
112 stringEquals(expectedImageSrc, data.imgSrc);
116 Handler dummyHandler = new Handler();
117 final Message focusNodeHrefMsg = dummyHandler.obtainMessage();
118 final Message imageRefMsg = dummyHandler.obtainMessage();
119 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
122 mAwContents.requestFocusNodeHref(focusNodeHrefMsg);
123 mAwContents.requestImageRef(imageRefMsg);
126 assertEquals(expectedHref, focusNodeHrefMsg.getData().getString("url"));
127 assertEquals(expectedAnchorText, focusNodeHrefMsg.getData().getString("title"));
128 assertEquals(expectedImageSrc, focusNodeHrefMsg.getData().getString("src"));
129 assertEquals(expectedImageSrc, imageRefMsg.getData().getString("url"));
132 private void srcAnchorTypeTestBody(boolean byTouch) throws Throwable {
133 String page = fullPageLink(HREF, ANCHOR_TEXT);
134 setServerResponseAndLoad(page);
135 simulateInput(byTouch);
136 pollForHitTestDataOnUiThread(HitTestResult.SRC_ANCHOR_TYPE, HREF);
137 pollForHrefAndImageSrcOnUiThread(HREF, ANCHOR_TEXT, null);
141 @Feature({"AndroidWebView", "WebKitHitTest"})
142 public void testSrcAnchorType() throws Throwable {
143 srcAnchorTypeTestBody(true);
147 @Feature({"AndroidWebView", "WebKitHitTest"})
148 public void testSrcAnchorTypeByFocus() throws Throwable {
149 srcAnchorTypeTestBody(false);
152 private void blankHrefTestBody(boolean byTouch) throws Throwable {
153 String fullPath = mWebServer.getResponseUrl("/hittest.html");
154 String page = fullPageLink("", ANCHOR_TEXT);
155 setServerResponseAndLoad(page);
156 simulateInput(byTouch);
157 pollForHitTestDataOnUiThread(HitTestResult.SRC_ANCHOR_TYPE, fullPath);
158 pollForHrefAndImageSrcOnUiThread(fullPath, ANCHOR_TEXT, null);
162 @Feature({"AndroidWebView", "WebKitHitTest"})
163 public void testSrcAnchorTypeBlankHref() throws Throwable {
164 blankHrefTestBody(true);
168 @Feature({"AndroidWebView", "WebKitHitTest"})
169 public void testSrcAnchorTypeBlankHrefByFocus() throws Throwable {
170 blankHrefTestBody(false);
173 private void srcAnchorTypeRelativeUrlTestBody(boolean byTouch) throws Throwable {
174 String relPath = "/foo.html";
175 String fullPath = mWebServer.getResponseUrl(relPath);
176 String page = fullPageLink(relPath, ANCHOR_TEXT);
177 setServerResponseAndLoad(page);
178 simulateInput(byTouch);
179 pollForHitTestDataOnUiThread(HitTestResult.SRC_ANCHOR_TYPE, fullPath);
180 pollForHrefAndImageSrcOnUiThread(fullPath, ANCHOR_TEXT, null);
184 @Feature({"AndroidWebView", "WebKitHitTest"})
185 public void testSrcAnchorTypeRelativeUrl() throws Throwable {
186 srcAnchorTypeRelativeUrlTestBody(true);
190 @Feature({"AndroidWebView", "WebKitHitTest"})
191 public void testSrcAnchorTypeRelativeUrlByFocus() throws Throwable {
192 srcAnchorTypeRelativeUrlTestBody(false);
195 private void srcEmailTypeTestBody(boolean byTouch) throws Throwable {
196 String email = "foo@bar.com";
197 String prefix = "mailto:";
198 String page = fullPageLink(prefix + email, ANCHOR_TEXT);
199 setServerResponseAndLoad(page);
200 simulateInput(byTouch);
201 pollForHitTestDataOnUiThread(HitTestResult.EMAIL_TYPE, email);
202 pollForHrefAndImageSrcOnUiThread(prefix + email, ANCHOR_TEXT, null);
206 @Feature({"AndroidWebView", "WebKitHitTest"})
207 public void testSrcEmailType() throws Throwable {
208 srcEmailTypeTestBody(true);
212 @Feature({"AndroidWebView", "WebKitHitTest"})
213 public void testSrcEmailTypeByFocus() throws Throwable {
214 srcEmailTypeTestBody(false);
217 private void srcGeoTypeTestBody(boolean byTouch) throws Throwable {
218 String location = "Jilin";
219 String prefix = "geo:0,0?q=";
220 String page = fullPageLink(prefix + location, ANCHOR_TEXT);
221 setServerResponseAndLoad(page);
222 simulateInput(byTouch);
223 pollForHitTestDataOnUiThread(HitTestResult.GEO_TYPE, location);
224 pollForHrefAndImageSrcOnUiThread(prefix + location, ANCHOR_TEXT, null);
228 @Feature({"AndroidWebView", "WebKitHitTest"})
229 public void testSrcGeoType() throws Throwable {
230 srcGeoTypeTestBody(true);
234 @Feature({"AndroidWebView", "WebKitHitTest"})
235 public void testSrcGeoTypeByFocus() throws Throwable {
236 srcGeoTypeTestBody(false);
239 private void srcPhoneTypeTestBody(boolean byTouch) throws Throwable {
240 String phone_num = "%2B1234567890";
241 String expected_phone_num = "+1234567890";
242 String prefix = "tel:";
243 String page = fullPageLink("tel:" + phone_num, ANCHOR_TEXT);
244 setServerResponseAndLoad(page);
245 simulateInput(byTouch);
246 pollForHitTestDataOnUiThread(HitTestResult.PHONE_TYPE, expected_phone_num);
247 pollForHrefAndImageSrcOnUiThread(prefix + phone_num, ANCHOR_TEXT, null);
251 @Feature({"AndroidWebView", "WebKitHitTest"})
252 public void testSrcPhoneType() throws Throwable {
253 srcPhoneTypeTestBody(true);
257 @Feature({"AndroidWebView", "WebKitHitTest"})
258 public void testSrcPhoneTypeByFocus() throws Throwable {
259 srcPhoneTypeTestBody(false);
262 private void srcImgeAnchorTypeTestBody(boolean byTouch) throws Throwable {
263 String fullImageSrc = "http://foo.bar/nonexistent.jpg";
264 String page = CommonResources.makeHtmlPageFrom("", "<a class=\"full_view\" href=\"" +
265 HREF + "\"onclick=\"return false;\"><img class=\"full_view\" src=\"" +
266 fullImageSrc + "\"></a>");
267 setServerResponseAndLoad(page);
268 simulateInput(byTouch);
269 pollForHitTestDataOnUiThread(HitTestResult.SRC_IMAGE_ANCHOR_TYPE, fullImageSrc);
270 pollForHrefAndImageSrcOnUiThread(HREF, null, fullImageSrc);
274 @Feature({"AndroidWebView", "WebKitHitTest"})
275 public void testSrcImgeAnchorType() throws Throwable {
276 srcImgeAnchorTypeTestBody(true);
280 @Feature({"AndroidWebView", "WebKitHitTest"})
281 public void testSrcImgeAnchorTypeByFocus() throws Throwable {
282 srcImgeAnchorTypeTestBody(false);
285 private void srcImgeAnchorTypeRelativeUrlTestBody(boolean byTouch) throws Throwable {
286 String relImageSrc = "/nonexistent.jpg";
287 String fullImageSrc = mWebServer.getResponseUrl(relImageSrc);
288 String relPath = "/foo.html";
289 String fullPath = mWebServer.getResponseUrl(relPath);
290 String page = CommonResources.makeHtmlPageFrom("", "<a class=\"full_view\" href=\"" +
291 relPath + "\"onclick=\"return false;\"><img class=\"full_view\" src=\"" +
292 relImageSrc + "\"></a>");
293 setServerResponseAndLoad(page);
294 simulateInput(byTouch);
295 pollForHitTestDataOnUiThread(HitTestResult.SRC_IMAGE_ANCHOR_TYPE, fullImageSrc);
296 pollForHrefAndImageSrcOnUiThread(fullPath, null, fullImageSrc);
300 @Feature({"AndroidWebView", "WebKitHitTest"})
301 public void testSrcImgeAnchorTypeRelativeUrl() throws Throwable {
302 srcImgeAnchorTypeRelativeUrlTestBody(true);
306 @Feature({"AndroidWebView", "WebKitHitTest"})
307 public void testSrcImgeAnchorTypeRelativeUrlByFocus() throws Throwable {
308 srcImgeAnchorTypeRelativeUrlTestBody(false);
312 @Feature({"AndroidWebView", "WebKitHitTest"})
313 public void testImgeType() throws Throwable {
314 String relImageSrc = "/nonexistent2.jpg";
315 String fullImageSrc = mWebServer.getResponseUrl(relImageSrc);
316 String page = CommonResources.makeHtmlPageFrom("",
317 "<img class=\"full_view\" src=\"" + relImageSrc + "\">");
318 setServerResponseAndLoad(page);
319 AwTestTouchUtils.simulateTouchCenterOfView(mTestView);
320 pollForHitTestDataOnUiThread(HitTestResult.IMAGE_TYPE, fullImageSrc);
321 pollForHrefAndImageSrcOnUiThread(null, null, fullImageSrc);
324 private void editTextTypeTestBody(boolean byTouch) throws Throwable {
325 String page = CommonResources.makeHtmlPageFrom("",
326 "<form><input class=\"full_view\" type=\"text\" name=\"test\"></form>");
327 setServerResponseAndLoad(page);
328 simulateInput(byTouch);
329 pollForHitTestDataOnUiThread(HitTestResult.EDIT_TEXT_TYPE, null);
330 pollForHrefAndImageSrcOnUiThread(null, null, null);
334 @Feature({"AndroidWebView", "WebKitHitTest"})
335 public void testEditTextType() throws Throwable {
336 editTextTypeTestBody(true);
340 @Feature({"AndroidWebView", "WebKitHitTest"})
341 public void testEditTextTypeByFocus() throws Throwable {
342 editTextTypeTestBody(false);
345 public void unknownTypeJavascriptSchemeTestBody(boolean byTouch) throws Throwable {
346 // Per documentation, javascript urls are special.
347 String javascript = "javascript:alert('foo');";
348 String page = fullPageLink(javascript, ANCHOR_TEXT);
349 setServerResponseAndLoad(page);
350 simulateInput(byTouch);
351 pollForHrefAndImageSrcOnUiThread(javascript, ANCHOR_TEXT, null);
352 pollForHitTestDataOnUiThread(HitTestResult.UNKNOWN_TYPE, null);
356 @Feature({"AndroidWebView", "WebKitHitTest"})
357 public void testUnknownTypeJavascriptScheme() throws Throwable {
358 unknownTypeJavascriptSchemeTestBody(true);
362 @Feature({"AndroidWebView", "WebKitHitTest"})
363 public void testUnknownTypeJavascriptSchemeByFocus() throws Throwable {
364 unknownTypeJavascriptSchemeTestBody(false);
368 @Feature({"AndroidWebView", "WebKitHitTest"})
369 public void testUnknownTypeUnrecognizedNode() throws Throwable {
370 // Since UNKNOWN_TYPE is the default, hit test another type first for
371 // this test to be valid.
374 final String title = "UNKNOWN_TYPE title";
376 String page = CommonResources.makeHtmlPageFrom(
377 "<title>" + title + "</title>",
378 "<div class=\"full_view\">div text</div>");
379 setServerResponseAndLoad(page);
381 // Wait for the new page to be loaded before trying hit test.
382 pollOnUiThread(new Callable<Boolean>() {
384 public Boolean call() {
385 return mAwContents.getTitle().equals(title);
388 AwTestTouchUtils.simulateTouchCenterOfView(mTestView);
389 pollForHitTestDataOnUiThread(HitTestResult.UNKNOWN_TYPE, null);
393 @Feature({"AndroidWebView", "WebKitHitTest"})
394 public void testUnfocusedNodeAndTouchRace() throws Throwable {
395 // Test when the touch and focus paths racing with setting different
398 String relImageSrc = "/nonexistent3.jpg";
399 String fullImageSrc = mWebServer.getResponseUrl(relImageSrc);
400 String html = CommonResources.makeHtmlPageFrom(
401 "<meta name=\"viewport\" content=\"width=device-width,height=device-height\" />" +
402 "<style type=\"text/css\">" +
403 ".full_width { width:100%; position:absolute; }" +
405 "<form><input class=\"full_width\" style=\"height:25%;\" " +
406 "type=\"text\" name=\"test\"></form>" +
407 "<img class=\"full_width\" style=\"height:50%;top:25%;\" " +
408 "src=\"" + relImageSrc + "\">");
409 setServerResponseAndLoad(html);
411 // Focus on input element and check the hit test results.
412 simulateTabDownUpOnUiThread();
413 pollForHitTestDataOnUiThread(HitTestResult.EDIT_TEXT_TYPE, null);
414 pollForHrefAndImageSrcOnUiThread(null, null, null);
416 // Touch image. Now the focus based hit test path will try to null out
417 // the results and the touch based path will update with the result of
419 AwTestTouchUtils.simulateTouchCenterOfView(mTestView);
421 // Make sure the result of image sticks.
422 for (int i = 0; i < 2; ++i) {
424 pollForHitTestDataOnUiThread(HitTestResult.IMAGE_TYPE, fullImageSrc);
425 pollForHrefAndImageSrcOnUiThread(null, null, fullImageSrc);