blob: c61416e0ba68a26915526208612605149753143f [file] [log] [blame]
// Copyright 2017 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_scheduler.h"
#include <list>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#import "ios/chrome/browser/ui/overlays/overlay_queue.h"
#import "ios/chrome/browser/ui/overlays/overlay_queue_manager.h"
#import "ios/chrome/browser/ui/overlays/overlay_scheduler_observer.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/web/public/web_state/web_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
DEFINE_BROWSER_USER_DATA_KEY(OverlayScheduler);
OverlayScheduler::OverlayScheduler(Browser* browser)
: web_state_list_(&browser->web_state_list()) {
// Create the OverlayQueueManager and add the scheduler as an observer to the
// manager and all its queues.
OverlayQueueManager::CreateForBrowser(browser);
queue_manager_ = OverlayQueueManager::FromBrowser(browser);
queue_manager_->AddObserver(this);
for (OverlayQueue* queue : queue_manager_->queues()) {
queue->AddObserver(this);
}
}
OverlayScheduler::~OverlayScheduler() {
// Disconnect() removes the scheduler as an observer and nils out
// |queue_manager_|. It is expected to be called before deallocation.
DCHECK(!queue_manager_);
}
#pragma mark - Public
void OverlayScheduler::AddObserver(OverlaySchedulerObserver* observer) {
observers_.AddObserver(observer);
}
void OverlayScheduler::RemoveObserver(OverlaySchedulerObserver* observer) {
observers_.RemoveObserver(observer);
}
void OverlayScheduler::SetQueueManager(OverlayQueueManager* queue_manager) {
if (queue_manager_) {
queue_manager_->RemoveObserver(this);
for (OverlayQueue* queue : queue_manager_->queues()) {
StopObservingQueue(queue);
}
}
queue_manager_ = queue_manager;
if (queue_manager_) {
queue_manager_->AddObserver(this);
for (OverlayQueue* queue : queue_manager_->queues()) {
queue->AddObserver(this);
}
}
}
bool OverlayScheduler::IsShowingOverlay() const {
return !overlay_queues_.empty() &&
overlay_queues_.front()->IsShowingOverlay();
}
void OverlayScheduler::SetPaused(bool paused) {
if (paused_ == paused)
return;
paused_ = paused;
if (!paused_)
TryToStartNextOverlay();
}
void OverlayScheduler::ReplaceVisibleOverlay(
OverlayCoordinator* overlay_coordinator) {
DCHECK(overlay_coordinator);
DCHECK(IsShowingOverlay());
overlay_queues_.front()->ReplaceVisibleOverlay(overlay_coordinator);
}
void OverlayScheduler::CancelOverlays() {
// |overlay_queues_| will be updated in OverlayQueueDidCancelOverlays(), so a
// while loop is used to avoid using invalidated iterators.
while (!overlay_queues_.empty()) {
overlay_queues_.front()->CancelOverlays();
}
}
void OverlayScheduler::Disconnect() {
queue_manager_->RemoveObserver(this);
for (OverlayQueue* queue : queue_manager_->queues()) {
StopObservingQueue(queue);
}
queue_manager_->Disconnect();
queue_manager_ = nullptr;
}
#pragma mark - OverlayQueueManagerObserver
void OverlayScheduler::OverlayQueueManagerDidAddQueue(
OverlayQueueManager* manager,
OverlayQueue* queue) {
queue->AddObserver(this);
}
void OverlayScheduler::OverlayQueueManagerWillRemoveQueue(
OverlayQueueManager* manager,
OverlayQueue* queue) {
StopObservingQueue(queue);
}
#pragma mark - OverlayQueueObserver
void OverlayScheduler::OverlayQueueDidAddOverlay(OverlayQueue* queue) {
DCHECK(queue);
overlay_queues_.push_back(queue);
TryToStartNextOverlay();
}
void OverlayScheduler::OverlayQueueWillReplaceVisibleOverlay(
OverlayQueue* queue) {
DCHECK(queue);
DCHECK_EQ(overlay_queues_.front(), queue);
DCHECK(queue->IsShowingOverlay());
// An OverlayQueue's visible overlay can only be replaced if it's the first
// queue in the scheduler and is already showing an overlay. The queue is
// added here to schedule its replacement overlay can be displayed when its
// currently-visible overlay is stopped.
overlay_queues_.push_front(queue);
}
void OverlayScheduler::OverlayQueueDidStopVisibleOverlay(OverlayQueue* queue) {
DCHECK(!overlay_queues_.empty());
DCHECK_EQ(overlay_queues_.front(), queue);
// Only the first queue in the scheduler can start overlays, so it is expected
// that this function is only called for that queue.
overlay_queues_.pop_front();
TryToStartNextOverlay();
}
void OverlayScheduler::OverlayQueueDidCancelOverlays(OverlayQueue* queue) {
DCHECK(queue);
// Remove all scheduled instances of |queue| from the |overlay_queues_|.
auto i = overlay_queues_.begin();
while (i != overlay_queues_.end()) {
if (*i == queue)
overlay_queues_.erase(i);
}
// If |queue| is currently showing an overlay, prepend it to
// |overlay_queues_|. It will be removed when the cancelled overlay is
// stopped.
if (queue->IsShowingOverlay())
overlay_queues_.push_front(queue);
}
#pragma mark - WebStateObserver
void OverlayScheduler::WasShown(web::WebState* web_state) {
StopObservingWebState(web_state);
}
void OverlayScheduler::WebStateDestroyed(web::WebState* web_state) {
StopObservingWebState(web_state);
}
#pragma mark -
void OverlayScheduler::TryToStartNextOverlay() {
// Overlays cannot be started if:
// - the service is paused,
// - there are no overlays to display,
// - an overlay is already being displayed,
// - an overlay is already scheduled to be started once a specific WebState
// has been shown.
if (paused_ || overlay_queues_.empty() || IsShowingOverlay() ||
observing_front_web_state_) {
return;
}
OverlayQueue* queue = overlay_queues_.front();
web::WebState* web_state = queue->GetWebState();
// Create a WebStateVisibilityObserver if |web_state| needs to be activated.
if (web_state && (web_state_list_->GetActiveWebState() != web_state ||
!web_state->IsVisible())) {
web_state->AddObserver(this);
observing_front_web_state_ = true;
}
// Notify the observers that an overlay will be shown for |web_state|. If
// |web_state| isn't nil, this callback is expected to update the active
// WebState and show its content area.
for (auto& observer : observers_) {
observer.OverlaySchedulerWillShowOverlay(this, web_state);
}
// If |web_state| was nil or already activated, then start |queue|'s next
// overlay. Otherwise, it will be started in OnWebStateShown(). The check
// for IsShowingOverlay() is to prevent calling StartNextOverlay() twice in
// the event that WasShown() was called directly from the observer callbacks
// above.
if (!observing_front_web_state_ && !IsShowingOverlay()) {
queue->StartNextOverlay();
}
}
void OverlayScheduler::StopObservingQueue(OverlayQueue* queue) {
queue->RemoveObserver(this);
queue->CancelOverlays();
}
void OverlayScheduler::StopObservingWebState(web::WebState* web_state) {
DCHECK(observing_front_web_state_);
DCHECK(web_state);
DCHECK_EQ(overlay_queues_.front()->GetWebState(), web_state);
web_state->RemoveObserver(this);
observing_front_web_state_ = false;
TryToStartNextOverlay();
}