desktop_notifications_unittest.cc revision 3f50c38dc070f4bb515c1b64450dae14f316474e
1// Copyright (c) 2010 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 "chrome/browser/notifications/desktop_notifications_unittest.h"
6
7#include "base/string_util.h"
8#include "base/utf_string_conversions.h"
9#include "chrome/common/pref_names.h"
10#include "chrome/common/render_messages_params.h"
11#include "chrome/test/testing_pref_service.h"
12
13// static
14const int MockBalloonCollection::kMockBalloonSpace = 5;
15
16// static
17std::string DesktopNotificationsTest::log_output_;
18
19void MockBalloonCollection::Add(const Notification& notification,
20                                Profile* profile) {
21  // Swap in a logging proxy for the purpose of logging calls that
22  // would be made into javascript, then pass this down to the
23  // balloon collection.
24  Notification test_notification(
25      notification.origin_url(),
26      notification.content_url(),
27      notification.display_source(),
28      notification.replace_id(),
29      new LoggingNotificationProxy(notification.notification_id()));
30  BalloonCollectionImpl::Add(test_notification, profile);
31}
32
33Balloon* MockBalloonCollection::MakeBalloon(const Notification& notification,
34                                            Profile* profile) {
35  // Start with a normal balloon but mock out the view.
36  Balloon* balloon = BalloonCollectionImpl::MakeBalloon(notification, profile);
37  balloon->set_view(new MockBalloonView(balloon));
38  balloons_.push_back(balloon);
39  return balloon;
40}
41
42void MockBalloonCollection::OnBalloonClosed(Balloon* source) {
43  std::deque<Balloon*>::iterator it;
44  for (it = balloons_.begin(); it != balloons_.end(); ++it) {
45    if (*it == source) {
46      balloons_.erase(it);
47      BalloonCollectionImpl::OnBalloonClosed(source);
48      break;
49    }
50  }
51}
52
53int MockBalloonCollection::UppermostVerticalPosition() {
54  int min = 0;
55  std::deque<Balloon*>::iterator iter;
56  for (iter = balloons_.begin(); iter != balloons_.end(); ++iter) {
57    int pos = (*iter)->GetPosition().y();
58    if (iter == balloons_.begin() || pos < min)
59      min = pos;
60  }
61  return min;
62}
63
64DesktopNotificationsTest::DesktopNotificationsTest()
65    : ui_thread_(BrowserThread::UI, &message_loop_) {
66}
67
68DesktopNotificationsTest::~DesktopNotificationsTest() {
69}
70
71void DesktopNotificationsTest::SetUp() {
72  profile_.reset(new TestingProfile());
73  balloon_collection_ = new MockBalloonCollection();
74  ui_manager_.reset(
75      new NotificationUIManager(profile_->GetTestingPrefService()));
76  ui_manager_->Initialize(balloon_collection_);
77  balloon_collection_->set_space_change_listener(ui_manager_.get());
78  service_.reset(new DesktopNotificationService(profile(), ui_manager_.get()));
79  log_output_.clear();
80}
81
82void DesktopNotificationsTest::TearDown() {
83  service_.reset(NULL);
84  ui_manager_.reset(NULL);
85  profile_.reset(NULL);
86}
87
88ViewHostMsg_ShowNotification_Params
89DesktopNotificationsTest::StandardTestNotification() {
90  ViewHostMsg_ShowNotification_Params params;
91  params.notification_id = 0;
92  params.origin = GURL("http://www.google.com");
93  params.is_html = false;
94  params.icon_url = GURL("/icon.png");
95  params.title = ASCIIToUTF16("Title");
96  params.body = ASCIIToUTF16("Text");
97  params.direction = WebKit::WebTextDirectionDefault;
98  return params;
99}
100
101TEST_F(DesktopNotificationsTest, TestShow) {
102  ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
103  params.notification_id = 1;
104
105  EXPECT_TRUE(service_->ShowDesktopNotification(
106      params, 0, 0, DesktopNotificationService::PageNotification));
107  MessageLoopForUI::current()->RunAllPending();
108  EXPECT_EQ(1, balloon_collection_->count());
109
110  ViewHostMsg_ShowNotification_Params params2;
111  params2.origin = GURL("http://www.google.com");
112  params2.is_html = true;
113  params2.contents_url = GURL("http://www.google.com/notification.html");
114  params2.notification_id = 2;
115
116  EXPECT_TRUE(service_->ShowDesktopNotification(
117      params2, 0, 0, DesktopNotificationService::PageNotification));
118  MessageLoopForUI::current()->RunAllPending();
119  EXPECT_EQ(2, balloon_collection_->count());
120
121  EXPECT_EQ("notification displayed\n"
122            "notification displayed\n",
123            log_output_);
124}
125
126TEST_F(DesktopNotificationsTest, TestClose) {
127  ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
128  params.notification_id = 1;
129
130  // Request a notification; should open a balloon.
131  EXPECT_TRUE(service_->ShowDesktopNotification(
132      params, 0, 0, DesktopNotificationService::PageNotification));
133  MessageLoopForUI::current()->RunAllPending();
134  EXPECT_EQ(1, balloon_collection_->count());
135
136  // Close all the open balloons.
137  while (balloon_collection_->count() > 0) {
138    (*(balloon_collection_->GetActiveBalloons().begin()))->OnClose(true);
139  }
140
141  EXPECT_EQ("notification displayed\n"
142            "notification closed by user\n",
143            log_output_);
144}
145
146TEST_F(DesktopNotificationsTest, TestCancel) {
147  int process_id = 0;
148  int route_id = 0;
149  int notification_id = 1;
150
151  ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
152  params.notification_id = notification_id;
153
154  // Request a notification; should open a balloon.
155  EXPECT_TRUE(service_->ShowDesktopNotification(
156      params, process_id, route_id,
157      DesktopNotificationService::PageNotification));
158  MessageLoopForUI::current()->RunAllPending();
159  EXPECT_EQ(1, balloon_collection_->count());
160
161  // Cancel the same notification
162  service_->CancelDesktopNotification(process_id,
163                                      route_id,
164                                      notification_id);
165  MessageLoopForUI::current()->RunAllPending();
166  // Verify that the balloon collection is now empty.
167  EXPECT_EQ(0, balloon_collection_->count());
168
169  EXPECT_EQ("notification displayed\n"
170            "notification closed by script\n",
171            log_output_);
172}
173
174#if defined(OS_WIN) || defined(TOOLKIT_VIEWS)
175TEST_F(DesktopNotificationsTest, TestPositioning) {
176  ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
177  std::string expected_log;
178  // Create some toasts.  After each but the first, make sure there
179  // is a minimum separation between the toasts.
180  int last_top = 0;
181  for (int id = 0; id <= 3; ++id) {
182    params.notification_id = id;
183    EXPECT_TRUE(service_->ShowDesktopNotification(
184        params, 0, 0, DesktopNotificationService::PageNotification));
185    expected_log.append("notification displayed\n");
186    int top = balloon_collection_->UppermostVerticalPosition();
187    if (id > 0)
188      EXPECT_LE(top, last_top - balloon_collection_->MinHeight());
189    last_top = top;
190  }
191
192  EXPECT_EQ(expected_log, log_output_);
193}
194
195TEST_F(DesktopNotificationsTest, TestVariableSize) {
196  ViewHostMsg_ShowNotification_Params params;
197  params.origin = GURL("http://long.google.com");
198  params.is_html = false;
199  params.icon_url = GURL("/icon.png");
200  params.title = ASCIIToUTF16("Really Really Really Really Really Really "
201                              "Really Really Really Really Really Really "
202                              "Really Really Really Really Really Really "
203                              "Really Long Title"),
204  params.body = ASCIIToUTF16("Text");
205  params.notification_id = 0;
206
207  std::string expected_log;
208  // Create some toasts.  After each but the first, make sure there
209  // is a minimum separation between the toasts.
210  EXPECT_TRUE(service_->ShowDesktopNotification(
211      params, 0, 0, DesktopNotificationService::PageNotification));
212  expected_log.append("notification displayed\n");
213
214  params.origin = GURL("http://short.google.com");
215  params.title = ASCIIToUTF16("Short title");
216  params.notification_id = 1;
217  EXPECT_TRUE(service_->ShowDesktopNotification(
218      params, 0, 0, DesktopNotificationService::PageNotification));
219  expected_log.append("notification displayed\n");
220
221  std::deque<Balloon*>& balloons = balloon_collection_->balloons();
222  std::deque<Balloon*>::iterator iter;
223  for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
224    if ((*iter)->notification().origin_url().host() == "long.google.com") {
225      EXPECT_GE((*iter)->GetViewSize().height(),
226                balloon_collection_->MinHeight());
227      EXPECT_LE((*iter)->GetViewSize().height(),
228                balloon_collection_->MaxHeight());
229    } else {
230      EXPECT_EQ((*iter)->GetViewSize().height(),
231                balloon_collection_->MinHeight());
232    }
233  }
234  EXPECT_EQ(expected_log, log_output_);
235}
236#endif
237
238TEST_F(DesktopNotificationsTest, TestQueueing) {
239  int process_id = 0;
240  int route_id = 0;
241
242  // Request lots of identical notifications.
243  ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
244  const int kLotsOfToasts = 20;
245  for (int id = 1; id <= kLotsOfToasts; ++id) {
246    params.notification_id = id;
247    EXPECT_TRUE(service_->ShowDesktopNotification(
248        params, process_id, route_id,
249        DesktopNotificationService::PageNotification));
250  }
251  MessageLoopForUI::current()->RunAllPending();
252
253  // Build up an expected log of what should be happening.
254  std::string expected_log;
255  for (int i = 0; i < balloon_collection_->max_balloon_count(); ++i) {
256    expected_log.append("notification displayed\n");
257  }
258
259  // The max number that our balloon collection can hold should be
260  // shown.
261  EXPECT_EQ(balloon_collection_->max_balloon_count(),
262            balloon_collection_->count());
263  EXPECT_EQ(expected_log, log_output_);
264
265  // Cancel the notifications from the start; the balloon space should
266  // remain full.
267  {
268    int id;
269    for (id = 1;
270         id <= kLotsOfToasts - balloon_collection_->max_balloon_count();
271         ++id) {
272      service_->CancelDesktopNotification(process_id, route_id, id);
273      MessageLoopForUI::current()->RunAllPending();
274      expected_log.append("notification closed by script\n");
275      expected_log.append("notification displayed\n");
276      EXPECT_EQ(balloon_collection_->max_balloon_count(),
277                balloon_collection_->count());
278      EXPECT_EQ(expected_log, log_output_);
279    }
280
281    // Now cancel the rest.  It should empty the balloon space.
282    for (; id <= kLotsOfToasts; ++id) {
283      service_->CancelDesktopNotification(process_id, route_id, id);
284      expected_log.append("notification closed by script\n");
285      MessageLoopForUI::current()->RunAllPending();
286      EXPECT_EQ(expected_log, log_output_);
287    }
288  }
289
290  // Verify that the balloon collection is now empty.
291  EXPECT_EQ(0, balloon_collection_->count());
292}
293
294TEST_F(DesktopNotificationsTest, TestEarlyDestruction) {
295  // Create some toasts and then prematurely delete the notification service,
296  // just to make sure nothing crashes/leaks.
297  ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
298  for (int id = 0; id <= 3; ++id) {
299    params.notification_id = id;
300    EXPECT_TRUE(service_->ShowDesktopNotification(
301        params, 0, 0, DesktopNotificationService::PageNotification));
302  }
303  service_.reset(NULL);
304}
305
306TEST_F(DesktopNotificationsTest, TestUserInputEscaping) {
307  // Create a test script with some HTML; assert that it doesn't get into the
308  // data:// URL that's produced for the balloon.
309  ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
310  params.title = ASCIIToUTF16("<script>window.alert('uh oh');</script>");
311  params.body = ASCIIToUTF16("<i>this text is in italics</i>");
312  params.notification_id = 1;
313  EXPECT_TRUE(service_->ShowDesktopNotification(
314      params, 0, 0, DesktopNotificationService::PageNotification));
315
316  MessageLoopForUI::current()->RunAllPending();
317  EXPECT_EQ(1, balloon_collection_->count());
318  Balloon* balloon = (*balloon_collection_->balloons().begin());
319  GURL data_url = balloon->notification().content_url();
320  EXPECT_EQ(std::string::npos, data_url.spec().find("<script>"));
321  EXPECT_EQ(std::string::npos, data_url.spec().find("<i>"));
322  // URL-encoded versions of tags should also not be found.
323  EXPECT_EQ(std::string::npos, data_url.spec().find("%3cscript%3e"));
324  EXPECT_EQ(std::string::npos, data_url.spec().find("%3ci%3e"));
325}
326
327TEST_F(DesktopNotificationsTest, TestPositionPreference) {
328  // Set position preference to lower right.
329  profile_->GetPrefs()->SetInteger(prefs::kDesktopNotificationPosition,
330                                   BalloonCollection::LOWER_RIGHT);
331
332  // Create some notifications.
333  ViewHostMsg_ShowNotification_Params params = StandardTestNotification();
334  for (int id = 0; id <= 3; ++id) {
335    params.notification_id = id;
336    EXPECT_TRUE(service_->ShowDesktopNotification(
337        params, 0, 0, DesktopNotificationService::PageNotification));
338  }
339
340  std::deque<Balloon*>& balloons = balloon_collection_->balloons();
341  std::deque<Balloon*>::iterator iter;
342
343  // Check that they decrease in y-position (for MAC, with reversed
344  // coordinates, they should increase).
345  int last_y = -1;
346  int last_x = -1;
347
348  for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
349    int current_x = (*iter)->GetPosition().x();
350    int current_y = (*iter)->GetPosition().y();
351    if (last_x > 0)
352      EXPECT_EQ(last_x, current_x);
353
354    if (last_y > 0) {
355#if defined(OS_MACOSX)
356      EXPECT_GT(current_y, last_y);
357#else
358      EXPECT_LT(current_y, last_y);
359#endif
360    }
361
362    last_x = current_x;
363    last_y = current_y;
364  }
365
366  // Now change the position to upper right.  This should cause an immediate
367  // repositioning, and we check for the reverse ordering.
368  profile_->GetPrefs()->SetInteger(prefs::kDesktopNotificationPosition,
369                                   BalloonCollection::UPPER_RIGHT);
370  last_x = -1;
371  last_y = -1;
372
373  for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
374    int current_x = (*iter)->GetPosition().x();
375    int current_y = (*iter)->GetPosition().y();
376
377    if (last_x > 0)
378      EXPECT_EQ(last_x, current_x);
379
380    if (last_y > 0) {
381#if defined(OS_MACOSX)
382      EXPECT_LT(current_y, last_y);
383#else
384      EXPECT_GT(current_y, last_y);
385#endif
386    }
387
388    last_x = current_x;
389    last_y = current_y;
390  }
391
392  // Now change the position to upper left.  Confirm that the X value for the
393  // balloons gets smaller.
394  profile_->GetPrefs()->SetInteger(prefs::kDesktopNotificationPosition,
395                                   BalloonCollection::UPPER_LEFT);
396
397  int current_x = (*balloons.begin())->GetPosition().x();
398  EXPECT_LT(current_x, last_x);
399}
400