blob: bf15e87f30a0fb96de59b55bfcda15f7541cbc9b [file] [log] [blame]
// Copyright 2015 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 "components/bubble/bubble_manager.h"
#include <utility>
#include <vector>
#include "components/bubble/bubble_controller.h"
#include "components/bubble/bubble_delegate.h"
BubbleManager::BubbleManager() : manager_state_(SHOW_BUBBLES) {}
BubbleManager::~BubbleManager() {
FinalizePendingRequests();
}
BubbleReference BubbleManager::ShowBubble(
std::unique_ptr<BubbleDelegate> bubble) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_NE(manager_state_, ITERATING_BUBBLES);
DCHECK(bubble);
std::unique_ptr<BubbleController> controller(
new BubbleController(this, std::move(bubble)));
BubbleReference bubble_ref = controller->AsWeakPtr();
switch (manager_state_) {
case SHOW_BUBBLES:
controller->Show();
controllers_.push_back(std::move(controller));
break;
case NO_MORE_BUBBLES:
for (auto& observer : observers_)
observer.OnBubbleNeverShown(controller->AsWeakPtr());
break;
default:
NOTREACHED();
break;
}
return bubble_ref;
}
bool BubbleManager::CloseBubble(BubbleReference bubble,
BubbleCloseReason reason) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_NE(manager_state_, ITERATING_BUBBLES);
return CloseAllMatchingBubbles(bubble.get(), nullptr, reason);
}
void BubbleManager::CloseAllBubbles(BubbleCloseReason reason) {
// The following close reasons don't make sense for multiple bubbles:
DCHECK_NE(reason, BUBBLE_CLOSE_ACCEPTED);
DCHECK_NE(reason, BUBBLE_CLOSE_CANCELED);
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_NE(manager_state_, ITERATING_BUBBLES);
CloseAllMatchingBubbles(nullptr, nullptr, reason);
}
void BubbleManager::UpdateAllBubbleAnchors() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_NE(manager_state_, ITERATING_BUBBLES);
// Guard against bubbles being added or removed while iterating the bubbles.
ManagerState original_state = manager_state_;
manager_state_ = ITERATING_BUBBLES;
for (auto& controller : controllers_)
controller->UpdateAnchorPosition();
manager_state_ = original_state;
}
void BubbleManager::AddBubbleManagerObserver(BubbleManagerObserver* observer) {
observers_.AddObserver(observer);
}
void BubbleManager::RemoveBubbleManagerObserver(
BubbleManagerObserver* observer) {
observers_.RemoveObserver(observer);
}
void BubbleManager::FinalizePendingRequests() {
// Return if already "Finalized".
if (manager_state_ == NO_MORE_BUBBLES)
return;
manager_state_ = NO_MORE_BUBBLES;
CloseAllBubbles(BUBBLE_CLOSE_FORCED);
}
void BubbleManager::CloseBubblesOwnedBy(const content::RenderFrameHost* frame) {
CloseAllMatchingBubbles(nullptr, frame, BUBBLE_CLOSE_FRAME_DESTROYED);
}
bool BubbleManager::CloseAllMatchingBubbles(
BubbleController* bubble,
const content::RenderFrameHost* owner,
BubbleCloseReason reason) {
// Specifying both an owning frame and a particular bubble to close doesn't
// make sense. If we have a frame, all bubbles owned by that frame need to
// have the opportunity to close. If we want to close a specific bubble, then
// it should get the close event regardless of which frame owns it. On the
// other hand, OR'ing the conditions needs a special case in order to be able
// to close all bubbles, so we disallow passing both until a need appears.
DCHECK(!bubble || !owner);
std::vector<std::unique_ptr<BubbleController>> close_queue;
// Guard against bubbles being added or removed while iterating the bubbles.
ManagerState original_state = manager_state_;
manager_state_ = ITERATING_BUBBLES;
for (auto i = controllers_.begin(); i != controllers_.end();) {
if ((!bubble || bubble == (*i).get()) &&
(!owner || (*i)->OwningFrameIs(owner)) && (*i)->ShouldClose(reason)) {
close_queue.push_back(std::move(*i));
i = controllers_.erase(i);
} else {
++i;
}
}
manager_state_ = original_state;
for (auto& controller : close_queue) {
controller->DoClose(reason);
for (auto& observer : observers_)
observer.OnBubbleClosed(controller->AsWeakPtr(), reason);
}
return !close_queue.empty();
}