Fix for bug 8607049: Improve javascript dialogs for classic

Make a common JsDialogHelper for classic and chromium webview
Improve on before unload dialog

Change-Id: Ib9711e4f95189ef8b2b31fc64aec7c0cb535d0e4
This commit is contained in:
Kristian Monsen
2013-04-10 19:01:50 -07:00
parent 77d94957d7
commit 98dedf7ccc
4 changed files with 213 additions and 229 deletions

View File

@@ -17,10 +17,8 @@
package android.webkit;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
@@ -33,10 +31,6 @@ import android.os.SystemClock;
import android.provider.Browser;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import com.android.internal.R;
import java.net.MalformedURLException;
@@ -92,10 +86,7 @@ class CallbackProxy extends Handler {
private static final int CREATE_WINDOW = 109;
private static final int CLOSE_WINDOW = 110;
private static final int SAVE_PASSWORD = 111;
private static final int JS_ALERT = 112;
private static final int JS_CONFIRM = 113;
private static final int JS_PROMPT = 114;
private static final int JS_UNLOAD = 115;
private static final int JS_DIALOG = 112;
private static final int ASYNC_KEYEVENTS = 116;
private static final int DOWNLOAD_FILE = 118;
private static final int REPORT_ERROR = 119;
@@ -566,188 +557,12 @@ class CallbackProxy extends Handler {
}
break;
case JS_ALERT:
case JS_DIALOG:
if (mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsAlert(mWebView.getWebView(), url, message,
res)) {
if (!canShowAlertDialog()) {
res.cancel();
receiver.setReady();
break;
}
new AlertDialog.Builder(mContext)
.setTitle(getJsDialogTitle(url))
.setMessage(message)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.confirm();
}
})
.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(
DialogInterface dialog) {
res.cancel();
}
})
.show();
}
receiver.setReady();
}
break;
case JS_CONFIRM:
if (mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsConfirm(mWebView.getWebView(), url, message,
res)) {
if (!canShowAlertDialog()) {
res.cancel();
receiver.setReady();
break;
}
new AlertDialog.Builder(mContext)
.setTitle(getJsDialogTitle(url))
.setMessage(message)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.confirm();
}})
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.cancel();
}})
.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(
DialogInterface dialog) {
res.cancel();
}
})
.show();
}
// Tell the JsResult that it is ready for client
// interaction.
receiver.setReady();
}
break;
case JS_PROMPT:
if (mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsPromptResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String defaultVal = msg.getData().getString("default");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsPrompt(mWebView.getWebView(), url, message,
defaultVal, res)) {
if (!canShowAlertDialog()) {
res.cancel();
receiver.setReady();
break;
}
final LayoutInflater factory = LayoutInflater
.from(mContext);
final View view = factory.inflate(R.layout.js_prompt,
null);
final EditText v = (EditText) view
.findViewById(R.id.value);
v.setText(defaultVal);
((TextView) view.findViewById(R.id.message))
.setText(message);
new AlertDialog.Builder(mContext)
.setTitle(getJsDialogTitle(url))
.setView(view)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int whichButton) {
res.confirm(v.getText()
.toString());
}
})
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int whichButton) {
res.cancel();
}
})
.setOnCancelListener(
new DialogInterface.OnCancelListener() {
public void onCancel(
DialogInterface dialog) {
res.cancel();
}
})
.show();
}
// Tell the JsResult that it is ready for client
// interaction.
receiver.setReady();
}
break;
case JS_UNLOAD:
if (mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsBeforeUnload(mWebView.getWebView(), url,
message, res)) {
if (!canShowAlertDialog()) {
res.cancel();
receiver.setReady();
break;
}
final String m = mContext.getString(
R.string.js_dialog_before_unload, message);
new AlertDialog.Builder(mContext)
.setMessage(m)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.confirm();
}
})
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialog,
int which) {
res.cancel();
}
})
.setOnCancelListener(
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(
DialogInterface dialog) {
res.cancel();
}
})
.show();
JsDialogHelper helper = new JsDialogHelper(receiver.mJsResult, msg);
if (!helper.invokeCallback(mWebChromeClient, mWebView.getWebView())) {
helper.showDialog(mContext);
}
receiver.setReady();
}
@@ -757,7 +572,7 @@ class CallbackProxy extends Handler {
if(mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsResult res = receiver.mJsResult;
if(mWebChromeClient.onJsTimeout()) {
if (mWebChromeClient.onJsTimeout()) {
res.confirm();
} else {
res.cancel();
@@ -895,24 +710,6 @@ class CallbackProxy extends Handler {
sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
}
private String getJsDialogTitle(String url) {
String title = url;
if (URLUtil.isDataUrl(url)) {
// For data: urls, we just display 'JavaScript' similar to Safari.
title = mContext.getString(R.string.js_dialog_title_default);
} else {
try {
URL aUrl = new URL(url);
// For example: "The page at 'http://www.mit.edu' says:"
title = mContext.getString(R.string.js_dialog_title,
aUrl.getProtocol() + "://" + aUrl.getHost());
} catch (MalformedURLException ex) {
// do nothing. just use the url as the title
}
}
return title;
}
//--------------------------------------------------------------------------
// WebViewClient functions.
// NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
@@ -1332,9 +1129,10 @@ class CallbackProxy extends Handler {
return;
}
JsResultReceiver result = new JsResultReceiver();
Message alert = obtainMessage(JS_ALERT, result);
Message alert = obtainMessage(JS_DIALOG, result);
alert.getData().putString("message", message);
alert.getData().putString("url", url);
alert.getData().putInt("type", JsDialogHelper.ALERT);
sendMessageToUiThreadSync(alert);
}
@@ -1345,9 +1143,10 @@ class CallbackProxy extends Handler {
return false;
}
JsResultReceiver result = new JsResultReceiver();
Message confirm = obtainMessage(JS_CONFIRM, result);
Message confirm = obtainMessage(JS_DIALOG, result);
confirm.getData().putString("message", message);
confirm.getData().putString("url", url);
confirm.getData().putInt("type", JsDialogHelper.CONFIRM);
sendMessageToUiThreadSync(confirm);
return result.mJsResult.getResult();
}
@@ -1359,10 +1158,11 @@ class CallbackProxy extends Handler {
return null;
}
JsResultReceiver result = new JsResultReceiver();
Message prompt = obtainMessage(JS_PROMPT, result);
Message prompt = obtainMessage(JS_DIALOG, result);
prompt.getData().putString("message", message);
prompt.getData().putString("default", defaultValue);
prompt.getData().putString("url", url);
prompt.getData().putInt("type", JsDialogHelper.PROMPT);
sendMessageToUiThreadSync(prompt);
return result.mJsResult.getStringResult();
}
@@ -1374,10 +1174,11 @@ class CallbackProxy extends Handler {
return true;
}
JsResultReceiver result = new JsResultReceiver();
Message confirm = obtainMessage(JS_UNLOAD, result);
confirm.getData().putString("message", message);
confirm.getData().putString("url", url);
sendMessageToUiThreadSync(confirm);
Message unload = obtainMessage(JS_DIALOG, result);
unload.getData().putString("message", message);
unload.getData().putString("url", url);
unload.getData().putInt("type", JsDialogHelper.UNLOAD);
sendMessageToUiThreadSync(unload);
return result.mJsResult.getResult();
}
@@ -1595,16 +1396,6 @@ class CallbackProxy extends Handler {
sendMessage(msg);
}
boolean canShowAlertDialog() {
// We can only display the alert dialog if mContext is
// an Activity context.
// FIXME: Should we display dialogs if mContext does
// not have the window focus (e.g. if the user is viewing
// another Activity when the alert should be displayed?
// See bug 3166409
return mContext instanceof Activity;
}
private synchronized void sendMessageToUiThreadSync(Message msg) {
sendMessage(msg);
WebCoreThreadWatchdog.pause();

View File

@@ -0,0 +1,185 @@
/*
* Copyright (C) 2013 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;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Message;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Helper class to create JavaScript dialogs. It is used by
* different WebView implementations.
*
* @hide Helper class for internal use
*/
public class JsDialogHelper {
private static final String TAG = "JsDialogHelper";
// Dialog types
public static final int ALERT = 1;
public static final int CONFIRM = 2;
public static final int PROMPT = 3;
public static final int UNLOAD = 4;
private final String mDefaultValue;
private final JsPromptResult mResult;
private final String mMessage;
private final int mType;
private final String mUrl;
public JsDialogHelper(JsPromptResult result, int type, String defaultValue, String message,
String url) {
mResult = result;
mDefaultValue = defaultValue;
mMessage = message;
mType = type;
mUrl = url;
}
public JsDialogHelper(JsPromptResult result, Message msg) {
mResult = result;
mDefaultValue = msg.getData().getString("default");
mMessage = msg.getData().getString("message");
mType = msg.getData().getInt("type");
mUrl = msg.getData().getString("url");
}
public boolean invokeCallback(WebChromeClient client, WebView webView) {
switch (mType) {
case ALERT:
return client.onJsAlert(webView, mUrl, mMessage, mResult);
case CONFIRM:
return client.onJsConfirm(webView, mUrl, mMessage, mResult);
case UNLOAD:
return client.onJsBeforeUnload(webView, mUrl, mMessage, mResult);
case PROMPT:
return client.onJsPrompt(webView, mUrl, mMessage, mDefaultValue, mResult);
default:
throw new IllegalArgumentException("Unexpected type: " + mType);
}
}
public void showDialog(Context context) {
if (!canShowAlertDialog(context)) {
Log.w(TAG, "Cannot create a dialog, the WebView context is not an Activity");
mResult.cancel();
return;
}
String title, displayMessage;
int positiveTextId, negativeTextId;
if (mType == UNLOAD) {
title = context.getString(com.android.internal.R.string.js_dialog_before_unload_title);
displayMessage = context.getString(
com.android.internal.R.string.js_dialog_before_unload, mMessage);
positiveTextId = com.android.internal.R.string.js_dialog_before_unload_positive_button;
negativeTextId = com.android.internal.R.string.js_dialog_before_unload_negative_button;
} else {
title = getJsDialogTitle(context);
displayMessage = mMessage;
positiveTextId = com.android.internal.R.string.ok;
negativeTextId = com.android.internal.R.string.cancel;
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(title);
builder.setOnCancelListener(new CancelListener());
if (mType != PROMPT) {
builder.setMessage(displayMessage);
builder.setPositiveButton(positiveTextId, new PositiveListener(null));
} else {
final View view = LayoutInflater.from(context).inflate(
com.android.internal.R.layout.js_prompt, null);
EditText edit = ((EditText) view.findViewById(com.android.internal.R.id.value));
edit.setText(mDefaultValue);
builder.setPositiveButton(positiveTextId, new PositiveListener(edit));
((TextView) view.findViewById(com.android.internal.R.id.message)).setText(mMessage);
builder.setView(view);
}
if (mType != ALERT) {
builder.setNegativeButton(negativeTextId, new CancelListener());
}
builder.show();
}
private class CancelListener implements DialogInterface.OnCancelListener,
DialogInterface.OnClickListener {
@Override
public void onCancel(DialogInterface dialog) {
mResult.cancel();
}
@Override
public void onClick(DialogInterface dialog, int which) {
mResult.cancel();
}
}
private class PositiveListener implements DialogInterface.OnClickListener {
private final EditText mEdit;
public PositiveListener(EditText edit) {
mEdit = edit;
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (mEdit == null) {
mResult.confirm();
} else {
mResult.confirm(mEdit.getText().toString());
}
}
}
private String getJsDialogTitle(Context context) {
String title = mUrl;
if (URLUtil.isDataUrl(mUrl)) {
// For data: urls, we just display 'JavaScript' similar to Chrome.
title = context.getString(com.android.internal.R.string.js_dialog_title_default);
} else {
try {
URL alertUrl = new URL(mUrl);
// For example: "The page at 'http://www.mit.edu' says:"
title = context.getString(com.android.internal.R.string.js_dialog_title,
alertUrl.getProtocol() + "://" + alertUrl.getHost());
} catch (MalformedURLException ex) {
// do nothing. just use the url as the title
}
}
return title;
}
private static boolean canShowAlertDialog(Context context) {
// We can only display the alert dialog if mContext is
// an Activity context.
// FIXME: Should we display dialogs if mContext does
// not have the window focus (e.g. if the user is viewing
// another Activity when the alert should be displayed) ?
// See bug 3166409
return context instanceof Activity;
}
}

View File

@@ -2406,9 +2406,14 @@
<string name="js_dialog_title">The page at \"<xliff:g id="title">%s</xliff:g>\" says:</string>
<!-- Default title for a javascript dialog -->
<string name="js_dialog_title_default">JavaScript</string>
<!-- Message in a javascript dialog asking if the user wishes to leave the
current page -->
<string name="js_dialog_before_unload">Navigate away from this page?\n\n<xliff:g id="message">%s</xliff:g>\n\nTouch OK to continue, or Cancel to stay on the current page.</string>
<!-- Title for the unload javascript dialog -->
<string name="js_dialog_before_unload_title">Confirm Navigation</string>
<!-- Text for the positive button on the unload javascript dialog -->
<string name="js_dialog_before_unload_positive_button">Leave this Page</string>
<!-- Text for the negative button on the unload javascript dialog -->
<string name="js_dialog_before_unload_negative_button">Stay on this Page</string>
<!-- Message in a javascript dialog asking if the user wishes to leave the current page -->
<string name="js_dialog_before_unload"><xliff:g id="message">%s</xliff:g>\n\nAre you sure you want to navigate away from this page?</string>
<!-- Title of the WebView save password dialog. If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. -->
<string name="save_password_label">Confirm</string>

View File

@@ -549,6 +549,9 @@
<java-symbol type="string" name="ime_action_search" />
<java-symbol type="string" name="ime_action_send" />
<java-symbol type="string" name="invalidPin" />
<java-symbol type="string" name="js_dialog_before_unload_positive_button" />
<java-symbol type="string" name="js_dialog_before_unload_negative_button" />
<java-symbol type="string" name="js_dialog_before_unload_title" />
<java-symbol type="string" name="js_dialog_before_unload" />
<java-symbol type="string" name="js_dialog_title" />
<java-symbol type="string" name="js_dialog_title_default" />