1 // Copyright 2019 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.
4 
5 #include <stdint.h>
6 
7 #include "base/bind.h"
8 #include "base/run_loop.h"
9 #include "base/test/bind.h"
10 #include "base/test/metrics/histogram_tester.h"
11 #include "base/test/scoped_feature_list.h"
12 #include "base/time/time.h"
13 #include "content/browser/notifications/notification_trigger_constants.h"
14 #include "content/browser/notifications/platform_notification_context_impl.h"
15 #include "content/browser/service_worker/service_worker_context_wrapper.h"
16 #include "content/public/browser/notification_database_data.h"
17 #include "content/public/common/content_client.h"
18 #include "content/public/common/content_features.h"
19 #include "content/public/test/browser_task_environment.h"
20 #include "content/public/test/test_browser_context.h"
21 #include "content/test/mock_platform_notification_service.h"
22 #include "content/test/test_content_browser_client.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "url/gurl.h"
25 
26 using base::Time;
27 using base::TimeDelta;
28 
29 namespace content {
30 
31 namespace {
32 
33 // Fake Service Worker registration id to use in tests requiring one.
34 const int64_t kFakeServiceWorkerRegistrationId = 42;
35 
36 class NotificationBrowserClient : public TestContentBrowserClient {
37  public:
NotificationBrowserClient(BrowserContext * browser_context)38   explicit NotificationBrowserClient(BrowserContext* browser_context)
39       : platform_notification_service_(
40             std::make_unique<MockPlatformNotificationService>(
41                 browser_context)) {}
42 
GetPlatformNotificationService(BrowserContext * browser_context)43   PlatformNotificationService* GetPlatformNotificationService(
44       BrowserContext* browser_context) override {
45     return platform_notification_service_.get();
46   }
47 
48  private:
49   std::unique_ptr<PlatformNotificationService> platform_notification_service_;
50 };
51 
52 }  // namespace
53 
54 class PlatformNotificationContextTriggerTest : public ::testing::Test {
55  public:
PlatformNotificationContextTriggerTest()56   PlatformNotificationContextTriggerTest()
57       : task_environment_(base::test::TaskEnvironment::MainThreadType::UI,
58                           base::test::TaskEnvironment::TimeSource::MOCK_TIME),
59         notification_browser_client_(&browser_context_),
60         success_(false) {
61     SetBrowserClientForTesting(&notification_browser_client_);
62   }
63 
SetUp()64   void SetUp() override {
65     scoped_feature_list_.InitAndEnableFeature(features::kNotificationTriggers);
66     platform_notification_context_ =
67         base::MakeRefCounted<PlatformNotificationContextImpl>(
68             base::FilePath(), &browser_context_, nullptr);
69     platform_notification_context_->SetTaskRunnerForTesting(
70         base::ThreadTaskRunnerHandle::Get());
71     platform_notification_context_->Initialize();
72     base::RunLoop().RunUntilIdle();
73   }
74 
TearDown()75   void TearDown() override {
76     // Destroy the context and allow its background tasks to run to close the
77     // database.
78     platform_notification_context_.reset();
79     task_environment_.RunUntilIdle();
80   }
81 
82   // Callback to provide when writing a notification to the database.
DidWriteNotificationData(bool success,const std::string & notification_id)83   void DidWriteNotificationData(bool success,
84                                 const std::string& notification_id) {
85     success_ = success;
86   }
87 
88  protected:
WriteNotificationData(const std::string & tag,base::Optional<base::Time> timestamp)89   void WriteNotificationData(const std::string& tag,
90                              base::Optional<base::Time> timestamp) {
91     ASSERT_TRUE(
92         TryWriteNotificationData("https://example.com", tag, timestamp));
93   }
94 
TryWriteNotificationData(const std::string & url,const std::string & tag,base::Optional<base::Time> timestamp)95   bool TryWriteNotificationData(const std::string& url,
96                                 const std::string& tag,
97                                 base::Optional<base::Time> timestamp) {
98     GURL origin(url);
99     NotificationDatabaseData notification_database_data;
100     notification_database_data.origin = origin;
101     notification_database_data.service_worker_registration_id =
102         kFakeServiceWorkerRegistrationId;
103     notification_database_data.notification_data.show_trigger_timestamp =
104         timestamp;
105     notification_database_data.notification_data.tag = tag;
106 
107     platform_notification_context_->WriteNotificationData(
108         next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
109         origin, notification_database_data,
110         base::BindOnce(
111             &PlatformNotificationContextTriggerTest::DidWriteNotificationData,
112             base::Unretained(this)));
113     base::RunLoop().RunUntilIdle();
114     return success_;
115   }
116 
117   // Gets the currently displayed notifications from
118   // |notification_browser_client_| synchronously.
GetDisplayedNotifications()119   std::set<std::string> GetDisplayedNotifications() {
120     std::set<std::string> displayed_notification_ids;
121     base::RunLoop run_loop;
122     notification_browser_client_
123         .GetPlatformNotificationService(&browser_context_)
124         ->GetDisplayedNotifications(base::BindLambdaForTesting(
125             [&](std::set<std::string> notification_ids, bool supports_sync) {
126               displayed_notification_ids = std::move(notification_ids);
127               run_loop.Quit();
128             }));
129     run_loop.Run();
130     return displayed_notification_ids;
131   }
132 
TriggerNotifications()133   void TriggerNotifications() {
134     platform_notification_context_->TriggerNotifications();
135     base::RunLoop().RunUntilIdle();
136   }
137 
138   BrowserTaskEnvironment task_environment_;  // Must be first member
139 
140  private:
141   base::test::ScopedFeatureList scoped_feature_list_;
142   TestBrowserContext browser_context_;
143   NotificationBrowserClient notification_browser_client_;
144   scoped_refptr<PlatformNotificationContextImpl> platform_notification_context_;
145 
146   // Returns the next persistent notification id for tests.
next_persistent_notification_id()147   int64_t next_persistent_notification_id() {
148     return next_persistent_notification_id_++;
149   }
150 
151   bool success_;
152   int64_t next_persistent_notification_id_ = 1;
153 };
154 
TEST_F(PlatformNotificationContextTriggerTest,TriggerInFuture)155 TEST_F(PlatformNotificationContextTriggerTest, TriggerInFuture) {
156   WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
157   ASSERT_EQ(0u, GetDisplayedNotifications().size());
158 
159   // Wait until the trigger timestamp is reached.
160   task_environment_.FastForwardBy(TimeDelta::FromSeconds(10));
161 
162   // This gets called by the notification scheduling system.
163   TriggerNotifications();
164 
165   ASSERT_EQ(1u, GetDisplayedNotifications().size());
166 }
167 
TEST_F(PlatformNotificationContextTriggerTest,TriggerInPast)168 TEST_F(PlatformNotificationContextTriggerTest, TriggerInPast) {
169   // Trigger timestamp in the past should immediately trigger.
170   WriteNotificationData("1", Time::Now() - TimeDelta::FromSeconds(10));
171 
172   // This gets called by the notification scheduling system.
173   TriggerNotifications();
174 
175   ASSERT_EQ(1u, GetDisplayedNotifications().size());
176 }
177 
TEST_F(PlatformNotificationContextTriggerTest,OverwriteExistingTriggerInFuture)178 TEST_F(PlatformNotificationContextTriggerTest,
179        OverwriteExistingTriggerInFuture) {
180   WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
181   ASSERT_EQ(0u, GetDisplayedNotifications().size());
182 
183   task_environment_.FastForwardBy(TimeDelta::FromSeconds(5));
184   ASSERT_EQ(0u, GetDisplayedNotifications().size());
185 
186   // Overwrites the scheduled notifications with a new trigger timestamp.
187   WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
188 
189   task_environment_.FastForwardBy(TimeDelta::FromSeconds(5));
190   ASSERT_EQ(0u, GetDisplayedNotifications().size());
191 
192   task_environment_.FastForwardBy(TimeDelta::FromSeconds(5));
193 
194   // This gets called by the notification scheduling system.
195   TriggerNotifications();
196 
197   ASSERT_EQ(1u, GetDisplayedNotifications().size());
198 }
199 
TEST_F(PlatformNotificationContextTriggerTest,OverwriteExistingTriggerToPast)200 TEST_F(PlatformNotificationContextTriggerTest, OverwriteExistingTriggerToPast) {
201   WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
202   ASSERT_EQ(0u, GetDisplayedNotifications().size());
203 
204   task_environment_.FastForwardBy(TimeDelta::FromSeconds(5));
205 
206   // Overwrites the scheduled notifications with a new trigger timestamp.
207   WriteNotificationData("1", Time::Now() - TimeDelta::FromSeconds(10));
208 
209   // This gets called by the notification scheduling system.
210   TriggerNotifications();
211 
212   ASSERT_EQ(1u, GetDisplayedNotifications().size());
213 }
214 
TEST_F(PlatformNotificationContextTriggerTest,OverwriteDisplayedNotificationToPast)215 TEST_F(PlatformNotificationContextTriggerTest,
216        OverwriteDisplayedNotificationToPast) {
217   WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
218   task_environment_.FastForwardBy(TimeDelta::FromSeconds(10));
219 
220   // Overwrites a displayed notification with a trigger timestamp in the past.
221   WriteNotificationData("1", Time::Now() - TimeDelta::FromSeconds(10));
222 
223   // This gets called by the notification scheduling system.
224   TriggerNotifications();
225 
226   ASSERT_EQ(1u, GetDisplayedNotifications().size());
227 }
228 
TEST_F(PlatformNotificationContextTriggerTest,OverwriteDisplayedNotificationToFuture)229 TEST_F(PlatformNotificationContextTriggerTest,
230        OverwriteDisplayedNotificationToFuture) {
231   WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
232   task_environment_.FastForwardBy(TimeDelta::FromSeconds(10));
233 
234   // This gets called by the notification scheduling system.
235   TriggerNotifications();
236 
237   // Overwrites a displayed notification which hides it until the trigger
238   // timestamp is reached.
239   WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
240 
241   ASSERT_EQ(0u, GetDisplayedNotifications().size());
242 
243   task_environment_.FastForwardBy(TimeDelta::FromSeconds(10));
244 
245   // This gets called by the notification scheduling system.
246   TriggerNotifications();
247 
248   ASSERT_EQ(1u, GetDisplayedNotifications().size());
249 }
250 
TEST_F(PlatformNotificationContextTriggerTest,LimitsNumberOfScheduledNotificationsPerOrigin)251 TEST_F(PlatformNotificationContextTriggerTest,
252        LimitsNumberOfScheduledNotificationsPerOrigin) {
253   for (int i = 1; i <= kMaximumScheduledNotificationsPerOrigin; ++i) {
254     WriteNotificationData(std::to_string(i),
255                           Time::Now() + TimeDelta::FromSeconds(i));
256   }
257 
258   ASSERT_FALSE(TryWriteNotificationData(
259       "https://example.com",
260       std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
261       Time::Now() +
262           TimeDelta::FromSeconds(kMaximumScheduledNotificationsPerOrigin + 1)));
263 
264   ASSERT_TRUE(TryWriteNotificationData(
265       "https://example2.com",
266       std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
267       Time::Now() +
268           TimeDelta::FromSeconds(kMaximumScheduledNotificationsPerOrigin + 1)));
269 }
270 
TEST_F(PlatformNotificationContextTriggerTest,EnforcesLimitOnUpdate)271 TEST_F(PlatformNotificationContextTriggerTest, EnforcesLimitOnUpdate) {
272   for (int i = 1; i <= kMaximumScheduledNotificationsPerOrigin; ++i) {
273     WriteNotificationData(std::to_string(i),
274                           Time::Now() + TimeDelta::FromSeconds(i));
275   }
276 
277   ASSERT_TRUE(TryWriteNotificationData(
278       "https://example.com",
279       std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
280       base::nullopt));
281 
282   ASSERT_FALSE(TryWriteNotificationData(
283       "https://example.com",
284       std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
285       Time::Now() +
286           TimeDelta::FromSeconds(kMaximumScheduledNotificationsPerOrigin + 1)));
287 }
288 
TEST_F(PlatformNotificationContextTriggerTest,RecordDisplayDelay)289 TEST_F(PlatformNotificationContextTriggerTest, RecordDisplayDelay) {
290   base::HistogramTester histogram_tester;
291   base::TimeDelta trigger_delay = TimeDelta::FromSeconds(10);
292   base::TimeDelta display_delay = TimeDelta::FromSeconds(8);
293 
294   WriteNotificationData("1", Time::Now() + trigger_delay);
295   ASSERT_EQ(0u, GetDisplayedNotifications().size());
296 
297   // Forward time until after the expected trigger time.
298   task_environment_.FastForwardBy(trigger_delay + display_delay);
299 
300   // Trigger notification |display_delay| after it should have been displayed.
301   TriggerNotifications();
302 
303   histogram_tester.ExpectUniqueSample("Notifications.Triggers.DisplayDelay",
304                                       display_delay.InMilliseconds(), 1);
305 }
306 
307 }  // namespace content
308