1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.webkit.cts; 18 19 import android.graphics.Bitmap; 20 import android.os.Message; 21 import android.os.SystemClock; 22 import android.platform.test.annotations.AppModeFull; 23 import android.test.ActivityInstrumentationTestCase2; 24 import android.util.Base64; 25 import android.view.MotionEvent; 26 import android.view.ViewGroup; 27 import android.webkit.ConsoleMessage; 28 import android.webkit.JsPromptResult; 29 import android.webkit.JsResult; 30 import android.webkit.WebIconDatabase; 31 import android.webkit.WebSettings; 32 import android.webkit.WebView; 33 import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient; 34 35 import com.android.compatibility.common.util.NullWebViewUtils; 36 import com.android.compatibility.common.util.PollingCheck; 37 import com.google.common.util.concurrent.SettableFuture; 38 39 import java.util.concurrent.ArrayBlockingQueue; 40 import java.util.concurrent.BlockingQueue; 41 import java.util.concurrent.ExecutionException; 42 import java.util.concurrent.Future; 43 import java.util.concurrent.TimeUnit; 44 45 @AppModeFull 46 public class WebChromeClientTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> { 47 private static final long TEST_TIMEOUT = 5000L; 48 private static final String JAVASCRIPT_UNLOAD = "javascript unload"; 49 private static final String LISTENER_ADDED = "listener added"; 50 private static final String TOUCH_RECEIVED = "touch received"; 51 52 private CtsTestServer mWebServer; 53 private WebIconDatabase mIconDb; 54 private WebViewOnUiThread mOnUiThread; 55 private boolean mBlockWindowCreationSync; 56 private boolean mBlockWindowCreationAsync; 57 WebChromeClientTest()58 public WebChromeClientTest() { 59 super(WebViewCtsActivity.class); 60 } 61 62 @Override setUp()63 protected void setUp() throws Exception { 64 super.setUp(); 65 WebView webview = getActivity().getWebView(); 66 if (webview != null) { 67 mOnUiThread = new WebViewOnUiThread(webview); 68 } 69 mWebServer = new CtsTestServer(getActivity()); 70 } 71 72 @Override tearDown()73 protected void tearDown() throws Exception { 74 if (mOnUiThread != null) { 75 mOnUiThread.cleanUp(); 76 } 77 if (mWebServer != null) { 78 mWebServer.shutdown(); 79 } 80 if (mIconDb != null) { 81 mIconDb.removeAllIcons(); 82 mIconDb.close(); 83 } 84 super.tearDown(); 85 } 86 testOnProgressChanged()87 public void testOnProgressChanged() { 88 if (!NullWebViewUtils.isWebViewAvailable()) { 89 return; 90 } 91 final MockWebChromeClient webChromeClient = new MockWebChromeClient(); 92 mOnUiThread.setWebChromeClient(webChromeClient); 93 94 assertFalse(webChromeClient.hadOnProgressChanged()); 95 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 96 mOnUiThread.loadUrlAndWaitForCompletion(url); 97 98 new PollingCheck(TEST_TIMEOUT) { 99 @Override 100 protected boolean check() { 101 return webChromeClient.hadOnProgressChanged(); 102 } 103 }.run(); 104 } 105 testOnReceivedTitle()106 public void testOnReceivedTitle() throws Exception { 107 if (!NullWebViewUtils.isWebViewAvailable()) { 108 return; 109 } 110 final MockWebChromeClient webChromeClient = new MockWebChromeClient(); 111 mOnUiThread.setWebChromeClient(webChromeClient); 112 113 assertFalse(webChromeClient.hadOnReceivedTitle()); 114 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 115 mOnUiThread.loadUrlAndWaitForCompletion(url); 116 117 new PollingCheck(TEST_TIMEOUT) { 118 @Override 119 protected boolean check() { 120 return webChromeClient.hadOnReceivedTitle(); 121 } 122 }.run(); 123 assertTrue(webChromeClient.hadOnReceivedTitle()); 124 assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, webChromeClient.getPageTitle()); 125 } 126 testOnReceivedIcon()127 public void testOnReceivedIcon() throws Throwable { 128 if (!NullWebViewUtils.isWebViewAvailable()) { 129 return; 130 } 131 final MockWebChromeClient webChromeClient = new MockWebChromeClient(); 132 mOnUiThread.setWebChromeClient(webChromeClient); 133 134 WebkitUtils.onMainThreadSync(() -> { 135 // getInstance must run on the UI thread 136 mIconDb = WebIconDatabase.getInstance(); 137 String dbPath = getActivity().getFilesDir().toString() + "/icons"; 138 mIconDb.open(dbPath); 139 }); 140 getInstrumentation().waitForIdleSync(); 141 Thread.sleep(100); // Wait for open to be received on the icon db thread. 142 143 assertFalse(webChromeClient.hadOnReceivedIcon()); 144 assertNull(mOnUiThread.getFavicon()); 145 146 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 147 mOnUiThread.loadUrlAndWaitForCompletion(url); 148 149 new PollingCheck(TEST_TIMEOUT) { 150 @Override 151 protected boolean check() { 152 return webChromeClient.hadOnReceivedIcon(); 153 } 154 }.run(); 155 assertNotNull(mOnUiThread.getFavicon()); 156 } 157 runWindowTest(boolean expectWindowClose)158 public void runWindowTest(boolean expectWindowClose) throws Exception { 159 final MockWebChromeClient webChromeClient = new MockWebChromeClient(); 160 mOnUiThread.setWebChromeClient(webChromeClient); 161 162 final WebSettings settings = mOnUiThread.getSettings(); 163 settings.setJavaScriptEnabled(true); 164 settings.setJavaScriptCanOpenWindowsAutomatically(true); 165 settings.setSupportMultipleWindows(true); 166 167 assertFalse(webChromeClient.hadOnCreateWindow()); 168 169 // Load a page that opens a child window and sets a timeout after which the child 170 // will be closed. 171 mOnUiThread.loadUrlAndWaitForCompletion(mWebServer. 172 getAssetUrl(TestHtmlConstants.JS_WINDOW_URL)); 173 174 new PollingCheck(TEST_TIMEOUT) { 175 @Override 176 protected boolean check() { 177 return webChromeClient.hadOnCreateWindow(); 178 } 179 }.run(); 180 181 if (expectWindowClose) { 182 new PollingCheck(TEST_TIMEOUT) { 183 @Override 184 protected boolean check() { 185 return webChromeClient.hadOnCloseWindow(); 186 } 187 }.run(); 188 } else { 189 assertFalse(webChromeClient.hadOnCloseWindow()); 190 } 191 } testWindows()192 public void testWindows() throws Exception { 193 if (!NullWebViewUtils.isWebViewAvailable()) { 194 return; 195 } 196 runWindowTest(true); 197 } 198 testBlockWindowsSync()199 public void testBlockWindowsSync() throws Exception { 200 if (!NullWebViewUtils.isWebViewAvailable()) { 201 return; 202 } 203 mBlockWindowCreationSync = true; 204 runWindowTest(false); 205 } 206 testBlockWindowsAsync()207 public void testBlockWindowsAsync() throws Exception { 208 if (!NullWebViewUtils.isWebViewAvailable()) { 209 return; 210 } 211 mBlockWindowCreationAsync = true; 212 runWindowTest(false); 213 } 214 215 // Note that test is still a little flaky. See b/119468441. testOnJsBeforeUnloadIsCalled()216 public void testOnJsBeforeUnloadIsCalled() throws Exception { 217 if (!NullWebViewUtils.isWebViewAvailable()) { 218 return; 219 } 220 221 final WebSettings settings = mOnUiThread.getSettings(); 222 settings.setJavaScriptEnabled(true); 223 settings.setJavaScriptCanOpenWindowsAutomatically(true); 224 225 final BlockingQueue<String> pageTitleQueue = new ArrayBlockingQueue<>(3); 226 final SettableFuture<Void> onJsBeforeUnloadFuture = SettableFuture.create(); 227 final MockWebChromeClient webChromeClientWaitTitle = new MockWebChromeClient() { 228 @Override 229 public void onReceivedTitle(WebView view, String title) { 230 super.onReceivedTitle(view, title); 231 pageTitleQueue.add(title); 232 } 233 234 @Override 235 public boolean onJsBeforeUnload( 236 WebView view, String url, String message, JsResult result) { 237 boolean ret = super.onJsBeforeUnload(view, url, message, result); 238 onJsBeforeUnloadFuture.set(null); 239 return ret; 240 } 241 }; 242 mOnUiThread.setWebChromeClient(webChromeClientWaitTitle); 243 244 mOnUiThread.loadUrlAndWaitForCompletion( 245 mWebServer.getAssetUrl(TestHtmlConstants.JS_UNLOAD_URL)); 246 247 assertEquals(JAVASCRIPT_UNLOAD, WebkitUtils.waitForNextQueueElement(pageTitleQueue)); 248 assertEquals(LISTENER_ADDED, WebkitUtils.waitForNextQueueElement(pageTitleQueue)); 249 // Send a user gesture, required for unload to execute since WebView version 60. 250 tapWebView(); 251 assertEquals(TOUCH_RECEIVED, WebkitUtils.waitForNextQueueElement(pageTitleQueue)); 252 253 // unload should trigger when we try to navigate away 254 mOnUiThread.loadUrlAndWaitForCompletion( 255 mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL)); 256 257 WebkitUtils.waitForFuture(onJsBeforeUnloadFuture); 258 } 259 testOnJsAlert()260 public void testOnJsAlert() throws Exception { 261 if (!NullWebViewUtils.isWebViewAvailable()) { 262 return; 263 } 264 final MockWebChromeClient webChromeClient = new MockWebChromeClient(); 265 mOnUiThread.setWebChromeClient(webChromeClient); 266 267 final WebSettings settings = mOnUiThread.getSettings(); 268 settings.setJavaScriptEnabled(true); 269 settings.setJavaScriptCanOpenWindowsAutomatically(true); 270 271 assertFalse(webChromeClient.hadOnJsAlert()); 272 273 String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_ALERT_URL); 274 mOnUiThread.loadUrlAndWaitForCompletion(url); 275 276 new PollingCheck(TEST_TIMEOUT) { 277 @Override 278 protected boolean check() { 279 return webChromeClient.hadOnJsAlert(); 280 } 281 }.run(); 282 assertEquals(webChromeClient.getMessage(), "testOnJsAlert"); 283 } 284 testOnJsConfirm()285 public void testOnJsConfirm() throws Exception { 286 if (!NullWebViewUtils.isWebViewAvailable()) { 287 return; 288 } 289 final MockWebChromeClient webChromeClient = new MockWebChromeClient(); 290 mOnUiThread.setWebChromeClient(webChromeClient); 291 292 final WebSettings settings = mOnUiThread.getSettings(); 293 settings.setJavaScriptEnabled(true); 294 settings.setJavaScriptCanOpenWindowsAutomatically(true); 295 296 assertFalse(webChromeClient.hadOnJsConfirm()); 297 298 String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_CONFIRM_URL); 299 mOnUiThread.loadUrlAndWaitForCompletion(url); 300 301 new PollingCheck(TEST_TIMEOUT) { 302 @Override 303 protected boolean check() { 304 return webChromeClient.hadOnJsConfirm(); 305 } 306 }.run(); 307 assertEquals(webChromeClient.getMessage(), "testOnJsConfirm"); 308 } 309 testOnJsPrompt()310 public void testOnJsPrompt() throws Exception { 311 if (!NullWebViewUtils.isWebViewAvailable()) { 312 return; 313 } 314 final MockWebChromeClient webChromeClient = new MockWebChromeClient(); 315 mOnUiThread.setWebChromeClient(webChromeClient); 316 317 final WebSettings settings = mOnUiThread.getSettings(); 318 settings.setJavaScriptEnabled(true); 319 settings.setJavaScriptCanOpenWindowsAutomatically(true); 320 321 assertFalse(webChromeClient.hadOnJsPrompt()); 322 323 final String promptResult = "CTS"; 324 webChromeClient.setPromptResult(promptResult); 325 String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_PROMPT_URL); 326 mOnUiThread.loadUrlAndWaitForCompletion(url); 327 328 new PollingCheck(TEST_TIMEOUT) { 329 @Override 330 protected boolean check() { 331 return webChromeClient.hadOnJsPrompt(); 332 } 333 }.run(); 334 // the result returned by the client gets set as the page title 335 new PollingCheck(TEST_TIMEOUT) { 336 @Override 337 protected boolean check() { 338 return mOnUiThread.getTitle().equals(promptResult); 339 } 340 }.run(); 341 assertEquals(webChromeClient.getMessage(), "testOnJsPrompt"); 342 } 343 testOnConsoleMessage()344 public void testOnConsoleMessage() throws Exception { 345 if (!NullWebViewUtils.isWebViewAvailable()) { 346 return; 347 } 348 int numConsoleMessages = 4; 349 final BlockingQueue<ConsoleMessage> consoleMessageQueue = 350 new ArrayBlockingQueue<>(numConsoleMessages); 351 final MockWebChromeClient webChromeClient = new MockWebChromeClient() { 352 @Override 353 public boolean onConsoleMessage(ConsoleMessage message) { 354 consoleMessageQueue.add(message); 355 // return false for default handling; i.e. printing the message. 356 return false; 357 } 358 }; 359 mOnUiThread.setWebChromeClient(webChromeClient); 360 361 mOnUiThread.getSettings().setJavaScriptEnabled(true); 362 // Note: we assert line numbers, which are relative to the line in the HTML file. So, "\n" 363 // is significant in this test, and make sure to update consoleLineNumberOffset when 364 // editing the HTML. 365 final int consoleLineNumberOffset = 3; 366 final String unencodedHtml = "<html>\n" 367 + "<script>\n" 368 + " console.log('message0');\n" 369 + " console.warn('message1');\n" 370 + " console.error('message2');\n" 371 + " console.info('message3');\n" 372 + "</script>\n" 373 + "</html>\n"; 374 final String mimeType = null; 375 final String encoding = "base64"; 376 String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING); 377 mOnUiThread.loadDataAndWaitForCompletion(encodedHtml, mimeType, encoding); 378 379 // Expected message levels correspond to the order of the console messages defined above. 380 ConsoleMessage.MessageLevel[] expectedMessageLevels = { 381 ConsoleMessage.MessageLevel.LOG, 382 ConsoleMessage.MessageLevel.WARNING, 383 ConsoleMessage.MessageLevel.ERROR, 384 ConsoleMessage.MessageLevel.LOG, 385 }; 386 for (int k = 0; k < numConsoleMessages; k++) { 387 final ConsoleMessage consoleMessage = 388 WebkitUtils.waitForNextQueueElement(consoleMessageQueue); 389 final ConsoleMessage.MessageLevel expectedMessageLevel = expectedMessageLevels[k]; 390 assertEquals("message " + k + " had wrong level", 391 expectedMessageLevel, 392 consoleMessage.messageLevel()); 393 final String expectedMessage = "message" + k; 394 assertEquals("message " + k + " had wrong message", 395 expectedMessage, 396 consoleMessage.message()); 397 final int expectedLineNumber = k + consoleLineNumberOffset; 398 assertEquals("message " + k + " had wrong line number", 399 expectedLineNumber, 400 consoleMessage.lineNumber()); 401 } 402 } 403 404 /** 405 * Taps in the the center of a webview. 406 */ tapWebView()407 private void tapWebView() { 408 int[] location = mOnUiThread.getLocationOnScreen(); 409 int middleX = location[0] + mOnUiThread.getWebView().getWidth() / 2; 410 int middleY = location[1] + mOnUiThread.getWebView().getHeight() / 2; 411 412 long timeDown = SystemClock.uptimeMillis(); 413 getInstrumentation().sendPointerSync( 414 MotionEvent.obtain(timeDown, timeDown, MotionEvent.ACTION_DOWN, 415 middleX, middleY, 0)); 416 417 long timeUp = SystemClock.uptimeMillis(); 418 getInstrumentation().sendPointerSync( 419 MotionEvent.obtain(timeUp, timeUp, MotionEvent.ACTION_UP, 420 middleX, middleY, 0)); 421 422 // Wait for the system to process all events in the queue 423 getInstrumentation().waitForIdleSync(); 424 } 425 426 private class MockWebChromeClient extends WaitForProgressClient { 427 private boolean mHadOnProgressChanged; 428 private boolean mHadOnReceivedTitle; 429 private String mPageTitle; 430 private boolean mHadOnJsAlert; 431 private boolean mHadOnJsConfirm; 432 private boolean mHadOnJsPrompt; 433 private boolean mHadOnJsBeforeUnload; 434 private String mMessage; 435 private String mPromptResult; 436 private boolean mHadOnCloseWindow; 437 private boolean mHadOnCreateWindow; 438 private boolean mHadOnRequestFocus; 439 private boolean mHadOnReceivedIcon; 440 MockWebChromeClient()441 public MockWebChromeClient() { 442 super(mOnUiThread); 443 } 444 setPromptResult(String promptResult)445 public void setPromptResult(String promptResult) { 446 mPromptResult = promptResult; 447 } 448 hadOnProgressChanged()449 public boolean hadOnProgressChanged() { 450 return mHadOnProgressChanged; 451 } 452 hadOnReceivedTitle()453 public boolean hadOnReceivedTitle() { 454 return mHadOnReceivedTitle; 455 } 456 getPageTitle()457 public String getPageTitle() { 458 return mPageTitle; 459 } 460 hadOnJsAlert()461 public boolean hadOnJsAlert() { 462 return mHadOnJsAlert; 463 } 464 hadOnJsConfirm()465 public boolean hadOnJsConfirm() { 466 return mHadOnJsConfirm; 467 } 468 hadOnJsPrompt()469 public boolean hadOnJsPrompt() { 470 return mHadOnJsPrompt; 471 } 472 hadOnJsBeforeUnload()473 public boolean hadOnJsBeforeUnload() { 474 return mHadOnJsBeforeUnload; 475 } 476 hadOnCreateWindow()477 public boolean hadOnCreateWindow() { 478 return mHadOnCreateWindow; 479 } 480 hadOnCloseWindow()481 public boolean hadOnCloseWindow() { 482 return mHadOnCloseWindow; 483 } 484 hadOnRequestFocus()485 public boolean hadOnRequestFocus() { 486 return mHadOnRequestFocus; 487 } 488 hadOnReceivedIcon()489 public boolean hadOnReceivedIcon() { 490 return mHadOnReceivedIcon; 491 } 492 getMessage()493 public String getMessage() { 494 return mMessage; 495 } 496 497 @Override onProgressChanged(WebView view, int newProgress)498 public void onProgressChanged(WebView view, int newProgress) { 499 super.onProgressChanged(view, newProgress); 500 mHadOnProgressChanged = true; 501 } 502 503 @Override onReceivedTitle(WebView view, String title)504 public void onReceivedTitle(WebView view, String title) { 505 super.onReceivedTitle(view, title); 506 mPageTitle = title; 507 mHadOnReceivedTitle = true; 508 } 509 510 @Override onJsAlert(WebView view, String url, String message, JsResult result)511 public boolean onJsAlert(WebView view, String url, String message, JsResult result) { 512 super.onJsAlert(view, url, message, result); 513 mHadOnJsAlert = true; 514 mMessage = message; 515 result.confirm(); 516 return true; 517 } 518 519 @Override onJsConfirm(WebView view, String url, String message, JsResult result)520 public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { 521 super.onJsConfirm(view, url, message, result); 522 mHadOnJsConfirm = true; 523 mMessage = message; 524 result.confirm(); 525 return true; 526 } 527 528 @Override onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result)529 public boolean onJsPrompt(WebView view, String url, String message, 530 String defaultValue, JsPromptResult result) { 531 super.onJsPrompt(view, url, message, defaultValue, result); 532 mHadOnJsPrompt = true; 533 mMessage = message; 534 result.confirm(mPromptResult); 535 return true; 536 } 537 538 @Override onJsBeforeUnload(WebView view, String url, String message, JsResult result)539 public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) { 540 super.onJsBeforeUnload(view, url, message, result); 541 mHadOnJsBeforeUnload = true; 542 mMessage = message; 543 result.confirm(); 544 return true; 545 } 546 547 @Override onCloseWindow(WebView window)548 public void onCloseWindow(WebView window) { 549 super.onCloseWindow(window); 550 mHadOnCloseWindow = true; 551 } 552 553 @Override onCreateWindow(WebView view, boolean dialog, boolean userGesture, Message resultMsg)554 public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, 555 Message resultMsg) { 556 mHadOnCreateWindow = true; 557 if (mBlockWindowCreationSync) { 558 return false; 559 } 560 WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; 561 if (mBlockWindowCreationAsync) { 562 transport.setWebView(null); 563 } else { 564 WebView childView = new WebView(getActivity()); 565 final WebSettings settings = childView.getSettings(); 566 settings.setJavaScriptEnabled(true); 567 childView.setWebChromeClient(this); 568 transport.setWebView(childView); 569 getActivity().addContentView(childView, new ViewGroup.LayoutParams( 570 ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 571 } 572 resultMsg.sendToTarget(); 573 return true; 574 } 575 576 @Override onRequestFocus(WebView view)577 public void onRequestFocus(WebView view) { 578 mHadOnRequestFocus = true; 579 } 580 581 @Override onReceivedIcon(WebView view, Bitmap icon)582 public void onReceivedIcon(WebView view, Bitmap icon) { 583 mHadOnReceivedIcon = true; 584 } 585 } 586 } 587