blob: 85ffa00d3793060cc080caf782eb70918a5052cb [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.webkit.cts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.graphics.Bitmap;
import android.os.Message;
import android.platform.test.annotations.AppModeFull;
import android.util.Base64;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebIconDatabase;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import com.android.compatibility.common.util.NullWebViewUtils;
import com.android.compatibility.common.util.PollingCheck;
import com.google.common.util.concurrent.SettableFuture;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@AppModeFull
@MediumTest
@RunWith(AndroidJUnit4.class)
public class WebChromeClientTest extends SharedWebViewTest{
private static final String JAVASCRIPT_UNLOAD = "javascript unload";
private static final String LISTENER_ADDED = "listener added";
private static final String TOUCH_RECEIVED = "touch received";
@Rule
public ActivityScenarioRule mActivityScenarioRule =
new ActivityScenarioRule(WebViewCtsActivity.class);
private SharedSdkWebServer mWebServer;
private WebIconDatabase mIconDb;
private WebViewOnUiThread mOnUiThread;
private boolean mBlockWindowCreationSync;
private boolean mBlockWindowCreationAsync;
@Before
public void setUp() throws Exception {
WebView webview = getTestEnvironment().getWebView();
if (webview != null) {
mOnUiThread = new WebViewOnUiThread(webview);
}
mWebServer = getTestEnvironment().getSetupWebServer(SslMode.INSECURE);
}
@After
public void tearDown() throws Exception {
if (mOnUiThread != null) {
mOnUiThread.cleanUp();
}
if (mWebServer != null) {
mWebServer.shutdown();
}
if (mIconDb != null) {
mIconDb.removeAllIcons();
mIconDb.close();
}
}
@Override
protected SharedWebViewTestEnvironment createTestEnvironment() {
Assume.assumeTrue("WebView is not available", NullWebViewUtils.isWebViewAvailable());
SharedWebViewTestEnvironment.Builder builder = new SharedWebViewTestEnvironment.Builder();
mActivityScenarioRule
.getScenario()
.onActivity(
activity -> {
WebView webView = ((WebViewCtsActivity) activity).getWebView();
builder.setHostAppInvoker(
SharedWebViewTestEnvironment.createHostAppInvoker(
activity))
.setContext(activity)
.setWebView(webView)
.setRootLayout(((WebViewCtsActivity) activity).getRootLayout());
});
SharedWebViewTestEnvironment environment = builder.build();
return environment;
}
@Test
public void testOnProgressChanged() {
final MockWebChromeClient webChromeClient = new MockWebChromeClient();
mOnUiThread.setWebChromeClient(webChromeClient);
assertFalse(webChromeClient.hadOnProgressChanged());
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnProgressChanged();
}
}.run();
}
@Test
public void testOnReceivedTitle() throws Exception {
final MockWebChromeClient webChromeClient = new MockWebChromeClient();
mOnUiThread.setWebChromeClient(webChromeClient);
assertFalse(webChromeClient.hadOnReceivedTitle());
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnReceivedTitle();
}
}.run();
assertTrue(webChromeClient.hadOnReceivedTitle());
assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, webChromeClient.getPageTitle());
}
@Test
public void testOnReceivedIcon() throws Throwable {
final MockWebChromeClient webChromeClient = new MockWebChromeClient();
mOnUiThread.setWebChromeClient(webChromeClient);
WebkitUtils.onMainThreadSync(() -> {
// getInstance must run on the UI thread
mIconDb = WebIconDatabase.getInstance();
String dbPath = getTestEnvironment().getContext().getFilesDir().toString() + "/icons";
mIconDb.open(dbPath);
});
getTestEnvironment().waitForIdleSync();
Thread.sleep(100); // Wait for open to be received on the icon db thread.
assertFalse(webChromeClient.hadOnReceivedIcon());
assertNull(mOnUiThread.getFavicon());
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnReceivedIcon();
}
}.run();
assertNotNull(mOnUiThread.getFavicon());
}
public void runWindowTest(boolean expectWindowClose) throws Exception {
final MockWebChromeClient webChromeClient = new MockWebChromeClient();
mOnUiThread.setWebChromeClient(webChromeClient);
final WebSettings settings = mOnUiThread.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setSupportMultipleWindows(true);
assertFalse(webChromeClient.hadOnCreateWindow());
// Load a page that opens a child window and sets a timeout after which the child
// will be closed.
mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.
getAssetUrl(TestHtmlConstants.JS_WINDOW_URL));
new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnCreateWindow();
}
}.run();
if (expectWindowClose) {
new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnCloseWindow();
}
}.run();
} else {
assertFalse(webChromeClient.hadOnCloseWindow());
}
}
@Test
public void testWindows() throws Exception {
runWindowTest(true);
}
@Test
public void testBlockWindowsSync() throws Exception {
mBlockWindowCreationSync = true;
runWindowTest(false);
}
@Test
public void testBlockWindowsAsync() throws Exception {
mBlockWindowCreationAsync = true;
runWindowTest(false);
}
// Note that test is still a little flaky. See b/119468441.
@Test
public void testOnJsBeforeUnloadIsCalled() throws Exception {
final WebSettings settings = mOnUiThread.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
final BlockingQueue<String> pageTitleQueue = new ArrayBlockingQueue<>(3);
final SettableFuture<Void> onJsBeforeUnloadFuture = SettableFuture.create();
final MockWebChromeClient webChromeClientWaitTitle = new MockWebChromeClient() {
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
pageTitleQueue.add(title);
}
@Override
public boolean onJsBeforeUnload(
WebView view, String url, String message, JsResult result) {
boolean ret = super.onJsBeforeUnload(view, url, message, result);
onJsBeforeUnloadFuture.set(null);
return ret;
}
};
mOnUiThread.setWebChromeClient(webChromeClientWaitTitle);
mOnUiThread.loadUrlAndWaitForCompletion(
mWebServer.getAssetUrl(TestHtmlConstants.JS_UNLOAD_URL));
assertEquals(JAVASCRIPT_UNLOAD, WebkitUtils.waitForNextQueueElement(pageTitleQueue));
assertEquals(LISTENER_ADDED, WebkitUtils.waitForNextQueueElement(pageTitleQueue));
// Send a user gesture, required for unload to execute since WebView version 60.
tapWebView();
assertEquals(TOUCH_RECEIVED, WebkitUtils.waitForNextQueueElement(pageTitleQueue));
// unload should trigger when we try to navigate away
mOnUiThread.loadUrlAndWaitForCompletion(
mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
WebkitUtils.waitForFuture(onJsBeforeUnloadFuture);
}
@Test
public void testOnJsAlert() throws Exception {
final MockWebChromeClient webChromeClient = new MockWebChromeClient();
mOnUiThread.setWebChromeClient(webChromeClient);
final WebSettings settings = mOnUiThread.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
assertFalse(webChromeClient.hadOnJsAlert());
String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_ALERT_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnJsAlert();
}
}.run();
assertEquals(webChromeClient.getMessage(), "testOnJsAlert");
}
@Test
public void testOnJsConfirm() throws Exception {
final MockWebChromeClient webChromeClient = new MockWebChromeClient();
mOnUiThread.setWebChromeClient(webChromeClient);
final WebSettings settings = mOnUiThread.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
assertFalse(webChromeClient.hadOnJsConfirm());
String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_CONFIRM_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnJsConfirm();
}
}.run();
assertEquals(webChromeClient.getMessage(), "testOnJsConfirm");
}
@Test
public void testOnJsPrompt() throws Exception {
final MockWebChromeClient webChromeClient = new MockWebChromeClient();
mOnUiThread.setWebChromeClient(webChromeClient);
final WebSettings settings = mOnUiThread.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
assertFalse(webChromeClient.hadOnJsPrompt());
final String promptResult = "CTS";
webChromeClient.setPromptResult(promptResult);
String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_PROMPT_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return webChromeClient.hadOnJsPrompt();
}
}.run();
// the result returned by the client gets set as the page title
new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
@Override
protected boolean check() {
return mOnUiThread.getTitle().equals(promptResult);
}
}.run();
assertEquals(webChromeClient.getMessage(), "testOnJsPrompt");
}
@Test
public void testOnConsoleMessage() throws Exception {
int numConsoleMessages = 4;
final BlockingQueue<ConsoleMessage> consoleMessageQueue =
new ArrayBlockingQueue<>(numConsoleMessages);
final MockWebChromeClient webChromeClient = new MockWebChromeClient() {
@Override
public boolean onConsoleMessage(ConsoleMessage message) {
consoleMessageQueue.add(message);
// return false for default handling; i.e. printing the message.
return false;
}
};
mOnUiThread.setWebChromeClient(webChromeClient);
mOnUiThread.getSettings().setJavaScriptEnabled(true);
// Note: we assert line numbers, which are relative to the line in the HTML file. So, "\n"
// is significant in this test, and make sure to update consoleLineNumberOffset when
// editing the HTML.
final int consoleLineNumberOffset = 3;
final String unencodedHtml = "<html>\n"
+ "<script>\n"
+ " console.log('message0');\n"
+ " console.warn('message1');\n"
+ " console.error('message2');\n"
+ " console.info('message3');\n"
+ "</script>\n"
+ "</html>\n";
final String mimeType = null;
final String encoding = "base64";
String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING);
mOnUiThread.loadDataAndWaitForCompletion(encodedHtml, mimeType, encoding);
// Expected message levels correspond to the order of the console messages defined above.
ConsoleMessage.MessageLevel[] expectedMessageLevels = {
ConsoleMessage.MessageLevel.LOG,
ConsoleMessage.MessageLevel.WARNING,
ConsoleMessage.MessageLevel.ERROR,
ConsoleMessage.MessageLevel.LOG,
};
for (int k = 0; k < numConsoleMessages; k++) {
final ConsoleMessage consoleMessage =
WebkitUtils.waitForNextQueueElement(consoleMessageQueue);
final ConsoleMessage.MessageLevel expectedMessageLevel = expectedMessageLevels[k];
assertEquals("message " + k + " had wrong level",
expectedMessageLevel,
consoleMessage.messageLevel());
final String expectedMessage = "message" + k;
assertEquals("message " + k + " had wrong message",
expectedMessage,
consoleMessage.message());
final int expectedLineNumber = k + consoleLineNumberOffset;
assertEquals("message " + k + " had wrong line number",
expectedLineNumber,
consoleMessage.lineNumber());
}
}
/**
* Taps in the the center of a webview.
*/
private void tapWebView() {
int[] location = mOnUiThread.getLocationOnScreen();
int middleX = location[0] + mOnUiThread.getWebView().getWidth() / 2;
int middleY = location[1] + mOnUiThread.getWebView().getHeight() / 2;
getTestEnvironment().sendTapSync(middleX, middleY);
// Wait for the system to process all events in the queue
getTestEnvironment().waitForIdleSync();
}
private class MockWebChromeClient extends WaitForProgressClient {
private boolean mHadOnProgressChanged;
private boolean mHadOnReceivedTitle;
private String mPageTitle;
private boolean mHadOnJsAlert;
private boolean mHadOnJsConfirm;
private boolean mHadOnJsPrompt;
private boolean mHadOnJsBeforeUnload;
private String mMessage;
private String mPromptResult;
private boolean mHadOnCloseWindow;
private boolean mHadOnCreateWindow;
private boolean mHadOnRequestFocus;
private boolean mHadOnReceivedIcon;
private WebView mChildWebView;
public MockWebChromeClient() {
super(mOnUiThread);
}
public void setPromptResult(String promptResult) {
mPromptResult = promptResult;
}
public boolean hadOnProgressChanged() {
return mHadOnProgressChanged;
}
public boolean hadOnReceivedTitle() {
return mHadOnReceivedTitle;
}
public String getPageTitle() {
return mPageTitle;
}
public boolean hadOnJsAlert() {
return mHadOnJsAlert;
}
public boolean hadOnJsConfirm() {
return mHadOnJsConfirm;
}
public boolean hadOnJsPrompt() {
return mHadOnJsPrompt;
}
public boolean hadOnJsBeforeUnload() {
return mHadOnJsBeforeUnload;
}
public boolean hadOnCreateWindow() {
return mHadOnCreateWindow;
}
public boolean hadOnCloseWindow() {
return mHadOnCloseWindow;
}
public boolean hadOnRequestFocus() {
return mHadOnRequestFocus;
}
public boolean hadOnReceivedIcon() {
return mHadOnReceivedIcon;
}
public String getMessage() {
return mMessage;
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
mHadOnProgressChanged = true;
}
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
mPageTitle = title;
mHadOnReceivedTitle = true;
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
super.onJsAlert(view, url, message, result);
mHadOnJsAlert = true;
mMessage = message;
result.confirm();
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
super.onJsConfirm(view, url, message, result);
mHadOnJsConfirm = true;
mMessage = message;
result.confirm();
return true;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
super.onJsPrompt(view, url, message, defaultValue, result);
mHadOnJsPrompt = true;
mMessage = message;
result.confirm(mPromptResult);
return true;
}
@Override
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
super.onJsBeforeUnload(view, url, message, result);
mHadOnJsBeforeUnload = true;
mMessage = message;
result.confirm();
return true;
}
@Override
public void onCloseWindow(WebView window) {
super.onCloseWindow(window);
mHadOnCloseWindow = true;
if (mChildWebView != null) {
ViewParent parent = mChildWebView.getParent();
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(mChildWebView);
}
mChildWebView.destroy();
}
}
@Override
public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture,
Message resultMsg) {
mHadOnCreateWindow = true;
if (mBlockWindowCreationSync) {
return false;
}
WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
if (mBlockWindowCreationAsync) {
transport.setWebView(null);
} else {
mChildWebView = new WebView(getTestEnvironment().getContext());
final WebSettings settings = mChildWebView.getSettings();
settings.setJavaScriptEnabled(true);
mChildWebView.setWebChromeClient(this);
transport.setWebView(mChildWebView);
getTestEnvironment().addContentView(mChildWebView, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
resultMsg.sendToTarget();
return true;
}
@Override
public void onRequestFocus(WebView view) {
mHadOnRequestFocus = true;
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
mHadOnReceivedIcon = true;
}
}
}