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