Commit 1f62bd23 authored by Kurt Horimoto's avatar Kurt Horimoto Committed by Commit Bot

[iOS] Add coordinator and mediator superclasses for infobar modal UI

These classes implement the behavior shared amongst all infobar modal
implementations.  The coordinator packages a modal view configured by
subclasses into a UINavigationController and presents it modally.  The
mediator conforms to InfobarModalDelegate and handles those callbacks.

Bug: 1030357
Change-Id: I2c20f8c545daa5038bcccca3c10d7a24a8b3d2b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1995687
Commit-Queue: Kurt Horimoto <kkhorimoto@chromium.org>
Reviewed-by: default avatarSergio Collazos <sczs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#731742}
parent 2df066be
......@@ -4,6 +4,8 @@
source_set("infobar_modal") {
sources = [
"infobar_modal_overlay_responses.h",
"infobar_modal_overlay_responses.mm",
"password_infobar_modal_overlay_request_config.h",
"password_infobar_modal_overlay_request_config.mm",
]
......
// Copyright 2020 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.
#ifndef IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_RESPONSES_H_
#define IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_RESPONSES_H_
#include "ios/chrome/browser/overlays/public/overlay_response_info.h"
// Response info used to create dispatched OverlayResponses that trigger the
// infobar's main action for the modal view.
class InfobarModalMainActionResponse
: public OverlayResponseInfo<InfobarModalMainActionResponse> {
public:
~InfobarModalMainActionResponse() override;
private:
OVERLAY_USER_DATA_SETUP(InfobarModalMainActionResponse);
InfobarModalMainActionResponse();
};
#endif // IOS_CHROME_BROWSER_OVERLAYS_PUBLIC_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_RESPONSES_H_
// Copyright 2020 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.
#import "ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
OVERLAY_USER_DATA_SETUP_IMPL(InfobarModalMainActionResponse);
InfobarModalMainActionResponse::InfobarModalMainActionResponse() = default;
InfobarModalMainActionResponse::~InfobarModalMainActionResponse() = default;
......@@ -71,6 +71,7 @@
- (void)infobarBannerWasDismissed {
// Only needed in legacy implementation. Dismissal completion cleanup occurs
// in InfobarBannerOverlayCoordinator.
// TODO(crbug.com/1041917): Remove once non-overlay implementation is deleted.
}
@end
......
......@@ -10,3 +10,73 @@ source_set("infobar_modal") {
deps = []
}
source_set("coordinators") {
sources = [
"infobar_modal_overlay_coordinator+modal_configuration.h",
"infobar_modal_overlay_coordinator.h",
"infobar_modal_overlay_coordinator.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
":mediators",
"//base",
"//ios/chrome/browser/overlays",
"//ios/chrome/browser/overlays/public/common/infobars",
"//ios/chrome/browser/ui/infobars/modals",
"//ios/chrome/browser/ui/overlays:coordinators",
]
}
source_set("mediators") {
sources = [
"infobar_modal_overlay_mediator.h",
"infobar_modal_overlay_mediator.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
"//base",
"//components/infobars/core",
"//ios/chrome/browser/overlays",
"//ios/chrome/browser/overlays/public/infobar_modal",
"//ios/chrome/browser/ui/infobars/modals",
"//ios/chrome/browser/ui/overlays:coordinators",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"infobar_modal_overlay_coordinator_unittest.mm",
"infobar_modal_overlay_mediator_unittest.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
":coordinators",
":mediators",
"//base/test:test_support",
"//components/infobars/core",
"//ios/chrome/browser/browser_state:test_support",
"//ios/chrome/browser/infobars/test",
"//ios/chrome/browser/main:test_support",
"//ios/chrome/browser/overlays",
"//ios/chrome/browser/overlays/public/infobar_modal",
"//ios/chrome/browser/overlays/test",
"//ios/chrome/browser/ui/overlays:coordinators",
"//ios/chrome/browser/ui/overlays/test",
"//ios/chrome/browser/web_state_list",
"//ios/chrome/browser/web_state_list:test_support",
"//ios/chrome/test:test_support",
"//ios/web/public/test",
"//testing/gmock",
"//testing/gtest",
"//third_party/ocmock",
"//ui/base",
]
}
// Copyright 2020 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.
#ifndef IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_MODAL_CONFIGURATION_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_MODAL_CONFIGURATION_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.h"
@class InfobarModalOverlayMediator;
// Category implemented by InfobarModalOverlayCoordinator subclasses to
// configure the view to display for infobar modals.
@interface InfobarModalOverlayCoordinator (ModalConfiguration)
// The mediator used to configure the modal view controller. Created in
// |-configureModal|.
@property(nonatomic, readonly) InfobarModalOverlayMediator* modalMediator;
// The view controller to display for the infobar modal. Created in
// |-configureModal|. This view controller is not the view controller returned
// by the InfobarModalOverlayCoordinator.viewController property, but is added
// as a child view controller to the top-level infobar modal container view.
@property(nonatomic, readonly) UIViewController* modalViewController;
// Creates a modal view controller and configures it with a new mediator.
// Resets |modalViewController| and |modalMediator| to the new instances.
- (void)configureModal;
// Resets |modalMediator| and |modalViewController|.
- (void)resetModal;
@end
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_MODAL_CONFIGURATION_H_
// Copyright 2020 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.
#ifndef IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_H_
#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator.h"
// A coordinator that displays infobar modal UI using OverlayPresenter.
@interface InfobarModalOverlayCoordinator : OverlayRequestCoordinator
@end
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_COORDINATOR_H_
// Copyright 2020 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.
#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.h"
#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator+modal_configuration.h"
#include "base/logging.h"
#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h"
#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator+subclassing.h"
#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator_delegate.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface InfobarModalOverlayCoordinator ()
// The navigation controller used to display the modal view.
@property(nonatomic) UINavigationController* modalNavController;
@end
@implementation InfobarModalOverlayCoordinator
#pragma mark - OverlayRequestCoordinator
- (void)startAnimated:(BOOL)animated {
if (self.started || !self.request)
return;
[self configureModal];
self.mediator = self.modalMediator;
self.modalNavController = [[UINavigationController alloc]
initWithRootViewController:self.modalViewController];
// TODO(crbug.com/1030357): Use custom presentation.
self.modalNavController.modalPresentationStyle =
UIModalPresentationOverCurrentContext;
self.modalNavController.modalTransitionStyle =
UIModalTransitionStyleCrossDissolve;
[self.baseViewController presentViewController:self.viewController
animated:animated
completion:^{
[self finishPresentation];
}];
self.started = YES;
}
- (void)stopAnimated:(BOOL)animated {
if (!self.started)
return;
[self.baseViewController dismissViewControllerAnimated:animated
completion:^{
[self finishDismissal];
}];
self.started = NO;
}
- (UIViewController*)viewController {
return self.modalNavController;
}
#pragma mark - Private
// Called when the presentation of the modal UI is completed.
- (void)finishPresentation {
// Notify the presentation context that the presentation has finished. This
// is necessary to synchronize OverlayPresenter scheduling logic with the UI
// layer.
self.delegate->OverlayUIDidFinishPresentation(self.request);
}
// Called when the dismissal of the modal UI is finished.
- (void)finishDismissal {
[self resetModal];
self.navigationController = nil;
// Notify the presentation context that the dismissal has finished. This
// is necessary to synchronize OverlayPresenter scheduling logic with the UI
// layer.
self.delegate->OverlayUIDidFinishDismissal(self.request);
}
@end
@implementation InfobarModalOverlayCoordinator (ModalConfiguration)
- (OverlayRequestMediator*)modalMediator {
NOTREACHED() << "Subclasses implement.";
return nullptr;
}
- (UIViewController*)modalViewController {
NOTREACHED() << "Subclasses implement.";
return nil;
}
- (void)configureModal {
NOTREACHED() << "Subclasses implement.";
}
- (void)resetModal {
NOTREACHED() << "Subclasses implement.";
}
@end
// Copyright 2020 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.
#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator.h"
#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_coordinator+modal_configuration.h"
#import "base/test/ios/wait_util.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
#import "ios/chrome/browser/main/test_browser.h"
#include "ios/chrome/browser/overlays/test/overlay_test_macros.h"
#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h"
#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator+subclassing.h"
#import "ios/chrome/browser/ui/overlays/test/mock_overlay_coordinator_delegate.h"
#import "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/test/scoped_key_window.h"
#include "ios/web/public/test/web_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using base::test::ios::WaitUntilConditionOrTimeout;
using base::test::ios::kWaitForUIElementTimeout;
namespace {
// Request config type used for testing.
DEFINE_TEST_OVERLAY_REQUEST_CONFIG(ModalConfig);
}
// Mediator used by FakeInfobarModalOverlayCoordinators.
@interface FakeModalMediator : InfobarModalOverlayMediator
@end
@implementation FakeModalMediator
+ (const OverlayRequestSupport*)requestSupport {
return ModalConfig::RequestSupport();
}
@end
// Modal coordinator for tests.
@interface FakeInfobarModalOverlayCoordinator : InfobarModalOverlayCoordinator {
FakeModalMediator* _mediator;
UIViewController* _viewController;
}
@end
@implementation FakeInfobarModalOverlayCoordinator
+ (const OverlayRequestSupport*)requestSupport {
return ModalConfig::RequestSupport();
}
- (OverlayRequestMediator*)modalMediator {
return _mediator;
}
- (UIViewController*)modalViewController {
return _viewController;
}
- (void)configureModal {
_mediator = [[FakeModalMediator alloc] initWithRequest:self.request];
_viewController = [[UIViewController alloc] init];
}
- (void)resetModal {
_mediator = nil;
_viewController = nil;
}
@end
// Test fixture for InfobarModalOverlayCoordinator.
class InfobarModalOverlayCoordinatorTest : public PlatformTest {
public:
InfobarModalOverlayCoordinatorTest()
: browser_state_(browser_state_builder_.Build()),
web_state_list_(&web_state_list_delegate_),
browser_(browser_state_.get(), &web_state_list_),
request_(OverlayRequest::CreateWithConfig<ModalConfig>()),
root_view_controller_([[UIViewController alloc] init]),
coordinator_([[FakeInfobarModalOverlayCoordinator alloc]
initWithBaseViewController:root_view_controller_
browser:&browser_
request:request_.get()
delegate:&delegate_]) {
scoped_window_.Get().rootViewController = root_view_controller_;
}
protected:
web::WebTaskEnvironment task_environment_;
TestChromeBrowserState::Builder browser_state_builder_;
std::unique_ptr<ios::ChromeBrowserState> browser_state_;
FakeWebStateListDelegate web_state_list_delegate_;
WebStateList web_state_list_;
TestBrowser browser_;
MockOverlayRequestCoordinatorDelegate delegate_;
std::unique_ptr<OverlayRequest> request_;
ScopedKeyWindow scoped_window_;
UIViewController* root_view_controller_ = nil;
FakeInfobarModalOverlayCoordinator* coordinator_ = nil;
};
// Tests the modal presentation flow for a FakeInfobarModalOverlayCoordinator.
TEST_F(InfobarModalOverlayCoordinatorTest, ModalPresentation) {
// Start the coordinator, expecting OverlayUIDidFinishPresentation() to be
// executed.
EXPECT_CALL(delegate_, OverlayUIDidFinishPresentation(request_.get()));
[coordinator_ startAnimated:NO];
// Wait for presentation to finish.
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, ^BOOL {
UIViewController* presented_view_controller =
root_view_controller_.presentedViewController;
return presented_view_controller &&
!presented_view_controller.beingPresented;
}));
// Verify that the view hierarchy is set up as expected. The coordinator
// should have added the configured modal view as the root view controller for
// a UINavigationController, then presented it on |root_view_controller_|.
UIViewController* presented_view_controller =
root_view_controller_.presentedViewController;
EXPECT_TRUE(presented_view_controller);
EXPECT_TRUE(
[presented_view_controller isKindOfClass:[UINavigationController class]]);
UINavigationController* navigation_controller =
static_cast<UINavigationController*>(presented_view_controller);
UIViewController* navigation_root_controller =
[navigation_controller.viewControllers firstObject];
EXPECT_TRUE(navigation_root_controller);
EXPECT_EQ(coordinator_.modalViewController, navigation_root_controller);
// Verify that the mediator has been set on the coordinator and that its
// delegate was set to the coordinator.
OverlayRequestMediator* mediator = coordinator_.mediator;
EXPECT_TRUE(mediator);
EXPECT_EQ(coordinator_.modalMediator, mediator);
EXPECT_EQ(coordinator_, mediator.delegate);
// Stop the coordinator, expecting OverlayUIDidFinishDismissal() to be
// executed.
EXPECT_CALL(delegate_, OverlayUIDidFinishDismissal(request_.get()));
[coordinator_ stopAnimated:NO];
// Wait for dismissal to finish.
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, ^BOOL {
return !root_view_controller_.presentedViewController;
}));
}
// Copyright 2020 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.
#ifndef IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_MEDIATOR_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_MEDIATOR_H_
#import "ios/chrome/browser/ui/overlays/overlay_request_mediator.h"
#import "ios/chrome/browser/ui/infobars/modals/infobar_modal_delegate.h"
// Mediator superclass for configuring infobar modal views.
@interface InfobarModalOverlayMediator
: OverlayRequestMediator <InfobarModalDelegate>
@end
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_INFOBAR_MODAL_INFOBAR_MODAL_OVERLAY_MEDIATOR_H_
// Copyright 2020 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.
#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h"
#import <UIKit/UIKit.h>
#include "ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.h"
#include "ios/chrome/browser/overlays/public/overlay_response.h"
#import "ios/chrome/browser/ui/overlays/overlay_request_mediator+subclassing.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@implementation InfobarModalOverlayMediator
#pragma mark - InfobarModalDelegate
- (void)dismissInfobarModal:(id)infobarModal {
[self.delegate stopOverlayForMediator:self];
}
- (void)modalInfobarButtonWasAccepted:(id)infobarModal {
[self dispatchResponseAndStopOverlay:OverlayResponse::CreateWithInfo<
InfobarModalMainActionResponse>()];
}
- (void)modalInfobarWasDismissed:(id)infobarModal {
// Only needed in legacy implementation. Dismissal completion cleanup occurs
// in InfobarModalOverlayCoordinator.
// TODO(crbug.com/1041917): Remove once non-overlay implementation is deleted.
}
@end
// Copyright 2020 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.
#import "ios/chrome/browser/ui/overlays/infobar_modal/infobar_modal_overlay_mediator.h"
#import "base/bind.h"
#include "ios/chrome/browser/overlays/public/infobar_modal/infobar_modal_overlay_responses.h"
#include "ios/chrome/browser/overlays/public/overlay_callback_manager.h"
#include "ios/chrome/browser/overlays/public/overlay_request.h"
#include "ios/chrome/browser/overlays/public/overlay_response.h"
#include "ios/chrome/browser/overlays/public/overlay_response_support.h"
#include "ios/chrome/browser/overlays/test/fake_overlay_user_data.h"
#include "ios/chrome/browser/overlays/test/overlay_test_macros.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Request ConfigType used for tests.
DEFINE_TEST_OVERLAY_REQUEST_CONFIG(ModalConfig);
}
// Mediator used in tests.
@interface FakeInfobarModalOverlayMediator : InfobarModalOverlayMediator
@end
@implementation FakeInfobarModalOverlayMediator
+ (const OverlayRequestSupport*)requestSupport {
return ModalConfig::RequestSupport();
}
@end
// Test fixture for InfobarModalOverlayMediator.
class InfobarModalOverlayMediatorTest : public PlatformTest {
public:
InfobarModalOverlayMediatorTest()
: request_(OverlayRequest::CreateWithConfig<ModalConfig>()),
delegate_(
OCMStrictProtocolMock(@protocol(OverlayRequestMediatorDelegate))),
mediator_([[FakeInfobarModalOverlayMediator alloc]
initWithRequest:request_.get()]) {
mediator_.delegate = delegate_;
}
~InfobarModalOverlayMediatorTest() override {
EXPECT_OCMOCK_VERIFY(delegate_);
}
protected:
std::unique_ptr<OverlayRequest> request_;
id<OverlayRequestMediatorDelegate> delegate_ = nil;
InfobarModalOverlayMediator* mediator_ = nil;
};
// Tests that |-dismissInfobarModal| triggers dismissal via the delegate.
TEST_F(InfobarModalOverlayMediatorTest, DismissInfobarModal) {
OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
[mediator_ dismissInfobarModal:nil];
}
// Tests that |-modalInfobarButtonWasAccepted| dispatches a main action response
// then dismisses the modal.
TEST_F(InfobarModalOverlayMediatorTest, ModalInfobarButtonWasAccepted) {
// Add a dispatch callback that resets |main_action_callback_executed| to true
// upon receiving an OverlayResponse created with an
// InfobarModalMainActionResponse.
__block bool main_action_callback_executed = false;
request_->GetCallbackManager()->AddDispatchCallback(OverlayDispatchCallback(
base::BindRepeating(^(OverlayResponse* response) {
main_action_callback_executed = true;
}),
InfobarModalMainActionResponse::ResponseSupport()));
OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
[mediator_ modalInfobarButtonWasAccepted:nil];
EXPECT_TRUE(main_action_callback_executed);
}
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator.h"
#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator+subclassing.h"
#include "base/logging.h"
#include "ios/chrome/browser/overlays/public/overlay_request_support.h"
......
......@@ -266,6 +266,7 @@ test("ios_chrome_unittests") {
"//ios/chrome/browser/ui/overlays/common/alerts:unit_tests",
"//ios/chrome/browser/ui/overlays/infobar_banner:unit_tests",
"//ios/chrome/browser/ui/overlays/infobar_banner/passwords:unit_tests",
"//ios/chrome/browser/ui/overlays/infobar_modal:unit_tests",
"//ios/chrome/browser/ui/overlays/web_content_area/app_launcher:unit_tests",
"//ios/chrome/browser/ui/overlays/web_content_area/http_auth_dialogs:unit_tests",
"//ios/chrome/browser/ui/overlays/web_content_area/java_script_dialogs:unit_tests",
......
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