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