blob: d0645627f7a00b870f6dd2e542476fc17a90b217 [file] [log] [blame]
// 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/overlays/overlay_presenter_impl.h"
#include "base/check_op.h"
#include "base/memory/ptr_util.h"
#include "ios/chrome/browser/overlays/public/overlay_callback_manager.h"
#import "ios/chrome/browser/overlays/public/overlay_presentation_context.h"
#import "ios/chrome/browser/overlays/public/overlay_presenter_observer.h"
#include "ios/chrome/browser/overlays/public/overlay_request.h"
#include "ios/chrome/browser/overlays/public/overlay_request_support.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#pragma mark - Factory method
// static
OverlayPresenter* OverlayPresenter::FromBrowser(Browser* browser,
OverlayModality modality) {
OverlayPresenterImpl::Container::CreateForUserData(browser, browser);
return OverlayPresenterImpl::Container::FromUserData(browser)
->PresenterForModality(modality);
}
#pragma mark - OverlayPresenterImpl::Container
OVERLAY_USER_DATA_SETUP_IMPL(OverlayPresenterImpl::Container);
OverlayPresenterImpl::Container::Container(Browser* browser)
: browser_(browser) {
DCHECK(browser_);
}
OverlayPresenterImpl::Container::~Container() = default;
OverlayPresenterImpl* OverlayPresenterImpl::Container::PresenterForModality(
OverlayModality modality) {
auto& presenter = presenters_[modality];
if (!presenter) {
presenter = base::WrapUnique(new OverlayPresenterImpl(browser_, modality));
}
return presenter.get();
}
#pragma mark - OverlayPresenterImpl
OverlayPresenterImpl::OverlayPresenterImpl(Browser* browser,
OverlayModality modality)
: modality_(modality), web_state_list_(browser->GetWebStateList()) {
browser_observation_.Observe(browser);
DCHECK(web_state_list_);
web_state_list_->AddObserver(this);
for (int i = 0; i < web_state_list_->count(); ++i) {
WebStateAddedToBrowser(web_state_list_->GetWebStateAt(i));
}
SetActiveWebState(web_state_list_->GetActiveWebState(),
ActiveWebStateChangeReason::Activated);
}
OverlayPresenterImpl::~OverlayPresenterImpl() {
// The presenter should be disconnected from WebStateList changes before
// destruction.
DCHECK(!presentation_context_);
DCHECK(!web_state_list_);
for (auto& observer : observers_) {
observer.OverlayPresenterDestroyed(this);
}
}
#pragma mark - Public
#pragma mark OverlayPresenter
OverlayModality OverlayPresenterImpl::GetModality() const {
return modality_;
}
void OverlayPresenterImpl::SetPresentationContext(
OverlayPresentationContext* presentation_context) {
// When the presentation context is reset, the presenter will begin showing
// overlays in the new presentation context. Cancel overlay state from the
// previous context since this Browser's overlays will no longer be presented
// there.
if (presentation_context_) {
CancelAllOverlayUI();
presentation_context_->RemoveObserver(this);
}
presentation_context_ = presentation_context;
// Reset |presenting| since it was tracking the status for the previous
// delegate's presentation context.
presenting_ = false;
presented_request_ = nullptr;
previously_presented_requests_.clear();
if (presentation_context_) {
presentation_context_->AddObserver(this);
PresentOverlayForActiveRequest();
}
}
void OverlayPresenterImpl::AddObserver(OverlayPresenterObserver* observer) {
observers_.AddObserver(observer);
}
void OverlayPresenterImpl::RemoveObserver(OverlayPresenterObserver* observer) {
observers_.RemoveObserver(observer);
}
bool OverlayPresenterImpl::IsShowingOverlayUI() const {
return presenting_;
}
#pragma mark - Private
#pragma mark Accessors
void OverlayPresenterImpl::SetActiveWebState(
web::WebState* web_state,
ActiveWebStateChangeReason reason) {
if (active_web_state_ == web_state)
return;
OverlayRequest* previously_active_request =
removed_request_awaiting_dismissal_ != nullptr
? removed_request_awaiting_dismissal_.get()
: GetActiveRequest();
// The UI should be cancelled instead of hidden if the presenter does not
// expect to show any more overlay UI for previously active WebState in the UI
// delegate's presentation context. This occurs:
// - when the presenting WebState is replaced, and
// - when the presenting WebState is detached from the WebStateList.
const bool should_cancel_ui =
(reason == ActiveWebStateChangeReason::Replaced) ||
detaching_presenting_web_state_;
active_web_state_ = web_state;
detaching_presenting_web_state_ = false;
// Early return if there's no UI delegate, since presentation cannot occur.
if (!presentation_context_)
return;
// If not already presenting, immediately show the next overlay.
if (!presenting_) {
PresentOverlayForActiveRequest();
return;
}
// If presenting_ is true and there is no previously active request, this
// is likely because the presenting overlay is still in the process of being
// dismissed and multiple tabs have been opened in the process.
if (!previously_active_request) {
return;
}
// If the active WebState changes while an overlay is being presented, the
// presented UI needs to be dismissed before the next overlay for the new
// active WebState can be shown. The new active WebState's overlays will be
// presented when the previous overlay's dismissal callback is executed.
DCHECK(previously_active_request);
if (should_cancel_ui) {
CancelOverlayUIForRequest(previously_active_request);
} else {
// For WebState activations, the overlay UI for the previously active
// WebState should be hidden, as it may be shown again upon reactivating.
presentation_context_->HideOverlayUI(previously_active_request);
}
}
OverlayRequestQueueImpl* OverlayPresenterImpl::GetQueueForWebState(
web::WebState* web_state) const {
if (!web_state)
return nullptr;
OverlayRequestQueueImpl::Container::CreateForWebState(web_state);
return OverlayRequestQueueImpl::Container::FromWebState(web_state)
->QueueForModality(modality_);
}
OverlayRequest* OverlayPresenterImpl::GetFrontRequestForWebState(
web::WebState* web_state) const {
OverlayRequestQueueImpl* queue = GetQueueForWebState(web_state);
return queue ? queue->front_request() : nullptr;
}
OverlayRequestQueueImpl* OverlayPresenterImpl::GetActiveQueue() const {
return GetQueueForWebState(active_web_state_);
}
OverlayRequest* OverlayPresenterImpl::GetActiveRequest() const {
return GetFrontRequestForWebState(active_web_state_);
}
#pragma mark UI Presentation and Dismissal helpers
void OverlayPresenterImpl::PresentOverlayForActiveRequest() {
// Overlays cannot be shown without a presentation context or if the
// presentation context is already showing overlay UI.
if (!presentation_context_ || presentation_context_->IsShowingOverlayUI())
return;
// No presentation is necessary if there is no active reqeust.
OverlayRequest* request = GetActiveRequest();
if (!request)
return;
// Presentation cannot occur if the context is currently unable to show the UI
// for |request|. Attempt to prepare the presentation context for |request|.
if (!presentation_context_->CanShowUIForRequest(request)) {
presentation_context_->PrepareToShowOverlayUI(request);
return;
}
// If an overlay is already presented, the Presentation Context should be
// marked as showing an Overlay.
DCHECK(!presenting_);
presenting_ = true;
presented_request_ = request;
// Notify the observers that the overlay UI is about to be shown.
bool initial_presentation = previously_presented_requests_.find(request) ==
previously_presented_requests_.end();
for (auto& observer : observers_) {
if (observer.GetRequestSupport(this)->IsRequestSupported(request))
observer.WillShowOverlay(this, request, initial_presentation);
}
// Record that the request was shown, and add the completion callback to
// remove the request from the set.
previously_presented_requests_.insert(request);
request->GetCallbackManager()->AddCompletionCallback(
base::BindOnce(&OverlayPresenterImpl::OverlayWasCompleted,
weak_factory_.GetWeakPtr(), request));
// Present the overlay UI via the UI delegate.
OverlayPresentationCallback presentation_callback = base::BindOnce(
&OverlayPresenterImpl::OverlayWasPresented, weak_factory_.GetWeakPtr(),
presentation_context_, request);
OverlayDismissalCallback dismissal_callback = base::BindOnce(
&OverlayPresenterImpl::OverlayWasDismissed, weak_factory_.GetWeakPtr(),
presentation_context_, request, GetActiveQueue()->GetWeakPtr());
presentation_context_->ShowOverlayUI(
request, std::move(presentation_callback), std::move(dismissal_callback));
}
void OverlayPresenterImpl::OverlayWasPresented(
OverlayPresentationContext* presentation_context,
OverlayRequest* request) {
DCHECK_EQ(presentation_context_, presentation_context);
DCHECK_EQ(presented_request_, request);
for (auto& observer : observers_) {
if (observer.GetRequestSupport(this)->IsRequestSupported(request))
observer.DidShowOverlay(this, request);
}
}
void OverlayPresenterImpl::OverlayWasDismissed(
OverlayPresentationContext* presentation_context,
OverlayRequest* request,
base::WeakPtr<OverlayRequestQueueImpl> queue,
OverlayDismissalReason reason) {
// If the UI delegate is reset while presenting an overlay, that overlay will
// be cancelled and dismissed. The presenter is now using the new UI
// delegate's presentation context, so this dismissal should not trigger
// presentation logic.
if (presentation_context_ != presentation_context)
return;
// When the presenter has been replaced as the delegate of the active
// OverlayRequestQueue, observers are notified of DidHideOverlay() and
// |presented_request_| is reset early. Thus, there is no need to do any
// dismissal bookkeeping since the request has been removed.
if (detached_queue_replaced_delegate_) {
presenting_ = false;
detached_queue_replaced_delegate_ = false;
if (GetActiveRequest()) {
PresentOverlayForActiveRequest();
}
return;
}
DCHECK_EQ(presented_request_, request);
// Pop the request for overlays dismissed by the user. The check against
// |removed_request_awaiting_dismissal_| prevents the queue's front request
// from being popped if this dismissal was caused by |request|'s removal from
// the queue.
if (reason == OverlayDismissalReason::kUserInteraction && queue &&
request != removed_request_awaiting_dismissal_.get()) {
queue->PopFrontRequest();
// Popping the request should transfer ownership of the request to the
// OverlayPresenter until the completion of DidHideOverlay() observer
// callbacks below.
DCHECK_EQ(removed_request_awaiting_dismissal_.get(), request);
}
presenting_ = false;
presented_request_ = nullptr;
// The OverlayPresenter remains as the delegate for
// |detached_presenting_request_queue_| to ensure that |presented_request_| is
// not deleted before the dismissal of its UI is finished. Since the UI is
// now being dismissed, this reference is not needed anymore.
detached_presenting_request_queue_ = nullptr;
// Notify the observers that the overlay UI was hidden.
for (auto& observer : observers_) {
if (observer.GetRequestSupport(this)->IsRequestSupported(request))
observer.DidHideOverlay(this, request);
}
// Now that observers have been notified that the UI for |request| was hidden,
// |removed_request_awaiting_dismissal_| can be reset since the request no
// longer needs to be kept alive.
removed_request_awaiting_dismissal_ = nullptr;
// Only show the next overlay if the active request has changed, either
// because the frontmost request was popped or because the active WebState has
// changed.
if (GetActiveRequest() != request)
PresentOverlayForActiveRequest();
}
void OverlayPresenterImpl::OverlayWasCompleted(OverlayRequest* request,
OverlayResponse* response) {
previously_presented_requests_.erase(request);
}
#pragma mark UI Cancellation helpers
void OverlayPresenterImpl::CancelOverlayUIForRequest(OverlayRequest* request) {
if (!presentation_context_ || !request)
return;
presentation_context_->CancelOverlayUI(request);
}
void OverlayPresenterImpl::CancelAllOverlayUI() {
for (int i = 0; i < web_state_list_->count(); ++i) {
CancelOverlayUIForRequest(
GetFrontRequestForWebState(web_state_list_->GetWebStateAt(i)));
}
}
#pragma mark WebState helpers
void OverlayPresenterImpl::WebStateAddedToBrowser(web::WebState* web_state) {
OverlayRequestQueueImpl* queue = GetQueueForWebState(web_state);
queue->AddObserver(this);
queue->SetDelegate(this);
}
void OverlayPresenterImpl::WebStateRemovedFromBrowser(
web::WebState* web_state) {
OverlayRequestQueueImpl* queue = GetQueueForWebState(web_state);
queue->RemoveObserver(this);
// Only reset the delegate if there isn't a currently presented overlay or
// |presented_request_|'s WebState is not the WebState being removed. This
// will allow the presenter to extend the lifetime of |presented_request_| if
// it is removed from the queue before its dismissal finishes.
if (!presented_request_ ||
presented_request_->GetQueueWebState() != web_state) {
queue->SetDelegate(nullptr);
}
if (presented_request_ &&
presented_request_->GetQueueWebState() == web_state) {
detached_presenting_request_queue_ = GetQueueForWebState(web_state);
} else {
// For inactive WebState removals, the overlay UI can be cancelled
// immediately.
CancelOverlayUIForRequest(GetFrontRequestForWebState(web_state));
}
}
#pragma mark -
#pragma mark BrowserObserver
void OverlayPresenterImpl::BrowserDestroyed(Browser* browser) {
SetPresentationContext(nullptr);
SetActiveWebState(nullptr, ActiveWebStateChangeReason::Closed);
for (int i = 0; i < web_state_list_->count(); ++i) {
WebStateRemovedFromBrowser(web_state_list_->GetWebStateAt(i));
}
// All Webstates are detached before the Browser is destroyed so all request
// must be cancelled at this point.
DCHECK(!detached_presenting_request_queue_);
web_state_list_->RemoveObserver(this);
web_state_list_ = nullptr;
removed_request_awaiting_dismissal_ = nullptr;
browser_observation_.Reset();
}
#pragma mark OverlayRequestQueueImpl::Delegate
void OverlayPresenterImpl::OverlayRequestRemoved(
OverlayRequestQueueImpl* queue,
std::unique_ptr<OverlayRequest> request,
bool cancelled) {
OverlayRequest* removed_request = request.get();
if (presented_request_ == removed_request) {
removed_request_awaiting_dismissal_ = std::move(request);
if (detached_presenting_request_queue_) {
detached_presenting_request_queue_ = nullptr;
queue->SetDelegate(nullptr);
}
}
if (cancelled)
CancelOverlayUIForRequest(removed_request);
}
void OverlayPresenterImpl::OverlayRequestQueueWillReplaceDelegate(
OverlayRequestQueueImpl* queue) {
if (!presented_request_ || presented_request_ != queue->front_request())
return;
// If |presented_request_| is in the queue that is replacing this presenter
// as the delegate, it is no longer possible to extend the lifetime of
// |presented_request_|. Thus, call DidHideOverlay while it is still valid
// and reset its reference.
for (auto& observer : observers_) {
if (observer.GetRequestSupport(this)->IsRequestSupported(
presented_request_)) {
observer.DidHideOverlay(this, presented_request_);
}
}
presented_request_ = nullptr;
detached_presenting_request_queue_ = nullptr;
detached_queue_replaced_delegate_ = true;
}
#pragma mark OverlayRequestQueueImpl::Observer
void OverlayPresenterImpl::RequestAddedToQueue(OverlayRequestQueueImpl* queue,
OverlayRequest* request,
size_t index) {
// If |request| is not active, there is no need to trigger any presentation.
if (request != GetActiveRequest())
return;
// If the added request is active and there is no presentation occurring,
// present the overlay UI immediately.
if (!presenting_) {
PresentOverlayForActiveRequest();
return;
}
// |request| is the new active request, but overlay UI is already
// presented. This occurs when:
// 1. |request| is added after |presented_request_| is cancelled, but
// before its UI is finished being dismissed,
// 2. |request| is added immediately after a WebState activation, but
// before the overlay UI from the previously active WebState's front
// request is finished being dismissed, or
// 3. |request| is inserted to the front of the active WebState's request
// queue.
//
// For scenarios (1) and (2), the UI is already in the process of being
// dismissed, and |request|'s UI will be presented when that dismissal
// finishes. For scenario (3), the UI for the presented request needs to
// be hidden so that the UI for |request| can be presented.
bool should_dismiss_for_inserted_request =
presented_request_ && queue->size() > 1 &&
queue->GetRequest(/*index=*/1) == presented_request_;
if (should_dismiss_for_inserted_request)
presentation_context_->HideOverlayUI(presented_request_);
}
void OverlayPresenterImpl::OverlayRequestQueueDestroyed(
OverlayRequestQueueImpl* queue) {
queue->RemoveObserver(this);
}
#pragma mark - OverlayPresentationContextObserver
void OverlayPresenterImpl::
OverlayPresentationContextWillChangePresentationCapabilities(
OverlayPresentationContext* presentation_context,
OverlayPresentationContext::UIPresentationCapabilities capabilities) {
DCHECK_EQ(presentation_context_, presentation_context);
// Hide the presented overlay UI if the presentation context is transitioning
// to a state where that UI is not supported.
if (presented_request_ && !presentation_context->CanShowUIForRequest(
presented_request_, capabilities)) {
DCHECK(presenting_);
presentation_context_->HideOverlayUI(presented_request_);
}
}
void OverlayPresenterImpl::
OverlayPresentationContextDidChangePresentationCapabilities(
OverlayPresentationContext* presentation_context) {
DCHECK_EQ(presentation_context_, presentation_context);
if (!presenting_)
PresentOverlayForActiveRequest();
}
void OverlayPresenterImpl::OverlayPresentationContextDidEnableUI(
OverlayPresentationContext* presentation_context) {
DCHECK_EQ(presentation_context_, presentation_context);
if (!presenting_) {
PresentOverlayForActiveRequest();
}
}
void OverlayPresenterImpl::OverlayPresentationContextDidMoveToWindow(
OverlayPresentationContext* presentation_context,
UIWindow* window) {
DCHECK_EQ(presentation_context_, presentation_context);
if (!presenting_ && window)
PresentOverlayForActiveRequest();
}
#pragma mark - WebStateListObserver
void OverlayPresenterImpl::WebStateInsertedAt(WebStateList* web_state_list,
web::WebState* web_state,
int index,
bool activating) {
WebStateAddedToBrowser(web_state);
}
void OverlayPresenterImpl::WebStateReplacedAt(WebStateList* web_state_list,
web::WebState* old_web_state,
web::WebState* new_web_state,
int index) {
WebStateRemovedFromBrowser(old_web_state);
WebStateAddedToBrowser(new_web_state);
}
void OverlayPresenterImpl::WillDetachWebStateAt(WebStateList* web_state_list,
web::WebState* web_state,
int index) {
detaching_presenting_web_state_ =
presented_request_ ? presented_request_->GetQueueWebState() == web_state
: false;
WebStateRemovedFromBrowser(web_state);
}
void OverlayPresenterImpl::WebStateActivatedAt(
WebStateList* web_state_list,
web::WebState* old_web_state,
web::WebState* new_web_state,
int active_index,
ActiveWebStateChangeReason reason) {
SetActiveWebState(new_web_state, reason);
}