blob: 49028114e94e7f66872001c72f23d17f1fc19dd7 [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/notifications/message_center_notification_manager.h"
#include <memory>
#include <utility>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "chrome/browser/notifications/extension_welcome_notification.h"
#include "chrome/browser/notifications/extension_welcome_notification_factory.h"
#include "chrome/browser/notifications/fullscreen_notification_blocker.h"
#include "chrome/browser/notifications/message_center_settings_controller.h"
#include "chrome/browser/notifications/notification.h"
#include "chrome/browser/notifications/profile_notification.h"
#include "chrome/browser/notifications/screen_lock_notification_blocker.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/message_center/message_center_style.h"
#include "ui/message_center/message_center_tray.h"
#include "ui/message_center/message_center_types.h"
#include "ui/message_center/notifier_settings.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/notifications/login_state_notification_blocker_chromeos.h"
#endif
#if defined(USE_ASH)
#include "ash/common/system/web_notification/web_notification_tray.h"
#include "ash/shell.h"
#endif
using message_center::NotifierId;
MessageCenterNotificationManager::MessageCenterNotificationManager(
message_center::MessageCenter* message_center,
std::unique_ptr<message_center::NotifierSettingsProvider> settings_provider)
: message_center_(message_center),
settings_provider_(std::move(settings_provider)),
system_observer_(this),
stats_collector_(message_center),
google_now_stats_collector_(message_center) {
message_center_->AddObserver(this);
message_center_->SetNotifierSettingsProvider(settings_provider_.get());
#if defined(OS_CHROMEOS)
blockers_.push_back(
base::MakeUnique<LoginStateNotificationBlockerChromeOS>(message_center));
#else
blockers_.push_back(
base::MakeUnique<ScreenLockNotificationBlocker>(message_center));
#endif
blockers_.push_back(
base::MakeUnique<FullscreenNotificationBlocker>(message_center));
#if defined(OS_WIN) || defined(OS_MACOSX) \
|| (defined(OS_LINUX) && !defined(OS_CHROMEOS))
// On Windows, Linux and Mac, the notification manager owns the tray icon and
// views.Other platforms have global ownership and Create will return NULL.
tray_.reset(message_center::CreateMessageCenterTray());
#endif
}
MessageCenterNotificationManager::~MessageCenterNotificationManager() {
message_center_->SetNotifierSettingsProvider(nullptr);
message_center_->RemoveObserver(this);
profile_notifications_.clear();
}
////////////////////////////////////////////////////////////////////////////////
// NotificationUIManager
void MessageCenterNotificationManager::Add(const Notification& notification,
Profile* profile) {
if (Update(notification, profile))
return;
std::unique_ptr<ProfileNotification> profile_notification_ptr =
base::MakeUnique<ProfileNotification>(profile, notification);
ProfileNotification* profile_notification = profile_notification_ptr.get();
ExtensionWelcomeNotificationFactory::GetForBrowserContext(profile)->
ShowWelcomeNotificationIfNecessary(profile_notification->notification());
// WARNING: You MUST use AddProfileNotification or update the message center
// via the notification within a ProfileNotification object or the profile ID
// will not be correctly set for ChromeOS.
// Takes ownership of profile_notification.
AddProfileNotification(std::move(profile_notification_ptr));
message_center_->AddNotification(
base::MakeUnique<message_center::Notification>(
profile_notification->notification()));
}
bool MessageCenterNotificationManager::Update(const Notification& notification,
Profile* profile) {
const std::string& tag = notification.tag();
if (tag.empty())
return false;
const GURL origin_url = notification.origin_url();
DCHECK(origin_url.is_valid());
// Since tag is provided by arbitrary JS, we need to use origin_url
// (which is an app url in case of app/extension) to scope the tags
// in the given profile.
for (auto iter = profile_notifications_.begin();
iter != profile_notifications_.end(); ++iter) {
ProfileNotification* old_notification = (*iter).second.get();
if (old_notification->notification().tag() == tag &&
old_notification->notification().origin_url() == origin_url &&
old_notification->profile_id() ==
NotificationUIManager::GetProfileID(profile)) {
// Changing the type from non-progress to progress does not count towards
// the immediate update allowed in the message center.
std::string old_id = old_notification->notification().id();
// Add/remove notification in the local list but just update the same
// one in MessageCenter.
std::unique_ptr<ProfileNotification> new_notification =
base::MakeUnique<ProfileNotification>(profile, notification);
const Notification& notification = new_notification->notification();
// Delete the old one after the new one is created to ensure we don't run
// out of KeepAlives.
profile_notifications_.erase(old_id);
profile_notifications_[notification.id()] = std::move(new_notification);
// TODO(liyanhou): Add routing updated notifications to alternative
// providers.
// Non-persistent Web Notifications rely on receiving the Display() event
// to inform the developer, even when replacing a previous notification.
if (notification.notifier_id().type == NotifierId::WEB_PAGE)
notification.delegate()->Display();
// WARNING: You MUST use AddProfileNotification or update the message
// center via the notification within a ProfileNotification object or the
// profile ID will not be correctly set for ChromeOS.
message_center_->UpdateNotification(
old_id, base::MakeUnique<message_center::Notification>(notification));
return true;
}
}
return false;
}
const Notification* MessageCenterNotificationManager::FindById(
const std::string& delegate_id,
ProfileID profile_id) const {
// The profile pointer can be weak, the instance may have been destroyed, so
// no profile method should be called inside this function.
std::string profile_notification_id =
ProfileNotification::GetProfileNotificationId(delegate_id, profile_id);
auto iter = profile_notifications_.find(profile_notification_id);
if (iter == profile_notifications_.end())
return nullptr;
return &(iter->second->notification());
}
bool MessageCenterNotificationManager::CancelById(
const std::string& delegate_id,
ProfileID profile_id) {
// The profile pointer can be weak, the instance may have been destroyed, so
// no profile method should be called inside this function.
std::string profile_notification_id =
ProfileNotification::GetProfileNotificationId(delegate_id, profile_id);
// See if this ID hasn't been shown yet.
// If it has been shown, remove it.
auto iter = profile_notifications_.find(profile_notification_id);
if (iter == profile_notifications_.end())
return false;
RemoveProfileNotification(iter->first);
message_center_->RemoveNotification(profile_notification_id,
/* by_user */ false);
return true;
}
std::set<std::string>
MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin(
ProfileID profile_id,
const GURL& source) {
std::set<std::string> delegate_ids;
for (const auto& pair : profile_notifications_) {
const Notification& notification = pair.second->notification();
if (pair.second->profile_id() == profile_id &&
notification.origin_url() == source) {
delegate_ids.insert(notification.delegate_id());
}
}
return delegate_ids;
}
std::set<std::string> MessageCenterNotificationManager::GetAllIdsByProfile(
ProfileID profile_id) {
std::set<std::string> delegate_ids;
for (const auto& pair : profile_notifications_) {
if (pair.second->profile_id() == profile_id)
delegate_ids.insert(pair.second->notification().delegate_id());
}
return delegate_ids;
}
bool MessageCenterNotificationManager::CancelAllBySourceOrigin(
const GURL& source) {
// Same pattern as CancelById, but more complicated than the above
// because there may be multiple notifications from the same source.
bool removed = false;
for (auto loopiter = profile_notifications_.begin();
loopiter != profile_notifications_.end();) {
auto curiter = loopiter++;
if ((*curiter).second->notification().origin_url() == source) {
const std::string id = curiter->first;
RemoveProfileNotification(id);
message_center_->RemoveNotification(id, /* by_user */ false);
removed = true;
}
}
return removed;
}
bool MessageCenterNotificationManager::CancelAllByProfile(
ProfileID profile_id) {
// Same pattern as CancelAllBySourceOrigin.
bool removed = false;
for (auto loopiter = profile_notifications_.begin();
loopiter != profile_notifications_.end();) {
auto curiter = loopiter++;
if (profile_id == (*curiter).second->profile_id()) {
const std::string id = curiter->first;
RemoveProfileNotification(id);
message_center_->RemoveNotification(id, /* by_user */ false);
removed = true;
}
}
return removed;
}
void MessageCenterNotificationManager::CancelAll() {
message_center_->RemoveAllNotifications(
false /* by_user */, message_center::MessageCenter::RemoveType::ALL);
}
////////////////////////////////////////////////////////////////////////////////
// MessageCenter::Observer
void MessageCenterNotificationManager::OnNotificationRemoved(
const std::string& id,
bool by_user) {
auto iter = profile_notifications_.find(id);
if (iter != profile_notifications_.end())
RemoveProfileNotification(iter->first);
}
void MessageCenterNotificationManager::OnCenterVisibilityChanged(
message_center::Visibility visibility) {
}
void MessageCenterNotificationManager::OnNotificationUpdated(
const std::string& id) {
}
void MessageCenterNotificationManager::EnsureMessageCenterClosed() {
if (tray_.get() && tray_->GetMessageCenterTray())
tray_->GetMessageCenterTray()->HideMessageCenterBubble();
#if defined(USE_ASH)
if (ash::Shell::HasInstance()) {
ash::WebNotificationTray* tray =
ash::Shell::GetInstance()->GetWebNotificationTray();
if (tray)
tray->GetMessageCenterTray()->HideMessageCenterBubble();
}
#endif
}
void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest(
message_center::MessageCenterTrayDelegate* delegate) {
tray_.reset(delegate);
}
std::string
MessageCenterNotificationManager::GetMessageCenterNotificationIdForTest(
const std::string& delegate_id,
Profile* profile) {
return ProfileNotification::GetProfileNotificationId(delegate_id,
GetProfileID(profile));
}
////////////////////////////////////////////////////////////////////////////////
// private
void MessageCenterNotificationManager::AddProfileNotification(
std::unique_ptr<ProfileNotification> profile_notification) {
const Notification& notification = profile_notification->notification();
std::string id = notification.id();
// Notification ids should be unique.
DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
profile_notifications_[id] = std::move(profile_notification);
}
void MessageCenterNotificationManager::RemoveProfileNotification(
const std::string& notification_id) {
auto it = profile_notifications_.find(notification_id);
if (it == profile_notifications_.end())
return;
// Delay destruction of the ProfileNotification until after all the work
// removing it from |profile_notifications_| is complete. This must be done
// because this ProfileNotification might have the one ScopedKeepAlive object
// that was keeping the browser alive, and destroying it would result in a re-
// entrant call to this class. Because every method in this class touches
// |profile_notifications_|, |profile_notifications_| must always be in a
// self-consistent state in moments where re-entrance might happen.
// https://crbug.com/649971
std::unique_ptr<ProfileNotification> notification = std::move(it->second);
profile_notifications_.erase(it);
// Now that the map modifications are complete, going out of scope will
// destroy the notification.
}
ProfileNotification* MessageCenterNotificationManager::FindProfileNotification(
const std::string& id) const {
auto iter = profile_notifications_.find(id);
if (iter == profile_notifications_.end())
return nullptr;
return (*iter).second.get();
}