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