Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2018 The Chromium Authors |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "content/browser/notifications/notification_storage.h" |
| 6 | |
Avi Drissman | adac2199 | 2023-01-11 23:46:39 | [diff] [blame] | 7 | #include "base/functional/bind.h" |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 8 | #include "base/run_loop.h" |
Claudio DeSouza | e843ed6 | 2023-04-17 10:58:43 | [diff] [blame] | 9 | #include "base/uuid.h" |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 10 | #include "content/browser/service_worker/embedded_worker_test_helper.h" |
David Sanders | c7dd8a8 | 2025-07-02 16:59:44 | [diff] [blame] | 11 | #include "content/browser/service_worker/service_worker_context_core.h" |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 12 | #include "content/browser/service_worker/service_worker_registration.h" |
Gabriel Charette | c710874 | 2019-08-23 03:31:40 | [diff] [blame] | 13 | #include "content/public/test/browser_task_environment.h" |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 14 | #include "content/public/test/test_browser_context.h" |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 15 | #include "testing/gtest/include/gtest/gtest.h" |
| 16 | #include "third_party/blink/public/common/service_worker/service_worker_status_code.h" |
Steven Bingler | fdc1ded | 2021-05-27 19:02:15 | [diff] [blame] | 17 | #include "third_party/blink/public/common/storage_key/storage_key.h" |
Hans Wennborg | 78b5218 | 2021-06-15 13:42:15 | [diff] [blame] | 18 | #include "third_party/blink/public/mojom/service_worker/service_worker_registration_options.mojom.h" |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 19 | #include "url/gurl.h" |
| 20 | |
| 21 | namespace content { |
| 22 | |
| 23 | class NotificationStorageTest : public ::testing::Test { |
| 24 | public: |
| 25 | NotificationStorageTest() |
Gabriel Charette | 798fde7 | 2019-08-20 22:24:04 | [diff] [blame] | 26 | : task_environment_(BrowserTaskEnvironment::IO_MAINLOOP), |
Nidhi Jaju | 96d3809 | 2020-09-11 07:01:30 | [diff] [blame] | 27 | url_(GURL("https://example.com")), |
| 28 | origin_(url::Origin::Create(url_)), |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 29 | success_(false), |
| 30 | service_worker_registration_id_( |
| 31 | blink::mojom::kInvalidServiceWorkerRegistrationId) { |
| 32 | helper_ = std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()); |
| 33 | storage_ = |
| 34 | std::make_unique<NotificationStorage>(helper_->context_wrapper()); |
| 35 | } |
| 36 | |
| 37 | void DidRegisterServiceWorker(base::OnceClosure quit_closure, |
| 38 | blink::ServiceWorkerStatusCode status, |
| 39 | const std::string& status_message, |
| 40 | int64_t service_worker_registration_id) { |
| 41 | DCHECK(service_worker_registration_id_); |
| 42 | EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status) << status_message; |
| 43 | |
| 44 | service_worker_registration_id_ = service_worker_registration_id; |
| 45 | |
| 46 | std::move(quit_closure).Run(); |
| 47 | } |
| 48 | |
| 49 | void DidFindServiceWorkerRegistration( |
| 50 | scoped_refptr<ServiceWorkerRegistration>* out_service_worker_registration, |
| 51 | base::OnceClosure quit_closure, |
| 52 | blink::ServiceWorkerStatusCode status, |
| 53 | scoped_refptr<ServiceWorkerRegistration> service_worker_registration) { |
| 54 | DCHECK(out_service_worker_registration); |
| 55 | EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status) |
| 56 | << blink::ServiceWorkerStatusToString(status); |
| 57 | |
| 58 | *out_service_worker_registration = service_worker_registration; |
| 59 | |
| 60 | std::move(quit_closure).Run(); |
| 61 | } |
| 62 | |
| 63 | // Registers a Service Worker for the testing origin and returns its |
| 64 | // |service_worker_registration_id|. If registration failed, this will be |
| 65 | // blink::mojom::kInvalidServiceWorkerRegistrationId. The |
| 66 | // ServiceWorkerRegistration will be kept alive for the test's lifetime. |
| 67 | int64_t RegisterServiceWorker() { |
Nidhi Jaju | 96d3809 | 2020-09-11 07:01:30 | [diff] [blame] | 68 | GURL script_url = url_; |
Ari Chivukula | c81e13e | 2023-02-15 20:44:57 | [diff] [blame] | 69 | const blink::StorageKey key = blink::StorageKey::CreateFirstParty(origin_); |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 70 | { |
| 71 | blink::mojom::ServiceWorkerRegistrationOptions options; |
Nidhi Jaju | 96d3809 | 2020-09-11 07:01:30 | [diff] [blame] | 72 | options.scope = url_; |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 73 | base::RunLoop run_loop; |
| 74 | helper_->context()->RegisterServiceWorker( |
Steven Bingler | 4155171d | 2021-05-14 17:24:51 | [diff] [blame] | 75 | script_url, key, options, |
| 76 | blink::mojom::FetchClientSettingsObject::New(), |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 77 | base::BindOnce(&NotificationStorageTest::DidRegisterServiceWorker, |
Arthur Hemery | ea98960f | 2021-06-04 11:07:53 | [diff] [blame] | 78 | base::Unretained(this), run_loop.QuitClosure()), |
Jonathan Hao | f6cd769 | 2022-08-26 09:18:21 | [diff] [blame] | 79 | /*requesting_frame_id=*/GlobalRenderFrameHostId(), |
| 80 | PolicyContainerPolicies()); |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 81 | run_loop.Run(); |
| 82 | } |
| 83 | |
| 84 | if (service_worker_registration_id_ == |
| 85 | blink::mojom::kInvalidServiceWorkerRegistrationId) { |
| 86 | ADD_FAILURE() << "Could not obtain a valid Service Worker registration"; |
| 87 | return blink::mojom::kInvalidServiceWorkerRegistrationId; |
| 88 | } |
| 89 | |
| 90 | scoped_refptr<ServiceWorkerRegistration> service_worker_registration; |
| 91 | |
| 92 | { |
| 93 | base::RunLoop run_loop; |
Minoru Chikamune | 2873ad4 | 2025-06-06 03:45:27 | [diff] [blame] | 94 | helper_->context()->registry().FindRegistrationForId( |
Steven Bingler | 4155171d | 2021-05-14 17:24:51 | [diff] [blame] | 95 | service_worker_registration_id_, key, |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 96 | base::BindOnce( |
| 97 | &NotificationStorageTest::DidFindServiceWorkerRegistration, |
| 98 | base::Unretained(this), &service_worker_registration, |
| 99 | run_loop.QuitClosure())); |
| 100 | run_loop.Run(); |
| 101 | } |
| 102 | |
| 103 | // Wait for the worker to be activated. |
Gabriel Charette | 798fde7 | 2019-08-20 22:24:04 | [diff] [blame] | 104 | task_environment_.RunUntilIdle(); |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 105 | |
| 106 | if (!service_worker_registration) { |
| 107 | ADD_FAILURE() << "Could not find the new Service Worker registration."; |
| 108 | return blink::mojom::kInvalidServiceWorkerRegistrationId; |
| 109 | } |
| 110 | |
| 111 | service_worker_registrations_.push_back( |
| 112 | std::move(service_worker_registration)); |
| 113 | |
| 114 | return service_worker_registration_id_; |
| 115 | } |
| 116 | |
| 117 | void DidWriteNotificationDataSynchronous(base::OnceClosure quit_closure, |
| 118 | bool success, |
| 119 | const std::string& notification_id) { |
| 120 | success_ = success; |
| 121 | notification_id_ = notification_id; |
| 122 | std::move(quit_closure).Run(); |
| 123 | } |
| 124 | |
| 125 | void WriteNotificationDataSynchronous(const NotificationDatabaseData& data) { |
| 126 | base::RunLoop run_loop; |
| 127 | storage_->WriteNotificationData( |
danakj | f4b9e94 | 2019-11-29 15:43:04 | [diff] [blame] | 128 | data, base::BindOnce( |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 129 | &NotificationStorageTest::DidWriteNotificationDataSynchronous, |
| 130 | base::Unretained(this), run_loop.QuitClosure())); |
| 131 | run_loop.Run(); |
| 132 | } |
| 133 | |
| 134 | void DidReadNotificationDataAndRecordInteractionSynchronous( |
| 135 | base::OnceClosure quit_closure, |
| 136 | bool success, |
| 137 | const NotificationDatabaseData& data) { |
| 138 | success_ = success; |
| 139 | out_data_ = data; |
| 140 | std::move(quit_closure).Run(); |
| 141 | } |
| 142 | |
| 143 | NotificationDatabaseData ReadNotificationDataAndRecordInteractionSynchronous( |
| 144 | int64_t service_worker_registration_id, |
| 145 | const std::string& notification_id, |
| 146 | PlatformNotificationContext::Interaction interaction) { |
| 147 | base::RunLoop run_loop; |
| 148 | storage_->ReadNotificationDataAndRecordInteraction( |
| 149 | service_worker_registration_id, notification_id, interaction, |
danakj | f4b9e94 | 2019-11-29 15:43:04 | [diff] [blame] | 150 | base::BindOnce( |
| 151 | &NotificationStorageTest:: |
| 152 | DidReadNotificationDataAndRecordInteractionSynchronous, |
| 153 | base::Unretained(this), run_loop.QuitClosure())); |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 154 | run_loop.Run(); |
| 155 | return out_data_; |
| 156 | } |
| 157 | |
| 158 | // Generates a random notification ID. The format of the ID is opaque. |
Claudio DeSouza | e843ed6 | 2023-04-17 10:58:43 | [diff] [blame] | 159 | std::string GenerateNotificationId() { |
| 160 | return base::Uuid::GenerateRandomV4().AsLowercaseString(); |
| 161 | } |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 162 | |
| 163 | protected: |
Gabriel Charette | 798fde7 | 2019-08-20 22:24:04 | [diff] [blame] | 164 | BrowserTaskEnvironment task_environment_; // Must be first member |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 165 | std::unique_ptr<EmbeddedWorkerTestHelper> helper_; |
Nidhi Jaju | 96d3809 | 2020-09-11 07:01:30 | [diff] [blame] | 166 | GURL url_; |
| 167 | url::Origin origin_; |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 168 | TestBrowserContext browser_context_; |
| 169 | bool success_; |
| 170 | int64_t service_worker_registration_id_; |
| 171 | NotificationDatabaseData out_data_; |
| 172 | |
| 173 | private: |
| 174 | std::unique_ptr<NotificationStorage> storage_; |
| 175 | std::string notification_id_; |
| 176 | |
| 177 | // Vector of ServiceWorkerRegistration instances that have to be kept alive |
| 178 | // for the lifetime of this test. |
| 179 | std::vector<scoped_refptr<ServiceWorkerRegistration>> |
| 180 | service_worker_registrations_; |
| 181 | }; |
| 182 | |
| 183 | TEST_F(NotificationStorageTest, WriteReadNotification) { |
| 184 | NotificationDatabaseData data; |
| 185 | data.notification_id = GenerateNotificationId(); |
Nidhi Jaju | 96d3809 | 2020-09-11 07:01:30 | [diff] [blame] | 186 | data.origin = url_; |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 187 | data.service_worker_registration_id = RegisterServiceWorker(); |
| 188 | ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId, |
| 189 | data.service_worker_registration_id); |
| 190 | WriteNotificationDataSynchronous(data); |
| 191 | ASSERT_TRUE(success_); |
| 192 | |
| 193 | NotificationDatabaseData read_data = |
| 194 | ReadNotificationDataAndRecordInteractionSynchronous( |
| 195 | data.service_worker_registration_id, data.notification_id, |
| 196 | PlatformNotificationContext::Interaction::NONE); |
| 197 | ASSERT_TRUE(success_); |
| 198 | EXPECT_EQ(data.origin, read_data.origin); |
| 199 | EXPECT_EQ(data.notification_id, read_data.notification_id); |
| 200 | EXPECT_EQ(data.service_worker_registration_id, |
| 201 | read_data.service_worker_registration_id); |
| 202 | } |
| 203 | |
| 204 | TEST_F(NotificationStorageTest, ReadInvalidNotification) { |
| 205 | int64_t service_worker_registration_id = RegisterServiceWorker(); |
| 206 | ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId, |
| 207 | service_worker_registration_id); |
| 208 | ReadNotificationDataAndRecordInteractionSynchronous( |
| 209 | service_worker_registration_id, "bad_id", |
| 210 | PlatformNotificationContext::Interaction::NONE); |
| 211 | ASSERT_FALSE(success_); |
| 212 | } |
| 213 | |
| 214 | TEST_F(NotificationStorageTest, ReadAndUpdateInteraction) { |
| 215 | NotificationDatabaseData data, read_data; |
| 216 | data.notification_id = GenerateNotificationId(); |
Nidhi Jaju | 96d3809 | 2020-09-11 07:01:30 | [diff] [blame] | 217 | data.origin = url_; |
Sharon Yang | addbe49 | 2018-08-01 18:16:43 | [diff] [blame] | 218 | data.service_worker_registration_id = RegisterServiceWorker(); |
| 219 | ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId, |
| 220 | data.service_worker_registration_id); |
| 221 | |
| 222 | WriteNotificationDataSynchronous(data); |
| 223 | ASSERT_TRUE(success_); |
| 224 | |
| 225 | // Check that the time deltas have not yet been set. |
| 226 | EXPECT_FALSE(read_data.time_until_first_click_millis.has_value()); |
| 227 | EXPECT_FALSE(read_data.time_until_last_click_millis.has_value()); |
| 228 | EXPECT_FALSE(read_data.time_until_close_millis.has_value()); |
| 229 | |
| 230 | // Check that when a notification has an interaction, the appropriate field is |
| 231 | // updated on the read. |
| 232 | read_data = ReadNotificationDataAndRecordInteractionSynchronous( |
| 233 | data.service_worker_registration_id, data.notification_id, |
| 234 | PlatformNotificationContext::Interaction::CLICKED); |
| 235 | ASSERT_TRUE(success_); |
| 236 | EXPECT_EQ(1, read_data.num_clicks); |
| 237 | |
| 238 | read_data = ReadNotificationDataAndRecordInteractionSynchronous( |
| 239 | data.service_worker_registration_id, data.notification_id, |
| 240 | PlatformNotificationContext::Interaction::ACTION_BUTTON_CLICKED); |
| 241 | ASSERT_TRUE(success_); |
| 242 | EXPECT_EQ(1, read_data.num_action_button_clicks); |
| 243 | |
| 244 | read_data = ReadNotificationDataAndRecordInteractionSynchronous( |
| 245 | data.service_worker_registration_id, data.notification_id, |
| 246 | PlatformNotificationContext::Interaction::ACTION_BUTTON_CLICKED); |
| 247 | ASSERT_TRUE(success_); |
| 248 | EXPECT_EQ(2, read_data.num_action_button_clicks); |
| 249 | |
| 250 | // Check that the click timestamps are correctly updated. |
| 251 | EXPECT_TRUE(read_data.time_until_first_click_millis.has_value()); |
| 252 | EXPECT_TRUE(read_data.time_until_last_click_millis.has_value()); |
| 253 | |
| 254 | // Check that when a read with a CLOSED interaction occurs, the correct |
| 255 | // field is updated. |
| 256 | read_data = ReadNotificationDataAndRecordInteractionSynchronous( |
| 257 | data.service_worker_registration_id, data.notification_id, |
| 258 | PlatformNotificationContext::Interaction::CLOSED); |
| 259 | ASSERT_TRUE(success_); |
| 260 | EXPECT_EQ(true, read_data.time_until_close_millis.has_value()); |
| 261 | } |
| 262 | |
| 263 | } // namespace content |