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 20 MockBalloonCollection::MockBalloonCollection() {} 21 22 MockBalloonCollection::~MockBalloonCollection() {} 23 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 38 bool MockBalloonCollection::HasSpace() const { 39 return count() < kMockBalloonSpace; 40 } 41 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 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 62 const BalloonCollection::Balloons& MockBalloonCollection::GetActiveBalloons() { 63 return balloons_; 64 } 65 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 77 DesktopNotificationsTest::DesktopNotificationsTest() 78 : ui_thread_(BrowserThread::UI, &message_loop_) { 79 } 80 81 DesktopNotificationsTest::~DesktopNotificationsTest() { 82 } 83 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 95 void DesktopNotificationsTest::TearDown() { 96 service_.reset(NULL); 97 ui_manager_.reset(NULL); 98 profile_.reset(NULL); 99 } 100 101 DesktopNotificationHostMsg_Show_Params 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 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 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 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) 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 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 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 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 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 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 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