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.os.SystemClock;
10 import android.test.suitebuilder.annotation.LargeTest;
11 import android.test.suitebuilder.annotation.SmallTest;
12 import android.view.KeyEvent;
13 import android.view.MotionEvent;
14 import android.webkit.WebView.HitTestResult;
16 import org.chromium.android_webview.AwContents;
17 import org.chromium.android_webview.test.util.CommonResources;
18 import org.chromium.base.ThreadUtils;
19 import org.chromium.base.test.util.Feature;
20 import org.chromium.net.test.util.TestWebServer;
22 import java.util.concurrent.Callable;
25 * Test for getHitTestResult, requestFocusNodeHref, and requestImageRef methods
27 public class WebKitHitTestTest extends AwTestBase {
28 private TestAwContentsClient mContentsClient;
29 private AwTestContainerView mTestView;
30 private AwContents mAwContents;
31 private TestWebServer mWebServer;
33 private static final String HREF = "http://foo/";
34 private static final String ANCHOR_TEXT = "anchor text";
37 public void setUp() throws Exception {
39 mContentsClient = new TestAwContentsClient();
40 mTestView = createAwTestContainerViewOnMainSync(mContentsClient);
41 mAwContents = mTestView.getAwContents();
42 mWebServer = new TestWebServer(false);
46 public void tearDown() throws Exception {
47 if (mWebServer != null) {
48 mWebServer.shutdown();
53 private void setServerResponseAndLoad(String response) throws Throwable {
54 String url = mWebServer.setResponse("/hittest.html", response, null);
55 loadUrlSync(mAwContents,
56 mContentsClient.getOnPageFinishedHelper(),
60 private static String fullPageLink(String href, String anchorText) {
61 return CommonResources.makeHtmlPageFrom("", "<a class=\"full_view\" href=\"" +
62 href + "\" " + "onclick=\"return false;\">" + anchorText + "</a>");
65 private void simulateTouchCenterOfWebViewOnUiThread() throws Throwable {
66 runTestOnUiThread(new Runnable() {
69 long eventTime = SystemClock.uptimeMillis();
70 float x = (float) (mTestView.getRight() - mTestView.getLeft()) / 2;
71 float y = (float) (mTestView.getBottom() - mTestView.getTop()) / 2;
72 mAwContents.onTouchEvent(MotionEvent.obtain(
73 eventTime, eventTime, MotionEvent.ACTION_DOWN,
75 mAwContents.onTouchEvent(MotionEvent.obtain(
76 eventTime, eventTime, MotionEvent.ACTION_UP,
82 private void simulateTabDownUpOnUiThread() throws Throwable {
83 runTestOnUiThread(new Runnable() {
86 mAwContents.getContentViewCore().dispatchKeyEvent(
87 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB));
88 mAwContents.getContentViewCore().dispatchKeyEvent(
89 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TAB));
94 private void simulateInput(boolean byTouch) throws Throwable {
95 // Send a touch click event if byTouch is true. Otherwise, send a TAB
96 // key event to change the focused element of the page.
98 simulateTouchCenterOfWebViewOnUiThread();
100 simulateTabDownUpOnUiThread();
104 private static boolean stringEquals(String a, String b) {
105 return a == null ? b == null : a.equals(b);
108 private void pollForHitTestDataOnUiThread(
109 final int expectedType, final String expectedExtra) throws Throwable {
110 pollOnUiThread(new Callable<Boolean>() {
112 public Boolean call() {
113 AwContents.HitTestData data = mAwContents.getLastHitTestResult();
114 return expectedType == data.hitTestResultType &&
115 stringEquals(expectedExtra, data.hitTestResultExtraData);
120 private void pollForHrefAndImageSrcOnUiThread(
121 final String expectedHref,
122 final String expectedAnchorText,
123 final String expectedImageSrc) throws Throwable {
124 pollOnUiThread(new Callable<Boolean>() {
126 public Boolean call() {
127 AwContents.HitTestData data = mAwContents.getLastHitTestResult();
128 return stringEquals(expectedHref, data.href) &&
129 stringEquals(expectedAnchorText, data.anchorText) &&
130 stringEquals(expectedImageSrc, data.imgSrc);
134 Handler dummyHandler = new Handler();
135 final Message focusNodeHrefMsg = dummyHandler.obtainMessage();
136 final Message imageRefMsg = dummyHandler.obtainMessage();
137 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
140 mAwContents.requestFocusNodeHref(focusNodeHrefMsg);
141 mAwContents.requestImageRef(imageRefMsg);
144 assertEquals(expectedHref, focusNodeHrefMsg.getData().getString("url"));
145 assertEquals(expectedAnchorText, focusNodeHrefMsg.getData().getString("title"));
146 assertEquals(expectedImageSrc, focusNodeHrefMsg.getData().getString("src"));
147 assertEquals(expectedImageSrc, imageRefMsg.getData().getString("url"));
150 private void srcAnchorTypeTestBody(boolean byTouch) throws Throwable {
151 String page = fullPageLink(HREF, ANCHOR_TEXT);
152 setServerResponseAndLoad(page);
153 simulateInput(byTouch);
154 pollForHitTestDataOnUiThread(HitTestResult.SRC_ANCHOR_TYPE, HREF);
155 pollForHrefAndImageSrcOnUiThread(HREF, ANCHOR_TEXT, null);
159 @Feature({"AndroidWebView", "WebKitHitTest"})
160 public void testSrcAnchorType() throws Throwable {
161 srcAnchorTypeTestBody(true);
165 @Feature({"AndroidWebView", "WebKitHitTest"})
166 public void testSrcAnchorTypeByFocus() throws Throwable {
167 srcAnchorTypeTestBody(false);
170 private void blankHrefTestBody(boolean byTouch) throws Throwable {
171 String fullPath = mWebServer.getResponseUrl("/hittest.html");
172 String page = fullPageLink("", ANCHOR_TEXT);
173 setServerResponseAndLoad(page);
174 simulateInput(byTouch);
175 pollForHitTestDataOnUiThread(HitTestResult.SRC_ANCHOR_TYPE, fullPath);
176 pollForHrefAndImageSrcOnUiThread(fullPath, ANCHOR_TEXT, null);
180 @Feature({"AndroidWebView", "WebKitHitTest"})
181 public void testSrcAnchorTypeBlankHref() throws Throwable {
182 blankHrefTestBody(true);
186 @Feature({"AndroidWebView", "WebKitHitTest"})
187 public void testSrcAnchorTypeBlankHrefByFocus() throws Throwable {
188 blankHrefTestBody(false);
191 private void srcAnchorTypeRelativeUrlTestBody(boolean byTouch) throws Throwable {
192 String relPath = "/foo.html";
193 String fullPath = mWebServer.getResponseUrl(relPath);
194 String page = fullPageLink(relPath, ANCHOR_TEXT);
195 setServerResponseAndLoad(page);
196 simulateInput(byTouch);
197 pollForHitTestDataOnUiThread(HitTestResult.SRC_ANCHOR_TYPE, fullPath);
198 pollForHrefAndImageSrcOnUiThread(fullPath, ANCHOR_TEXT, null);
202 @Feature({"AndroidWebView", "WebKitHitTest"})
203 public void testSrcAnchorTypeRelativeUrl() throws Throwable {
204 srcAnchorTypeRelativeUrlTestBody(true);
208 @Feature({"AndroidWebView", "WebKitHitTest"})
209 public void testSrcAnchorTypeRelativeUrlByFocus() throws Throwable {
210 srcAnchorTypeRelativeUrlTestBody(false);
213 private void srcEmailTypeTestBody(boolean byTouch) throws Throwable {
214 String email = "foo@bar.com";
215 String prefix = "mailto:";
216 String page = fullPageLink(prefix + email, ANCHOR_TEXT);
217 setServerResponseAndLoad(page);
218 simulateInput(byTouch);
219 pollForHitTestDataOnUiThread(HitTestResult.EMAIL_TYPE, email);
220 pollForHrefAndImageSrcOnUiThread(prefix + email, ANCHOR_TEXT, null);
224 @Feature({"AndroidWebView", "WebKitHitTest"})
225 public void testSrcEmailType() throws Throwable {
226 srcEmailTypeTestBody(true);
230 @Feature({"AndroidWebView", "WebKitHitTest"})
231 public void testSrcEmailTypeByFocus() throws Throwable {
232 srcEmailTypeTestBody(false);
235 private void srcGeoTypeTestBody(boolean byTouch) throws Throwable {
236 String location = "Jilin";
237 String prefix = "geo:0,0?q=";
238 String page = fullPageLink(prefix + location, ANCHOR_TEXT);
239 setServerResponseAndLoad(page);
240 simulateInput(byTouch);
241 pollForHitTestDataOnUiThread(HitTestResult.GEO_TYPE, location);
242 pollForHrefAndImageSrcOnUiThread(prefix + location, ANCHOR_TEXT, null);
246 @Feature({"AndroidWebView", "WebKitHitTest"})
247 public void testSrcGeoType() throws Throwable {
248 srcGeoTypeTestBody(true);
252 @Feature({"AndroidWebView", "WebKitHitTest"})
253 public void testSrcGeoTypeByFocus() throws Throwable {
254 srcGeoTypeTestBody(false);
257 private void srcPhoneTypeTestBody(boolean byTouch) throws Throwable {
258 String phone_num = "%2B1234567890";
259 String expected_phone_num = "+1234567890";
260 String prefix = "tel:";
261 String page = fullPageLink("tel:" + phone_num, ANCHOR_TEXT);
262 setServerResponseAndLoad(page);
263 simulateInput(byTouch);
264 pollForHitTestDataOnUiThread(HitTestResult.PHONE_TYPE, expected_phone_num);
265 pollForHrefAndImageSrcOnUiThread(prefix + phone_num, ANCHOR_TEXT, null);
269 @Feature({"AndroidWebView", "WebKitHitTest"})
270 public void testSrcPhoneType() throws Throwable {
271 srcPhoneTypeTestBody(true);
275 @Feature({"AndroidWebView", "WebKitHitTest"})
276 public void testSrcPhoneTypeByFocus() throws Throwable {
277 srcPhoneTypeTestBody(false);
280 private void srcImgeAnchorTypeTestBody(boolean byTouch) throws Throwable {
281 String fullImageSrc = "http://foo.bar/nonexistent.jpg";
282 String page = CommonResources.makeHtmlPageFrom("", "<a class=\"full_view\" href=\"" +
283 HREF + "\"onclick=\"return false;\"><img class=\"full_view\" src=\"" +
284 fullImageSrc + "\"></a>");
285 setServerResponseAndLoad(page);
286 simulateInput(byTouch);
287 pollForHitTestDataOnUiThread(HitTestResult.SRC_IMAGE_ANCHOR_TYPE, fullImageSrc);
288 pollForHrefAndImageSrcOnUiThread(HREF, null, fullImageSrc);
292 @Feature({"AndroidWebView", "WebKitHitTest"})
293 public void testSrcImgeAnchorType() throws Throwable {
294 srcImgeAnchorTypeTestBody(true);
298 @Feature({"AndroidWebView", "WebKitHitTest"})
299 public void testSrcImgeAnchorTypeByFocus() throws Throwable {
300 srcImgeAnchorTypeTestBody(false);
303 private void srcImgeAnchorTypeRelativeUrlTestBody(boolean byTouch) throws Throwable {
304 String relImageSrc = "/nonexistent.jpg";
305 String fullImageSrc = mWebServer.getResponseUrl(relImageSrc);
306 String relPath = "/foo.html";
307 String fullPath = mWebServer.getResponseUrl(relPath);
308 String page = CommonResources.makeHtmlPageFrom("", "<a class=\"full_view\" href=\"" +
309 relPath + "\"onclick=\"return false;\"><img class=\"full_view\" src=\"" +
310 relImageSrc + "\"></a>");
311 setServerResponseAndLoad(page);
312 simulateInput(byTouch);
313 pollForHitTestDataOnUiThread(HitTestResult.SRC_IMAGE_ANCHOR_TYPE, fullImageSrc);
314 pollForHrefAndImageSrcOnUiThread(fullPath, null, fullImageSrc);
318 @Feature({"AndroidWebView", "WebKitHitTest"})
319 public void testSrcImgeAnchorTypeRelativeUrl() throws Throwable {
320 srcImgeAnchorTypeRelativeUrlTestBody(true);
324 @Feature({"AndroidWebView", "WebKitHitTest"})
325 public void testSrcImgeAnchorTypeRelativeUrlByFocus() throws Throwable {
326 srcImgeAnchorTypeRelativeUrlTestBody(false);
330 @Feature({"AndroidWebView", "WebKitHitTest"})
331 public void testImgeType() throws Throwable {
332 String relImageSrc = "/nonexistent2.jpg";
333 String fullImageSrc = mWebServer.getResponseUrl(relImageSrc);
334 String page = CommonResources.makeHtmlPageFrom("",
335 "<img class=\"full_view\" src=\"" + relImageSrc + "\">");
336 setServerResponseAndLoad(page);
337 simulateTouchCenterOfWebViewOnUiThread();
338 pollForHitTestDataOnUiThread(HitTestResult.IMAGE_TYPE, fullImageSrc);
339 pollForHrefAndImageSrcOnUiThread(null, null, fullImageSrc);
342 private void editTextTypeTestBody(boolean byTouch) throws Throwable {
343 String page = CommonResources.makeHtmlPageFrom("",
344 "<form><input class=\"full_view\" type=\"text\" name=\"test\"></form>");
345 setServerResponseAndLoad(page);
346 simulateInput(byTouch);
347 pollForHitTestDataOnUiThread(HitTestResult.EDIT_TEXT_TYPE, null);
348 pollForHrefAndImageSrcOnUiThread(null, null, null);
352 @Feature({"AndroidWebView", "WebKitHitTest"})
353 public void testEditTextType() throws Throwable {
354 editTextTypeTestBody(true);
358 @Feature({"AndroidWebView", "WebKitHitTest"})
359 public void testEditTextTypeByFocus() throws Throwable {
360 editTextTypeTestBody(false);
363 public void unknownTypeJavascriptSchemeTestBody(boolean byTouch) throws Throwable {
364 // Per documentation, javascript urls are special.
365 String javascript = "javascript:alert('foo');";
366 String page = fullPageLink(javascript, ANCHOR_TEXT);
367 setServerResponseAndLoad(page);
368 simulateInput(byTouch);
369 pollForHrefAndImageSrcOnUiThread(javascript, ANCHOR_TEXT, null);
370 pollForHitTestDataOnUiThread(HitTestResult.UNKNOWN_TYPE, null);
374 @Feature({"AndroidWebView", "WebKitHitTest"})
375 public void testUnknownTypeJavascriptScheme() throws Throwable {
376 unknownTypeJavascriptSchemeTestBody(true);
380 @Feature({"AndroidWebView", "WebKitHitTest"})
381 public void testUnknownTypeJavascriptSchemeByFocus() throws Throwable {
382 unknownTypeJavascriptSchemeTestBody(false);
386 @Feature({"AndroidWebView", "WebKitHitTest"})
387 public void testUnknownTypeUnrecognizedNode() throws Throwable {
388 // Since UNKNOWN_TYPE is the default, hit test another type first for
389 // this test to be valid.
392 final String title = "UNKNOWN_TYPE title";
394 String page = CommonResources.makeHtmlPageFrom(
395 "<title>" + title + "</title>",
396 "<div class=\"full_view\">div text</div>");
397 setServerResponseAndLoad(page);
399 // Wait for the new page to be loaded before trying hit test.
400 pollOnUiThread(new Callable<Boolean>() {
402 public Boolean call() {
403 return mAwContents.getContentViewCore().getTitle().equals(title);
406 simulateTouchCenterOfWebViewOnUiThread();
407 pollForHitTestDataOnUiThread(HitTestResult.UNKNOWN_TYPE, null);
411 @Feature({"AndroidWebView", "WebKitHitTest"})
412 public void testUnfocusedNodeAndTouchRace() throws Throwable {
413 // Test when the touch and focus paths racing with setting different
416 String relImageSrc = "/nonexistent3.jpg";
417 String fullImageSrc = mWebServer.getResponseUrl(relImageSrc);
418 String html = CommonResources.makeHtmlPageFrom(
419 "<meta name=\"viewport\" content=\"width=device-width,height=device-height\" />" +
420 "<style type=\"text/css\">" +
421 ".full_width { width:100%; position:absolute; }" +
423 "<form><input class=\"full_width\" style=\"height:25%;\" " +
424 "type=\"text\" name=\"test\"></form>" +
425 "<img class=\"full_width\" style=\"height:50%;top:25%;\" " +
426 "src=\"" + relImageSrc + "\">");
427 setServerResponseAndLoad(html);
429 // Focus on input element and check the hit test results.
430 simulateTabDownUpOnUiThread();
431 pollForHitTestDataOnUiThread(HitTestResult.EDIT_TEXT_TYPE, null);
432 pollForHrefAndImageSrcOnUiThread(null, null, null);
434 // Touch image. Now the focus based hit test path will try to null out
435 // the results and the touch based path will update with the result of
437 simulateTouchCenterOfWebViewOnUiThread();
439 // Make sure the result of image sticks.
440 for (int i = 0; i < 2; ++i) {
442 pollForHitTestDataOnUiThread(HitTestResult.IMAGE_TYPE, fullImageSrc);
443 pollForHrefAndImageSrcOnUiThread(null, null, fullImageSrc);