/* MonkeyTalk - a cross-platform functional testing tool
Copyright (C) 2012 Gorilla Logic, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package com.gorillalogic.fonemonkey.web;
import java.lang.reflect.Method;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions.Callback;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebStorage.QuotaUpdater;
import android.webkit.WebView;
import android.widget.EditText;
import com.gorillalogic.fonemonkey.FunctionalityAdder;
import com.gorillalogic.fonemonkey.automators.WebViewAutomator;
/*
* Hack alert:
* WebView.addJavaScriptInterface doesn't work if called after a page has already been loaded.
* AndroidWebDriver relies on addJavaScriptInterface to bridge between JS and Java.
* Because we might not find out about a WebView until after its initial page has been loaded,
* we use this mechanism instead whereby we intercept messages being written to the Web Console
* via Android's built-in JavaScript console() function.
*/
public class WebChromeClientWrapper extends WebChromeClient {
private WebChromeClient client;
private WebViewAutomator auto;
// private WebViewRecorder recorder;
public WebChromeClientWrapper(WebViewAutomator auto) {
WebView view = auto.getWebView();
this.auto = auto;
// recorder = new WebViewRecorder(view);
Method meth;
try {
meth = WebView.class.getDeclaredMethod("getWebChromeClient", (Class<?>[]) null);
this.client = (WebChromeClient) meth.invoke(view, (Object[]) null);
if (client == null) {
client = new WebChromeClient();
}
} catch (Exception e) {
// webviewclient moved inside WebProvider in 4.1 JellyBean
try {
meth = WebView.class.getDeclaredMethod("getWebViewProvider", (Class<?>[]) null);
Object provider = meth.invoke(view, (Object[]) null);
if (provider != null) {
meth = provider.getClass().getMethod("getWebChromeClient", (Class<?>[]) null);
this.client = (WebChromeClient) meth.invoke(provider, (Object[]) null);
}
if (client == null) {
client = new WebChromeClient();
}
} catch (Exception e1) {
throw new IllegalStateException(
"Error getting WebChromeClient: " + e1.getMessage(), e1);
}
}
view.setWebChromeClient(this);
}
/**
* This is how we return data from JS to Java!
*
* For messages beginning with "monkeytalk:", LOG level messages provide normal return values.
* ERROR level messages signify that an error ocurred and contain error message. All other
* sources are passed through for actual writing to the console.
*/
@Override
public boolean onConsoleMessage(ConsoleMessage msg) {
if (msg.message().startsWith("mtrecorder:")) {
String parts[] = msg.message().split("mtrecorder:");
String json = parts[1];
auto.getRecorder().recordJson(json);
return false;
}
if (msg.message().startsWith("monkeytalk:")) {
String parts[] = msg.message().split("monkeytalk:");
if (parts.length < 2) {
WebViewAutomator.reportResult("");
return true;
}
String message = parts[1];
if (msg.messageLevel() == ConsoleMessage.MessageLevel.LOG) {
WebViewAutomator.reportResult(message);
return false;
}
if (msg.messageLevel() == ConsoleMessage.MessageLevel.ERROR) {
WebViewAutomator.reportError(message);
return true;
}
}
if (client != null) {
return client.onConsoleMessage(msg);
}
return false;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
if (client.onJsConfirm(view, url, message, result)) {
return true;
}
AlertDialog dialog = new AlertDialog.Builder(auto.getWebView().getContext()).setTitle(url)
.setMessage(message).setPositiveButton("OK", new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
auto.setJsPopupOpen(false);
}
}).setNegativeButton("Cancel", new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.cancel();
auto.setJsPopupOpen(false);
}
}).setCancelable(false).create();
dialog.show();
FunctionalityAdder.walkTree(dialog.getWindow().getDecorView());
auto.setJsPopupOpen(true);
return true;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
final JsPromptResult result) {
if (client.onJsPrompt(view, url, message, defaultValue, result)) {
return true;
}
Context context = auto.getWebView().getContext();
EditText input = new EditText(context);
input.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
AlertDialog dialog = new AlertDialog.Builder(auto.getWebView().getContext()).setTitle(url)
.setView(input).setMessage(message)
.setPositiveButton("OK", new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
auto.setJsPopupOpen(false);
}
}).setNegativeButton("Cancel", new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.cancel();
auto.setJsPopupOpen(false);
}
}).setCancelable(false).create();
dialog.show();
FunctionalityAdder.walkTree(dialog.getWindow().getDecorView());
auto.setJsPopupOpen(true);
return true;
}
@Override
public boolean onJsTimeout() {
return true;
}
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
auto.setJsPopupOpen(true);
if (client.onJsAlert(view, url, message, result)) {
return true;
}
AlertDialog dialog = new AlertDialog.Builder(auto.getWebView().getContext()).setTitle(url)
.setMessage(message).setPositiveButton("OK", new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
auto.setJsPopupOpen(false);
}
}).setCancelable(false).create();
dialog.show();
FunctionalityAdder.walkTree(dialog.getWindow().getDecorView());
return true;
}
public boolean equals(Object obj) {
return client.equals(obj);
}
public void onProgressChanged(WebView view, int newProgress) {
client.onProgressChanged(view, newProgress);
// view.requestFocus(View.FOCUSABLES_TOUCH_MODE);
// if (newProgress > 50)
WebViewRecorder.attachJs(view);
// else
// recorder.setJsAttached(false);
}
public void onHideCustomView() {
client.onHideCustomView();
}
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
return client.onJsBeforeUnload(view, url, message, result);
}
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
client.onGeolocationPermissionsShowPrompt(origin, callback);
}
public Bitmap getDefaultVideoPoster() {
return client.getDefaultVideoPoster();
}
public View getVideoLoadingProgressView() {
return client.getVideoLoadingProgressView();
}
public void getVisitedHistory(ValueCallback<String[]> callback) {
client.getVisitedHistory(callback);
}
public int hashCode() {
return client.hashCode();
}
public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture,
Message resultMsg) {
return client.onCreateWindow(view, dialog, userGesture, resultMsg);
}
public void onCloseWindow(WebView window) {
client.onCloseWindow(window);
}
public void onConsoleMessage(String message, int lineNumber, String sourceID) {
client.onConsoleMessage(message, lineNumber, sourceID);
}
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota,
long estimatedSize, long totalUsedQuota, QuotaUpdater quotaUpdater) {
client.onExceededDatabaseQuota(url, databaseIdentifier, currentQuota, estimatedSize,
totalUsedQuota, quotaUpdater);
}
public void onGeolocationPermissionsHidePrompt() {
client.onGeolocationPermissionsHidePrompt();
}
public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
QuotaUpdater quotaUpdater) {
client.onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota, quotaUpdater);
}
public void onReceivedTitle(WebView view, String title) {
client.onReceivedTitle(view, title);
}
public void onReceivedIcon(WebView view, Bitmap icon) {
client.onReceivedIcon(view, icon);
}
public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
client.onReceivedTouchIconUrl(view, url, precomposed);
}
public void onShowCustomView(View view, CustomViewCallback callback) {
client.onShowCustomView(view, callback);
}
public void onRequestFocus(WebView view) {
client.onRequestFocus(view);
}
public String toString() {
return client.toString();
}
}