Commit f7ad46e7 authored by Evan Stade's avatar Evan Stade Committed by Commit Bot

WebLayer: basic JS dialog support for Android.

This hooks up app-modal JS dialogs without adding any new APIs.

Most functionality is "free" i.e. already componentized, including
- alerts, prompts, and confirms (such as onbeforeunload)
- anti-spam suppressions ("prevent this webpage..." checkbox)

We will want to convert all except onBeforeUnload confirmations to
tab-modal, but we'll still need app-modal for onBeforeUnload, so this
is a reasonable stepping stone.

TBR=twellington@chromium.org

Bug: 1025256
Change-Id: I66575619dbfafcc0a575082c23f1e567568c29d5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1979154
Commit-Queue: Evan Stade <estade@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarClark DuVall <cduvall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#741168}
parent 05f6503f
......@@ -6,9 +6,10 @@ package org.chromium.components.browser_ui.modaldialog;
import android.app.Activity;
import android.app.Dialog;
import android.support.v4.view.ViewCompat;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.VisibleForTesting;
......@@ -21,7 +22,7 @@ import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/** The presenter that shows a {@link ModalDialogView} in an Android dialog. */
public class AppModalPresenter extends ModalDialogManager.Presenter {
private final Activity mActivity;
private final Context mContext;
private Dialog mDialog;
private PropertyModelChangeProcessor<PropertyModel, ModalDialogView, PropertyKey>
mModelChangeProcessor;
......@@ -41,25 +42,16 @@ public class AppModalPresenter extends ModalDialogManager.Presenter {
/**
* @param activity The {@link Activity} on which dialog views will be created and shown.
*/
public AppModalPresenter(Activity activity) {
mActivity = activity;
public AppModalPresenter(Context context) {
mContext = context;
}
@Override
protected void addDialogView(PropertyModel model) {
// If the activity's decor view is not attached to window, we don't show the dialog because
// the window manager might have revoked the window token for this activity. See
// https://crbug.com/926688.
Window window = mActivity.getWindow();
if (window == null || !ViewCompat.isAttachedToWindow(window.getDecorView())) {
dismissCurrentDialog(DialogDismissalCause.NOT_ATTACHED_TO_WINDOW);
return;
}
int style = model.get(ModalDialogProperties.PRIMARY_BUTTON_FILLED)
? R.style.Theme_Chromium_ModalDialog_FilledPrimaryButton
: R.style.Theme_Chromium_ModalDialog_TextPrimaryButton;
mDialog = new Dialog(mActivity, style);
mDialog = new Dialog(mContext, style);
mDialog.setOnCancelListener(dialogInterface
-> dismissCurrentDialog(DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE));
// Cancel on touch outside should be disabled by default. The ModelChangeProcessor wouldn't
......@@ -70,7 +62,15 @@ public class AppModalPresenter extends ModalDialogManager.Presenter {
mModelChangeProcessor =
PropertyModelChangeProcessor.create(model, dialogView, new ViewBinder());
mDialog.setContentView(dialogView);
mDialog.show();
try {
mDialog.show();
} catch (WindowManager.BadTokenException badToken) {
// See https://crbug.com/926688.
dismissCurrentDialog(DialogDismissalCause.NOT_ATTACHED_TO_WINDOW);
return;
}
dialogView.announceForAccessibility(getContentDescription(model));
}
......
......@@ -318,6 +318,7 @@ jumbo_static_library("weblayer_lib") {
"//components/autofill/android:provider",
"//components/crash/android:crashpad_main",
"//components/embedder_support/android/metrics",
"//components/javascript_dialogs",
"//components/metrics",
"//components/minidump_uploader",
"//components/safe_browsing/content/common:interfaces",
......
......@@ -5,11 +5,13 @@ include_rules = [
"+components/autofill/core/browser",
"+components/autofill/core/common",
"+components/base32",
"+components/browser_ui",
"+components/captive_portal",
"+components/crash/content/browser",
"+components/download/public/common",
"+components/embedder_support",
"+components/find_in_page",
"+components/javascript_dialogs",
"+components/keyed_service/content",
"+components/metrics",
"+components/network_time",
......
......@@ -68,6 +68,7 @@ android_library("java") {
"//base:base_java",
"//base:jni_java",
"//components/autofill/android:provider_java",
"//components/browser_ui/modaldialog/android:java",
"//components/browser_ui/styles/android:java",
"//components/browser_ui/util/android:java",
"//components/crash/android:handler_java",
......@@ -76,6 +77,7 @@ android_library("java") {
"//components/embedder_support/android:application_java",
"//components/embedder_support/android:web_contents_delegate_java",
"//components/find_in_page/android:java",
"//components/javascript_dialogs/android:java",
"//components/metrics:metrics_java",
"//components/minidump_uploader:minidump_uploader_java",
"//components/security_interstitials/content/android:java",
......
......@@ -9,8 +9,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate;
import org.chromium.ui.base.IntentWindowAndroid;
import org.chromium.ui.modaldialog.ModalDialogManager;
import java.lang.ref.WeakReference;
......@@ -20,6 +22,7 @@ import java.lang.ref.WeakReference;
*/
public class FragmentWindowAndroid extends IntentWindowAndroid {
private BrowserFragmentImpl mFragment;
private ModalDialogManager mModalDialogManager;
FragmentWindowAndroid(Context context, BrowserFragmentImpl fragment) {
super(context);
......@@ -44,4 +47,22 @@ public class FragmentWindowAndroid extends IntentWindowAndroid {
public final WeakReference<Activity> getActivity() {
return new WeakReference<>(mFragment.getActivity());
}
@Override
public final ModalDialogManager getModalDialogManager() {
if (mModalDialogManager == null) {
mModalDialogManager = new ModalDialogManager(new AppModalPresenter(getContext().get()),
ModalDialogManager.ModalDialogType.APP);
}
return mModalDialogManager;
}
@Override
public void destroy() {
if (mModalDialogManager != null) {
mModalDialogManager.destroy();
mModalDialogManager = null;
}
super.destroy();
}
}
......@@ -48,6 +48,9 @@
#include "base/trace_event/trace_event.h"
#include "components/autofill/android/autofill_provider_android.h"
#include "components/embedder_support/android/delegate/color_chooser_android.h"
#include "components/javascript_dialogs/android/app_modal_dialog_view_android.h" // nogncheck
#include "components/javascript_dialogs/app_modal_dialog_manager.h" // nogncheck
#include "ui/android/view_android.h"
#include "weblayer/browser/controls_visibility_reason.h"
#include "weblayer/browser/java/jni/TabImpl_jni.h"
#include "weblayer/browser/top_controls_container_view.h"
......@@ -293,6 +296,10 @@ void TabImpl::AttachToView(views::WebView* web_view) {
}
#endif
bool TabImpl::IsActive() {
return browser_->GetActiveTab() == this;
}
#if defined(OS_ANDROID)
// static
void TabImpl::DisableAutofillSystemIntegrationForTesting() {
......@@ -414,6 +421,32 @@ void TabImpl::DidNavigateMainFramePostCommit(
observer.DisplayedUrlChanged(web_contents->GetVisibleURL());
}
content::JavaScriptDialogManager* TabImpl::GetJavaScriptDialogManager(
content::WebContents* web_contents) {
#if defined(OS_ANDROID)
// Simply ignore javascript dialog requests from non-foremost tabs.
// TODO(crbug.com/1025256): revisit this behavior.
if (!IsActive())
return nullptr;
auto* dialog_manager =
javascript_dialogs::AppModalDialogManager::GetInstance();
if (dialog_manager->view_factory()->is_null()) {
dialog_manager->SetNativeDialogFactory(base::BindRepeating(
[](javascript_dialogs::AppModalDialogController* controller)
-> javascript_dialogs::AppModalDialogView* {
return new javascript_dialogs::AppModalDialogViewAndroid(
base::android::AttachCurrentThread(), controller,
controller->web_contents()->GetTopLevelNativeWindow());
}));
}
return dialog_manager;
#else
return nullptr;
#endif
}
content::ColorChooser* TabImpl::OpenColorChooser(
content::WebContents* web_contents,
SkColor color,
......
......@@ -75,6 +75,8 @@ class TabImpl : public Tab,
bool has_new_tab_delegate() const { return new_tab_delegate_ != nullptr; }
bool IsActive();
#if defined(OS_ANDROID)
base::android::ScopedJavaGlobalRef<jobject> GetJavaTab() {
return java_impl_;
......@@ -146,6 +148,8 @@ class TabImpl : public Tab,
const content::OpenURLParams& params) override;
void DidNavigateMainFramePostCommit(
content::WebContents* web_contents) override;
content::JavaScriptDialogManager* GetJavaScriptDialogManager(
content::WebContents* web_contents) override;
content::ColorChooser* OpenColorChooser(
content::WebContents* web_contents,
SkColor color,
......
IDS_BEFORERELOAD_APP_MESSAGEBOX_TITLE
IDS_BEFORERELOAD_MESSAGEBOX_TITLE
IDS_BEFOREUNLOAD_APP_MESSAGEBOX_TITLE
IDS_BEFOREUNLOAD_MESSAGEBOX_TITLE
IDS_CAPTIVE_PORTAL_BUTTON_OPEN_LOGIN_PAGE
IDS_CAPTIVE_PORTAL_HEADING_WIFI
IDS_CAPTIVE_PORTAL_HEADING_WIRED
IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIFI
IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIRED
IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIFI_SSID
IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIRED
IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_WIFI
IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_WIRED
IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_WIFI_SSID
IDS_CERT_ERROR_AUTHORITY_INVALID_DESCRIPTION
IDS_CERT_ERROR_AUTHORITY_INVALID_DETAILS
IDS_CERT_ERROR_CERTIFICATE_TRANSPARENCY_REQUIRED_DESCRIPTION
IDS_CERT_ERROR_CERTIFICATE_TRANSPARENCY_REQUIRED_DETAILS
IDS_CERT_ERROR_COMMON_NAME_INVALID_DESCRIPTION
IDS_CERT_ERROR_COMMON_NAME_INVALID_DETAILS
IDS_CERT_ERROR_CONTAINS_ERRORS_DESCRIPTION
IDS_CERT_ERROR_CONTAINS_ERRORS_DETAILS
IDS_CERT_ERROR_EXPIRED_DESCRIPTION
IDS_CERT_ERROR_EXPIRED_DETAILS
IDS_CERT_ERROR_INVALID_CERT_DESCRIPTION
IDS_CERT_ERROR_INVALID_CERT_DETAILS
IDS_CERT_ERROR_NAME_CONSTRAINT_VIOLATION_DESCRIPTION
IDS_CERT_ERROR_NAME_CONSTRAINT_VIOLATION_DETAILS
IDS_CERT_ERROR_NOT_VALID_AT_THIS_TIME_DESCRIPTION
IDS_CERT_ERROR_NOT_VALID_AT_THIS_TIME_DETAILS
IDS_CERT_ERROR_NOT_YET_VALID_DESCRIPTION
IDS_CERT_ERROR_NOT_YET_VALID_DETAILS
IDS_CERT_ERROR_NO_REVOCATION_MECHANISM_DESCRIPTION
IDS_CERT_ERROR_NO_REVOCATION_MECHANISM_DETAILS
IDS_CERT_ERROR_NO_SUBJECT_ALTERNATIVE_NAMES_DETAILS
IDS_CERT_ERROR_REVOKED_CERT_DESCRIPTION
IDS_CERT_ERROR_REVOKED_CERT_DETAILS
IDS_CERT_ERROR_SUMMARY_PINNING_FAILURE_DESCRIPTION
IDS_CERT_ERROR_SUMMARY_PINNING_FAILURE_DETAILS
IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_DESCRIPTION
IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_DETAILS
IDS_CERT_ERROR_UNKNOWN_ERROR_DESCRIPTION
IDS_CERT_ERROR_UNKNOWN_ERROR_DETAILS
IDS_CERT_ERROR_VALIDITY_TOO_LONG_DESCRIPTION
IDS_CERT_ERROR_VALIDITY_TOO_LONG_DETAILS
IDS_CERT_ERROR_WEAK_KEY_DESCRIPTION
IDS_CERT_ERROR_WEAK_KEY_DETAILS
IDS_CERT_ERROR_WEAK_SIGNATURE_ALGORITHM_DESCRIPTION
IDS_CERT_ERROR_WEAK_SIGNATURE_ALGORITHM_DETAILS
IDS_CLOCK_ERROR_AHEAD_HEADING
IDS_CLOCK_ERROR_BEHIND_HEADING
IDS_CLOCK_ERROR_EXPLANATION
IDS_CLOCK_ERROR_PRIMARY_PARAGRAPH
IDS_CLOCK_ERROR_TITLE
IDS_CLOCK_ERROR_UPDATE_DATE_AND_TIME
IDS_SSL_OPEN_DETAILS_BUTTON
IDS_JAVASCRIPT_MESSAGEBOX_TITLE
IDS_JAVASCRIPT_MESSAGEBOX_TITLE_IFRAME
IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL
IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL_IFRAME
IDS_SSL_CLOSE_DETAILS_BUTTON
IDS_SSL_NONOVERRIDABLE_HSTS
IDS_SSL_NONOVERRIDABLE_INVALID
IDS_SSL_NONOVERRIDABLE_MORE
IDS_SSL_NONOVERRIDABLE_PINNED
IDS_SSL_NONOVERRIDABLE_REVOKED
IDS_SSL_OVERRIDABLE_SAFETY_BUTTON
IDS_SSL_OPEN_DETAILS_BUTTON
IDS_SSL_OVERRIDABLE_PROCEED_PARAGRAPH
IDS_SSL_OVERRIDABLE_SAFETY_BUTTON
IDS_SSL_RELOAD
IDS_SSL_V2_TITLE
IDS_SSL_V2_HEADING
IDS_SSL_V2_PRIMARY_PARAGRAPH
IDS_SSL_V2_RECURRENT_ERROR_PARAGRAPH
IDS_CERT_ERROR_COMMON_NAME_INVALID_DETAILS
IDS_CERT_ERROR_COMMON_NAME_INVALID_DESCRIPTION
IDS_CERT_ERROR_EXPIRED_DETAILS
IDS_CERT_ERROR_EXPIRED_DESCRIPTION
IDS_CERT_ERROR_NOT_YET_VALID_DETAILS
IDS_CERT_ERROR_NOT_YET_VALID_DESCRIPTION
IDS_CERT_ERROR_NOT_VALID_AT_THIS_TIME_DETAILS
IDS_CERT_ERROR_NOT_VALID_AT_THIS_TIME_DESCRIPTION
IDS_CERT_ERROR_AUTHORITY_INVALID_DETAILS
IDS_CERT_ERROR_AUTHORITY_INVALID_DESCRIPTION
IDS_CERT_ERROR_CONTAINS_ERRORS_DETAILS
IDS_CERT_ERROR_CONTAINS_ERRORS_DESCRIPTION
IDS_CERT_ERROR_NO_REVOCATION_MECHANISM_DETAILS
IDS_CERT_ERROR_NO_REVOCATION_MECHANISM_DESCRIPTION
IDS_CERT_ERROR_REVOKED_CERT_DETAILS
IDS_CERT_ERROR_REVOKED_CERT_DESCRIPTION
IDS_CERT_ERROR_INVALID_CERT_DETAILS
IDS_CERT_ERROR_INVALID_CERT_DESCRIPTION
IDS_CERT_ERROR_WEAK_SIGNATURE_ALGORITHM_DETAILS
IDS_CERT_ERROR_WEAK_SIGNATURE_ALGORITHM_DESCRIPTION
IDS_CERT_ERROR_WEAK_KEY_DETAILS
IDS_CERT_ERROR_WEAK_KEY_DESCRIPTION
IDS_CERT_ERROR_NAME_CONSTRAINT_VIOLATION_DETAILS
IDS_CERT_ERROR_NAME_CONSTRAINT_VIOLATION_DESCRIPTION
IDS_CERT_ERROR_VALIDITY_TOO_LONG_DETAILS
IDS_CERT_ERROR_VALIDITY_TOO_LONG_DESCRIPTION
IDS_CERT_ERROR_SUMMARY_PINNING_FAILURE_DETAILS
IDS_CERT_ERROR_SUMMARY_PINNING_FAILURE_DESCRIPTION
IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_DETAILS
IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_DESCRIPTION
IDS_CERT_ERROR_CERTIFICATE_TRANSPARENCY_REQUIRED_DETAILS
IDS_CERT_ERROR_CERTIFICATE_TRANSPARENCY_REQUIRED_DESCRIPTION
IDS_CERT_ERROR_UNKNOWN_ERROR_DETAILS
IDS_CERT_ERROR_UNKNOWN_ERROR_DESCRIPTION
IDS_CERT_ERROR_NO_SUBJECT_ALTERNATIVE_NAMES_DETAILS
IDS_SSL_V2_TITLE
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