Commit 8f761268 authored by peter's avatar peter Committed by Commit bot

Introduce the PlatformNotificationContext class.

This class provides an interface, to be used on the IO thread, on top
of the NotificationDatabase class. It abstracts away the thread
requirements of the database class and is responsible for managing the
status and the lifetime of the database.

Right now it's only used in unit tests, but it will eventually live as
a member of StoragePartitionImpl, and will be used by the notification
message filter.

Design document:
  http://goo.gl/TciXVp

BUG=447628

Review URL: https://codereview.chromium.org/1006493005

Cr-Commit-Position: refs/heads/master@{#320781}
parent a98a10a4
......@@ -68,7 +68,6 @@ std::string CreateDataKey(int64_t notification_id, const GURL& origin) {
NotificationDatabase::NotificationDatabase(const base::FilePath& path)
: path_(path) {
sequence_checker_.DetachFromSequence();
}
NotificationDatabase::~NotificationDatabase() {
......
......@@ -7,7 +7,6 @@
#include <stdint.h>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/sequence_checker.h"
#include "content/common/content_export.h"
......@@ -26,9 +25,13 @@ struct NotificationDatabaseData;
// Implementation of the persistent notification database.
//
// This class can be constructed on any thread, but method calls must only be
// made on a thread or sequenced task runner that allows file I/O. The same
// thread or task runner must be used for all method calls.
// The database is built on top of a LevelDB database, either in memory or on
// the filesystem depending on the path passed to the constructor. When writing
// a new notification, it will be assigned an id guaranteed to be unique for the
// lifetime of the database.
//
// This class must only be used on a thread or sequenced task runner that allows
// file I/O. The same thread or task runner must be used for all method calls.
class CONTENT_EXPORT NotificationDatabase {
public:
// Result status codes for interations with the database. Will be used for
......
// 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.h"
#include "base/threading/sequenced_worker_pool.h"
#include "content/browser/notifications/notification_database.h"
#include "content/browser/notifications/notification_database_data.h"
#include "content/public/browser/browser_thread.h"
namespace content {
// 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");
PlatformNotificationContext::PlatformNotificationContext(
const base::FilePath& path)
: path_(path) {
}
PlatformNotificationContext::~PlatformNotificationContext() {
// 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 PlatformNotificationContext::ReadNotificationData(
int64_t notification_id,
const GURL& origin,
const ReadResultCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
LazyInitialize(
base::Bind(&PlatformNotificationContext::DoReadNotificationData,
this, notification_id, origin, callback),
base::Bind(callback, false /* success */, NotificationDatabaseData()));
}
void PlatformNotificationContext::DoReadNotificationData(
int64_t 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);
if (status == NotificationDatabase::STATUS_OK) {
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(callback,
true /* success */,
database_data));
return;
}
// TODO(peter): Record UMA on |status| for reading from the database.
// TODO(peter): Do the DeleteAndStartOver dance for STATUS_ERROR_CORRUPTED.
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(callback, false /* success */, NotificationDatabaseData()));
}
void PlatformNotificationContext::WriteNotificationData(
const GURL& origin,
const NotificationDatabaseData& database_data,
const WriteResultCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
LazyInitialize(
base::Bind(&PlatformNotificationContext::DoWriteNotificationData,
this, origin, database_data, callback),
base::Bind(callback, false /* success */, 0 /* notification_id */));
}
void PlatformNotificationContext::DoWriteNotificationData(
const GURL& origin,
const NotificationDatabaseData& database_data,
const WriteResultCallback& callback) {
DCHECK(task_runner_->RunsTasksOnCurrentThread());
int64_t notification_id = 0;
NotificationDatabase::Status status =
database_->WriteNotificationData(origin,
database_data,
&notification_id);
DCHECK_GT(notification_id, 0);
if (status == NotificationDatabase::STATUS_OK) {
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(callback,
true /* success */,
notification_id));
return;
}
// TODO(peter): Record UMA on |status| for reading from the database.
// TODO(peter): Do the DeleteAndStartOver dance for STATUS_ERROR_CORRUPTED.
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(callback, false /* success */, 0 /* notification_id */));
}
void PlatformNotificationContext::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(&PlatformNotificationContext::OpenDatabase,
this, success_closure, failure_closure));
}
void PlatformNotificationContext::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()));
// TODO(peter): Record UMA on |status| for opening the database.
// TODO(peter): Do the DeleteAndStartOver dance for STATUS_ERROR_CORRUPTED.
NotificationDatabase::Status status =
database_->Open(true /* create_if_missing */);
if (status == NotificationDatabase::STATUS_OK) {
success_closure.Run();
return;
}
// TODO(peter): Properly handle failures when opening the database.
database_.reset();
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, failure_closure);
}
base::FilePath PlatformNotificationContext::GetDatabasePath() const {
if (path_.empty())
return path_;
return path_.Append(kPlatformNotificationsDirectory);
}
void PlatformNotificationContext::SetTaskRunnerForTesting(
const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
task_runner_ = task_runner;
}
} // namespace content
// 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.
#ifndef CONTENT_BROWSER_NOTIFICATIONS_PLATFORM_NOTIFICATION_CONTEXT_H_
#define CONTENT_BROWSER_NOTIFICATIONS_PLATFORM_NOTIFICATION_CONTEXT_H_
#include <stdint.h>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "content/common/content_export.h"
class GURL;
namespace base {
class SequencedTaskRunner;
}
namespace content {
class NotificationDatabase;
struct NotificationDatabaseData;
// Implementation of the Web Notification storage context.
//
// Represents the storage context for persistent Web Notifications, specific to
// the storage partition owning the instance. The public methods defined in this
// interface must only be called on the IO thread.
class CONTENT_EXPORT PlatformNotificationContext
: public base::RefCountedThreadSafe<PlatformNotificationContext> {
public:
// Constructs a new platform notification context. If |path| is non-empty, the
// database will be initialized in the "Platform Notifications" subdirectory
// of |path|. Otherwise, the database will be initialized in memory.
explicit PlatformNotificationContext(const base::FilePath& path);
using ReadResultCallback =
base::Callback<void(bool /* success */,
const NotificationDatabaseData&)>;
using WriteResultCallback =
base::Callback<void(bool /* success */,
int64_t /* notification_id */)>;
// Reads the data associated with |notification_id| belonging to |origin|
// from the database. |callback| will be invoked with the success status
// and a reference to the notification database data when completed.
void ReadNotificationData(int64_t notification_id,
const GURL& origin,
const ReadResultCallback& callback);
// Writes the data associated with a notification to a database. When this
// action completed, |callback| will be invoked with the success status and
// the persistent notification id when written successfully.
void WriteNotificationData(const GURL& origin,
const NotificationDatabaseData& database_data,
const WriteResultCallback& callback);
private:
friend class base::RefCountedThreadSafe<PlatformNotificationContext>;
friend class PlatformNotificationContextTest;
virtual ~PlatformNotificationContext();
// Initializes the database if neccesary. Must be called on the IO thread.
// |success_closure| will be invoked on a the |task_runner_| thread when
// everything is available, or |failure_closure_| will be invoked on the
// IO thread when initialization fails.
void LazyInitialize(const base::Closure& success_closure,
const base::Closure& failure_closure);
// Opens the database. Must be called on the |task_runner_| thread. When the
// database has been opened, |success_closure| will be invoked on the task
// thread, otherwise |failure_closure_| will be invoked on the IO thread.
void OpenDatabase(const base::Closure& success_closure,
const base::Closure& failure_closure);
// Actually reads the notification data from the database. Must only be
// called on the |task_runner_| thread. Will post a task to |callback| on
// the IO thread when the operation has completed.
void DoReadNotificationData(int64_t notification_id,
const GURL& origin,
const ReadResultCallback& callback);
// Actually writes the notification database to the database. Must only be
// called on the |task_runner_| thread. Will post a task to |callback| on
// the IO thread when the operation has completed.
void DoWriteNotificationData(const GURL& origin,
const NotificationDatabaseData& database_data,
const WriteResultCallback& callback);
// Returns the path in which the database should be initialized. May be empty.
base::FilePath GetDatabasePath() const;
// Sets the task runner to use for testing purposes.
void SetTaskRunnerForTesting(
const scoped_refptr<base::SequencedTaskRunner>& task_runner);
base::FilePath path_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
scoped_ptr<NotificationDatabase> database_;
DISALLOW_COPY_AND_ASSIGN(PlatformNotificationContext);
};
} // namespace content
#endif // CONTENT_BROWSER_NOTIFICATIONS_PLATFORM_NOTIFICATION_CONTEXT_H_
// 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.h"
#include "base/bind.h"
#include "base/run_loop.h"
#include "content/browser/notifications/notification_database_data.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace content {
class PlatformNotificationContextTest : public ::testing::Test {
public:
PlatformNotificationContextTest()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
success_(false) {}
// Callback to provide when reading a single notification from the database.
void DidReadNotificationData(
bool success, const NotificationDatabaseData& database_data) {
success_ = success;
database_data_ = database_data;
}
// Callback to provide when writing a notification to the database.
void DidWriteNotificationData(bool success, int64_t notification_id) {
success_ = success;
notification_id_ = notification_id;
}
protected:
// Creates a new PlatformNotificationContext instance. When using this method,
// the underlying database will always be created in memory. The current
// message loop proxy will be used as the task runner.
PlatformNotificationContext* CreatePlatformNotificationContext() {
PlatformNotificationContext* context =
new PlatformNotificationContext(base::FilePath());
context->SetTaskRunnerForTesting(base::MessageLoopProxy::current());
return context;
}
// Returns whether the last invoked callback finished successfully.
bool success() const { return success_; }
// Returns the NotificationDatabaseData associated with the last invoked
// ReadNotificationData callback.
const NotificationDatabaseData& database_data() const {
return database_data_;
}
// Returns the notification id of the notification last written.
int64_t notification_id() const { return notification_id_; }
private:
TestBrowserThreadBundle thread_bundle_;
bool success_;
NotificationDatabaseData database_data_;
int64_t notification_id_;
};
TEST_F(PlatformNotificationContextTest, ReadNonExistentNotification) {
scoped_refptr<PlatformNotificationContext> context =
CreatePlatformNotificationContext();
context->ReadNotificationData(
42 /* notification_id */,
GURL("https://example.com"),
base::Bind(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The read operation should have failed, as it does not exist.
ASSERT_FALSE(success());
}
TEST_F(PlatformNotificationContextTest, WriteReadNotification) {
scoped_refptr<PlatformNotificationContext> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData notification_database_data;
notification_database_data.origin = origin;
context->WriteNotificationData(
origin,
notification_database_data,
base::Bind(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The write operation should have succeeded with a notification id.
ASSERT_TRUE(success());
EXPECT_GT(notification_id(), 0);
context->ReadNotificationData(
notification_id(),
origin,
base::Bind(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The read operation should have succeeded, with the right notification.
ASSERT_TRUE(success());
const NotificationDatabaseData& read_database_data = database_data();
EXPECT_EQ(notification_database_data.origin, read_database_data.origin);
}
} // namespace content
......@@ -1003,6 +1003,8 @@
'browser/notifications/notification_message_filter.h',
'browser/notifications/page_notification_delegate.cc',
'browser/notifications/page_notification_delegate.h',
'browser/notifications/platform_notification_context.cc',
'browser/notifications/platform_notification_context.h',
'browser/permissions/permission_service_context.cc',
'browser/permissions/permission_service_context.h',
'browser/permissions/permission_service_impl.cc',
......
......@@ -474,6 +474,7 @@
'browser/net/sqlite_persistent_cookie_store_unittest.cc',
'browser/notifications/notification_database_data_unittest.cc',
'browser/notifications/notification_database_unittest.cc',
'browser/notifications/platform_notification_context_unittest.cc',
'browser/notification_service_impl_unittest.cc',
'browser/power_monitor_message_broadcaster_unittest.cc',
'browser/power_profiler/power_profiler_service_unittest.cc',
......
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