Commit 23c8f28b authored by Becky Zhou's avatar Becky Zhou Committed by Commit Bot

[TabModal] Fix metrics dismissal cause for JavaScript dialog

+ Record dialog dismissal on Android side as kDialogClosed
+ Add metrics recording support for modal dialogs, and remove onCancel
  callback

Bug: 873236
Change-Id: Idbe990f2c70883f851bf5214dbf0a3e7b96f4260
Reviewed-on: https://chromium-review.googlesource.com/1222990Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Commit-Queue: Becky Zhou <huayinz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#593634}
parent c79cc2cb
......@@ -20,6 +20,7 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.jsdialog.JavascriptModalDialogView;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
import org.chromium.chrome.browser.vr.VrModuleProvider;
......@@ -151,12 +152,12 @@ public class JavascriptAppModalDialog
public void onClick(@ModalDialogView.ButtonType int buttonType) {
switch (buttonType) {
case ModalDialogView.ButtonType.POSITIVE:
confirm(mDialogView.getPromptText(), false);
mModalDialogManager.dismissDialog(mDialogView);
mModalDialogManager.dismissDialog(
mDialogView, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
break;
case ModalDialogView.ButtonType.NEGATIVE:
cancel(false);
mModalDialogManager.dismissDialog(mDialogView);
mModalDialogManager.dismissDialog(
mDialogView, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
break;
default:
Log.e(TAG, "Unexpected button pressed in dialog: " + buttonType);
......@@ -164,13 +165,21 @@ public class JavascriptAppModalDialog
}
@Override
public void onCancel() {
cancel(false);
public void onDismiss(@DialogDismissalCause int dismissalCause) {
// TODO(https://crbug.com/874537): Add suppression logic in the refactor and make sure it
// doesn't break VR.
switch (dismissalCause) {
case DialogDismissalCause.POSITIVE_BUTTON_CLICKED:
confirm(mDialogView.getPromptText(), false);
break;
case DialogDismissalCause.DISMISSED_BY_NATIVE:
break;
default:
cancel(false);
}
mDialogView = null;
}
@Override
public void onDismiss() {}
protected void prepare(final ViewGroup layout) {
// Display the checkbox for suppressing dialogs if necessary.
layout.findViewById(R.id.suppress_js_modal_dialogs).setVisibility(
......@@ -213,7 +222,8 @@ public class JavascriptAppModalDialog
if (mDialog != null) {
mDialog.dismiss();
} else {
mModalDialogManager.dismissDialog(mDialogView);
mModalDialogManager.dismissDialog(
mDialogView, DialogDismissalCause.DISMISSED_BY_NATIVE);
}
mNativeDialogPointer = 0;
}
......
......@@ -39,6 +39,7 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.base.task.AsyncTask;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
......@@ -746,15 +747,13 @@ public class CardUnmaskPrompt implements TextWatcher, OnClickListener, ModalDial
mMonthInput.getText().toString(), Integer.toString(getFourDigitYear()),
mStoreLocallyCheckbox != null && mStoreLocallyCheckbox.isChecked());
} else if (buttonType == ModalDialogView.ButtonType.NEGATIVE) {
mModalDialogManager.cancelDialog(mDialog);
mModalDialogManager.dismissDialog(
mDialog, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
}
}
@Override
public void onCancel() {}
@Override
public void onDismiss() {
public void onDismiss(@DialogDismissalCause int dismissalCause) {
mDelegate.dismissed();
}
......
......@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.download;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
......@@ -34,7 +35,10 @@ public class DownloadLocationDialogBridge implements ModalDialogView.Controller
@CalledByNative
private void destroy() {
mNativeDownloadLocationDialogBridge = 0;
if (mModalDialogManager != null) mModalDialogManager.dismissDialog(mLocationDialog);
if (mModalDialogManager != null) {
mModalDialogManager.dismissDialog(
mLocationDialog, DialogDismissalCause.DISMISSED_BY_NATIVE);
}
}
@CalledByNative
......@@ -43,7 +47,7 @@ public class DownloadLocationDialogBridge implements ModalDialogView.Controller
ChromeActivity activity = (ChromeActivity) windowAndroid.getActivity().get();
// If the activity has gone away, just clean up the native pointer.
if (activity == null) {
onCancel();
onDismiss(DialogDismissalCause.ACTIVITY_DESTROYED);
return;
}
......@@ -60,29 +64,33 @@ public class DownloadLocationDialogBridge implements ModalDialogView.Controller
public void onClick(@ModalDialogView.ButtonType int buttonType) {
switch (buttonType) {
case ModalDialogView.ButtonType.POSITIVE:
handleResponses(mLocationDialog.getFileName(), mLocationDialog.getDirectoryOption(),
mLocationDialog.getDontShowAgain());
mModalDialogManager.dismissDialog(mLocationDialog);
mModalDialogManager.dismissDialog(
mLocationDialog, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
break;
case ModalDialogView.ButtonType.NEGATIVE:
// Intentional fall-through.
mModalDialogManager.dismissDialog(
mLocationDialog, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
break;
default:
cancel();
mModalDialogManager.dismissDialog(mLocationDialog);
}
mLocationDialog = null;
}
@Override
public void onCancel() {
cancel();
public void onDismiss(@DialogDismissalCause int dismissalCause) {
switch (dismissalCause) {
case DialogDismissalCause.POSITIVE_BUTTON_CLICKED:
handleResponses(mLocationDialog.getFileName(), mLocationDialog.getDirectoryOption(),
mLocationDialog.getDontShowAgain());
break;
default:
cancel();
break;
}
mLocationDialog = null;
}
@Override
public void onDismiss() {}
/**
* Pass along information from location dialog to native.
*
......
......@@ -15,6 +15,7 @@ import android.widget.CheckBox;
import org.chromium.base.task.AsyncTask;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.modaldialog.AppModalPresenter;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager.ModalDialogType;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
......@@ -86,12 +87,7 @@ public class IncognitoDisclosureActivity extends AppCompatActivity {
}
@Override
public void onCancel() {
finish();
}
@Override
public void onDismiss() {
public void onDismiss(@DialogDismissalCause int dismissalCause) {
finish();
}
};
......
......@@ -8,6 +8,7 @@ import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
import org.chromium.ui.base.WindowAndroid;
......@@ -70,7 +71,7 @@ public class JavascriptTabModalDialog implements ModalDialogView.Controller {
ChromeActivity activity = (ChromeActivity) window.getActivity().get();
// If the activity has gone away, then just clean up the native pointer.
if (activity == null) {
nativeCancel(nativeDialogPointer);
nativeCancel(nativeDialogPointer, false);
return;
}
......@@ -90,7 +91,7 @@ public class JavascriptTabModalDialog implements ModalDialogView.Controller {
@CalledByNative
private void dismiss() {
mModalDialogManager.dismissDialog(mDialogView);
mModalDialogManager.dismissDialog(mDialogView, DialogDismissalCause.DISMISSED_BY_NATIVE);
mNativeDialogPointer = 0;
}
......@@ -98,12 +99,12 @@ public class JavascriptTabModalDialog implements ModalDialogView.Controller {
public void onClick(@ModalDialogView.ButtonType int buttonType) {
switch (buttonType) {
case ModalDialogView.ButtonType.POSITIVE:
accept(mDialogView.getPromptText());
mModalDialogManager.dismissDialog(mDialogView);
mModalDialogManager.dismissDialog(
mDialogView, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
break;
case ModalDialogView.ButtonType.NEGATIVE:
cancel();
mModalDialogManager.dismissDialog(mDialogView);
mModalDialogManager.dismissDialog(
mDialogView, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
break;
default:
Log.e(TAG, "Unexpected button pressed in dialog: " + buttonType);
......@@ -111,32 +112,40 @@ public class JavascriptTabModalDialog implements ModalDialogView.Controller {
}
@Override
public void onCancel() {
cancel();
public void onDismiss(@DialogDismissalCause int dismissalCause) {
switch (dismissalCause) {
case DialogDismissalCause.POSITIVE_BUTTON_CLICKED:
accept(mDialogView.getPromptText());
break;
case DialogDismissalCause.NEGATIVE_BUTTON_CLICKED:
cancel(true);
break;
case DialogDismissalCause.DISMISSED_BY_NATIVE:
// We don't need to call native back in this case.
break;
default:
cancel(false);
}
mDialogView = null;
}
@Override
public void onDismiss() {}
/**
* Sends notification to native that the user accepts the dialog.
* @param promptResult The text edited by user.
*/
private void accept(String promptResult) {
if (mNativeDialogPointer != 0) {
nativeAccept(mNativeDialogPointer, promptResult);
}
if (mNativeDialogPointer == 0) return;
nativeAccept(mNativeDialogPointer, promptResult);
}
/**
* Sends notification to native that the user cancels the dialog.
*/
private void cancel() {
if (mNativeDialogPointer != 0) {
nativeCancel(mNativeDialogPointer);
}
private void cancel(boolean buttonClicked) {
if (mNativeDialogPointer == 0) return;
nativeCancel(mNativeDialogPointer, buttonClicked);
}
private native void nativeAccept(long nativeJavaScriptDialogAndroid, String prompt);
private native void nativeCancel(long nativeJavaScriptDialogAndroid);
private native void nativeCancel(long nativeJavaScriptDialogAndroid, boolean buttonClicked);
}
......@@ -18,6 +18,7 @@ import android.widget.TextView;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
......@@ -225,16 +226,15 @@ public class LanguageAskPrompt implements ModalDialogView.Controller {
@Override
public void onClick(int buttonType) {
if (buttonType == ModalDialogView.ButtonType.NEGATIVE) {
mModalDialogManager.cancelDialog(mDialog);
mModalDialogManager.dismissDialog(
mDialog, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
} else {
saveLanguages();
mModalDialogManager.dismissDialog(mDialog);
mModalDialogManager.dismissDialog(
mDialog, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
}
}
@Override
public void onCancel() {}
@Override
public void onDismiss() {}
public void onDismiss(@DialogDismissalCause int dismissalCause) {}
}
......@@ -26,7 +26,8 @@ public class AppModalPresenter extends ModalDialogManager.Presenter {
@Override
protected void addDialogView(View dialogView) {
mDialog = new Dialog(mContext, R.style.ModalDialogTheme);
mDialog.setOnCancelListener(dialogInterface -> cancelCurrentDialog());
mDialog.setOnCancelListener(dialogInterface
-> dismissCurrentDialog(DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE));
ViewGroup container = (ViewGroup) LayoutInflater.from(mContext).inflate(
R.layout.modal_dialog_container, null);
// We use the Android Dialog dim for app modal dialog, so a custom scrim is not needed.
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.modaldialog;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@IntDef({DialogDismissalCause.UNKNOWN, DialogDismissalCause.POSITIVE_BUTTON_CLICKED,
DialogDismissalCause.NEGATIVE_BUTTON_CLICKED, DialogDismissalCause.ACTION_ON_CONTENT,
DialogDismissalCause.DISMISSED_BY_NATIVE,
DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE, DialogDismissalCause.TAB_SWITCHED,
DialogDismissalCause.TAB_DESTROYED, DialogDismissalCause.ACTIVITY_DESTROYED})
@Retention(RetentionPolicy.SOURCE)
public @interface DialogDismissalCause {
// Please do not remove or change the order of the existing values, and add new value at the end
// of the enum. Dismissal causes that are fully controlled by clients (i.e. are not used inside
// the dialog manager or the dialog presenters) are marked "Controlled by client" on comments.
/** No specified reason for the dialog dismissal. */
int UNKNOWN = 0;
/** Controlled by client: Positive button (e.g. OK button) is clicked by the user. */
int POSITIVE_BUTTON_CLICKED = 1;
/** Controlled by client: Negative button (e.g. Cancel button) is clicked by the user. */
int NEGATIVE_BUTTON_CLICKED = 2;
/** Controlled by client: Action taken on the dialog content triggers the dialog dismissal. */
int ACTION_ON_CONTENT = 3;
/** Controlled by client: Dialog is dismissed by native c++ objects. */
int DISMISSED_BY_NATIVE = 4;
/** User clicks the navigate back button or touches the scrim outside the dialog. */
int NAVIGATE_BACK_OR_TOUCH_OUTSIDE = 5;
/** User switches away the tab associated with the dialog. */
int TAB_SWITCHED = 6;
/** The Tab associated with the dialog is destroyed. */
int TAB_DESTROYED = 7;
/** The activity associated with the dialog is destroyed. */
int ACTIVITY_DESTROYED = 8;
}
......@@ -10,6 +10,7 @@ import android.support.annotation.Nullable;
import android.util.SparseArray;
import android.view.View;
import org.chromium.base.Callback;
import org.chromium.base.VisibleForTesting;
import org.chromium.ui.UiUtils;
......@@ -28,7 +29,7 @@ public class ModalDialogManager {
* Present a {@link ModalDialogView} in a container.
*/
public static abstract class Presenter {
private Runnable mCancelCallback;
private Callback<Integer> mDismissCallback;
private ModalDialogView mModalDialog;
private View mCurrentView;
......@@ -37,17 +38,17 @@ public class ModalDialogManager {
* is currently showing.
*/
private void setModalDialog(
@Nullable ModalDialogView dialog, @Nullable Runnable cancelCallback) {
@Nullable ModalDialogView dialog, @Nullable Callback<Integer> dismissCallback) {
if (dialog == null) {
removeDialogView(mCurrentView);
mModalDialog = null;
mCancelCallback = null;
mDismissCallback = null;
} else {
assert mModalDialog
== null : "Should call setModalDialog(null) before setting a modal dialog.";
mModalDialog = dialog;
mCurrentView = dialog.getView();
mCancelCallback = cancelCallback;
mDismissCallback = dismissCallback;
// Make sure the view is detached from any parent before adding it to the container.
// This is not detached after removeDialogView() because there could be animation
// running on removing the dialog view.
......@@ -59,14 +60,14 @@ public class ModalDialogManager {
/**
* Run the cached cancel callback and reset the cached callback.
*/
protected final void cancelCurrentDialog() {
if (mCancelCallback == null) return;
protected final void dismissCurrentDialog(@DialogDismissalCause int dismissalCause) {
if (mDismissCallback == null) return;
// Set #mCancelCallback to null before calling the callback to avoid it being
// updated during the callback.
Runnable callback = mCancelCallback;
mCancelCallback = null;
callback.run();
Callback<Integer> callback = mDismissCallback;
mDismissCallback = null;
callback.onResult(dismissalCause);
}
/**
......@@ -143,7 +144,7 @@ public class ModalDialogManager {
/** Clears any dependencies on the showing or pending dialogs. */
public void destroy() {
cancelAllDialogs();
dismissAllDialogs(DialogDismissalCause.ACTIVITY_DESTROYED);
}
/**
......@@ -201,7 +202,8 @@ public class ModalDialogManager {
dialog.prepareBeforeShow();
mCurrentType = dialogType;
mCurrentPresenter = mPresenters.get(dialogType, mDefaultPresenter);
mCurrentPresenter.setModalDialog(dialog, () -> cancelDialog(dialog));
mCurrentPresenter.setModalDialog(
dialog, (dismissalCause) -> dismissDialog(dialog, dismissalCause));
}
/**
......@@ -209,14 +211,29 @@ public class ModalDialogManager {
* the pending dialog list. If the dialog is currently being dismissed this function does
* nothing.
* @param dialog The dialog to be dismissed or removed from pending list.
*
* TODO(huayinz): Remove this method and use the one with dismissal cause.
*/
public void dismissDialog(ModalDialogView dialog) {
dismissDialog(dialog, DialogDismissalCause.UNKNOWN);
}
/**
* Dismiss the specified dialog. If the dialog is not currently showing, it will be removed from
* the pending dialog list. If the dialog is currently being dismissed this function does
* nothing.
* @param dialog The dialog to be dismissed or removed from pending list.
* @param dismissalCause The {@link DialogDismissalCause} that describes why the dialog is
* dismissed.
*/
public void dismissDialog(ModalDialogView dialog, @DialogDismissalCause int dismissalCause) {
if (dialog == null) return;
if (mCurrentPresenter == null || dialog != mCurrentPresenter.getModalDialog()) {
for (int i = 0; i < mPendingDialogs.size(); ++i) {
List<ModalDialogView> dialogs = mPendingDialogs.valueAt(i);
for (int j = 0; j < dialogs.size(); ++j) {
if (dialogs.get(j) == dialog) {
dialogs.remove(j).getController().onDismiss();
dialogs.remove(j).getController().onDismiss(dismissalCause);
return;
}
}
......@@ -229,7 +246,7 @@ public class ModalDialogManager {
assert dialog == mCurrentPresenter.getModalDialog();
if (mDismissingCurrentDialog) return;
mDismissingCurrentDialog = true;
dialog.getController().onDismiss();
dialog.getController().onDismiss(dismissalCause);
mCurrentPresenter.setModalDialog(null, null);
mCurrentPresenter = null;
mDismissingCurrentDialog = false;
......@@ -237,48 +254,40 @@ public class ModalDialogManager {
}
/**
* Cancel showing the specified dialog. This is essentially the same as
* {@link #dismissDialog(ModalDialogView)} but will also call the onCancelled callback from the
* modal dialog.
* @param dialog The dialog to be cancelled.
*/
public void cancelDialog(ModalDialogView dialog) {
dialog.getController().onCancel();
dismissDialog(dialog);
}
/**
* Dismiss the dialog currently shown and remove all pending dialogs and call the onCancelled
* callbacks from the modal dialogs.
* Dismiss the dialog currently shown and remove all pending dialogs.
* @param dismissalCause The {@link DialogDismissalCause} that describes why the dialogs are
* dismissed.
*/
public void cancelAllDialogs() {
public void dismissAllDialogs(@DialogDismissalCause int dismissalCause) {
for (int i = 0; i < mPendingDialogs.size(); ++i) {
cancelPendingDialogs(mPendingDialogs.keyAt(i));
dismissPendingDialogsOfType(mPendingDialogs.keyAt(i), dismissalCause);
}
if (isShowing()) cancelDialog(mCurrentPresenter.getModalDialog());
if (isShowing()) dismissDialog(mCurrentPresenter.getModalDialog());
}
/**
* Dismiss the dialog currently shown and remove all pending dialogs of the specified type and
* call the onCancelled callbacks from the modal dialogs.
* Dismiss the dialog currently shown and remove all pending dialogs of the specified type.
* @param dialogType The specified type of dialog.
* @param dismissalCause The {@link DialogDismissalCause} that describes why the dialogs are
* dismissed.
*/
protected void cancelAllDialogs(@ModalDialogType int dialogType) {
cancelPendingDialogs(dialogType);
protected void dismissDialogsOfType(
@ModalDialogType int dialogType, @DialogDismissalCause int dismissalCause) {
dismissPendingDialogsOfType(dialogType, dismissalCause);
if (isShowing() && dialogType == mCurrentType) {
cancelDialog(mCurrentPresenter.getModalDialog());
dismissDialog(mCurrentPresenter.getModalDialog(), dismissalCause);
}
}
/** Helper method to cancel pending dialogs of the specified type. */
private void cancelPendingDialogs(@ModalDialogType int dialogType) {
/** Helper method to dismiss pending dialogs of the specified type. */
private void dismissPendingDialogsOfType(
@ModalDialogType int dialogType, @DialogDismissalCause int dismissalCause) {
List<ModalDialogView> dialogs = mPendingDialogs.get(dialogType);
if (dialogs == null) return;
while (!dialogs.isEmpty()) {
ModalDialogView.Controller controller = dialogs.remove(0).getController();
controller.onDismiss();
controller.onCancel();
controller.onDismiss(dismissalCause);
}
}
......
......@@ -40,15 +40,16 @@ public class ModalDialogView implements View.OnClickListener {
void onClick(@ButtonType int buttonType);
/**
* Handle cancel event when the dialog is not dismissed by actions on the dialog such as
* back press, and on tab modal dialog, tab switcher button click.
* Handle dismiss event when the dialog is dismissed by actions on the dialog. Note that it
* can be dangerous to the {@code dismissalCause} for business logic other than metrics
* recording, unless the dismissal cause is fully controlled by the client (e.g. button
* clicked), because the dismissal cause can be different values depending on modal dialog
* type and mode of presentation (e.g. it could be unknown on VR but a specific value on
* non-VR).
* @param dismissalCause The reason of the dialog being dismissed.
* @see DialogDismissalCause
*/
void onCancel();
/**
* Handle dismiss event when the dialog is dismissed by actions on the dialog.
*/
void onDismiss();
void onDismiss(@DialogDismissalCause int dismissalCause);
}
/** Parameters that can be used to create a new ModalDialogView. */
......
......@@ -28,7 +28,8 @@ public class TabModalLifetimeHandler {
@Override
public void onDestroyed(Tab tab) {
if (mActiveTab == tab) {
mManager.cancelAllDialogs(ModalDialogType.TAB);
mManager.dismissDialogsOfType(
ModalDialogType.TAB, DialogDismissalCause.TAB_DESTROYED);
mActiveTab = null;
}
}
......@@ -58,7 +59,8 @@ public class TabModalLifetimeHandler {
// Do not use lastId here since it can be the selected tab's ID if model is switched
// inside tab switcher.
if (tab != mActiveTab) {
mManager.cancelAllDialogs(ModalDialogType.TAB);
mManager.dismissDialogsOfType(
ModalDialogType.TAB, DialogDismissalCause.TAB_SWITCHED);
if (mActiveTab != null) mActiveTab.removeObserver(mTabObserver);
mActiveTab = tab;
......@@ -87,7 +89,7 @@ public class TabModalLifetimeHandler {
*/
public boolean handleBackPress() {
if (mPresenter.getModalDialog() == null) return false;
mPresenter.cancelCurrentDialog();
mPresenter.dismissCurrentDialog(DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE);
return true;
}
......
......@@ -24,6 +24,7 @@ import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ResourceId;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
import org.chromium.chrome.browser.modaldialog.ModalDialogView.ButtonType;
......@@ -231,10 +232,7 @@ public class ConnectionInfoPopup implements OnClickListener, ModalDialogView.Con
public void onClick(@ButtonType int buttonType) {}
@Override
public void onCancel() {}
@Override
public void onDismiss() {
public void onDismiss(@DialogDismissalCause int dismissalCause) {
assert mNativeConnectionInfoPopup != 0;
mWebContentsObserver.destroy();
nativeDestroy(mNativeConnectionInfoPopup);
......
......@@ -31,6 +31,7 @@ import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.instantapps.InstantAppsHandler;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
import org.chromium.chrome.browser.modaldialog.ModalDialogView.ButtonType;
import org.chromium.chrome.browser.offlinepages.OfflinePageItem;
......@@ -479,10 +480,7 @@ public class PageInfoController
public void onClick(@ButtonType int buttonType) {}
@Override
public void onCancel() {}
@Override
public void onDismiss() {
public void onDismiss(@DialogDismissalCause int dismissalCause) {
assert mNativePageInfoController != 0;
if (mPendingRunAfterDismissTask != null) {
mPendingRunAfterDismissTask.run();
......
......@@ -21,6 +21,7 @@ import android.view.Window;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
import org.chromium.ui.interpolators.BakedBezierInterpolator;
......@@ -157,7 +158,7 @@ class PageInfoDialog {
sheetDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
mController.onDismiss();
mController.onDismiss(DialogDismissalCause.UNKNOWN);
}
});
......
......@@ -5,6 +5,7 @@
package org.chromium.chrome.browser.password_manager;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
/** Class responsible for binding the model and the view. On bind, it lazily initializes the view
......@@ -33,12 +34,7 @@ public class PasswordGenerationDialogViewBinder {
}
@Override
public void onCancel() {
mPasswordActionCallback.onResult(false);
}
@Override
public void onDismiss() {
public void onDismiss(@DialogDismissalCause int dismissalCause) {
mPasswordActionCallback.onResult(false);
}
}
......
......@@ -12,6 +12,7 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
import org.chromium.chrome.browser.vr.VrModuleProvider;
......@@ -297,10 +298,7 @@ public class PermissionDialogController
}
@Override
public void onCancel() {}
@Override
public void onDismiss() {
public void onDismiss(@DialogDismissalCause int dismissalCause) {
mDismissListener.onDismiss(null);
mAppModalDialogView = null;
}
......
......@@ -9,6 +9,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.view.View;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
......@@ -98,14 +99,11 @@ public class VrAlertDialog extends AlertDialog {
*/
@Override
public void dismiss() {
mModalDialogManager.cancelDialog(mModalDialogView);
mModalDialogManager.dismissDialog(mModalDialogView);
}
private ModalDialogView createView() {
ModalDialogView.Controller controller = new ModalDialogView.Controller() {
@Override
public void onCancel() {}
@Override
public void onClick(int buttonType) {
if (buttonType == ModalDialogView.ButtonType.POSITIVE) {
......@@ -117,7 +115,7 @@ public class VrAlertDialog extends AlertDialog {
}
@Override
public void onDismiss() {}
public void onDismiss(@DialogDismissalCause int dialogDismissal) {}
};
final ModalDialogView.Params params = new ModalDialogView.Params();
......
......@@ -9,6 +9,7 @@ import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.FrameLayout;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
/** The presenter that shows a {@link ModalDialogView} in an Android dialog. */
......@@ -42,6 +43,6 @@ public class VrModalPresenter extends ModalDialogManager.Presenter {
}
public void closeCurrentDialog() {
cancelCurrentDialog();
dismissCurrentDialog(DialogDismissalCause.UNKNOWN);
}
}
......@@ -36,6 +36,7 @@ import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.compositor.CompositorView;
import org.chromium.chrome.browser.modaldialog.DialogDismissalCause;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.page_info.PageInfoController;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
......@@ -329,7 +330,7 @@ public class VrShell extends GvrLayout
private void injectVrHostedUiView() {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.VR_BROWSING_NATIVE_ANDROID_UI)) return;
mNonVrModalDialogManager = mActivity.getModalDialogManager();
mNonVrModalDialogManager.cancelAllDialogs();
mNonVrModalDialogManager.dismissAllDialogs(DialogDismissalCause.UNKNOWN);
mVrModalPresenter = new VrModalPresenter(this);
mVrModalDialogManager =
new ModalDialogManager(mVrModalPresenter, ModalDialogManager.ModalDialogType.APP);
......@@ -754,7 +755,7 @@ public class VrShell extends GvrLayout
public void shutdown() {
if (mVrBrowsingEnabled) {
if (mVrModalDialogManager != null) {
mVrModalDialogManager.cancelAllDialogs();
mVrModalDialogManager.dismissAllDialogs(DialogDismissalCause.UNKNOWN);
mActivity.setModalDialogManager(mNonVrModalDialogManager);
mVrModalDialogManager = null;
}
......
......@@ -848,6 +848,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/metrics/WebApkUma.java",
"java/src/org/chromium/chrome/browser/metrics/WebappUma.java",
"java/src/org/chromium/chrome/browser/modaldialog/AppModalPresenter.java",
"java/src/org/chromium/chrome/browser/modaldialog/DialogDismissalCause.java",
"java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java",
"java/src/org/chromium/chrome/browser/modaldialog/ModalDialogView.java",
"java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java",
......
......@@ -54,11 +54,16 @@ import java.util.List;
public class ModalDialogManagerTest {
private static class TestObserver implements UrlFocusChangeListener {
public final CallbackHelper onUrlFocusChangedCallback = new CallbackHelper();
public final CallbackHelper onDialogDismissedCallback = new CallbackHelper();
@Override
public void onUrlFocusChange(boolean hasFocus) {
onUrlFocusChangedCallback.notifyCalled();
}
public void onDialogDismissed() {
onDialogDismissedCallback.notifyCalled();
}
}
@Rule
......@@ -69,6 +74,7 @@ public class ModalDialogManagerTest {
private ModalDialogManager mManager;
private ModalDialogView[] mModalDialogViews;
private TestObserver mTestObserver;
private Integer mExpectedDismissalCause;
@Before
public void setUp() throws Exception {
......@@ -680,14 +686,67 @@ public class ModalDialogManagerTest {
checkCurrentPresenter(null);
}
@Test
@SmallTest
public void testDismiss_DismissalCause_BackPressed() throws Exception {
mExpectedDismissalCause = DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE;
int callCount = mTestObserver.onDialogDismissedCallback.getCallCount();
showDialog(0, ModalDialogType.APP);
showDialog(1, ModalDialogType.TAB);
// Dismiss the app modal dialog and veirify dimissal cause.
Espresso.pressBack();
mTestObserver.onDialogDismissedCallback.waitForCallback(callCount);
// Dismiss the tab modal dialog and veirify dimissal cause.
callCount = mTestObserver.onDialogDismissedCallback.getCallCount();
Espresso.pressBack();
mTestObserver.onDialogDismissedCallback.waitForCallback(callCount);
mExpectedDismissalCause = null;
}
@Test
@SmallTest
public void testDismiss_DismissalCause_TabSwitched() throws Exception {
mExpectedDismissalCause = DialogDismissalCause.TAB_SWITCHED;
int callCount = mTestObserver.onDialogDismissedCallback.getCallCount();
// Open a new tab and make sure that the current tab is at index 0.
mActivityTestRule.loadUrlInNewTab("about:blank");
ChromeTabUtils.switchTabInCurrentTabModel(mActivity, 0);
// Show a tab modal dialog and then switch tab.
showDialog(0, ModalDialogType.TAB);
ChromeTabUtils.switchTabInCurrentTabModel(mActivity, 1);
mTestObserver.onDialogDismissedCallback.waitForCallback(callCount);
mExpectedDismissalCause = null;
}
@Test
@SmallTest
public void testDismiss_DismissalCause_TabDestroyed() throws Exception {
mExpectedDismissalCause = DialogDismissalCause.TAB_DESTROYED;
int callCount = mTestObserver.onDialogDismissedCallback.getCallCount();
// Show a tab modal dialog and then close tab.
showDialog(0, ModalDialogType.TAB);
ChromeTabUtils.closeCurrentTab(InstrumentationRegistry.getInstrumentation(), mActivity);
mTestObserver.onDialogDismissedCallback.waitForCallback(callCount);
mExpectedDismissalCause = null;
}
private ModalDialogView createDialog(final int index) throws Exception {
return ThreadUtils.runOnUiThreadBlocking(() -> {
ModalDialogView.Controller controller = new ModalDialogView.Controller() {
@Override
public void onCancel() {}
@Override
public void onDismiss() {}
public void onDismiss(@DialogDismissalCause int dismissalCause) {
mTestObserver.onDialogDismissed();
checkDialogDismissalCause(dismissalCause);
}
@Override
public void onClick(int buttonType) {
......@@ -755,4 +814,9 @@ public class ModalDialogManagerTest {
onView(withId(R.id.menu_button)).check(matches(isEnabled()));
}
}
private void checkDialogDismissalCause(int dismissalCause) {
if (mExpectedDismissalCause == null) return;
Assert.assertEquals(mExpectedDismissalCause.intValue(), dismissalCause);
}
}
......@@ -39,10 +39,14 @@ base::WeakPtr<JavaScriptDialogAndroid> JavaScriptDialogAndroid::Create(
content::JavaScriptDialogType dialog_type,
const base::string16& message_text,
const base::string16& default_prompt_text,
content::JavaScriptDialogManager::DialogClosedCallback dialog_callback) {
return (new JavaScriptDialogAndroid(
parent_web_contents, alerting_web_contents, title, dialog_type,
message_text, default_prompt_text, std::move(dialog_callback)))
content::JavaScriptDialogManager::DialogClosedCallback
callback_on_button_clicked,
base::OnceClosure callback_on_cancelled) {
return (new JavaScriptDialogAndroid(parent_web_contents,
alerting_web_contents, title, dialog_type,
message_text, default_prompt_text,
std::move(callback_on_button_clicked),
std::move(callback_on_cancelled)))
->weak_factory_.GetWeakPtr();
}
......@@ -60,20 +64,23 @@ base::string16 JavaScriptDialogAndroid::GetUserInput() {
void JavaScriptDialogAndroid::Accept(JNIEnv* env,
const JavaParamRef<jobject>&,
const JavaParamRef<jstring>& prompt) {
if (dialog_callback_) {
if (callback_on_button_clicked_) {
base::string16 prompt_text =
base::android::ConvertJavaStringToUTF16(env, prompt);
std::move(dialog_callback_).Run(true, prompt_text);
std::move(dialog_callback_).Reset();
std::move(callback_on_button_clicked_).Run(true, prompt_text);
}
delete this;
}
void JavaScriptDialogAndroid::Cancel(JNIEnv* env,
const JavaParamRef<jobject>&) {
if (dialog_callback_) {
std::move(dialog_callback_).Run(false, base::string16());
std::move(dialog_callback_).Reset();
const JavaParamRef<jobject>&,
jboolean button_clicked) {
if (button_clicked) {
if (callback_on_button_clicked_) {
std::move(callback_on_button_clicked_).Run(false, base::string16());
}
} else if (callback_on_cancelled_) {
std::move(callback_on_cancelled_).Run();
}
delete this;
}
......@@ -85,8 +92,12 @@ JavaScriptDialogAndroid::JavaScriptDialogAndroid(
content::JavaScriptDialogType dialog_type,
const base::string16& message_text,
const base::string16& default_prompt_text,
content::JavaScriptDialogManager::DialogClosedCallback dialog_callback)
: dialog_callback_(std::move(dialog_callback)), weak_factory_(this) {
content::JavaScriptDialogManager::DialogClosedCallback
callback_on_button_clicked,
base::OnceClosure callback_on_cancelled)
: callback_on_button_clicked_(std::move(callback_on_button_clicked)),
callback_on_cancelled_(std::move(callback_on_cancelled)),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
JNIEnv* env = AttachCurrentThread();
......
......@@ -23,6 +23,10 @@ class JavaScriptDialogAndroid : public JavaScriptDialog {
public:
~JavaScriptDialogAndroid() override;
// Note on the two callbacks: |dialog_callback_on_button_clicked| is for the
// case where user responds to the dialog. |dialog_callback_on_cancelled| is
// for the case where user cancels the dialog without interacting with the
// dialog (e.g. clicks the navigate back button on Android).
static base::WeakPtr<JavaScriptDialogAndroid> Create(
content::WebContents* parent_web_contents,
content::WebContents* alerting_web_contents,
......@@ -30,7 +34,9 @@ class JavaScriptDialogAndroid : public JavaScriptDialog {
content::JavaScriptDialogType dialog_type,
const base::string16& message_text,
const base::string16& default_prompt_text,
content::JavaScriptDialogManager::DialogClosedCallback dialog_callback);
content::JavaScriptDialogManager::DialogClosedCallback
callback_on_button_clicked,
base::OnceClosure callback_on_cancelled);
// JavaScriptDialog:
void CloseDialogWithoutCallback() override;
......@@ -39,23 +45,28 @@ class JavaScriptDialogAndroid : public JavaScriptDialog {
void Accept(JNIEnv* env,
const base::android::JavaParamRef<jobject>&,
const base::android::JavaParamRef<jstring>& prompt);
void Cancel(JNIEnv* env, const base::android::JavaParamRef<jobject>&);
void Cancel(JNIEnv* env,
const base::android::JavaParamRef<jobject>&,
jboolean button_clicked);
private:
JavaScriptDialogAndroid(
content::WebContents* parent_web_contents,
content::WebContents* alerting_web_contents,
const base::string16& title,
content::JavaScriptDialogType dialog_type,
const base::string16& message_text,
const base::string16& default_prompt_text,
content::JavaScriptDialogManager::DialogClosedCallback dialog_callback);
JavaScriptDialogAndroid(content::WebContents* parent_web_contents,
content::WebContents* alerting_web_contents,
const base::string16& title,
content::JavaScriptDialogType dialog_type,
const base::string16& message_text,
const base::string16& default_prompt_text,
content::JavaScriptDialogManager::DialogClosedCallback
callback_on_button_clicked,
base::OnceClosure callback_on_cancelled);
std::unique_ptr<JavaScriptDialogAndroid> dialog_;
ScopedJavaGlobalRef<jobject> dialog_jobject_;
JavaObjectWeakGlobalRef jwindow_weak_ref_;
content::JavaScriptDialogManager::DialogClosedCallback dialog_callback_;
content::JavaScriptDialogManager::DialogClosedCallback
callback_on_button_clicked_;
base::OnceClosure callback_on_cancelled_;
base::WeakPtrFactory<JavaScriptDialogAndroid> weak_factory_;
......
......@@ -150,7 +150,8 @@ base::WeakPtr<JavaScriptDialog> CreateNewDialog(
#if defined(OS_ANDROID)
return JavaScriptDialogAndroid::Create(
parent_web_contents, alerting_web_contents, title, dialog_type,
message_text, default_prompt_text, std::move(dialog_callback));
message_text, default_prompt_text, std::move(dialog_callback),
std::move(dialog_closed_callback));
#else
return JavaScriptDialogViews::Create(
parent_web_contents, alerting_web_contents, title, dialog_type,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment