blob: fa5c1387e1ccacdb81ebfc43ce707dca5e069afe [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 "content/browser/notifications/platform_notification_context_impl.h"
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/sequenced_worker_pool.h"
#include "content/browser/notifications/blink_notification_service_impl.h"
#include "content/browser/notifications/notification_database.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/notification_database_data.h"
#include "content/public/browser/platform_notification_service.h"
using base::DoNothing;
namespace content {
namespace {
// Name of the directory in the user's profile directory where the notification
// database files should be stored.
const base::FilePath::CharType kPlatformNotificationsDirectory[] =
FILE_PATH_LITERAL("Platform Notifications");
} // namespace
PlatformNotificationContextImpl::PlatformNotificationContextImpl(
const base::FilePath& path,
BrowserContext* browser_context,
const scoped_refptr<ServiceWorkerContextWrapper>& service_worker_context)
: path_(path),
browser_context_(browser_context),
service_worker_context_(service_worker_context),
notification_id_generator_(browser_context) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
PlatformNotificationContextImpl::~PlatformNotificationContextImpl() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// If the database has been initialized, it must be deleted on the task runner
// thread as closing it may cause file I/O.
if (database_) {
DCHECK(task_runner_);
task_runner_->DeleteSoon(FROM_HERE, database_.release());
}
}
void PlatformNotificationContextImpl::Initialize() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PlatformNotificationService* service =
GetContentClient()->browser()->GetPlatformNotificationService();
if (service) {
std::set<std::string> displayed_notifications;
bool notification_synchronization_supported =
service->GetDisplayedPersistentNotifications(browser_context_,
&displayed_notifications);
// Synchronize the notifications stored in the database with the set of
// displaying notifications in |displayed_notifications|. This is necessary
// because flakiness may cause a platform to inform Chrome of a notification
// that has since been closed, or because the platform does not support
// notifications that exceed the lifetime of the browser process.
// TODO(peter): Synchronizing the actual notifications will be done when the
// persistent notification ids are stable. For M44 we need to support the
// case where there may be no notifications after a Chrome restart.
if (notification_synchronization_supported &&
displayed_notifications.empty()) {
prune_database_on_open_ = true;
}
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&PlatformNotificationContextImpl::InitializeOnIO, this));
}
void PlatformNotificationContextImpl::InitializeOnIO() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// |service_worker_context_| may be NULL in tests.
if (service_worker_context_)
service_worker_context_->AddObserver(this);
}
void PlatformNotificationContextImpl::Shutdown() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&PlatformNotificationContextImpl::ShutdownOnIO, this));
}
void PlatformNotificationContextImpl::ShutdownOnIO() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
services_.clear();
// |service_worker_context_| may be NULL in tests.
if (service_worker_context_)
service_worker_context_->RemoveObserver(this);
}
void PlatformNotificationContextImpl::CreateService(
int render_process_id,
mojo::InterfaceRequest<blink::mojom::NotificationService> request) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&PlatformNotificationContextImpl::CreateServiceOnIO, this,
render_process_id, browser_context_->GetResourceContext(),
base::Passed(&request)));
}
void PlatformNotificationContextImpl::CreateServiceOnIO(
int render_process_id,
ResourceContext* resource_context,
mojo::InterfaceRequest<blink::mojom::NotificationService> request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
services_.push_back(base::MakeUnique<BlinkNotificationServiceImpl>(
this, resource_context, render_process_id, std::move(request)));
}
void PlatformNotificationContextImpl::RemoveService(
BlinkNotificationServiceImpl* service) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto services_to_remove = std::remove_if(
services_.begin(), services_.end(),
[service](const std::unique_ptr<BlinkNotificationServiceImpl>& ptr) {
return ptr.get() == service;
});
services_.erase(services_to_remove, services_.end());
}
void PlatformNotificationContextImpl::ReadNotificationData(
const std::string& notification_id,
const GURL& origin,
const ReadResultCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
LazyInitialize(
base::Bind(&PlatformNotificationContextImpl::DoReadNotificationData, this,
notification_id, origin, callback),
base::Bind(callback, false /* success */, NotificationDatabaseData()));
}
void PlatformNotificationContextImpl::DoReadNotificationData(
const std::string& notification_id,
const GURL& origin,
const ReadResultCallback& callback) {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
NotificationDatabaseData database_data;
NotificationDatabase::Status status =
database_->ReadNotificationData(notification_id, origin, &database_data);
UMA_HISTOGRAM_ENUMERATION("Notifications.Database.ReadResult", status,
NotificationDatabase::STATUS_COUNT);
if (status == NotificationDatabase::STATUS_OK) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(callback, true /* success */, database_data));
return;
}
// Blow away the database if reading data failed due to corruption.
if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
DestroyDatabase();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(callback, false /* success */, NotificationDatabaseData()));
}
void PlatformNotificationContextImpl::
ReadAllNotificationDataForServiceWorkerRegistration(
const GURL& origin,
int64_t service_worker_registration_id,
const ReadAllResultCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
LazyInitialize(
base::Bind(&PlatformNotificationContextImpl::
DoReadAllNotificationDataForServiceWorkerRegistration,
this, origin, service_worker_registration_id, callback),
base::Bind(callback, false /* success */,
std::vector<NotificationDatabaseData>()));
}
void PlatformNotificationContextImpl::
DoReadAllNotificationDataForServiceWorkerRegistration(
const GURL& origin,
int64_t service_worker_registration_id,
const ReadAllResultCallback& callback) {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
std::vector<NotificationDatabaseData> notification_datas;
NotificationDatabase::Status status =
database_->ReadAllNotificationDataForServiceWorkerRegistration(
origin, service_worker_registration_id, &notification_datas);
UMA_HISTOGRAM_ENUMERATION("Notifications.Database.ReadForServiceWorkerResult",
status, NotificationDatabase::STATUS_COUNT);
if (status == NotificationDatabase::STATUS_OK) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(callback, true /* success */, notification_datas));
return;
}
// Blow away the database if reading data failed due to corruption.
if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
DestroyDatabase();
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(callback, false /* success */,
std::vector<NotificationDatabaseData>()));
}
void PlatformNotificationContextImpl::WriteNotificationData(
const GURL& origin,
const NotificationDatabaseData& database_data,
const WriteResultCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
LazyInitialize(
base::Bind(&PlatformNotificationContextImpl::DoWriteNotificationData,
this, origin, database_data, callback),
base::Bind(callback, false /* success */, "" /* notification_id */));
}
void PlatformNotificationContextImpl::DoWriteNotificationData(
const GURL& origin,
const NotificationDatabaseData& database_data,
const WriteResultCallback& callback) {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
DCHECK(database_data.notification_id.empty());
// Eagerly delete data for replaced notifications from the database.
if (!database_data.notification_data.tag.empty()) {
std::set<std::string> deleted_notification_ids;
NotificationDatabase::Status delete_status =
database_->DeleteAllNotificationDataForOrigin(
origin, database_data.notification_data.tag,
&deleted_notification_ids);
UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DeleteBeforeWriteResult",
delete_status,
NotificationDatabase::STATUS_COUNT);
// Unless the database was corrupted following this change, there is no
// reason to bail out here in event of failure because the notification
// display logic will handle notification replacement for the user.
if (delete_status == NotificationDatabase::STATUS_ERROR_CORRUPTED) {
DestroyDatabase();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(callback, false /* success */, "" /* notification_id */));
return;
}
}
// Create a copy of the |database_data| to store a generated notification ID.
NotificationDatabaseData write_database_data = database_data;
write_database_data.notification_id =
notification_id_generator_.GenerateForPersistentNotification(
origin, database_data.notification_data.tag,
database_->GetNextPersistentNotificationId());
NotificationDatabase::Status status =
database_->WriteNotificationData(origin, write_database_data);
UMA_HISTOGRAM_ENUMERATION("Notifications.Database.WriteResult", status,
NotificationDatabase::STATUS_COUNT);
if (status == NotificationDatabase::STATUS_OK) {
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(callback, true /* success */,
write_database_data.notification_id));
return;
}
// Blow away the database if writing data failed due to corruption.
if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
DestroyDatabase();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(callback, false /* success */, "" /* notification_id */));
}
void PlatformNotificationContextImpl::DeleteNotificationData(
const std::string& notification_id,
const GURL& origin,
const DeleteResultCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
LazyInitialize(
base::Bind(&PlatformNotificationContextImpl::DoDeleteNotificationData,
this, notification_id, origin, callback),
base::Bind(callback, false /* success */));
}
void PlatformNotificationContextImpl::DoDeleteNotificationData(
const std::string& notification_id,
const GURL& origin,
const DeleteResultCallback& callback) {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
NotificationDatabase::Status status =
database_->DeleteNotificationData(notification_id, origin);
UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DeleteResult", status,
NotificationDatabase::STATUS_COUNT);
bool success = status == NotificationDatabase::STATUS_OK;
// Blow away the database if deleting data failed due to corruption. Following
// the contract of the delete methods, consider this to be a success as the
// caller's goal has been achieved: the data is gone.
if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED) {
DestroyDatabase();
success = true;
}
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(callback, success));
}
void PlatformNotificationContextImpl::OnRegistrationDeleted(
int64_t registration_id,
const GURL& pattern) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
LazyInitialize(
base::Bind(&PlatformNotificationContextImpl::
DoDeleteNotificationsForServiceWorkerRegistration,
this, pattern.GetOrigin(), registration_id),
base::Bind(&DoNothing));
}
void PlatformNotificationContextImpl::
DoDeleteNotificationsForServiceWorkerRegistration(
const GURL& origin,
int64_t service_worker_registration_id) {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
std::set<std::string> deleted_notification_ids;
NotificationDatabase::Status status =
database_->DeleteAllNotificationDataForServiceWorkerRegistration(
origin, service_worker_registration_id, &deleted_notification_ids);
UMA_HISTOGRAM_ENUMERATION(
"Notifications.Database.DeleteServiceWorkerRegistrationResult", status,
NotificationDatabase::STATUS_COUNT);
// Blow away the database if a corruption error occurred during the deletion.
if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
DestroyDatabase();
// TODO(peter): Close the notifications in |deleted_notification_ids|. See
// https://crbug.com/532436.
}
void PlatformNotificationContextImpl::OnStorageWiped() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
LazyInitialize(
base::Bind(
base::IgnoreResult(&PlatformNotificationContextImpl::DestroyDatabase),
this),
base::Bind(&DoNothing));
}
void PlatformNotificationContextImpl::LazyInitialize(
const base::Closure& success_closure,
const base::Closure& failure_closure) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!task_runner_) {
base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken();
task_runner_ = pool->GetSequencedTaskRunner(token);
}
task_runner_->PostTask(
FROM_HERE, base::Bind(&PlatformNotificationContextImpl::OpenDatabase,
this, success_closure, failure_closure));
}
void PlatformNotificationContextImpl::OpenDatabase(
const base::Closure& success_closure,
const base::Closure& failure_closure) {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
if (database_) {
success_closure.Run();
return;
}
database_.reset(new NotificationDatabase(GetDatabasePath()));
NotificationDatabase::Status status =
database_->Open(true /* create_if_missing */);
UMA_HISTOGRAM_ENUMERATION("Notifications.Database.OpenResult", status,
NotificationDatabase::STATUS_COUNT);
// TODO(peter): Do finer-grained synchronization here.
if (prune_database_on_open_) {
prune_database_on_open_ = false;
DestroyDatabase();
database_.reset(new NotificationDatabase(GetDatabasePath()));
status = database_->Open(true /* create_if_missing */);
// TODO(peter): Find the appropriate UMA to cover in regards to
// synchronizing notifications after the implementation is complete.
}
// When the database could not be opened due to corruption, destroy it, blow
// away the contents of the directory and try re-opening the database.
if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED) {
if (DestroyDatabase()) {
database_.reset(new NotificationDatabase(GetDatabasePath()));
status = database_->Open(true /* create_if_missing */);
UMA_HISTOGRAM_ENUMERATION(
"Notifications.Database.OpenAfterCorruptionResult", status,
NotificationDatabase::STATUS_COUNT);
}
}
if (status == NotificationDatabase::STATUS_OK) {
success_closure.Run();
return;
}
database_.reset();
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, failure_closure);
}
bool PlatformNotificationContextImpl::DestroyDatabase() {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
DCHECK(database_);
NotificationDatabase::Status status = database_->Destroy();
UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DestroyResult", status,
NotificationDatabase::STATUS_COUNT);
database_.reset();
// TODO(peter): Close any existing persistent notifications on the platform.
// Remove all files in the directory that the database was previously located
// in, to make sure that any left-over files are gone as well.
base::FilePath database_path = GetDatabasePath();
if (!database_path.empty())
return base::DeleteFile(database_path, true);
return true;
}
base::FilePath PlatformNotificationContextImpl::GetDatabasePath() const {
if (path_.empty())
return path_;
return path_.Append(kPlatformNotificationsDirectory);
}
void PlatformNotificationContextImpl::SetTaskRunnerForTesting(
const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
task_runner_ = task_runner;
}
} // namespace content