Implement cancelling card unmask authentication selection on iOS.
Before tapping the cancel button did nothing. Now, the card unmask
authentication selection bottom sheet is dismissed (and metrics are
logged).
Screencap: https://drive.google.com/file/d/1tu5euQcOFQEHD6EMYDQcWRuCklW8P2Qk/view?usp=sharing&resourcekey=0-nE9pkf01wdbFCGSdl3XyiQ
Bug: 40282545, b/324887950
Change-Id: Ia8466af593094236dd5529c11405628cd7f10066
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5402067
Reviewed-by: Siyu An <siyua@chromium.org>
Reviewed-by: Tommy Martino <tmartino@chromium.org>
Commit-Queue: Slobodan Pejic <slobodan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1283376}
diff --git a/ios/chrome/browser/ui/autofill/authentication/BUILD.gn b/ios/chrome/browser/ui/autofill/authentication/BUILD.gn
index d780b2c..bc3ddb6 100644
--- a/ios/chrome/browser/ui/autofill/authentication/BUILD.gn
+++ b/ios/chrome/browser/ui/autofill/authentication/BUILD.gn
@@ -60,6 +60,7 @@
"//ios/chrome/browser/shared/coordinator/chrome_coordinator",
"//ios/chrome/browser/shared/model/browser",
"//ios/chrome/browser/shared/model/web_state_list",
+ "//ios/chrome/browser/shared/public/commands",
]
}
@@ -67,6 +68,7 @@
sources = [
"card_unmask_authentication_selection_mediator.h",
"card_unmask_authentication_selection_mediator.mm",
+ "card_unmask_authentication_selection_mediator_delegate.h",
]
deps = [
":card_unmask_authentication_selection_mutator_bridge",
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_coordinator.h b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_coordinator.h
index 34058d99..80b85ce 100644
--- a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_coordinator.h
+++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_coordinator.h
@@ -6,6 +6,7 @@
#define IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_CARD_UNMASK_AUTHENTICATION_SELECTION_COORDINATOR_H_
#import <Foundation/Foundation.h>
+
#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h"
// This class coordinates the selecting the authentication method for unmasking
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_coordinator.mm b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_coordinator.mm
index a68fea1..fa1f115 100644
--- a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_coordinator.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_coordinator.mm
@@ -8,9 +8,16 @@
#import "ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
+#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
+#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.h"
+#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_delegate.h"
#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_view_controller.h"
+@interface CardUnmaskAuthenticationSelectionCoordinator () <
+ CardUnmaskAuthenticationSelectionMediatorDelegate>
+@end
+
@implementation CardUnmaskAuthenticationSelectionCoordinator {
// A reference to the base view controller with UINavigationController type.
__weak UINavigationController* _baseNavigationController;
@@ -27,6 +34,8 @@
_modelController;
std::unique_ptr<CardUnmaskAuthenticationSelectionMediator> _mediator;
+
+ id<BrowserCoordinatorCommands> _browserCoordinatorCommands;
}
- (instancetype)initWithBaseNavigationController:
@@ -40,6 +49,8 @@
browser->GetWebStateList()->GetActiveWebState());
_modelController =
tabHelper->GetCardUnmaskAuthenticationSelectionDialogController();
+ _browserCoordinatorCommands = HandlerForProtocol(
+ browser->GetCommandDispatcher(), BrowserCoordinatorCommands);
CHECK(_modelController);
}
return self;
@@ -53,6 +64,7 @@
_mediator = std::make_unique<CardUnmaskAuthenticationSelectionMediator>(
_modelController->GetWeakPtr(),
/*consumer=*/selectionViewController);
+ _mediator->set_delegate(self);
selectionViewController.mutator = _mediator->AsMutator();
_selectionViewController = selectionViewController;
@@ -65,4 +77,10 @@
_selectionViewController.mutator = nil;
}
+#pragma mark - CardUnmaskAuthenticationSelectionMediatorDelegate
+
+- (void)dismissAuthenticationSelection {
+ [_browserCoordinatorCommands dismissCardUnmaskAuthentication];
+}
+
@end
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.h b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.h
index 2ed3256..45486bd 100644
--- a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.h
+++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.h
@@ -8,6 +8,7 @@
#import "base/memory/weak_ptr.h"
#import "components/autofill/core/browser/ui/payments/card_unmask_authentication_selection_dialog.h"
#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_consumer.h"
+#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_delegate.h"
#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mutator_bridge_target.h"
@protocol CardUnmaskAuthenticationSelectionMutator;
@@ -47,6 +48,12 @@
void Dismiss(bool user_closed_dialog, bool server_success) override;
void UpdateContent() override;
+ // Set the delegate of this mediator.
+ void set_delegate(
+ id<CardUnmaskAuthenticationSelectionMediatorDelegate> delegate) {
+ delegate_ = delegate;
+ }
+
// Returns an implementation of the mutator that forwards to this mediator.
// We need this bridge since this mediator is C++ whereas the ViewController
// expects the Objective-C protocol.
@@ -56,7 +63,12 @@
base::WeakPtr<autofill::CardUnmaskAuthenticationSelectionDialogControllerImpl>
model_controller_;
__weak id<CardUnmaskAuthenticationSelectionConsumer> consumer_;
+ __weak id<CardUnmaskAuthenticationSelectionMediatorDelegate> delegate_;
CardUnmaskAuthenticationSelectionMutatorBridge* mutator_bridge_;
+
+ // Set to true the prompt has been dismissed.
+ bool was_dismissed_ = false;
+
base::WeakPtrFactory<CardUnmaskAuthenticationSelectionMutatorBridgeTarget>
weak_ptr_factory_{this};
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm
index cd8c3f9..d808d6f 100644
--- a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm
@@ -7,6 +7,7 @@
#import "base/memory/weak_ptr.h"
#import "base/strings/sys_string_conversions.h"
#import "components/autofill/core/browser/ui/payments/card_unmask_authentication_selection_dialog_controller_impl.h"
+#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_delegate.h"
#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mutator_bridge.h"
namespace {
@@ -59,17 +60,23 @@
}
void CardUnmaskAuthenticationSelectionMediator::DidCancelSelection() {
- // TODO(crbug.com/40282545): Implement cancelling out of the authentication
- // selection.
+ Dismiss(/*user_closed_dialog=*/true, /*server_success=*/false);
}
// Implemention of autofill::CardUnmaskAuthenticationSelectionDialog follows:
void CardUnmaskAuthenticationSelectionMediator::Dismiss(bool user_closed_dialog,
bool server_success) {
- // TODO(crbug.com/40282545): Implement dismissal of the authentication
- // selection view by delegating to the coordinator.
+ DCHECK(!was_dismissed_);
+ was_dismissed_ = true;
model_controller_->OnDialogClosed(user_closed_dialog, server_success);
+ // If the user dismissed the authentication selection, then the dismissal
+ // needs to be delegated up through our delegate (a coordinator).
+ // If Dismiss() was called after a response from the server, then we expect
+ // another prompt to be initiated without dismissal of the overall flow.
+ if (user_closed_dialog) {
+ [delegate_ dismissAuthenticationSelection];
+ }
}
void CardUnmaskAuthenticationSelectionMediator::UpdateContent() {
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_delegate.h b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_delegate.h
new file mode 100644
index 0000000..ad52bbf
--- /dev/null
+++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_delegate.h
@@ -0,0 +1,16 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_CARD_UNMASK_AUTHENTICATION_SELECTION_MEDIATOR_DELEGATE_H_
+#define IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_CARD_UNMASK_AUTHENTICATION_SELECTION_MEDIATOR_DELEGATE_H_
+
+// The authentication selection mediator delegates dismissal to the coordinator.
+@protocol CardUnmaskAuthenticationSelectionMediatorDelegate <NSObject>
+
+// Dismiss the authentication selection view.
+- (void)dismissAuthenticationSelection;
+
+@end
+
+#endif // IOS_CHROME_BROWSER_UI_AUTOFILL_AUTHENTICATION_CARD_UNMASK_AUTHENTICATION_SELECTION_MEDIATOR_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_unittest.mm b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_unittest.mm
index 0fcb924..68ecaddd 100644
--- a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_unittest.mm
@@ -10,6 +10,7 @@
#import "components/autofill/core/browser/ui/payments/card_unmask_authentication_selection_dialog_controller_impl.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_consumer.h"
+#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_delegate.h"
#import "ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mutator.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
@@ -27,12 +28,17 @@
PlatformTest::SetUp();
consumer_ =
OCMProtocolMock(@protocol(CardUnmaskAuthenticationSelectionConsumer));
+ delegate_ = OCMProtocolMock(
+ @protocol(CardUnmaskAuthenticationSelectionMediatorDelegate));
}
void TearDown() override {
if (consumer_) {
EXPECT_OCMOCK_VERIFY((id)consumer_);
}
+ if (delegate_) {
+ EXPECT_OCMOCK_VERIFY((id)delegate_);
+ }
}
CardUnmaskAuthenticationSelectionMediator* InitializeMediator(
@@ -42,14 +48,19 @@
controller_ = std::make_unique<
autofill::CardUnmaskAuthenticationSelectionDialogControllerImpl>(
challenge_options, confirm_unmasking_method_callback_.Get(),
- base::DoNothing());
+ cancel_unmasking_closure_.Get());
mediator_ = std::make_unique<CardUnmaskAuthenticationSelectionMediator>(
controller_->GetWeakPtr(), consumer_);
+ mediator_->set_delegate(delegate_);
return mediator_.get();
}
id<CardUnmaskAuthenticationSelectionConsumer> consumer() { return consumer_; }
+ id<CardUnmaskAuthenticationSelectionMediatorDelegate> delegate() {
+ return delegate_;
+ }
+
autofill::CardUnmaskAuthenticationSelectionDialogControllerImpl*
controller() {
return controller_.get();
@@ -91,9 +102,11 @@
protected:
base::MockOnceCallback<void(const std::string&)>
confirm_unmasking_method_callback_;
+ base::MockOnceClosure cancel_unmasking_closure_;
private:
id<CardUnmaskAuthenticationSelectionConsumer> consumer_;
+ id<CardUnmaskAuthenticationSelectionMediatorDelegate> delegate_;
// Mediator listed first to destruct the controller (which holds a reference
// to the mediator) before the mediator.
std::unique_ptr<CardUnmaskAuthenticationSelectionMediator> mediator_;
@@ -158,3 +171,46 @@
Run(CvcAutofillChallengeOption().id.value()));
[mediator->AsMutator() didAcceptSelection];
}
+
+TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
+ OnCancelSelection_CallsCancelUnmaskingClosure) {
+ CardUnmaskAuthenticationSelectionMediator* mediator = InitializeMediator(
+ {SmsAutofillChallengeOption(), CvcAutofillChallengeOption()});
+ mediator->DidSelectChallengeOption(CvcIOSChallengeOption());
+
+ EXPECT_CALL(cancel_unmasking_closure_, Run());
+ [mediator->AsMutator() didCancelSelection];
+}
+
+TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
+ OnCancelSelection_CallsDelegateToDismiss) {
+ CardUnmaskAuthenticationSelectionMediator* mediator =
+ InitializeMediator({SmsAutofillChallengeOption()});
+
+ OCMExpect([delegate() dismissAuthenticationSelection]);
+ [mediator->AsMutator() didCancelSelection];
+}
+
+TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
+ ServerProcessedAuthentication_DoesNotCallCancelUnmaskingClosure) {
+ InitializeMediator({SmsAutofillChallengeOption()});
+
+ EXPECT_CALL(cancel_unmasking_closure_, Run()).Times(0);
+ controller()->DismissDialogUponServerProcessedAuthenticationMethodRequest(
+ /*server_success=*/true);
+}
+
+TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
+ ServerProcessedAuthentication_DoesNotDismissAuthenticationSelection) {
+ id<CardUnmaskAuthenticationSelectionMediatorDelegate> delegate =
+ OCMStrictProtocolMock(
+ @protocol(CardUnmaskAuthenticationSelectionMediatorDelegate));
+ CardUnmaskAuthenticationSelectionMediator* mediator =
+ InitializeMediator({SmsAutofillChallengeOption()});
+ mediator->set_delegate(delegate);
+
+ // No calls to delegate() are expected, and OCMStrictProtocolMock will fail
+ // this test if [delegate() dismissAuthenticationSelection] is called.
+ controller()->DismissDialogUponServerProcessedAuthenticationMethodRequest(
+ /*server_success=*/true);
+}