Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / notifications / platform_notification_context_impl.cc
blobc2ce26d3521d1feb23fd6521cc0898e515e2813e
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;
20 namespace content {
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)
31 : path_(path),
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.
42 if (database_) {
43 DCHECK(task_runner_);
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();
52 if (service) {
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(
75 BrowserThread::IO,
76 FROM_HERE,
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(
91 BrowserThread::IO,
92 FROM_HERE,
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,
106 const GURL& origin,
107 const ReadResultCallback& callback) {
108 DCHECK_CURRENTLY_ON(BrowserThread::IO);
109 LazyInitialize(
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,
117 const GURL& origin,
118 const ReadResultCallback& callback) {
119 DCHECK(task_runner_->RunsTasksOnCurrentThread());
121 NotificationDatabaseData database_data;
122 NotificationDatabase::Status status =
123 database_->ReadNotificationData(notification_id,
124 origin,
125 &database_data);
127 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.ReadResult",
128 status, NotificationDatabase::STATUS_COUNT);
130 if (status == NotificationDatabase::STATUS_OK) {
131 BrowserThread::PostTask(BrowserThread::IO,
132 FROM_HERE,
133 base::Bind(callback,
134 true /* success */,
135 database_data));
136 return;
139 // Blow away the database if reading data failed due to corruption.
140 if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
141 DestroyDatabase();
143 BrowserThread::PostTask(
144 BrowserThread::IO,
145 FROM_HERE,
146 base::Bind(callback, false /* success */, NotificationDatabaseData()));
149 void PlatformNotificationContextImpl::
150 ReadAllNotificationDataForServiceWorkerRegistration(
151 const GURL& origin,
152 int64_t service_worker_registration_id,
153 const ReadAllResultCallback& callback) {
154 DCHECK_CURRENTLY_ON(BrowserThread::IO);
155 LazyInitialize(
156 base::Bind(&PlatformNotificationContextImpl::
157 DoReadAllNotificationDataForServiceWorkerRegistration,
158 this, origin, service_worker_registration_id, callback),
159 base::Bind(callback,
160 false /* success */,
161 std::vector<NotificationDatabaseData>()));
164 void PlatformNotificationContextImpl::
165 DoReadAllNotificationDataForServiceWorkerRegistration(
166 const GURL& origin,
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, &notification_datas);
177 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.ReadForServiceWorkerResult",
178 status, NotificationDatabase::STATUS_COUNT);
180 if (status == NotificationDatabase::STATUS_OK) {
181 BrowserThread::PostTask(BrowserThread::IO,
182 FROM_HERE,
183 base::Bind(callback,
184 true /* success */,
185 notification_datas));
186 return;
189 // Blow away the database if reading data failed due to corruption.
190 if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
191 DestroyDatabase();
193 BrowserThread::PostTask(
194 BrowserThread::IO,
195 FROM_HERE,
196 base::Bind(callback,
197 false /* success */,
198 std::vector<NotificationDatabaseData>()));
201 void PlatformNotificationContextImpl::WriteNotificationData(
202 const GURL& origin,
203 const NotificationDatabaseData& database_data,
204 const WriteResultCallback& callback) {
205 DCHECK_CURRENTLY_ON(BrowserThread::IO);
206 LazyInitialize(
207 base::Bind(&PlatformNotificationContextImpl::DoWriteNotificationData,
208 this, origin, database_data, callback),
209 base::Bind(callback, false /* success */, 0 /* notification_id */));
212 void PlatformNotificationContextImpl::DoWriteNotificationData(
213 const GURL& origin,
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,
221 database_data,
222 &notification_id);
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,
230 FROM_HERE,
231 base::Bind(callback,
232 true /* success */,
233 notification_id));
234 return;
237 // Blow away the database if writing data failed due to corruption.
238 if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
239 DestroyDatabase();
241 BrowserThread::PostTask(
242 BrowserThread::IO,
243 FROM_HERE,
244 base::Bind(callback, false /* success */, 0 /* notification_id */));
247 void PlatformNotificationContextImpl::DeleteNotificationData(
248 int64_t notification_id,
249 const GURL& origin,
250 const DeleteResultCallback& callback) {
251 DCHECK_CURRENTLY_ON(BrowserThread::IO);
252 LazyInitialize(
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,
260 const GURL& origin,
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) {
276 DestroyDatabase();
277 success = true;
280 BrowserThread::PostTask(BrowserThread::IO,
281 FROM_HERE,
282 base::Bind(callback, success));
285 void PlatformNotificationContextImpl::OnRegistrationDeleted(
286 int64_t registration_id,
287 const GURL& pattern) {
288 DCHECK_CURRENTLY_ON(BrowserThread::IO);
289 LazyInitialize(
290 base::Bind(&PlatformNotificationContextImpl::
291 DoDeleteNotificationsForServiceWorkerRegistration,
292 this, pattern.GetOrigin(), registration_id),
293 base::Bind(&DoNothing));
296 void PlatformNotificationContextImpl::
297 DoDeleteNotificationsForServiceWorkerRegistration(
298 const GURL& origin,
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)
313 DestroyDatabase();
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);
321 LazyInitialize(
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);
332 if (!task_runner_) {
333 base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
334 base::SequencedWorkerPool::SequenceToken token = pool->GetSequenceToken();
336 task_runner_ = pool->GetSequencedTaskRunner(token);
339 task_runner_->PostTask(
340 FROM_HERE,
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());
350 if (database_) {
351 success_closure.Run();
352 return;
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;
365 DestroyDatabase();
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();
389 return;
392 database_.reset();
394 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, failure_closure);
397 bool PlatformNotificationContextImpl::DestroyDatabase() {
398 DCHECK(task_runner_->RunsTasksOnCurrentThread());
399 DCHECK(database_);
401 NotificationDatabase::Status status = database_->Destroy();
402 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DestroyResult",
403 status, NotificationDatabase::STATUS_COUNT);
405 database_.reset();
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);
415 return true;
418 base::FilePath PlatformNotificationContextImpl::GetDatabasePath() const {
419 if (path_.empty())
420 return path_;
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