Commit 6e0155cb authored by Kurt Horimoto's avatar Kurt Horimoto Committed by Commit Bot

[iOS] Implement OverlayPresenter UI support.

This CL creates OverlayPresenterUIDelegateImpl, which handles the UI
for OverlayPresenters.  Additionally, it adds
OverlayContainerCoordinator, which provides the presentation context
upon which to present requested overlay UI.

Bug: 941745
Change-Id: Id44c906952260fbd0cb9e7f541c93fd70d2d7d4c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1614696
Commit-Queue: Kurt Horimoto <kkhorimoto@chromium.org>
Reviewed-by: default avatarMike Dougherty <michaeldo@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#664003}
parent 9ca2ffbf
# Copyright 2019 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.
source_set("overlays") {
public = [
"overlay_container_coordinator.h",
"overlay_coordinator_factory.h",
]
sources = [
"overlay_container_coordinator.mm",
"overlay_coordinator_factory.mm",
"overlay_presenter_ui_delegate_impl.h",
"overlay_presenter_ui_delegate_impl.mm",
"overlay_request_ui_state.h",
"overlay_request_ui_state.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
friend = [ ":unit_tests" ]
deps = [
":container_ui",
":coordinators",
"//base",
"//ios/chrome/browser/main",
"//ios/chrome/browser/overlays",
"//ios/chrome/browser/ui/coordinators:chrome_coordinators",
"//ios/chrome/common/ui_util",
]
}
source_set("container_ui") {
sources = [
"overlay_container_view_controller.h",
"overlay_container_view_controller.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
"//base",
]
}
source_set("coordinators") {
sources = [
"overlay_request_coordinator.h",
"overlay_request_coordinator.mm",
"overlay_ui_dismissal_delegate.h",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
"//base",
"//ios/chrome/browser/ui/coordinators:chrome_coordinators",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"overlay_request_ui_state_unittest.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
":coordinators",
":overlays",
"//base/test:test_support",
"//ios/chrome/browser/overlays",
"//ios/chrome/browser/overlays/test",
"//ios/chrome/browser/ui/overlays/test",
"//testing/gtest",
]
}
// Copyright 2019 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_OVERLAY_CONTAINER_COORDINATOR_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_CONTAINER_COORDINATOR_H_
#import <UIKit/UIKit.h>
#include "ios/chrome/browser/overlays/public/overlay_modality.h"
#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
// Coordinator that manages displaying of UI for OverlayRequests. An instance
// of this coordinator should be created for each Browser at every
// OverlayModality.
@interface OverlayContainerCoordinator : ChromeCoordinator
// Initializer for an overlay container that presents overlay for |browser| at
// |modality|.
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browser:(Browser*)browser
modality:(OverlayModality)modality
NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browserState:
(ios::ChromeBrowserState*)browserState
NS_UNAVAILABLE;
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browser:(Browser*)browser NS_UNAVAILABLE;
// The view controller whose presentation context is used to present overlays
// for the OverlayPresenter corresponding with the container's Browser and
// OverlayModality.
@property(nonatomic, readonly) UIViewController* viewController;
@end
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_CONTAINER_COORDINATOR_H_
// Copyright 2019 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/overlay_container_coordinator.h"
#include <map>
#include <memory>
#include "base/logging.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/ui/overlays/overlay_container_view_controller.h"
#import "ios/chrome/browser/ui/overlays/overlay_presenter_ui_delegate_impl.h"
#import "ios/chrome/common/ui_util/constraints_ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface OverlayContainerCoordinator () <
OverlayContainerViewControllerDelegate>
// Whether the coordinator is started.
@property(nonatomic, assign, getter=isStarted) BOOL started;
// The UI delegate that is used to drive presentation for this container.
@property(nonatomic, readonly) OverlayPresenterUIDelegateImpl* UIDelegate;
@end
@implementation OverlayContainerCoordinator
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browser:(Browser*)browser
modality:(OverlayModality)modality {
if (self = [super initWithBaseViewController:viewController
browser:browser]) {
OverlayPresenterUIDelegateImpl::Container::CreateForUserData(browser,
browser);
_UIDelegate =
OverlayPresenterUIDelegateImpl::Container::FromUserData(browser)
->UIDelegateForModality(modality);
DCHECK(_UIDelegate);
}
return self;
}
#pragma mark - ChromeCoordinator
- (void)start {
if (self.started)
return;
self.started = YES;
// Create the container view controller and add it to the base view
// controller.
OverlayContainerViewController* viewController =
[[OverlayContainerViewController alloc] init];
viewController.definesPresentationContext = YES;
viewController.delegate = self;
_viewController = viewController;
UIView* containerView = _viewController.view;
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.baseViewController addChildViewController:_viewController];
[self.baseViewController.view addSubview:containerView];
AddSameConstraints(containerView, self.baseViewController.view);
[_viewController didMoveToParentViewController:self.baseViewController];
}
- (void)stop {
if (!self.started)
return;
self.started = NO;
self.UIDelegate->SetCoordinator(nil);
// Remove the container view and reset the view controller.
[_viewController willMoveToParentViewController:nil];
[_viewController.view removeFromSuperview];
[_viewController removeFromParentViewController];
_viewController = nil;
}
#pragma mark - OverlayContainerViewControllerDelegate
- (void)containerViewController:
(OverlayContainerViewController*)containerViewController
didMoveToWindow:(UIWindow*)window {
// UIViewController presentation no-ops when attempted on window-less parent
// view controllers. Wait to set UI delegate's coordinator until the
// container is added to a window.
self.UIDelegate->SetCoordinator(window ? self : nil);
}
@end
// Copyright 2019 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_OVERLAY_CONTAINER_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_CONTAINER_VIEW_CONTROLLER_H_
#import <UIKit/UIKit.h>
@protocol OverlayContainerViewControllerDelegate;
// View controller used to show overlay UI.
@interface OverlayContainerViewController : UIViewController
@property(nonatomic, weak) id<OverlayContainerViewControllerDelegate> delegate;
@end
// Delegate protocol for the container view.
@protocol OverlayContainerViewControllerDelegate <NSObject>
// Called when |containerViewController|'s view moves to a new window. Overlay
// presentation should not be attempted until the container is added to
// a window.
- (void)containerViewController:
(OverlayContainerViewController*)containerViewController
didMoveToWindow:(UIWindow*)window;
@end
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_CONTAINER_VIEW_CONTROLLER_H_
// Copyright 2019 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/overlay_container_view_controller.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface OverlayContainerView : UIView
// The owning view controller.
@property(nonatomic, weak) OverlayContainerViewController* viewController;
@end
@implementation OverlayContainerView
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {
// Allow touches to go to subviews, but ignore touches that are routed to the
// container view itself.
UIView* hitView = [super hitTest:point withEvent:event];
return hitView == self ? nil : hitView;
}
- (void)didMoveToWindow {
[super didMoveToWindow];
[self.viewController.delegate containerViewController:self.viewController
didMoveToWindow:self.window];
}
@end
@implementation OverlayContainerViewController
- (void)loadView {
OverlayContainerView* containerView = [[OverlayContainerView alloc] init];
containerView.viewController = self;
self.view = containerView;
}
@end
// Copyright 2019 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_OVERLAY_COORDINATOR_FACTORY_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_COORDINATOR_FACTORY_H_
#import <UIKit/UIKit.h>
#include "ios/chrome/browser/overlays/public/overlay_modality.h"
class Browser;
class OverlayUIDismissalDelegate;
@class OverlayRequestCoordinator;
class OverlayRequest;
// Factory object provided to OverlayContainerCoordinators that supply overlay
// coordinators for a request.
@interface OverlayRequestCoordinatorFactory : NSObject
// Returns a coordinator factory for |browser| at |modality|.
+ (instancetype)factoryForBrowser:(Browser*)browser
modality:(OverlayModality)modality;
// OverlayRequestCoordinatorFactory must be fetched using
// |+factoryForBrowser:modality:|.
- (instancetype)init NS_UNAVAILABLE;
// Creates a coordinator to show |request|'s overlay UI.
- (OverlayRequestCoordinator*)
newCoordinatorForRequest:(OverlayRequest*)request
dismissalDelegate:(OverlayUIDismissalDelegate*)dismissalDelegate
baseViewController:(UIViewController*)baseViewController;
@end
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_COORDINATOR_FACTORY_H_
// Copyright 2019 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/overlay_coordinator_factory.h"
#include "base/logging.h"
#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface OverlayRequestCoordinatorFactory ()
// The Browser passed on initialization.
@property(nonatomic, readonly) Browser* browser;
// The OverlayRequestCoordinator subclasses that are supported at the modality
// associated with this coordinator factory.
@property(nonatomic, readonly)
NSArray<Class>* supportedOverlayRequestCoordinatorClasses;
// Initializer used by |+factoryForBrowser:modality:|.
- (instancetype)initWithBrowser:(Browser*)browser
supportedOverlayRequestCoordinatorClasses:
(NSArray<Class>*)supportedOverlayClasses NS_DESIGNATED_INITIALIZER;
@end
@implementation OverlayRequestCoordinatorFactory
+ (instancetype)factoryForBrowser:(Browser*)browser
modality:(OverlayModality)modality {
DCHECK(browser);
NSArray<Class>* supportedCoordinatorClasses = @[];
switch (modality) {
case OverlayModality::kWebContentArea:
// TODO(crbug.com/941745): Reset |supportedCoordinatorClasses| to contain
// OverlayRequestCoordinator classes once implemented.
break;
}
return [[self alloc] initWithBrowser:browser
supportedOverlayRequestCoordinatorClasses:supportedCoordinatorClasses];
}
- (instancetype)initWithBrowser:(Browser*)browser
supportedOverlayRequestCoordinatorClasses:
(NSArray<Class>*)supportedOverlayClasses {
if (self = [super init]) {
_browser = browser;
DCHECK(_browser);
_supportedOverlayRequestCoordinatorClasses = supportedOverlayClasses;
DCHECK(_supportedOverlayRequestCoordinatorClasses.count);
}
return self;
}
- (OverlayRequestCoordinator*)
newCoordinatorForRequest:(OverlayRequest*)request
dismissalDelegate:(OverlayUIDismissalDelegate*)dismissalDelegate
baseViewController:(UIViewController*)baseViewController {
for (Class coordinatorClass in self
.supportedOverlayRequestCoordinatorClasses) {
if ([coordinatorClass supportsRequest:request]) {
return [[coordinatorClass alloc]
initWithBaseViewController:baseViewController
browser:self.browser
request:request
dismissalDelegate:dismissalDelegate];
}
}
NOTREACHED() << "Received unsupported request type.";
return nil;
}
@end
// Copyright 2019 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_OVERLAY_PRESENTER_UI_DELEGATE_IMPL_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_PRESENTER_UI_DELEGATE_IMPL_H_
#include "base/memory/weak_ptr.h"
#import "ios/chrome/browser/main/browser_observer.h"
#import "ios/chrome/browser/overlays/public/overlay_presenter.h"
#import "ios/chrome/browser/overlays/public/overlay_user_data.h"
#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator.h"
#import "ios/chrome/browser/ui/overlays/overlay_request_ui_state.h"
#import "ios/chrome/browser/ui/overlays/overlay_ui_dismissal_delegate.h"
@class OverlayRequestCoordinatorFactory;
@class OverlayContainerCoordinator;
// Implementation of OverlayPresenter::UIDelegate. An instance of this class
// exists for every OverlayModality for each Browser. This delegate is scoped
// to the Browser because it needs to store state even when a Browser's UI is
// not on screen. When a Browser's UI is shown, the OverlayContainerCoordinator
// for each of its OverlayModalities will supply itself to the delegate, which
// will then present the UI using the container coordinator's presentation
// context.
class OverlayPresenterUIDelegateImpl : public OverlayPresenter::UIDelegate {
public:
~OverlayPresenterUIDelegateImpl() override;
// Container that stores the UI delegate for each modality. Usage example:
//
// OverlayPresenterUIDelegateImpl::Container::FromUserData(browser)->
// UIDelegateForModality(OverlayModality::kWebContentArea);
class Container : public OverlayUserData<Container> {
public:
~Container() override;
// Returns the OverlayPresenterUIDelegateImpl for |modality|.
OverlayPresenterUIDelegateImpl* UIDelegateForModality(
OverlayModality modality);
private:
OVERLAY_USER_DATA_SETUP(Container);
explicit Container(Browser* browser);
Browser* browser_ = nullptr;
std::map<OverlayModality, std::unique_ptr<OverlayPresenterUIDelegateImpl>>
ui_delegates_;
};
// The OverlayContainerCoordinator is used to present the overlay UI at the
// correct modality in the app. Should only be set when the coordinator's
// presentation context is able to present.
OverlayContainerCoordinator* coordinator() const { return coordinator_; }
void SetCoordinator(OverlayContainerCoordinator* coordinator);
// OverlayPresenter::UIDelegate:
void ShowOverlayUI(OverlayPresenter* presenter,
OverlayRequest* request,
OverlayDismissalCallback dismissal_callback) override;
void HideOverlayUI(OverlayPresenter* presenter,
OverlayRequest* request) override;
void CancelOverlayUI(OverlayPresenter* presenter,
OverlayRequest* request) override;
private:
OverlayPresenterUIDelegateImpl(Browser* browser, OverlayModality modality);
// Setter for |request_|. Setting to a new value will attempt to
// present the UI for |request|.
void SetRequest(OverlayRequest* request);
// Returns the UI state for |request|.
OverlayRequestUIState* GetRequestUIState(OverlayRequest* request);
// Shows the UI for the presented request using the container coordinator.
void ShowUIForPresentedRequest();
// Dismisses the UI for the presented request for |reason|.
void DismissPresentedUI(OverlayDismissalReason reason);
// Called when the UI for |request_| has finished being dismissed.
void OverlayUIWasDismissed();
// Notifies the state for |request_| that its UI has finished being dismissed.
void NotifyStateOfDismissal();
// Helper object that detaches the UI delegate for Browser shudown.
class BrowserShutdownHelper : public BrowserObserver {
public:
BrowserShutdownHelper(Browser* browser, OverlayPresenter* presenter);
~BrowserShutdownHelper() override;
// BrowserObserver:
void BrowserDestroyed(Browser* browser) override;
private:
// The presenter whose delegate needs to be reset.
OverlayPresenter* presenter_ = nullptr;
};
// Helper object that listens for UI dismissal events.
class OverlayDismissalHelper : public OverlayUIDismissalDelegate {
public:
OverlayDismissalHelper(OverlayPresenterUIDelegateImpl* ui_delegate);
~OverlayDismissalHelper() override;
// OverlayUIDismissalDelegate:
void OverlayUIDidFinishDismissal(OverlayRequest* request) override;
private:
OverlayPresenterUIDelegateImpl* ui_delegate_ = nullptr;
};
// The presenter whose UI is being handled by this delegate.
OverlayPresenter* presenter_ = nullptr;
// The cleanup helper.
BrowserShutdownHelper shutdown_helper_;
// The UI dismissal helper.
OverlayDismissalHelper ui_dismissal_helper_;
// The coordinator factory that provides the UI for the overlays at this
// modality.
OverlayRequestCoordinatorFactory* coordinator_factory_ = nil;
// The coordinator responsible for presenting the UI delegate's UI.
OverlayContainerCoordinator* coordinator_ = nil;
// The request that is currently presented by |presenter_|. The UI for this
// request might not yet be visible if no OverlayContainerCoordinator has been
// provided. When a new request is presented, the UI state for the request
// will be added to |states_|.
OverlayRequest* request_ = nullptr;
// Map storing the UI state for each OverlayRequest.
std::map<OverlayRequest*, std::unique_ptr<OverlayRequestUIState>> states_;
// Weak pointer factory.
base::WeakPtrFactory<OverlayPresenterUIDelegateImpl> weak_factory_;
};
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_PRESENTER_UI_DELEGATE_IMPL_H_
// Copyright 2019 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/overlay_presenter_ui_delegate_impl.h"
#include "base/bind.h"
#include "base/callback.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/ui/overlays/overlay_container_coordinator.h"
#import "ios/chrome/browser/ui/overlays/overlay_coordinator_factory.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#pragma mark - OverlayPresenterUIDelegateImpl::Container
OVERLAY_USER_DATA_SETUP_IMPL(OverlayPresenterUIDelegateImpl::Container);
OverlayPresenterUIDelegateImpl::Container::Container(Browser* browser)
: browser_(browser) {
DCHECK(browser_);
}
OverlayPresenterUIDelegateImpl::Container::~Container() = default;
OverlayPresenterUIDelegateImpl*
OverlayPresenterUIDelegateImpl::Container::UIDelegateForModality(
OverlayModality modality) {
auto& ui_delegate = ui_delegates_[modality];
if (!ui_delegate) {
ui_delegate = base::WrapUnique(
new OverlayPresenterUIDelegateImpl(browser_, modality));
}
return ui_delegate.get();
}
#pragma mark - OverlayPresenterUIDelegateImpl
OverlayPresenterUIDelegateImpl::OverlayPresenterUIDelegateImpl(
Browser* browser,
OverlayModality modality)
: presenter_(OverlayPresenter::FromBrowser(browser, modality)),
shutdown_helper_(browser, presenter_),
ui_dismissal_helper_(this),
coordinator_factory_([OverlayRequestCoordinatorFactory
factoryForBrowser:browser
modality:modality]),
weak_factory_(this) {
DCHECK(presenter_);
DCHECK(coordinator_factory_);
presenter_->SetUIDelegate(this);
}
OverlayPresenterUIDelegateImpl::~OverlayPresenterUIDelegateImpl() = default;
#pragma mark Public
void OverlayPresenterUIDelegateImpl::SetCoordinator(
OverlayContainerCoordinator* coordinator) {
if (coordinator_ == coordinator)
return;
if (coordinator_ && request_)
DismissPresentedUI(OverlayDismissalReason::kHiding);
coordinator_ = coordinator;
// The new coordinator should be started before provided to the UI delegate.
DCHECK(!coordinator_ || coordinator_.viewController);
ShowUIForPresentedRequest();
}
#pragma mark OverlayPresenter::UIDelegate
void OverlayPresenterUIDelegateImpl::ShowOverlayUI(
OverlayPresenter* presenter,
OverlayRequest* request,
OverlayDismissalCallback dismissal_callback) {
DCHECK_EQ(presenter_, presenter);
// Create the UI state for |request| if necessary.
if (!GetRequestUIState(request))
states_[request] = std::make_unique<OverlayRequestUIState>(request);
// Present the overlay UI and update the UI state.
GetRequestUIState(request)->OverlayPresentionRequested(
std::move(dismissal_callback));
SetRequest(request);
}
void OverlayPresenterUIDelegateImpl::HideOverlayUI(OverlayPresenter* presenter,
OverlayRequest* request) {
DCHECK_EQ(presenter_, presenter);
DCHECK_EQ(request_, request);
OverlayRequestUIState* state = GetRequestUIState(request_);
DCHECK(state->has_callback());
if (coordinator_) {
// If the request's UI is presented by the coordinator, dismiss it. The
// presented request will be reset when the dismissal animation finishes.
DismissPresentedUI(OverlayDismissalReason::kHiding);
} else {
// Simulate dismissal if there is no container coordinator.
state->set_dismissal_reason(OverlayDismissalReason::kHiding);
NotifyStateOfDismissal();
}
}
void OverlayPresenterUIDelegateImpl::CancelOverlayUI(
OverlayPresenter* presenter,
OverlayRequest* request) {
DCHECK_EQ(presenter_, presenter);
// If the coordinator is not presenting the overlay UI for |state|, it can
// be deleted immediately.
OverlayRequestUIState* state = GetRequestUIState(request);
DCHECK(state);
if (!state->has_callback() || !coordinator_) {
states_.erase(request);
return;
}
DismissPresentedUI(OverlayDismissalReason::kCancellation);
}
#pragma mark Accesors
void OverlayPresenterUIDelegateImpl::SetRequest(OverlayRequest* request) {
if (request_ == request)
return;
if (request_) {
OverlayRequestUIState* state = GetRequestUIState(request_);
// The presented request should only be reset when the previously presented
// request's UI has finished being dismissed.
DCHECK(state);
DCHECK(!state->has_callback());
DCHECK(!state->coordinator().viewController.view.superview);
// If the overlay was dismissed for user interaction or cancellation, then
// the state can be destroyed, since the UI for the previously presented
// request will never be shown again.
OverlayDismissalReason reason = state->dismissal_reason();
if (reason == OverlayDismissalReason::kUserInteraction ||
reason == OverlayDismissalReason::kCancellation) {
states_.erase(request_);
}
}
request_ = request;
// The UI state should be created before resetting the presented request.
DCHECK(!request_ || GetRequestUIState(request_));
ShowUIForPresentedRequest();
}
OverlayRequestUIState* OverlayPresenterUIDelegateImpl::GetRequestUIState(
OverlayRequest* request) {
return request ? states_[request].get() : nullptr;
}
#pragma mark Presentation and Dismissal helpers
void OverlayPresenterUIDelegateImpl::ShowUIForPresentedRequest() {
OverlayRequestUIState* state = GetRequestUIState(request_);
if (!state || !coordinator_)
return;
// Create the coordinator if necessary.
UIViewController* container_view_controller = coordinator_.viewController;
OverlayRequestCoordinator* overlay_coordinator = state->coordinator();
if (!overlay_coordinator ||
overlay_coordinator.baseViewController != container_view_controller) {
overlay_coordinator = [coordinator_factory_
newCoordinatorForRequest:request_
dismissalDelegate:&ui_dismissal_helper_
baseViewController:container_view_controller];
state->OverlayUIWillBePresented(overlay_coordinator);
}
[overlay_coordinator startAnimated:!state->has_ui_been_presented()];
state->OverlayUIWasPresented();
}
void OverlayPresenterUIDelegateImpl::DismissPresentedUI(
OverlayDismissalReason reason) {
OverlayRequestUIState* state = GetRequestUIState(request_);
DCHECK(state);
DCHECK(coordinator_);
DCHECK(state->coordinator());
state->set_dismissal_reason(reason);
[state->coordinator()
stopAnimated:reason == OverlayDismissalReason::kUserInteraction];
}
void OverlayPresenterUIDelegateImpl::OverlayUIWasDismissed() {
DCHECK(request_);
// Overlays are dismissed without animation when the container coordinator is
// reset, but the state should not be notified of these dismissals since the
// same UI will be presented again once a new container coordinator is
// provided.
if (!coordinator_)
return;
NotifyStateOfDismissal();
}
void OverlayPresenterUIDelegateImpl::NotifyStateOfDismissal() {
DCHECK(request_);
DCHECK(GetRequestUIState(request_)->has_callback());
// If there is another request in the active WebState's OverlayRequestQueue,
// executing the state's dismissal callback will trigger the presentation of
// the next request. If the presented request remains unchanged after calling
// the dismissal callback, reset it to nullptr since the UI is no longer
// presented.
OverlayRequest* previously_presented_request = request_;
GetRequestUIState(request_)->OverlayUIWasDismissed();
if (request_ == previously_presented_request)
SetRequest(nullptr);
}
#pragma mark BrowserShutdownHelper
OverlayPresenterUIDelegateImpl::BrowserShutdownHelper::BrowserShutdownHelper(
Browser* browser,
OverlayPresenter* presenter)
: presenter_(presenter) {
DCHECK(presenter_);
browser->AddObserver(this);
}
OverlayPresenterUIDelegateImpl::BrowserShutdownHelper::
~BrowserShutdownHelper() = default;
void OverlayPresenterUIDelegateImpl::BrowserShutdownHelper::BrowserDestroyed(
Browser* browser) {
presenter_->SetUIDelegate(nullptr);
browser->RemoveObserver(this);
}
#pragma mark OverlayDismissalHelper
OverlayPresenterUIDelegateImpl::OverlayDismissalHelper::OverlayDismissalHelper(
OverlayPresenterUIDelegateImpl* ui_delegate)
: ui_delegate_(ui_delegate) {
DCHECK(ui_delegate_);
}
OverlayPresenterUIDelegateImpl::OverlayDismissalHelper::
~OverlayDismissalHelper() = default;
void OverlayPresenterUIDelegateImpl::OverlayDismissalHelper::
OverlayUIDidFinishDismissal(OverlayRequest* request) {
DCHECK(request);
DCHECK_EQ(ui_delegate_->request_, request);
ui_delegate_->OverlayUIWasDismissed();
}
// Copyright 2019 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_OVERLAY_REQUEST_COORDINATOR_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_REQUEST_COORDINATOR_H_
#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
class OverlayUIDismissalDelegate;
class OverlayRequest;
// Coordinator superclass used to present UI for an OverlayRequest.
@interface OverlayRequestCoordinator : ChromeCoordinator
// Returns whether this overlay coordinator type supports |request|.
+ (BOOL)supportsRequest:(OverlayRequest*)request;
// Initializer for a coordinator for |request|.
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browser:(Browser*)browser
request:(OverlayRequest*)request
dismissalDelegate:
(OverlayUIDismissalDelegate*)dismissalDelegate
NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browserState:
(ios::ChromeBrowserState*)browserState
NS_UNAVAILABLE;
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browser:(Browser*)browser NS_UNAVAILABLE;
// The OverlayUIDismissalDelegate passed on initialization. Used to communicate
// when the overlay UI is finished being dismissed, which may occur after
// |-stop| even if the overlay is stopped without animation. This notifies
// OverlayPresenter that the presentation context is clear to show the next
// requested overlay.
@property(nonatomic, readonly) OverlayUIDismissalDelegate* dismissalDelegate;
// The request used to configure the overlay UI.
@property(nonatomic, readonly) OverlayRequest* request;
// The view controller that displays the UI for |request|.
@property(nonatomic, readonly) UIViewController* viewController;
// OverlayRequestCoordinator's |-start| and |-stop| need to support versions
// both with and without animation, as hidden overlays should be shown without
// animation for subsequent presentations.
- (void)startAnimated:(BOOL)animated;
- (void)stopAnimated:(BOOL)animated;
@end
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_REQUEST_COORDINATOR_H_
// Copyright 2019 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/overlay_request_coordinator.h"
#include "base/logging.h"
#import "ios/chrome/browser/ui/overlays/overlay_ui_dismissal_delegate.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@implementation OverlayRequestCoordinator
+ (BOOL)supportsRequest:(OverlayRequest*)request {
return NO;
}
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browser:(Browser*)browser
request:(OverlayRequest*)request
dismissalDelegate:
(OverlayUIDismissalDelegate*)dismissalDelegate {
DCHECK([[self class] supportsRequest:request]);
self = [super initWithBaseViewController:viewController browser:browser];
if (self) {
_request = request;
DCHECK(_request);
_dismissalDelegate = dismissalDelegate;
DCHECK(_dismissalDelegate);
}
return self;
}
#pragma mark - Public
- (void)startAnimated:(BOOL)animated {
NOTREACHED() << "Subclasses must implement.";
}
- (void)stopAnimated:(BOOL)animated {
NOTREACHED() << "Subclasses must implement.";
}
#pragma mark - ChromeCoordinator
- (void)start {
[self startAnimated:YES];
}
- (void)stop {
[self stopAnimated:YES];
}
@end
// Copyright 2019 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_OVERLAY_REQUEST_UI_STATE_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_REQUEST_UI_STATE_H_
#import <Foundation/Foundation.h>
#include "ios/chrome/browser/overlays/public/overlay_dismissal_callback.h"
@class OverlayRequestCoordinator;
class OverlayRequest;
// Container holding information about the state of overlay UI for a given
// request.
class OverlayRequestUIState {
public:
explicit OverlayRequestUIState(OverlayRequest* request);
~OverlayRequestUIState();
// Called when the OverlayPresenter requests the presentation of |request_|.
// This may or may not correspond with an OverlayUIWasPresented() if the
// hierarchy is not ready for presentation (i.e. overlay presentation for a
// Browser whose UI is not currently on-screen).
void OverlayPresentionRequested(OverlayDismissalCallback callback);
// Notifies the state that the UI is about to be presented using
// |coordinator|.
void OverlayUIWillBePresented(OverlayRequestCoordinator* coordinator);
// Notifies the state that the UI was presented.
void OverlayUIWasPresented();
// Notifies the state the the UI was dismissed.
void OverlayUIWasDismissed();
// Accessors.
OverlayRequestCoordinator* coordinator() const { return coordinator_; }
bool has_ui_been_presented() const { return has_ui_been_presented_; }
bool has_callback() const { return !dismissal_callback_.is_null(); }
OverlayDismissalReason dismissal_reason() const { return dismissal_reason_; }
void set_dismissal_reason(OverlayDismissalReason dismissal_reason) {
dismissal_reason_ = dismissal_reason;
}
private:
OverlayRequest* request_ = nullptr;
OverlayRequestCoordinator* coordinator_ = nil;
bool has_ui_been_presented_ = false;
OverlayDismissalReason dismissal_reason_ =
OverlayDismissalReason::kUserInteraction;
OverlayDismissalCallback dismissal_callback_;
};
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_REQUEST_UI_STATE_H_
// Copyright 2019 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/overlay_request_ui_state.h"
#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
OverlayRequestUIState::OverlayRequestUIState(OverlayRequest* request)
: request_(request) {
DCHECK(request_);
}
OverlayRequestUIState::~OverlayRequestUIState() {
if (has_callback()) {
set_dismissal_reason(OverlayDismissalReason::kCancellation);
OverlayUIWasDismissed();
}
}
void OverlayRequestUIState::OverlayPresentionRequested(
OverlayDismissalCallback callback) {
DCHECK(dismissal_callback_.is_null());
dismissal_callback_ = std::move(callback);
// The default dismissal reason is kUserInteraction. This is to avoid
// additional bookkeeping for overlays dismissed by user interaction.
// Overlays explicitly dismissed by OverlayPresenter set the reason to kHide
// or kCancellation before dismissal.
dismissal_reason_ = OverlayDismissalReason::kUserInteraction;
}
void OverlayRequestUIState::OverlayUIWillBePresented(
OverlayRequestCoordinator* coordinator) {
DCHECK(coordinator);
coordinator_ = coordinator;
}
void OverlayRequestUIState::OverlayUIWasPresented() {
has_ui_been_presented_ = true;
}
void OverlayRequestUIState::OverlayUIWasDismissed() {
DCHECK(has_callback());
std::move(dismissal_callback_).Run(dismissal_reason_);
}
// Copyright 2019 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/overlay_request_ui_state.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "ios/chrome/browser/overlays/public/overlay_request.h"
#include "ios/chrome/browser/overlays/test/fake_overlay_user_data.h"
#import "ios/chrome/browser/ui/overlays/test/fake_overlay_request_coordinator.h"
#include "ios/chrome/browser/ui/overlays/test/fake_overlay_ui_dismissal_delegate.h"
#include "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
// Test fixture for OverlayRequestUIState.
class OverlayRequestUIStateTest : public PlatformTest {
public:
OverlayRequestUIStateTest()
: PlatformTest(),
request_(
OverlayRequest::CreateWithConfig<FakeOverlayUserData>(nullptr)),
state_(request_.get()) {}
OverlayRequest* request() { return request_.get(); }
OverlayRequestUIState& state() { return state_; }
private:
std::unique_ptr<OverlayRequest> request_;
OverlayRequestUIState state_;
};
// Tests that OverlayPresentionRequested() passes the callback to the state and
// resets the default dismissal reason to kUserInteraction.
TEST_F(OverlayRequestUIStateTest, OverlayPresentionRequested) {
ASSERT_FALSE(state().has_callback());
state().OverlayPresentionRequested(base::BindOnce(^(OverlayDismissalReason){
}));
EXPECT_TRUE(state().has_callback());
EXPECT_EQ(OverlayDismissalReason::kUserInteraction,
state().dismissal_reason());
}
// Tests that OverlayUIWillBePresented() stores the coordinator in the state.
TEST_F(OverlayRequestUIStateTest, OverlayUIWillBePresented) {
FakeDismissalDelegate dismissal_delegate;
FakeOverlayRequestCoordinator* coordinator =
[[FakeOverlayRequestCoordinator alloc]
initWithBaseViewController:nil
browser:nullptr
request:request()
dismissalDelegate:&dismissal_delegate];
state().OverlayUIWillBePresented(coordinator);
EXPECT_EQ(coordinator, state().coordinator());
}
// Tests that OverlayUIWasPresented() correctly updates has_ui_been_presented().
TEST_F(OverlayRequestUIStateTest, OverlayUIWasPresented) {
ASSERT_FALSE(state().has_ui_been_presented());
state().OverlayUIWasPresented();
EXPECT_TRUE(state().has_ui_been_presented());
}
// Tests that OverlayUIWasDismissed() executes the dismissal callback.
TEST_F(OverlayRequestUIStateTest, OverlayUIWasDismissed) {
__block bool dismissal_callback_executed = false;
state().OverlayPresentionRequested(base::BindOnce(^(OverlayDismissalReason) {
dismissal_callback_executed = true;
}));
FakeDismissalDelegate dismissal_delegate;
FakeOverlayRequestCoordinator* coordinator =
[[FakeOverlayRequestCoordinator alloc]
initWithBaseViewController:nil
browser:nullptr
request:request()
dismissalDelegate:&dismissal_delegate];
state().OverlayUIWillBePresented(coordinator);
state().OverlayUIWasPresented();
state().OverlayUIWasDismissed();
EXPECT_TRUE(dismissal_callback_executed);
}
// Copyright 2019 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_OVERLAY_UI_DISMISSAL_DELEGATE_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_UI_DISMISSAL_DELEGATE_H_
class OverlayRequest;
// Delegate class used to communicate dismissal events back to OverlayPresenter.
class OverlayUIDismissalDelegate {
public:
OverlayUIDismissalDelegate() = default;
virtual ~OverlayUIDismissalDelegate() = default;
// Called to notify the delegate that the UI for |request|'s UI is finished
// being dismissed.
virtual void OverlayUIDidFinishDismissal(OverlayRequest* request) = 0;
};
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_OVERLAY_UI_DISMISSAL_DELEGATE_H_
# Copyright 2019 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.
source_set("test") {
testonly = true
sources = [
"fake_overlay_request_coordinator.h",
"fake_overlay_request_coordinator.mm",
"fake_overlay_ui_dismissal_delegate.cc",
"fake_overlay_ui_dismissal_delegate.h",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
"//base",
"//ios/chrome/browser/ui/overlays:coordinators",
"//testing/gtest",
]
}
// Copyright 2019 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_TEST_FAKE_OVERLAY_REQUEST_COORDINATOR_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_TEST_FAKE_OVERLAY_REQUEST_COORDINATOR_H_
#import "ios/chrome/browser/ui/overlays/overlay_request_coordinator.h"
// Fake version of OverlayRequestCoordinator that supports all OverlayRequests.
@interface FakeOverlayRequestCoordinator : OverlayRequestCoordinator
@end
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_TEST_FAKE_OVERLAY_REQUEST_COORDINATOR_H_
// Copyright 2019 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/test/fake_overlay_request_coordinator.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@implementation FakeOverlayRequestCoordinator
+ (BOOL)supportsRequest:(OverlayRequest*)request {
return YES;
}
- (void)startAnimated:(BOOL)animated {
}
- (void)stopAnimated:(BOOL)animated {
}
@end
// Copyright 2019 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.
#include "ios/chrome/browser/ui/overlays/test/fake_overlay_ui_dismissal_delegate.h"
FakeDismissalDelegate::FakeDismissalDelegate() = default;
FakeDismissalDelegate::~FakeDismissalDelegate() = default;
bool FakeDismissalDelegate::HasUIBeenDismissed(OverlayRequest* request) const {
return dismissed_requests_.find(request) != dismissed_requests_.end();
}
void FakeDismissalDelegate::OverlayUIDidFinishDismissal(
OverlayRequest* request) {
dismissed_requests_.insert(request);
}
// Copyright 2019 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_TEST_FAKE_OVERLAY_UI_DISMISSAL_DELEGATE_H_
#define IOS_CHROME_BROWSER_UI_OVERLAYS_TEST_FAKE_OVERLAY_UI_DISMISSAL_DELEGATE_H_
#import "ios/chrome/browser/ui/overlays/overlay_ui_dismissal_delegate.h"
#include <set>
// Fake implementation of OverlayUIDismissalDelegate.
class FakeDismissalDelegate : public OverlayUIDismissalDelegate {
public:
FakeDismissalDelegate();
~FakeDismissalDelegate() override;
// Whether the overlay UI for |request| has been dismissed.
bool HasUIBeenDismissed(OverlayRequest* request) const;
// OverlayUIDismissalDelegate:
void OverlayUIDidFinishDismissal(OverlayRequest* request) override;
private:
std::set<OverlayRequest*> dismissed_requests_;
};
#endif // IOS_CHROME_BROWSER_UI_OVERLAYS_TEST_FAKE_OVERLAY_UI_DISMISSAL_DELEGATE_H_
......@@ -224,6 +224,7 @@ test("ios_chrome_unittests") {
"//ios/chrome/browser/ui/omnibox:unit_tests",
"//ios/chrome/browser/ui/omnibox/popup:unit_tests",
"//ios/chrome/browser/ui/open_in:unit_tests",
"//ios/chrome/browser/ui/overlays:unit_tests",
"//ios/chrome/browser/ui/payments:unit_tests",
"//ios/chrome/browser/ui/payments/cells:unit_tests",
"//ios/chrome/browser/ui/popup_menu: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