Commit c13b16f9 authored by yoshiki iguchi's avatar yoshiki iguchi Committed by Commit Bot

Add a queue to defer requests during itarating notification in MessageCenterImpl

A crash might happen when an observer to MessageCenter removes notification in handler, because future observer couldn't access to the notification. This patch fixes the issue by adding a queue and defering requests during itaration.

Bug: 762803
Test: Added test passes
Change-Id: Id2c9a34602e14869c9b6db7d6dd7cff94b0089f0
Reviewed-on: https://chromium-review.googlesource.com/676624Reviewed-by: default avatarEliot Courtney <edcourtney@chromium.org>
Commit-Queue: Yoshiki Iguchi <yoshiki@chromium.org>
Cr-Commit-Position: refs/heads/master@{#508609}
parent 09db708d
...@@ -57,6 +57,8 @@ component("message_center") { ...@@ -57,6 +57,8 @@ component("message_center") {
"//build/config/compiler:no_size_t_to_int_warning", "//build/config/compiler:no_size_t_to_int_warning",
] ]
sources = [ sources = [
"change_queue.cc",
"change_queue.h",
"cocoa/notification_controller.h", "cocoa/notification_controller.h",
"cocoa/notification_controller.mm", "cocoa/notification_controller.mm",
"cocoa/opaque_views.h", "cocoa/opaque_views.h",
...@@ -205,6 +207,7 @@ if (enable_message_center) { ...@@ -205,6 +207,7 @@ if (enable_message_center) {
test("message_center_unittests") { test("message_center_unittests") {
sources = [ sources = [
"change_queue_unittest.cc",
"cocoa/notification_controller_unittest.mm", "cocoa/notification_controller_unittest.mm",
"cocoa/popup_collection_unittest.mm", "cocoa/popup_collection_unittest.mm",
"cocoa/popup_controller_unittest.mm", "cocoa/popup_controller_unittest.mm",
......
// 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.
#include "ui/message_center/change_queue.h"
#include "ui/message_center/message_center_impl.h"
#include "ui/message_center/notification.h"
namespace message_center {
// Change represents an operation made on a notification. Since it contains
// the final state of the notification, except complex cases, we generally
// optimize the list and keep only the last change for a particular notification
// that is in the notification list around. There are two ids; |id_| is the
// post-update notification id that has been assigned by an update, and
// |previous_id| is the previous id of the notification before the Change.
// The two ids are same unless the Change changes the id of the notification.
// See the comments of id() and previous_id() for reference.
class ChangeQueue::Change {
public:
Change(ChangeType type,
const std::string& id,
std::unique_ptr<Notification> notification);
~Change();
// Used to transfer ownership of the contained notification.
std::unique_ptr<Notification> PassNotification();
Notification* notification() const { return notification_.get(); }
// Returns the post-update ID. It means:
// - ADD event: ID of the notification to be added. In this case, this must be
// same as |previous_id()|.
// - UPDATE event: ID of the notification after the change. If the change
// doesn't update its ID, this value is same as |previous_id()|.
// - DELETE event: ID of the notification to be deleted. This must be same as
// |previous_id()|.
const std::string& id() const { return id_; }
ChangeType type() const { return type_; }
bool by_user() const { return by_user_; }
void set_by_user(bool by_user) { by_user_ = by_user; }
// Returns the ID which is used in the notification list. In other word, it
// means the ID before the change.
const std::string& previous_id() const { return previous_id_; }
void set_type(const ChangeType new_type) { type_ = new_type; }
void ReplaceNotification(std::unique_ptr<Notification> new_notification);
private:
ChangeType type_;
std::string id_;
std::string previous_id_;
bool by_user_;
std::unique_ptr<Notification> notification_;
DISALLOW_COPY_AND_ASSIGN(Change);
};
////////////////////////////////////////////////////////////////////////////////
// ChangeFinder
struct ChangeFinder {
explicit ChangeFinder(const std::string& id) : id(id) {}
bool operator()(const std::unique_ptr<ChangeQueue::Change>& change) {
return change->id() == id;
}
std::string id;
};
////////////////////////////////////////////////////////////////////////////////
// ChangeQueue::Change
ChangeQueue::Change::Change(ChangeType type,
const std::string& id,
std::unique_ptr<Notification> notification)
: type_(type),
previous_id_(id),
by_user_(false),
notification_(std::move(notification)) {
DCHECK(!id.empty());
DCHECK(type != CHANGE_TYPE_DELETE || !notification_);
id_ = notification_ ? notification_->id() : previous_id_;
}
ChangeQueue::Change::~Change() {}
std::unique_ptr<Notification> ChangeQueue::Change::PassNotification() {
return std::move(notification_);
}
void ChangeQueue::Change::ReplaceNotification(
std::unique_ptr<Notification> new_notification) {
id_ = new_notification ? new_notification->id() : previous_id_;
notification_.swap(new_notification);
}
////////////////////////////////////////////////////////////////////////////////
// ChangeQueue
ChangeQueue::ChangeQueue() = default;
ChangeQueue::~ChangeQueue() = default;
void ChangeQueue::ApplyChanges(MessageCenterImpl* message_center) {
while (!changes_.empty()) {
auto iter = changes_.begin();
std::unique_ptr<Change> change(std::move(*iter));
changes_.erase(iter);
ApplyChangeInternal(message_center, std::move(change));
}
}
void ChangeQueue::AddNotification(std::unique_ptr<Notification> notification) {
std::string id = notification->id();
changes_.push_back(
std::make_unique<Change>(CHANGE_TYPE_ADD, id, std::move(notification)));
}
void ChangeQueue::UpdateNotification(
const std::string& old_id,
std::unique_ptr<Notification> notification) {
changes_.push_back(std::make_unique<Change>(CHANGE_TYPE_UPDATE, old_id,
std::move(notification)));
}
void ChangeQueue::RemoveNotification(const std::string& id, bool by_user) {
auto change = std::make_unique<Change>(CHANGE_TYPE_DELETE, id, nullptr);
change->set_by_user(by_user);
changes_.push_back(std::move(change));
}
Notification* ChangeQueue::GetLatestNotification(const std::string& id) const {
auto iter =
std::find_if(changes_.rbegin(), changes_.rend(), ChangeFinder(id));
if (iter == changes_.rend())
return nullptr;
if ((*iter)->type() == CHANGE_TYPE_DELETE)
return nullptr;
return (*iter)->notification();
}
void ChangeQueue::ApplyChangeInternal(MessageCenterImpl* message_center,
std::unique_ptr<Change> change) {
switch (change->type()) {
case CHANGE_TYPE_ADD:
message_center->AddNotificationImmediately(change->PassNotification());
break;
case CHANGE_TYPE_UPDATE:
message_center->UpdateNotificationImmediately(change->previous_id(),
change->PassNotification());
break;
case CHANGE_TYPE_DELETE:
message_center->RemoveNotificationImmediately(change->previous_id(),
change->by_user());
break;
default:
NOTREACHED();
}
}
} // namespace message_center
// 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.
#ifndef UI_MESSAGE_CENTER_CHANGE_QUEUE_H_
#define UI_MESSAGE_CENTER_CHANGE_QUEUE_H_
#include <vector>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "ui/message_center/message_center_export.h"
namespace message_center {
class MessageCenterImpl;
class Notification;
// ChangeQueue keeps track of all the changes. This class can be used for
// queuing changes during the message center can't be updated.
class MESSAGE_CENTER_EXPORT ChangeQueue {
public:
enum ChangeType {
CHANGE_TYPE_ADD = 0,
CHANGE_TYPE_UPDATE,
CHANGE_TYPE_DELETE
};
// Change represents an operation made on a notification. Since it contains
// the final state of the notification, we only keep the last change for a
// particular notification that is in the notification list around. There are
// two ids; |id_| is the newest notification id that has been assigned by an
// update, and |notification_list_id_| is the id of the notification it should
// be updating as it exists in the notification list.
class Change;
ChangeQueue();
~ChangeQueue();
// Called when the message center has appropriate visibility. Modifies
// |message_center| but does not retain it. This also causes the queue to
// empty itself.
void ApplyChanges(MessageCenterImpl* message_center);
// Causes a TYPE_ADD change to be added to the queue.
void AddNotification(std::unique_ptr<Notification> notification);
// Causes a TYPE_UPDATE change to be added to the queue.
void UpdateNotification(const std::string& old_id,
std::unique_ptr<Notification> notification);
// Causes a TYPE_DELETE change to be added to the queue.
void RemoveNotification(const std::string& id, bool by_user);
// Returns the pointer to the Notification in the latest Change which has the
// given ID. If the Change is UPDATE, the ID after the change is searched.
// If the Change id REMOVE, this returns nullptr.
// ChangeQueue keeps retaining ownership of the Notification. The returned
// notification can be modified or be copied by caller.
Notification* GetLatestNotification(const std::string& id) const;
private:
void ApplyChangeInternal(MessageCenterImpl* message_center,
std::unique_ptr<Change> change);
std::vector<std::unique_ptr<Change>> changes_;
DISALLOW_COPY_AND_ASSIGN(ChangeQueue);
};
} // namespace message_center
#endif // UI_MESSAGE_CENTER_CHANGE_QUEUE_H_
// 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.
#include "ui/message_center/change_queue.h"
#include <utility>
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/message_center/message_center_impl.h"
using base::UTF8ToUTF16;
namespace message_center {
namespace {
Notification* CreateSimpleNotification(const std::string& id) {
RichNotificationData optional_fields;
optional_fields.buttons.push_back(ButtonInfo(UTF8ToUTF16("foo")));
optional_fields.buttons.push_back(ButtonInfo(UTF8ToUTF16("foo")));
return new Notification(
NOTIFICATION_TYPE_SIMPLE, id, UTF8ToUTF16("title"), UTF8ToUTF16(id),
gfx::Image() /* icon */, base::string16() /* display_source */, GURL(),
NotifierId(NotifierId::APPLICATION, "change_queue_test"), optional_fields,
NULL);
}
} // namespace
class ChangeQueueTest : public testing::Test {
public:
ChangeQueueTest() = default;
~ChangeQueueTest() override = default;
MessageCenterImpl* message_center() { return &message_center_; }
private:
MessageCenterImpl message_center_;
};
TEST_F(ChangeQueueTest, Queueing) {
ChangeQueue queue;
std::string id("id1");
std::string id2("id2");
NotifierId notifier_id1(NotifierId::APPLICATION, "app1");
// First, add and update a notification to ensure updates happen
// normally.
std::unique_ptr<Notification> notification(CreateSimpleNotification(id));
message_center()->AddNotification(std::move(notification));
notification.reset(CreateSimpleNotification(id2));
message_center()->UpdateNotification(id, std::move(notification));
EXPECT_TRUE(message_center()->FindVisibleNotificationById(id2));
EXPECT_FALSE(message_center()->FindVisibleNotificationById(id));
// Then update a notification; nothing should have happened.
notification.reset(CreateSimpleNotification(id));
queue.UpdateNotification(id2, std::move(notification));
EXPECT_TRUE(message_center()->FindVisibleNotificationById(id2));
EXPECT_FALSE(message_center()->FindVisibleNotificationById(id));
// Apply the changes from the queue.
queue.ApplyChanges(message_center());
// Close the message center; then the update should have propagated.
EXPECT_FALSE(message_center()->FindVisibleNotificationById(id2));
EXPECT_TRUE(message_center()->FindVisibleNotificationById(id));
}
TEST_F(ChangeQueueTest, SimpleQueueing) {
ChangeQueue queue;
std::string ids[6] = {"0", "1", "2", "3", "4", "5"};
NotifierId notifier_id1(NotifierId::APPLICATION, "app1");
std::unique_ptr<Notification> notification;
// Prepare initial notifications on NotificationList.
// NL: ["0", "1", "2", "4"]
int i = 0;
for (; i < 3; i++) {
notification.reset(CreateSimpleNotification(ids[i]));
message_center()->AddNotification(std::move(notification));
}
for (i = 0; i < 3; i++) {
EXPECT_TRUE(message_center()->FindVisibleNotificationById(ids[i]));
}
for (; i < 6; i++) {
EXPECT_FALSE(message_center()->FindVisibleNotificationById(ids[i]));
}
// This should update notification "1" to have id "4".
notification.reset(CreateSimpleNotification(ids[4]));
queue.AddNotification(std::move(notification));
// Change the ID: "2" -> "5", "5" -> "3".
notification.reset(CreateSimpleNotification(ids[5]));
queue.UpdateNotification(ids[2], std::move(notification));
notification.reset(CreateSimpleNotification(ids[3]));
queue.UpdateNotification(ids[5], std::move(notification));
// This should update notification "4" to "5".
notification.reset(CreateSimpleNotification(ids[5]));
queue.UpdateNotification(ids[4], std::move(notification));
// This should create a new "4", that doesn't overwrite the update from 4
// before.
notification.reset(CreateSimpleNotification(ids[4]));
queue.AddNotification(std::move(notification));
// The NL should still be the same: ["0", "1", "2", "4"]
EXPECT_TRUE(message_center()->FindVisibleNotificationById(ids[0]));
EXPECT_TRUE(message_center()->FindVisibleNotificationById(ids[1]));
EXPECT_TRUE(message_center()->FindVisibleNotificationById(ids[2]));
EXPECT_FALSE(message_center()->FindVisibleNotificationById(ids[3]));
EXPECT_FALSE(message_center()->FindVisibleNotificationById(ids[4]));
EXPECT_FALSE(message_center()->FindVisibleNotificationById(ids[5]));
EXPECT_EQ(message_center()->GetVisibleNotifications().size(), 3u);
// Apply the changes from the queue.
queue.ApplyChanges(message_center());
// Changes should be applied.
EXPECT_TRUE(message_center()->FindVisibleNotificationById(ids[0]));
EXPECT_TRUE(message_center()->FindVisibleNotificationById(ids[1]));
EXPECT_FALSE(message_center()->FindVisibleNotificationById(ids[2]));
EXPECT_TRUE(message_center()->FindVisibleNotificationById(ids[3]));
EXPECT_TRUE(message_center()->FindVisibleNotificationById(ids[4]));
EXPECT_TRUE(message_center()->FindVisibleNotificationById(ids[5]));
EXPECT_EQ(message_center()->GetVisibleNotifications().size(), 5u);
}
} // namespace message_center
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/auto_reset.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
...@@ -49,6 +50,40 @@ void MessageCenterImpl::NotificationCache::RecountUnread() { ...@@ -49,6 +50,40 @@ void MessageCenterImpl::NotificationCache::RecountUnread() {
} }
} }
namespace internal {
////////////////////////////////////////////////////////////////////////////////
// ScopedNotificationsIterationLock
class ScopedNotificationsIterationLock {
public:
// Lock may be used recursively.
explicit ScopedNotificationsIterationLock(MessageCenterImpl* message_center)
: message_center_(message_center),
original_value_(message_center->iterating_) {
message_center_->iterating_ = true;
}
~ScopedNotificationsIterationLock() {
DCHECK(message_center_->iterating_);
// |original_value_| must be false. But handle the other case just in case.
message_center_->iterating_ = original_value_;
if (!original_value_) {
message_center_->notification_change_queue_->ApplyChanges(
message_center_);
}
}
private:
MessageCenterImpl* message_center_;
const bool original_value_;
DISALLOW_COPY_AND_ASSIGN(ScopedNotificationsIterationLock);
};
} // namespace internal
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// MessageCenterImpl // MessageCenterImpl
...@@ -57,6 +92,7 @@ MessageCenterImpl::MessageCenterImpl() ...@@ -57,6 +92,7 @@ MessageCenterImpl::MessageCenterImpl()
popup_timers_controller_(new PopupTimersController(this)), popup_timers_controller_(new PopupTimersController(this)),
settings_provider_(NULL) { settings_provider_(NULL) {
notification_list_.reset(new NotificationList(this)); notification_list_.reset(new NotificationList(this));
notification_change_queue_.reset(new ChangeQueue());
} }
MessageCenterImpl::~MessageCenterImpl() { MessageCenterImpl::~MessageCenterImpl() {
...@@ -95,6 +131,7 @@ void MessageCenterImpl::RemoveNotificationBlocker( ...@@ -95,6 +131,7 @@ void MessageCenterImpl::RemoveNotificationBlocker(
void MessageCenterImpl::OnBlockingStateChanged(NotificationBlocker* blocker) { void MessageCenterImpl::OnBlockingStateChanged(NotificationBlocker* blocker) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
std::list<std::string> blocked_ids; std::list<std::string> blocked_ids;
NotificationList::PopupNotifications popups = NotificationList::PopupNotifications popups =
notification_list_->GetPopupNotifications(blockers_, &blocked_ids); notification_list_->GetPopupNotifications(blockers_, &blocked_ids);
...@@ -110,6 +147,7 @@ void MessageCenterImpl::OnBlockingStateChanged(NotificationBlocker* blocker) { ...@@ -110,6 +147,7 @@ void MessageCenterImpl::OnBlockingStateChanged(NotificationBlocker* blocker) {
notification_list_->GetVisibleNotifications(blockers_)); notification_list_->GetVisibleNotifications(blockers_));
for (const auto& id : blocked_ids) { for (const auto& id : blocked_ids) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
observer.OnNotificationUpdated(id); observer.OnNotificationUpdated(id);
} }
...@@ -136,6 +174,7 @@ void MessageCenterImpl::NotifierEnabledChanged( ...@@ -136,6 +174,7 @@ void MessageCenterImpl::NotifierEnabledChanged(
void MessageCenterImpl::SetVisibility(Visibility visibility) { void MessageCenterImpl::SetVisibility(Visibility visibility) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
visible_ = (visibility == VISIBILITY_MESSAGE_CENTER); visible_ = (visibility == VISIBILITY_MESSAGE_CENTER);
if (visible_ && !locked_) { if (visible_ && !locked_) {
...@@ -143,9 +182,12 @@ void MessageCenterImpl::SetVisibility(Visibility visibility) { ...@@ -143,9 +182,12 @@ void MessageCenterImpl::SetVisibility(Visibility visibility) {
notification_list_->SetNotificationsShown(blockers_, &updated_ids); notification_list_->SetNotificationsShown(blockers_, &updated_ids);
notification_cache_.RecountUnread(); notification_cache_.RecountUnread();
for (const auto& id : updated_ids) { {
for (auto& observer : observer_list_) internal::ScopedNotificationsIterationLock lock(this);
observer.OnNotificationUpdated(id); for (const auto& id : updated_ids) {
for (auto& observer : observer_list_)
observer.OnNotificationUpdated(id);
}
} }
} }
...@@ -219,12 +261,18 @@ void MessageCenterImpl::AddNotification( ...@@ -219,12 +261,18 @@ void MessageCenterImpl::AddNotification(
for (size_t i = 0; i < blockers_.size(); ++i) for (size_t i = 0; i < blockers_.size(); ++i)
blockers_[i]->CheckState(); blockers_[i]->CheckState();
if (iterating_) {
notification_change_queue_->AddNotification(std::move(notification));
return;
}
AddNotificationImmediately(std::move(notification)); AddNotificationImmediately(std::move(notification));
} }
void MessageCenterImpl::AddNotificationImmediately( void MessageCenterImpl::AddNotificationImmediately(
std::unique_ptr<Notification> notification) { std::unique_ptr<Notification> notification) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
const std::string id = notification->id(); const std::string id = notification->id();
// Sometimes the notification can be added with the same id and the // Sometimes the notification can be added with the same id and the
...@@ -236,9 +284,11 @@ void MessageCenterImpl::AddNotificationImmediately( ...@@ -236,9 +284,11 @@ void MessageCenterImpl::AddNotificationImmediately(
notification_list_->GetVisibleNotifications(blockers_)); notification_list_->GetVisibleNotifications(blockers_));
if (already_exists) { if (already_exists) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
observer.OnNotificationUpdated(id); observer.OnNotificationUpdated(id);
} else { } else {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
observer.OnNotificationAdded(id); observer.OnNotificationAdded(id);
} }
...@@ -251,6 +301,12 @@ void MessageCenterImpl::UpdateNotification( ...@@ -251,6 +301,12 @@ void MessageCenterImpl::UpdateNotification(
for (size_t i = 0; i < blockers_.size(); ++i) for (size_t i = 0; i < blockers_.size(); ++i)
blockers_[i]->CheckState(); blockers_[i]->CheckState();
if (iterating_) {
notification_change_queue_->UpdateNotification(old_id,
std::move(new_notification));
return;
}
UpdateNotificationImmediately(old_id, std::move(new_notification)); UpdateNotificationImmediately(old_id, std::move(new_notification));
} }
...@@ -258,15 +314,18 @@ void MessageCenterImpl::UpdateNotificationImmediately( ...@@ -258,15 +314,18 @@ void MessageCenterImpl::UpdateNotificationImmediately(
const std::string& old_id, const std::string& old_id,
std::unique_ptr<Notification> new_notification) { std::unique_ptr<Notification> new_notification) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
std::string new_id = new_notification->id(); std::string new_id = new_notification->id();
notification_list_->UpdateNotificationMessage(old_id, notification_list_->UpdateNotificationMessage(old_id,
std::move(new_notification)); std::move(new_notification));
notification_cache_.Rebuild( notification_cache_.Rebuild(
notification_list_->GetVisibleNotifications(blockers_)); notification_list_->GetVisibleNotifications(blockers_));
if (old_id == new_id) { if (old_id == new_id) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
observer.OnNotificationUpdated(new_id); observer.OnNotificationUpdated(new_id);
} else { } else {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
observer.OnNotificationRemoved(old_id, false); observer.OnNotificationRemoved(old_id, false);
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
...@@ -277,12 +336,20 @@ void MessageCenterImpl::UpdateNotificationImmediately( ...@@ -277,12 +336,20 @@ void MessageCenterImpl::UpdateNotificationImmediately(
void MessageCenterImpl::RemoveNotification(const std::string& id, void MessageCenterImpl::RemoveNotification(const std::string& id,
bool by_user) { bool by_user) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (iterating_) {
notification_change_queue_->RemoveNotification(id, by_user);
return;
}
RemoveNotificationImmediately(id, by_user); RemoveNotificationImmediately(id, by_user);
} }
void MessageCenterImpl::RemoveNotificationImmediately( void MessageCenterImpl::RemoveNotificationImmediately(
const std::string& id, bool by_user) { const std::string& id, bool by_user) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
Notification* notification = FindVisibleNotificationById(id); Notification* notification = FindVisibleNotificationById(id);
if (notification == NULL) if (notification == NULL)
return; return;
...@@ -303,8 +370,27 @@ void MessageCenterImpl::RemoveNotificationImmediately( ...@@ -303,8 +370,27 @@ void MessageCenterImpl::RemoveNotificationImmediately(
notification_list_->RemoveNotification(copied_id); notification_list_->RemoveNotification(copied_id);
notification_cache_.Rebuild( notification_cache_.Rebuild(
notification_list_->GetVisibleNotifications(blockers_)); notification_list_->GetVisibleNotifications(blockers_));
for (auto& observer : observer_list_) {
observer.OnNotificationRemoved(copied_id, by_user); internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationRemoved(copied_id, by_user);
}
}
Notification* MessageCenterImpl::GetLatestNotificationIncludingQueued(
const std::string& id) const {
Notification* queued_notification =
notification_change_queue_->GetLatestNotification(id);
if (queued_notification) {
DCHECK(iterating_);
return queued_notification;
}
Notification* notification = notification_list_->GetNotificationById(id);
if (notification)
return notification;
return nullptr;
} }
void MessageCenterImpl::RemoveNotificationsForNotifierId( void MessageCenterImpl::RemoveNotificationsForNotifierId(
...@@ -322,6 +408,7 @@ void MessageCenterImpl::RemoveNotificationsForNotifierId( ...@@ -322,6 +408,7 @@ void MessageCenterImpl::RemoveNotificationsForNotifierId(
void MessageCenterImpl::RemoveAllNotifications(bool by_user, RemoveType type) { void MessageCenterImpl::RemoveAllNotifications(bool by_user, RemoveType type) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
bool remove_pinned = (type == RemoveType::ALL); bool remove_pinned = (type == RemoveType::ALL);
const NotificationBlockers& blockers = const NotificationBlockers& blockers =
...@@ -346,16 +433,34 @@ void MessageCenterImpl::RemoveAllNotifications(bool by_user, RemoveType type) { ...@@ -346,16 +433,34 @@ void MessageCenterImpl::RemoveAllNotifications(bool by_user, RemoveType type) {
notification_cache_.Rebuild( notification_cache_.Rebuild(
notification_list_->GetVisibleNotifications(blockers_)); notification_list_->GetVisibleNotifications(blockers_));
} }
for (const auto& id : ids) { {
for (auto& observer : observer_list_) internal::ScopedNotificationsIterationLock lock(this);
observer.OnNotificationRemoved(id, by_user); for (const auto& id : ids) {
for (auto& observer : observer_list_)
observer.OnNotificationRemoved(id, by_user);
}
} }
} }
void MessageCenterImpl::SetNotificationIcon(const std::string& notification_id, void MessageCenterImpl::SetNotificationIcon(const std::string& notification_id,
const gfx::Image& image) { const gfx::Image& image) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (iterating_) {
Notification* notification =
GetLatestNotificationIncludingQueued(notification_id);
if (notification) {
std::unique_ptr<Notification> copied_notification =
std::make_unique<Notification>(*notification);
copied_notification->set_icon(image);
notification_change_queue_->UpdateNotification(
notification_id, std::move(copied_notification));
}
return;
}
if (notification_list_->SetNotificationIcon(notification_id, image)) { if (notification_list_->SetNotificationIcon(notification_id, image)) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
observer.OnNotificationUpdated(notification_id); observer.OnNotificationUpdated(notification_id);
} }
...@@ -363,8 +468,22 @@ void MessageCenterImpl::SetNotificationIcon(const std::string& notification_id, ...@@ -363,8 +468,22 @@ void MessageCenterImpl::SetNotificationIcon(const std::string& notification_id,
void MessageCenterImpl::SetNotificationImage(const std::string& notification_id, void MessageCenterImpl::SetNotificationImage(const std::string& notification_id,
const gfx::Image& image) { const gfx::Image& image) {
if (iterating_) {
Notification* notification =
GetLatestNotificationIncludingQueued(notification_id);
if (notification) {
std::unique_ptr<Notification> copied_notification =
std::make_unique<Notification>(*notification);
copied_notification->set_image(image);
notification_change_queue_->UpdateNotification(
notification_id, std::move(copied_notification));
}
return;
}
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (notification_list_->SetNotificationImage(notification_id, image)) { if (notification_list_->SetNotificationImage(notification_id, image)) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
observer.OnNotificationUpdated(notification_id); observer.OnNotificationUpdated(notification_id);
} }
...@@ -373,9 +492,23 @@ void MessageCenterImpl::SetNotificationImage(const std::string& notification_id, ...@@ -373,9 +492,23 @@ void MessageCenterImpl::SetNotificationImage(const std::string& notification_id,
void MessageCenterImpl::SetNotificationButtonIcon( void MessageCenterImpl::SetNotificationButtonIcon(
const std::string& notification_id, int button_index, const std::string& notification_id, int button_index,
const gfx::Image& image) { const gfx::Image& image) {
if (iterating_) {
Notification* notification =
GetLatestNotificationIncludingQueued(notification_id);
if (notification) {
std::unique_ptr<Notification> copied_notification =
std::make_unique<Notification>(*notification);
copied_notification->SetButtonIcon(button_index, image);
notification_change_queue_->UpdateNotification(
notification_id, std::move(copied_notification));
}
return;
}
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (notification_list_->SetNotificationButtonIcon(notification_id, if (notification_list_->SetNotificationButtonIcon(notification_id,
button_index, image)) { button_index, image)) {
internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
observer.OnNotificationUpdated(notification_id); observer.OnNotificationUpdated(notification_id);
} }
...@@ -383,6 +516,7 @@ void MessageCenterImpl::SetNotificationButtonIcon( ...@@ -383,6 +516,7 @@ void MessageCenterImpl::SetNotificationButtonIcon(
void MessageCenterImpl::ClickOnNotification(const std::string& id) { void MessageCenterImpl::ClickOnNotification(const std::string& id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
if (FindVisibleNotificationById(id) == NULL) if (FindVisibleNotificationById(id) == NULL)
return; return;
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
...@@ -393,13 +527,17 @@ void MessageCenterImpl::ClickOnNotification(const std::string& id) { ...@@ -393,13 +527,17 @@ void MessageCenterImpl::ClickOnNotification(const std::string& id) {
notification_list_->GetNotificationDelegate(id); notification_list_->GetNotificationDelegate(id);
if (delegate.get()) if (delegate.get())
delegate->Click(); delegate->Click();
for (auto& observer : observer_list_) {
observer.OnNotificationClicked(id); internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationClicked(id);
}
} }
void MessageCenterImpl::ClickOnNotificationButton(const std::string& id, void MessageCenterImpl::ClickOnNotificationButton(const std::string& id,
int button_index) { int button_index) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
if (FindVisibleNotificationById(id) == NULL) if (FindVisibleNotificationById(id) == NULL)
return; return;
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
...@@ -410,12 +548,16 @@ void MessageCenterImpl::ClickOnNotificationButton(const std::string& id, ...@@ -410,12 +548,16 @@ void MessageCenterImpl::ClickOnNotificationButton(const std::string& id,
notification_list_->GetNotificationDelegate(id); notification_list_->GetNotificationDelegate(id);
if (delegate.get()) if (delegate.get())
delegate->ButtonClick(button_index); delegate->ButtonClick(button_index);
for (auto& observer : observer_list_) {
observer.OnNotificationButtonClicked(id, button_index); internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationButtonClicked(id, button_index);
}
} }
void MessageCenterImpl::ClickOnSettingsButton(const std::string& id) { void MessageCenterImpl::ClickOnSettingsButton(const std::string& id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!iterating_);
scoped_refptr<NotificationDelegate> delegate = scoped_refptr<NotificationDelegate> delegate =
notification_list_->GetNotificationDelegate(id); notification_list_->GetNotificationDelegate(id);
...@@ -423,8 +565,11 @@ void MessageCenterImpl::ClickOnSettingsButton(const std::string& id) { ...@@ -423,8 +565,11 @@ void MessageCenterImpl::ClickOnSettingsButton(const std::string& id) {
if (delegate.get()) if (delegate.get())
handled_by_delegate = delegate->SettingsClick(); handled_by_delegate = delegate->SettingsClick();
for (auto& observer : observer_list_) {
observer.OnNotificationSettingsClicked(handled_by_delegate); internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationSettingsClicked(handled_by_delegate);
}
} }
void MessageCenterImpl::MarkSinglePopupAsShown(const std::string& id, void MessageCenterImpl::MarkSinglePopupAsShown(const std::string& id,
...@@ -432,13 +577,22 @@ void MessageCenterImpl::MarkSinglePopupAsShown(const std::string& id, ...@@ -432,13 +577,22 @@ void MessageCenterImpl::MarkSinglePopupAsShown(const std::string& id,
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (FindVisibleNotificationById(id) == NULL) if (FindVisibleNotificationById(id) == NULL)
return; return;
// This method doesn't check the |iterating_| flag, since this is called
// during iteration.
// TODO(yoshiki): Investigate and not to call this method during iteration if
// necessary.
#if !defined(OS_CHROMEOS) #if !defined(OS_CHROMEOS)
return this->RemoveNotification(id, false); return this->RemoveNotification(id, false);
#else #else
notification_list_->MarkSinglePopupAsShown(id, mark_notification_as_read); notification_list_->MarkSinglePopupAsShown(id, mark_notification_as_read);
notification_cache_.RecountUnread(); notification_cache_.RecountUnread();
for (auto& observer : observer_list_) {
observer.OnNotificationUpdated(id); internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationUpdated(id);
}
#endif // defined(OS_CHROMEOS) #endif // defined(OS_CHROMEOS)
} }
...@@ -446,6 +600,10 @@ void MessageCenterImpl::DisplayedNotification( ...@@ -446,6 +600,10 @@ void MessageCenterImpl::DisplayedNotification(
const std::string& id, const std::string& id,
const DisplaySource source) { const DisplaySource source) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// This method may be called from the handlers, so we shouldn't manipulate
// notifications in this method.
if (FindVisibleNotificationById(id) == NULL) if (FindVisibleNotificationById(id) == NULL)
return; return;
...@@ -456,8 +614,11 @@ void MessageCenterImpl::DisplayedNotification( ...@@ -456,8 +614,11 @@ void MessageCenterImpl::DisplayedNotification(
notification_list_->GetNotificationDelegate(id); notification_list_->GetNotificationDelegate(id);
if (delegate.get()) if (delegate.get())
delegate->Display(); delegate->Display();
for (auto& observer : observer_list_) {
observer.OnNotificationDisplayed(id, source); internal::ScopedNotificationsIterationLock lock(this);
for (auto& observer : observer_list_)
observer.OnNotificationDisplayed(id, source);
}
} }
void MessageCenterImpl::SetNotifierSettingsProvider( void MessageCenterImpl::SetNotifierSettingsProvider(
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "base/threading/thread_checker.h" #include "base/threading/thread_checker.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "ui/message_center/change_queue.h"
#include "ui/message_center/message_center.h" #include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_observer.h" #include "ui/message_center/message_center_observer.h"
#include "ui/message_center/message_center_types.h" #include "ui/message_center/message_center_types.h"
...@@ -25,10 +26,15 @@ ...@@ -25,10 +26,15 @@
namespace message_center { namespace message_center {
namespace internal {
class ScopedNotificationsIterationLock;
};
// The default implementation of MessageCenter. // The default implementation of MessageCenter.
class MessageCenterImpl : public MessageCenter, class MESSAGE_CENTER_EXPORT MessageCenterImpl
public NotificationBlocker::Observer, : public MessageCenter,
public message_center::NotifierSettingsObserver { public NotificationBlocker::Observer,
public message_center::NotifierSettingsObserver {
public: public:
MessageCenterImpl(); MessageCenterImpl();
~MessageCenterImpl() override; ~MessageCenterImpl() override;
...@@ -102,6 +108,7 @@ class MessageCenterImpl : public MessageCenter, ...@@ -102,6 +108,7 @@ class MessageCenterImpl : public MessageCenter,
void DisableTimersForTest() override; void DisableTimersForTest() override;
private: private:
friend internal::ScopedNotificationsIterationLock;
struct NotificationCache { struct NotificationCache {
NotificationCache(); NotificationCache();
~NotificationCache(); ~NotificationCache();
...@@ -111,7 +118,10 @@ class MessageCenterImpl : public MessageCenter, ...@@ -111,7 +118,10 @@ class MessageCenterImpl : public MessageCenter,
NotificationList::Notifications visible_notifications; NotificationList::Notifications visible_notifications;
size_t unread_count; size_t unread_count;
}; };
class ScopedNotificationsLock;
Notification* GetLatestNotificationIncludingQueued(
const std::string& id) const;
void RemoveNotificationsForNotifierId(const NotifierId& notifier_id); void RemoveNotificationsForNotifierId(const NotifierId& notifier_id);
THREAD_CHECKER(thread_checker_); THREAD_CHECKER(thread_checker_);
...@@ -123,10 +133,14 @@ class MessageCenterImpl : public MessageCenter, ...@@ -123,10 +133,14 @@ class MessageCenterImpl : public MessageCenter,
std::unique_ptr<base::OneShotTimer> quiet_mode_timer_; std::unique_ptr<base::OneShotTimer> quiet_mode_timer_;
NotifierSettingsProvider* settings_provider_; NotifierSettingsProvider* settings_provider_;
std::vector<NotificationBlocker*> blockers_; std::vector<NotificationBlocker*> blockers_;
std::unique_ptr<ChangeQueue> notification_change_queue_;
bool locked_ = false; bool locked_ = false;
bool visible_ = false; bool visible_ = false;
// modified by ScopedNotificationsIterationLock.
bool iterating_ = false;
base::string16 product_os_name_; base::string16 product_os_name_;
DISALLOW_COPY_AND_ASSIGN(MessageCenterImpl); DISALLOW_COPY_AND_ASSIGN(MessageCenterImpl);
......
...@@ -31,6 +31,48 @@ using base::UTF8ToUTF16; ...@@ -31,6 +31,48 @@ using base::UTF8ToUTF16;
namespace message_center { namespace message_center {
namespace {
class CheckObserver : public MessageCenterObserver {
public:
CheckObserver(MessageCenter* message_center, const std::string& target_id)
: message_center_(message_center), target_id_(target_id) {
DCHECK(message_center);
DCHECK(!target_id.empty());
}
void OnNotificationUpdated(const std::string& notification_id) override {
EXPECT_TRUE(message_center_->FindVisibleNotificationById(target_id_));
}
private:
MessageCenter* message_center_;
std::string target_id_;
DISALLOW_COPY_AND_ASSIGN(CheckObserver);
};
class RemoveObserver : public MessageCenterObserver {
public:
RemoveObserver(MessageCenter* message_center, const std::string& target_id)
: message_center_(message_center), target_id_(target_id) {
DCHECK(message_center);
DCHECK(!target_id.empty());
}
void OnNotificationUpdated(const std::string& notification_id) override {
message_center_->RemoveNotification(target_id_, false);
}
private:
MessageCenter* message_center_;
std::string target_id_;
DISALLOW_COPY_AND_ASSIGN(RemoveObserver);
};
} // anonymous namespace
class MessageCenterImplTest : public testing::Test, class MessageCenterImplTest : public testing::Test,
public MessageCenterObserver { public MessageCenterObserver {
public: public:
...@@ -768,5 +810,36 @@ TEST_F(MessageCenterImplTest, RemoveWhileMessageCenterVisible) { ...@@ -768,5 +810,36 @@ TEST_F(MessageCenterImplTest, RemoveWhileMessageCenterVisible) {
EXPECT_FALSE(message_center()->FindVisibleNotificationById(id)); EXPECT_FALSE(message_center()->FindVisibleNotificationById(id));
} }
TEST_F(MessageCenterImplTest, RemoveWhileIteratingObserver) {
std::string id("id1");
CheckObserver check1(message_center(), id);
CheckObserver check2(message_center(), id);
RemoveObserver remove(message_center(), id);
// Prepare a notification
std::unique_ptr<Notification> notification(CreateSimpleNotification(id));
message_center()->AddNotification(std::move(notification));
EXPECT_TRUE(message_center()->FindVisibleNotificationById(id));
// Install the test handlers
message_center()->AddObserver(&check1);
message_center()->AddObserver(&remove);
message_center()->AddObserver(&check2);
// Update the notification. The notification will be removed in the observer,
// but the actual removal will be done at the end of the iteration.
// Notification keeps alive during iteration. This is checked by
// CheckObserver.
notification.reset(CreateSimpleNotification(id));
message_center()->UpdateNotification(id, std::move(notification));
// Notification is removed correctly.
EXPECT_FALSE(message_center()->FindVisibleNotificationById(id));
message_center()->RemoveObserver(&check1);
message_center()->RemoveObserver(&remove);
message_center()->RemoveObserver(&check2);
}
} // namespace internal } // namespace internal
} // namespace message_center } // namespace message_center
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