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