0

Reland "WebLayer: add form repost confirmation dialog."

This relands commit ce01fae55d but instead
of including dialog_strings.grdp from two different grd targets, it uses
the WebLayer components string whitelist. A small amount of extra work
had to be done to package the strings tagged
formatter_data="android_java" in components_strings.grd into a java
string target.

TBR=tedchoc@chromium.org

Bug: 1058495
Change-Id: I94aae6e9f75f5252123fb83b6ade28cbf5c1f3ba
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2124699
Reviewed-by: Evan Stade <estade@chromium.org>
Reviewed-by: Clark DuVall <cduvall@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Commit-Queue: Evan Stade <estade@chromium.org>
Cr-Commit-Position: refs/heads/master@{#754138}
This commit is contained in:
Evan Stade
2020-03-27 20:14:12 +00:00
committed by Commit Bot
parent ece7729f5c
commit 45a5782ca5
15 changed files with 200 additions and 42 deletions
chrome/android/java/src/org/chromium/chrome/browser/tab_activity_glue
components
ui/android
BUILD.gn
java
src
org
weblayer

@ -46,6 +46,7 @@ import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modaldialog.SimpleModalDialogController;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.mojom.WindowOpenDisposition;
@ -374,36 +375,22 @@ public class ActivityTabWebContentsDelegateAndroid extends TabWebContentsDelegat
}
ModalDialogManager modalDialogManager = mActivity.getModalDialogManager();
ModalDialogProperties.Controller dialogController = new ModalDialogProperties.Controller() {
@Override
public void onClick(PropertyModel model, int buttonType) {
if (buttonType == ModalDialogProperties.ButtonType.POSITIVE) {
modalDialogManager.dismissDialog(
model, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
} else if (buttonType == ModalDialogProperties.ButtonType.NEGATIVE) {
modalDialogManager.dismissDialog(
model, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
}
}
@Override
public void onDismiss(PropertyModel model, int dismissalCause) {
if (!mTab.isInitialized()) return;
switch (dismissalCause) {
case DialogDismissalCause.POSITIVE_BUTTON_CLICKED:
mTab.getWebContents().getNavigationController().continuePendingReload();
break;
case DialogDismissalCause.ACTIVITY_DESTROYED:
case DialogDismissalCause.TAB_DESTROYED:
// Intentionally ignored as the tab object is gone.
break;
default:
mTab.getWebContents().getNavigationController().cancelPendingReload();
break;
}
}
};
ModalDialogProperties.Controller dialogController =
new SimpleModalDialogController(modalDialogManager, (Integer dismissalCause) -> {
if (!mTab.isInitialized()) return;
switch (dismissalCause) {
case DialogDismissalCause.POSITIVE_BUTTON_CLICKED:
mTab.getWebContents().getNavigationController().continuePendingReload();
break;
case DialogDismissalCause.ACTIVITY_DESTROYED:
case DialogDismissalCause.TAB_DESTROYED:
// Intentionally ignored as the tab object is gone.
break;
default:
mTab.getWebContents().getNavigationController().cancelPendingReload();
break;
}
});
Resources resources = mActivity.getResources();
PropertyModel dialogModel =

@ -179,7 +179,7 @@
<message name="IDS_INFOBAR_MISSING_LOCATION_PERMISSION_TEXT" desc="Text shown in an infobar when a website has requested access to the location capabilities, but Chrome is missing the Android location permission.">
Chrome needs access to your location to share your location with this site.
</message>
<message name="IDS_INFOBAR_UPDATE_PERMISSIONS_BUTTON_TEXT" desc="Button text shown when Chrome does not have the necessary permission required to complete the requested tasks (e.g. a website has request location information, but Chrome is missing that Android permission)." formatter_data="android_java">
<message name="IDS_INFOBAR_UPDATE_PERMISSIONS_BUTTON_TEXT" desc="Button text shown when Chrome does not have the necessary permission required to complete the requested tasks (e.g. a website has request location information, but Chrome is missing that Android permission).">
Continue
</message>

@ -45,7 +45,7 @@
<message name="IDS_STORAGE_ACCESS_INFOBAR_TEXT" desc="Permission request shown if the user is visiting a site needs access to its data while it is embedded into another site.">
<ph name="EMBEDDED_URL">$1<ex>news.site</ex></ph> wants to use cookies and site data on <ph name="TOP_LEVEL_URL">$2<ex>content_domain.site</ex></ph>
</message>
<message name="IDS_INFOBAR_UPDATE_PERMISSIONS_BUTTON_TEXT" desc="Button text shown when Chrome does not have the necessary permission required to complete the requested tasks (e.g. a website has request location information, but Chrome is missing that Android permission)." formatter_data="android_java">
<message name="IDS_INFOBAR_UPDATE_PERMISSIONS_BUTTON_TEXT" desc="Button text shown when Chrome does not have the necessary permission required to complete the requested tasks (e.g. a website has request location information, but Chrome is missing that Android permission).">
Continue
</message>
<message name="IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO_INFOBAR_TEXT" desc="Text requesting permission for a site to access the computer's microphone and camera.">

@ -322,6 +322,7 @@ android_library("ui_full_java") {
"java/src/org/chromium/ui/modaldialog/ModalDialogManager.java",
"java/src/org/chromium/ui/modaldialog/ModalDialogManagerHolder.java",
"java/src/org/chromium/ui/modaldialog/ModalDialogProperties.java",
"java/src/org/chromium/ui/modaldialog/SimpleModalDialogController.java",
"java/src/org/chromium/ui/modelutil/ForwardingListObservable.java",
"java/src/org/chromium/ui/modelutil/LayoutViewBuilder.java",
"java/src/org/chromium/ui/modelutil/LazyConstructionPropertyMcp.java",

@ -0,0 +1,46 @@
// 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.ui.modaldialog;
import androidx.annotation.NonNull;
import org.chromium.base.Callback;
import org.chromium.ui.modelutil.PropertyModel;
/**
* A default implementation of Controller which dismisses the dialog when a button is clicked.
*
* The result of the dialog is passed back via a Callback.
*/
public class SimpleModalDialogController implements ModalDialogProperties.Controller {
private final ModalDialogManager mModalDialogManager;
private Callback<Integer> mActionCallback;
/**
* @param modalDialogManager the dialog manager where the dialog will be shown.
* @param action a callback which will be run with the result of the confirmation.
*/
public SimpleModalDialogController(
ModalDialogManager modalDialogManager, @NonNull Callback<Integer> action) {
mModalDialogManager = modalDialogManager;
mActionCallback = action;
}
@Override
public void onClick(PropertyModel model, int buttonType) {
if (buttonType == ModalDialogProperties.ButtonType.POSITIVE) {
mModalDialogManager.dismissDialog(model, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
} else {
mModalDialogManager.dismissDialog(model, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
}
}
@Override
public void onDismiss(PropertyModel model, int dismissalCause) {
Callback<Integer> action = mActionCallback;
mActionCallback = null;
action.onResult(dismissalCause);
}
}

@ -18,6 +18,7 @@ import("//tools/grit/repack.gni")
import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
if (is_android) {
import("//build/config/android/config.gni")
import("//build/config/android/rules.gni")
} else if (is_mac) {
import("//build/config/mac/rules.gni")
import("//build/mac/tweak_info_plist.gni")
@ -33,6 +34,12 @@ source_set("android_descriptors") {
}
if (is_android) {
weblayer_components_strings_java_resources =
[ "java/res/values/components_strings.xml" ] +
process_file_template(
android_bundle_locales_as_resources,
[ "java/res/values-{{source_name_part}}/components_strings.xml" ])
grit("generate_components_strings") {
source = "../components/components_strings.grd"
@ -46,13 +53,8 @@ if (is_android) {
whitelist,
]
outputs =
[
"grit/components_strings.h",
"java/res/values/components_strings.xml",
] +
process_file_template(
android_bundle_locales_as_resources,
[ "java/res/values-{{source_name_part}}/components_strings.xml" ]) +
weblayer_components_strings_java_resources +
[ "grit/components_strings.h" ] +
process_file_template(locales_with_fake_bidi,
[ "components_strings_{{source_name_part}}.pak" ])
}
@ -84,6 +86,13 @@ if (is_android) {
treat_as_locale_paks = true
deps = [ ":weblayer_locales" ]
}
java_strings_grd_prebuilt("components_java_strings") {
grit_output_dir = "$root_gen_dir/weblayer/java/res"
generated_files =
rebase_path(weblayer_components_strings_java_resources, "java/res", ".")
deps = [ ":generate_components_strings" ]
}
}
source_set("weblayer_lib_base") {

@ -7,6 +7,7 @@ package org.chromium.weblayer.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
@ -27,6 +28,7 @@ import org.chromium.weblayer.NavigationCallback;
import org.chromium.weblayer.NavigationController;
import org.chromium.weblayer.NavigationState;
import org.chromium.weblayer.Tab;
import org.chromium.weblayer.TabCallback;
import org.chromium.weblayer.shell.InstrumentationActivity;
import java.util.ArrayList;
@ -442,6 +444,41 @@ public class NavigationTest {
assertEquals(mCallback.onCompletedCallback.getNavigationState(), NavigationState.COMPLETE);
}
@Test
@SmallTest
public void testRepostConfirmation() throws Exception {
// Load a page with a form.
InstrumentationActivity activity =
mActivityTestRule.launchShellWithUrl(mActivityTestRule.getTestDataURL("form.html"));
assertNotNull(activity);
setNavigationCallback(activity);
// Touch the page; this should submit the form.
int currentCallCount = mCallback.onCompletedCallback.getCallCount();
EventUtils.simulateTouchCenterOfView(activity.getWindow().getDecorView());
String targetUrl = mActivityTestRule.getTestDataURL("simple_page.html");
mCallback.onCompletedCallback.assertCalledWith(currentCallCount, targetUrl);
// Make sure a tab modal shows after we attempt a reload.
Boolean isTabModalShowingResult[] = new Boolean[1];
CallbackHelper callbackHelper = new CallbackHelper();
runOnUiThreadBlocking(() -> {
Tab tab = activity.getTab();
TabCallback callback = new TabCallback() {
@Override
public void onTabModalStateChanged(boolean isTabModalShowing) {
isTabModalShowingResult[0] = isTabModalShowing;
callbackHelper.notifyCalled();
}
};
tab.registerTabCallback(callback);
tab.getNavigationController().reload();
});
callbackHelper.waitForFirst();
assertTrue(isTabModalShowingResult[0]);
}
private void setNavigationCallback(InstrumentationActivity activity) {
runOnUiThreadBlocking(
()

@ -9,7 +9,11 @@ import("//weblayer/variables.gni")
android_resources("weblayer_resources") {
sources = [ "res/layout/weblayer_url_bar.xml" ]
custom_package = "org.chromium.weblayer_private"
deps = [ "//components/permissions/android:java_resources" ]
deps = [
"//components/browser_ui/strings/android:browser_ui_strings_grd",
"//components/permissions/android:java_resources",
"//weblayer:components_java_strings",
]
}
generate_product_config_srcjar("weblayer_product_config") {

@ -5,6 +5,7 @@
package org.chromium.weblayer_private;
import android.content.Context;
import android.content.res.Resources;
import android.os.RemoteException;
import android.util.AndroidRuntimeException;
import android.view.View;
@ -20,6 +21,8 @@ import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modaldialog.SimpleModalDialogController;
import org.chromium.ui.modelutil.PropertyModel;
/**
@ -223,4 +226,39 @@ public final class BrowserViewController
return mModalDialogManager.dismissActiveDialogOfType(
ModalDialogType.TAB, DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE);
}
/**
* Asks the user to confirm a page reload on a POSTed page.
*/
public void showRepostFormWarningDialog() {
ModalDialogProperties.Controller dialogController =
new SimpleModalDialogController(mModalDialogManager, (Integer dismissalCause) -> {
WebContents webContents = mTab == null ? null : mTab.getWebContents();
if (webContents == null) return;
switch (dismissalCause) {
case DialogDismissalCause.POSITIVE_BUTTON_CLICKED:
webContents.getNavigationController().continuePendingReload();
break;
default:
webContents.getNavigationController().cancelPendingReload();
break;
}
});
Resources resources = mWindowAndroid.getContext().get().getResources();
PropertyModel dialogModel =
new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
.with(ModalDialogProperties.CONTROLLER, dialogController)
.with(ModalDialogProperties.TITLE, resources,
R.string.http_post_warning_title)
.with(ModalDialogProperties.MESSAGE, resources, R.string.http_post_warning)
.with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, resources,
R.string.http_post_warning_resend)
.with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, resources,
R.string.cancel)
.with(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE, true)
.build();
mModalDialogManager.showDialog(dialogModel, ModalDialogManager.ModalDialogType.TAB, true);
}
}

@ -560,6 +560,16 @@ public final class TabImpl extends ITab.Stub {
mBrowserControlsDelegates.get(reason).set(constraint);
}
@CalledByNative
public void showRepostFormWarningDialog() {
BrowserViewController viewController = getViewController();
if (viewController == null) {
mWebContents.getNavigationController().cancelPendingReload();
} else {
viewController.showRepostFormWarningDialog();
}
}
private static String nonEmptyOrNull(String s) {
return TextUtils.isEmpty(s) ? null : s;
}

@ -113,7 +113,7 @@ void NavigationControllerImpl::GoToIndex(int index) {
}
void NavigationControllerImpl::Reload() {
web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
web_contents()->GetController().Reload(content::ReloadType::NORMAL, true);
}
void NavigationControllerImpl::Stop() {

@ -449,6 +449,15 @@ content::WebContents* TabImpl::OpenURLFromTab(
return source;
}
void TabImpl::ShowRepostFormWarningDialog(content::WebContents* source) {
#if defined(OS_ANDROID)
Java_TabImpl_showRepostFormWarningDialog(base::android::AttachCurrentThread(),
java_impl_);
#else
source->GetController().CancelPendingReload();
#endif
}
void TabImpl::NavigationStateChanged(content::WebContents* source,
content::InvalidateTypes changed_flags) {
DCHECK_EQ(web_contents_.get(), source);

@ -152,6 +152,7 @@ class TabImpl : public Tab,
content::WebContents* OpenURLFromTab(
content::WebContents* source,
const content::OpenURLParams& params) override;
void ShowRepostFormWarningDialog(content::WebContents* source) override;
void NavigationStateChanged(content::WebContents* source,
content::InvalidateTypes changed_flags) override;
content::JavaScriptDialogManager* GetJavaScriptDialogManager(

@ -62,7 +62,9 @@ IDS_FLASH_PERMISSION_WARNING_FRAGMENT
IDS_GEOLOCATION_INFOBAR_PERMISSION_FRAGMENT
IDS_GEOLOCATION_INFOBAR_TEXT
IDS_GEOLOCATION_INFOBAR_TEXT
IDS_INFOBAR_UPDATE_PERMISSIONS_BUTTON_TEXT
IDS_HTTP_POST_WARNING
IDS_HTTP_POST_WARNING_RESEND
IDS_HTTP_POST_WARNING_TITLE
IDS_JAVASCRIPT_MESSAGEBOX_TITLE
IDS_JAVASCRIPT_MESSAGEBOX_TITLE_IFRAME
IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL

@ -0,0 +1,14 @@
<html>
<body>
<form id="form1" action="simple_page.html" method="post">
<input type="text" name="name" value="Name"><br>
<input type="text" name="address" value="Address"><br>
<input type="text" name="city" value="City"><br>
</form>
</body>
<script>
document.addEventListener('touchend', function(e) {
document.forms['form1'].submit();
}, false);
</script>
</html>