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. 4 5package org.chromium.android_webview.test; 6 7import android.os.Handler; 8import android.os.Message; 9import android.test.suitebuilder.annotation.LargeTest; 10import android.test.suitebuilder.annotation.SmallTest; 11import android.view.KeyEvent; 12import android.webkit.WebView.HitTestResult; 13 14import org.chromium.android_webview.AwContents; 15import org.chromium.android_webview.test.util.AwTestTouchUtils; 16import org.chromium.android_webview.test.util.CommonResources; 17import org.chromium.base.ThreadUtils; 18import org.chromium.base.test.util.Feature; 19import org.chromium.net.test.util.TestWebServer; 20 21import java.util.concurrent.Callable; 22 23/** 24 * Test for getHitTestResult, requestFocusNodeHref, and requestImageRef methods 25 */ 26public class WebKitHitTestTest extends AwTestBase { 27 private TestAwContentsClient mContentsClient; 28 private AwTestContainerView mTestView; 29 private AwContents mAwContents; 30 private TestWebServer mWebServer; 31 32 private static final String HREF = "http://foo/"; 33 private static final String ANCHOR_TEXT = "anchor text"; 34 35 @Override 36 public void setUp() throws Exception { 37 super.setUp(); 38 mContentsClient = new TestAwContentsClient(); 39 mTestView = createAwTestContainerViewOnMainSync(mContentsClient); 40 mAwContents = mTestView.getAwContents(); 41 mWebServer = new TestWebServer(false); 42 } 43 44 @Override 45 public void tearDown() throws Exception { 46 if (mWebServer != null) { 47 mWebServer.shutdown(); 48 } 49 super.tearDown(); 50 } 51 52 private void setServerResponseAndLoad(String response) throws Throwable { 53 String url = mWebServer.setResponse("/hittest.html", response, null); 54 loadUrlSync(mAwContents, 55 mContentsClient.getOnPageFinishedHelper(), 56 url); 57 } 58 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>"); 62 } 63 64 private void simulateTabDownUpOnUiThread() throws Throwable { 65 runTestOnUiThread(new Runnable() { 66 @Override 67 public void run() { 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)); 72 } 73 }); 74 } 75 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. 79 if (byTouch) { 80 AwTestTouchUtils.simulateTouchCenterOfView(mTestView); 81 } else { 82 simulateTabDownUpOnUiThread(); 83 } 84 } 85 86 private static boolean stringEquals(String a, String b) { 87 return a == null ? b == null : a.equals(b); 88 } 89 90 private void pollForHitTestDataOnUiThread( 91 final int expectedType, final String expectedExtra) throws Throwable { 92 pollOnUiThread(new Callable<Boolean>() { 93 @Override 94 public Boolean call() { 95 AwContents.HitTestData data = mAwContents.getLastHitTestResult(); 96 return expectedType == data.hitTestResultType && 97 stringEquals(expectedExtra, data.hitTestResultExtraData); 98 } 99 }); 100 } 101 102 private void pollForHrefAndImageSrcOnUiThread( 103 final String expectedHref, 104 final String expectedAnchorText, 105 final String expectedImageSrc) throws Throwable { 106 pollOnUiThread(new Callable<Boolean>() { 107 @Override 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); 113 } 114 }); 115 116 Handler dummyHandler = new Handler(); 117 final Message focusNodeHrefMsg = dummyHandler.obtainMessage(); 118 final Message imageRefMsg = dummyHandler.obtainMessage(); 119 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 120 @Override 121 public void run() { 122 mAwContents.requestFocusNodeHref(focusNodeHrefMsg); 123 mAwContents.requestImageRef(imageRefMsg); 124 } 125 }); 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")); 130 } 131 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); 138 } 139 140 @SmallTest 141 @Feature({"AndroidWebView", "WebKitHitTest"}) 142 public void testSrcAnchorType() throws Throwable { 143 srcAnchorTypeTestBody(true); 144 } 145 146 @SmallTest 147 @Feature({"AndroidWebView", "WebKitHitTest"}) 148 public void testSrcAnchorTypeByFocus() throws Throwable { 149 srcAnchorTypeTestBody(false); 150 } 151 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); 159 } 160 161 @SmallTest 162 @Feature({"AndroidWebView", "WebKitHitTest"}) 163 public void testSrcAnchorTypeBlankHref() throws Throwable { 164 blankHrefTestBody(true); 165 } 166 167 @SmallTest 168 @Feature({"AndroidWebView", "WebKitHitTest"}) 169 public void testSrcAnchorTypeBlankHrefByFocus() throws Throwable { 170 blankHrefTestBody(false); 171 } 172 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); 181 } 182 183 @SmallTest 184 @Feature({"AndroidWebView", "WebKitHitTest"}) 185 public void testSrcAnchorTypeRelativeUrl() throws Throwable { 186 srcAnchorTypeRelativeUrlTestBody(true); 187 } 188 189 @SmallTest 190 @Feature({"AndroidWebView", "WebKitHitTest"}) 191 public void testSrcAnchorTypeRelativeUrlByFocus() throws Throwable { 192 srcAnchorTypeRelativeUrlTestBody(false); 193 } 194 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); 203 } 204 205 @SmallTest 206 @Feature({"AndroidWebView", "WebKitHitTest"}) 207 public void testSrcEmailType() throws Throwable { 208 srcEmailTypeTestBody(true); 209 } 210 211 @SmallTest 212 @Feature({"AndroidWebView", "WebKitHitTest"}) 213 public void testSrcEmailTypeByFocus() throws Throwable { 214 srcEmailTypeTestBody(false); 215 } 216 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); 225 } 226 227 @SmallTest 228 @Feature({"AndroidWebView", "WebKitHitTest"}) 229 public void testSrcGeoType() throws Throwable { 230 srcGeoTypeTestBody(true); 231 } 232 233 @SmallTest 234 @Feature({"AndroidWebView", "WebKitHitTest"}) 235 public void testSrcGeoTypeByFocus() throws Throwable { 236 srcGeoTypeTestBody(false); 237 } 238 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); 248 } 249 250 @SmallTest 251 @Feature({"AndroidWebView", "WebKitHitTest"}) 252 public void testSrcPhoneType() throws Throwable { 253 srcPhoneTypeTestBody(true); 254 } 255 256 @SmallTest 257 @Feature({"AndroidWebView", "WebKitHitTest"}) 258 public void testSrcPhoneTypeByFocus() throws Throwable { 259 srcPhoneTypeTestBody(false); 260 } 261 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); 271 } 272 273 @SmallTest 274 @Feature({"AndroidWebView", "WebKitHitTest"}) 275 public void testSrcImgeAnchorType() throws Throwable { 276 srcImgeAnchorTypeTestBody(true); 277 } 278 279 @SmallTest 280 @Feature({"AndroidWebView", "WebKitHitTest"}) 281 public void testSrcImgeAnchorTypeByFocus() throws Throwable { 282 srcImgeAnchorTypeTestBody(false); 283 } 284 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); 297 } 298 299 @SmallTest 300 @Feature({"AndroidWebView", "WebKitHitTest"}) 301 public void testSrcImgeAnchorTypeRelativeUrl() throws Throwable { 302 srcImgeAnchorTypeRelativeUrlTestBody(true); 303 } 304 305 @SmallTest 306 @Feature({"AndroidWebView", "WebKitHitTest"}) 307 public void testSrcImgeAnchorTypeRelativeUrlByFocus() throws Throwable { 308 srcImgeAnchorTypeRelativeUrlTestBody(false); 309 } 310 311 @SmallTest 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); 322 } 323 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); 331 } 332 333 @SmallTest 334 @Feature({"AndroidWebView", "WebKitHitTest"}) 335 public void testEditTextType() throws Throwable { 336 editTextTypeTestBody(true); 337 } 338 339 @SmallTest 340 @Feature({"AndroidWebView", "WebKitHitTest"}) 341 public void testEditTextTypeByFocus() throws Throwable { 342 editTextTypeTestBody(false); 343 } 344 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); 353 } 354 355 @SmallTest 356 @Feature({"AndroidWebView", "WebKitHitTest"}) 357 public void testUnknownTypeJavascriptScheme() throws Throwable { 358 unknownTypeJavascriptSchemeTestBody(true); 359 } 360 361 @SmallTest 362 @Feature({"AndroidWebView", "WebKitHitTest"}) 363 public void testUnknownTypeJavascriptSchemeByFocus() throws Throwable { 364 unknownTypeJavascriptSchemeTestBody(false); 365 } 366 367 @SmallTest 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. 372 testSrcAnchorType(); 373 374 final String title = "UNKNOWN_TYPE title"; 375 376 String page = CommonResources.makeHtmlPageFrom( 377 "<title>" + title + "</title>", 378 "<div class=\"full_view\">div text</div>"); 379 setServerResponseAndLoad(page); 380 381 // Wait for the new page to be loaded before trying hit test. 382 pollOnUiThread(new Callable<Boolean>() { 383 @Override 384 public Boolean call() { 385 return mAwContents.getTitle().equals(title); 386 } 387 }); 388 AwTestTouchUtils.simulateTouchCenterOfView(mTestView); 389 pollForHitTestDataOnUiThread(HitTestResult.UNKNOWN_TYPE, null); 390 } 391 392 @LargeTest 393 @Feature({"AndroidWebView", "WebKitHitTest"}) 394 public void testUnfocusedNodeAndTouchRace() throws Throwable { 395 // Test when the touch and focus paths racing with setting different 396 // results. 397 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; }" + 404 "</style>", 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); 410 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); 415 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 418 // the image. 419 AwTestTouchUtils.simulateTouchCenterOfView(mTestView); 420 421 // Make sure the result of image sticks. 422 for (int i = 0; i < 2; ++i) { 423 Thread.sleep(500); 424 pollForHitTestDataOnUiThread(HitTestResult.IMAGE_TYPE, fullImageSrc); 425 pollForHrefAndImageSrcOnUiThread(null, null, fullImageSrc); 426 } 427 } 428} 429