1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/notifications/platform_notification_context_impl.h"
7 #include "base/bind_helpers.h"
8 #include "base/files/file_util.h"
9 #include "base/metrics/histogram_macros.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "content/browser/notifications/notification_database.h"
12 #include "content/browser/service_worker/service_worker_context_wrapper.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "content/public/browser/content_browser_client.h"
15 #include "content/public/browser/notification_database_data.h"
16 #include "content/public/browser/platform_notification_service.h"
18 using base::DoNothing
;
22 // Name of the directory in the user's profile directory where the notification
23 // database files should be stored.
24 const base::FilePath::CharType kPlatformNotificationsDirectory
[] =
25 FILE_PATH_LITERAL("Platform Notifications");
27 PlatformNotificationContextImpl::PlatformNotificationContextImpl(
28 const base::FilePath
& path
,
29 BrowserContext
* browser_context
,
30 const scoped_refptr
<ServiceWorkerContextWrapper
>& service_worker_context
)
32 browser_context_(browser_context
),
33 service_worker_context_(service_worker_context
) {
34 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
37 PlatformNotificationContextImpl::~PlatformNotificationContextImpl() {
38 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
40 // If the database has been initialized, it must be deleted on the task runner
41 // thread as closing it may cause file I/O.
44 task_runner_
->DeleteSoon(FROM_HERE
, database_
.release());
48 void PlatformNotificationContextImpl::Initialize() {
49 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
50 PlatformNotificationService
* service
=
51 GetContentClient()->browser()->GetPlatformNotificationService();
53 std::set
<std::string
> displayed_notifications
;
55 bool notification_synchronization_supported
=
56 service
->GetDisplayedPersistentNotifications(browser_context_
,
57 &displayed_notifications
);
59 // Synchronize the notifications stored in the database with the set of
60 // displaying notifications in |displayed_notifications|. This is necessary
61 // because flakiness may cause a platform to inform Chrome of a notification
62 // that has since been closed, or because the platform does not support
63 // notifications that exceed the lifetime of the browser process.
65 // TODO(peter): Synchronizing the actual notifications will be done when the
66 // persistent notification ids are stable. For M44 we need to support the
67 // case where there may be no notifications after a Chrome restart.
68 if (notification_synchronization_supported
&&
69 !displayed_notifications
.size()) {
70 prune_database_on_open_
= true;
74 BrowserThread::PostTask(
77 base::Bind(&PlatformNotificationContextImpl::InitializeOnIO
, this));
80 void PlatformNotificationContextImpl::InitializeOnIO() {
81 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
83 // |service_worker_context_| may be NULL in tests.
84 if (service_worker_context_
)
85 service_worker_context_
->AddObserver(this);
88 void PlatformNotificationContextImpl::Shutdown() {
89 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
90 BrowserThread::PostTask(
93 base::Bind(&PlatformNotificationContextImpl::ShutdownOnIO
, this));
96 void PlatformNotificationContextImpl::ShutdownOnIO() {
97 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
99 // |service_worker_context_| may be NULL in tests.
100 if (service_worker_context_
)
101 service_worker_context_
->RemoveObserver(this);
104 void PlatformNotificationContextImpl::ReadNotificationData(
105 int64_t notification_id
,
107 const ReadResultCallback
& callback
) {
108 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
110 base::Bind(&PlatformNotificationContextImpl::DoReadNotificationData
,
111 this, notification_id
, origin
, callback
),
112 base::Bind(callback
, false /* success */, NotificationDatabaseData()));
115 void PlatformNotificationContextImpl::DoReadNotificationData(
116 int64_t notification_id
,
118 const ReadResultCallback
& callback
) {
119 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
121 NotificationDatabaseData database_data
;
122 NotificationDatabase::Status status
=
123 database_
->ReadNotificationData(notification_id
,
127 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.ReadResult",
128 status
, NotificationDatabase::STATUS_COUNT
);
130 if (status
== NotificationDatabase::STATUS_OK
) {
131 BrowserThread::PostTask(BrowserThread::IO
,
139 // Blow away the database if reading data failed due to corruption.
140 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
)
143 BrowserThread::PostTask(
146 base::Bind(callback
, false /* success */, NotificationDatabaseData()));
149 void PlatformNotificationContextImpl::
150 ReadAllNotificationDataForServiceWorkerRegistration(
152 int64_t service_worker_registration_id
,
153 const ReadAllResultCallback
& callback
) {
154 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
156 base::Bind(&PlatformNotificationContextImpl::
157 DoReadAllNotificationDataForServiceWorkerRegistration
,
158 this, origin
, service_worker_registration_id
, callback
),
161 std::vector
<NotificationDatabaseData
>()));
164 void PlatformNotificationContextImpl::
165 DoReadAllNotificationDataForServiceWorkerRegistration(
167 int64_t service_worker_registration_id
,
168 const ReadAllResultCallback
& callback
) {
169 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
171 std::vector
<NotificationDatabaseData
> notification_datas
;
173 NotificationDatabase::Status status
=
174 database_
->ReadAllNotificationDataForServiceWorkerRegistration(
175 origin
, service_worker_registration_id
, ¬ification_datas
);
177 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.ReadForServiceWorkerResult",
178 status
, NotificationDatabase::STATUS_COUNT
);
180 if (status
== NotificationDatabase::STATUS_OK
) {
181 BrowserThread::PostTask(BrowserThread::IO
,
185 notification_datas
));
189 // Blow away the database if reading data failed due to corruption.
190 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
)
193 BrowserThread::PostTask(
198 std::vector
<NotificationDatabaseData
>()));
201 void PlatformNotificationContextImpl::WriteNotificationData(
203 const NotificationDatabaseData
& database_data
,
204 const WriteResultCallback
& callback
) {
205 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
207 base::Bind(&PlatformNotificationContextImpl::DoWriteNotificationData
,
208 this, origin
, database_data
, callback
),
209 base::Bind(callback
, false /* success */, 0 /* notification_id */));
212 void PlatformNotificationContextImpl::DoWriteNotificationData(
214 const NotificationDatabaseData
& database_data
,
215 const WriteResultCallback
& callback
) {
216 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
218 int64_t notification_id
= 0;
219 NotificationDatabase::Status status
=
220 database_
->WriteNotificationData(origin
,
224 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.WriteResult",
225 status
, NotificationDatabase::STATUS_COUNT
);
227 if (status
== NotificationDatabase::STATUS_OK
) {
228 DCHECK_GT(notification_id
, 0);
229 BrowserThread::PostTask(BrowserThread::IO
,
237 // Blow away the database if writing data failed due to corruption.
238 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
)
241 BrowserThread::PostTask(
244 base::Bind(callback
, false /* success */, 0 /* notification_id */));
247 void PlatformNotificationContextImpl::DeleteNotificationData(
248 int64_t notification_id
,
250 const DeleteResultCallback
& callback
) {
251 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
253 base::Bind(&PlatformNotificationContextImpl::DoDeleteNotificationData
,
254 this, notification_id
, origin
, callback
),
255 base::Bind(callback
, false /* success */));
258 void PlatformNotificationContextImpl::DoDeleteNotificationData(
259 int64_t notification_id
,
261 const DeleteResultCallback
& callback
) {
262 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
264 NotificationDatabase::Status status
=
265 database_
->DeleteNotificationData(notification_id
, origin
);
267 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DeleteResult",
268 status
, NotificationDatabase::STATUS_COUNT
);
270 bool success
= status
== NotificationDatabase::STATUS_OK
;
272 // Blow away the database if deleting data failed due to corruption. Following
273 // the contract of the delete methods, consider this to be a success as the
274 // caller's goal has been achieved: the data is gone.
275 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
) {
280 BrowserThread::PostTask(BrowserThread::IO
,
282 base::Bind(callback
, success
));
285 void PlatformNotificationContextImpl::OnRegistrationDeleted(
286 int64_t registration_id
,
287 const GURL
& pattern
) {
288 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
290 base::Bind(&PlatformNotificationContextImpl::
291 DoDeleteNotificationsForServiceWorkerRegistration
,
292 this, pattern
.GetOrigin(), registration_id
),
293 base::Bind(&DoNothing
));
296 void PlatformNotificationContextImpl::
297 DoDeleteNotificationsForServiceWorkerRegistration(
299 int64_t service_worker_registration_id
) {
300 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
302 std::set
<int64_t> deleted_notifications_set
;
303 NotificationDatabase::Status status
=
304 database_
->DeleteAllNotificationDataForServiceWorkerRegistration(
305 origin
, service_worker_registration_id
, &deleted_notifications_set
);
307 UMA_HISTOGRAM_ENUMERATION(
308 "Notifications.Database.DeleteServiceWorkerRegistrationResult",
309 status
, NotificationDatabase::STATUS_COUNT
);
311 // Blow away the database if a corruption error occurred during the deletion.
312 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
)
315 // TODO(peter): Close the notifications in |deleted_notifications_set|. See
316 // https://crbug.com/532436.
319 void PlatformNotificationContextImpl::OnStorageWiped() {
320 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
322 base::Bind(base::IgnoreResult(
323 &PlatformNotificationContextImpl::DestroyDatabase
), this),
324 base::Bind(&DoNothing
));
327 void PlatformNotificationContextImpl::LazyInitialize(
328 const base::Closure
& success_closure
,
329 const base::Closure
& failure_closure
) {
330 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
333 base::SequencedWorkerPool
* pool
= BrowserThread::GetBlockingPool();
334 base::SequencedWorkerPool::SequenceToken token
= pool
->GetSequenceToken();
336 task_runner_
= pool
->GetSequencedTaskRunner(token
);
339 task_runner_
->PostTask(
341 base::Bind(&PlatformNotificationContextImpl::OpenDatabase
,
342 this, success_closure
, failure_closure
));
345 void PlatformNotificationContextImpl::OpenDatabase(
346 const base::Closure
& success_closure
,
347 const base::Closure
& failure_closure
) {
348 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
351 success_closure
.Run();
355 database_
.reset(new NotificationDatabase(GetDatabasePath()));
356 NotificationDatabase::Status status
=
357 database_
->Open(true /* create_if_missing */);
359 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.OpenResult",
360 status
, NotificationDatabase::STATUS_COUNT
);
362 // TODO(peter): Do finer-grained synchronization here.
363 if (prune_database_on_open_
) {
364 prune_database_on_open_
= false;
367 database_
.reset(new NotificationDatabase(GetDatabasePath()));
368 status
= database_
->Open(true /* create_if_missing */);
370 // TODO(peter): Find the appropriate UMA to cover in regards to
371 // synchronizing notifications after the implementation is complete.
374 // When the database could not be opened due to corruption, destroy it, blow
375 // away the contents of the directory and try re-opening the database.
376 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
) {
377 if (DestroyDatabase()) {
378 database_
.reset(new NotificationDatabase(GetDatabasePath()));
379 status
= database_
->Open(true /* create_if_missing */);
381 UMA_HISTOGRAM_ENUMERATION(
382 "Notifications.Database.OpenAfterCorruptionResult",
383 status
, NotificationDatabase::STATUS_COUNT
);
387 if (status
== NotificationDatabase::STATUS_OK
) {
388 success_closure
.Run();
394 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
, failure_closure
);
397 bool PlatformNotificationContextImpl::DestroyDatabase() {
398 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
401 NotificationDatabase::Status status
= database_
->Destroy();
402 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DestroyResult",
403 status
, NotificationDatabase::STATUS_COUNT
);
407 // TODO(peter): Close any existing persistent notifications on the platform.
409 // Remove all files in the directory that the database was previously located
410 // in, to make sure that any left-over files are gone as well.
411 base::FilePath database_path
= GetDatabasePath();
412 if (!database_path
.empty())
413 return base::DeleteFile(database_path
, true);
418 base::FilePath
PlatformNotificationContextImpl::GetDatabasePath() const {
422 return path_
.Append(kPlatformNotificationsDirectory
);
425 void PlatformNotificationContextImpl::SetTaskRunnerForTesting(
426 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
) {
427 task_runner_
= task_runner
;
430 } // namespace content