1 // Copyright (c) 2012 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/prefs/testing_pref_service.h" 8 #include "base/strings/string_util.h" 9 #include "base/strings/utf_string_conversions.h" 10 #include "chrome/browser/notifications/balloon_notification_ui_manager.h" 11 #include "chrome/browser/notifications/fake_balloon_view.h" 12 #include "chrome/browser/prefs/browser_prefs.h" 13 #include "chrome/common/pref_names.h" 14 #include "chrome/test/base/testing_browser_process.h" 15 #include "chrome/test/base/testing_profile.h" 16 #include "chrome/test/base/testing_profile_manager.h" 17 #include "content/public/common/show_desktop_notification_params.h" 18 #include "ui/base/ime/input_method_initializer.h" 19 #include "ui/message_center/message_center.h" 20 21 #if defined(USE_ASH) 22 #include "ash/shell.h" 23 #include "ash/test/test_shell_delegate.h" 24 #include "ui/aura/env.h" 25 #include "ui/aura/root_window.h" 26 #include "ui/compositor/scoped_animation_duration_scale_mode.h" 27 #endif 28 29 30 using content::BrowserThread; 31 32 // static 33 const int MockBalloonCollection::kMockBalloonSpace = 5; 34 35 // static 36 std::string DesktopNotificationsTest::log_output_; 37 38 MockBalloonCollection::MockBalloonCollection() {} 39 40 MockBalloonCollection::~MockBalloonCollection() {} 41 42 void MockBalloonCollection::Add(const Notification& notification, 43 Profile* profile) { 44 // Swap in a logging proxy for the purpose of logging calls that 45 // would be made into javascript, then pass this down to the 46 // balloon collection. 47 Notification test_notification( 48 notification.origin_url(), 49 notification.content_url(), 50 notification.display_source(), 51 notification.replace_id(), 52 new LoggingNotificationProxy(notification.notification_id())); 53 BalloonCollectionImpl::Add(test_notification, profile); 54 } 55 56 bool MockBalloonCollection::HasSpace() const { 57 return count() < kMockBalloonSpace; 58 } 59 60 Balloon* MockBalloonCollection::MakeBalloon(const Notification& notification, 61 Profile* profile) { 62 // Start with a normal balloon but mock out the view. 63 Balloon* balloon = BalloonCollectionImpl::MakeBalloon(notification, profile); 64 balloon->set_view(new FakeBalloonView(balloon)); 65 balloons_.push_back(balloon); 66 return balloon; 67 } 68 69 void MockBalloonCollection::OnBalloonClosed(Balloon* source) { 70 std::deque<Balloon*>::iterator it; 71 for (it = balloons_.begin(); it != balloons_.end(); ++it) { 72 if (*it == source) { 73 balloons_.erase(it); 74 BalloonCollectionImpl::OnBalloonClosed(source); 75 break; 76 } 77 } 78 } 79 80 const BalloonCollection::Balloons& MockBalloonCollection::GetActiveBalloons() { 81 return balloons_; 82 } 83 84 int MockBalloonCollection::UppermostVerticalPosition() { 85 int min = 0; 86 std::deque<Balloon*>::iterator iter; 87 for (iter = balloons_.begin(); iter != balloons_.end(); ++iter) { 88 int pos = (*iter)->GetPosition().y(); 89 if (iter == balloons_.begin() || pos < min) 90 min = pos; 91 } 92 return min; 93 } 94 95 DesktopNotificationsTest::DesktopNotificationsTest() 96 : ui_thread_(BrowserThread::UI, &message_loop_) { 97 } 98 99 DesktopNotificationsTest::~DesktopNotificationsTest() { 100 } 101 102 void DesktopNotificationsTest::SetUp() { 103 ui::InitializeInputMethodForTesting(); 104 #if defined(USE_ASH) 105 ui::ScopedAnimationDurationScaleMode normal_duration_mode( 106 ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); 107 // The message center is notmally initialized on |g_browser_process| which 108 // is not created for these tests. 109 message_center::MessageCenter::Initialize(); 110 // MockBalloonCollection retrieves information about the screen on creation. 111 // So it is necessary to make sure the desktop gets created first. 112 ash::Shell::CreateInstance(new ash::test::TestShellDelegate); 113 #endif 114 115 chrome::RegisterLocalState(local_state_.registry()); 116 profile_.reset(new TestingProfile()); 117 ui_manager_.reset(new BalloonNotificationUIManager(&local_state_)); 118 balloon_collection_ = new MockBalloonCollection(); 119 ui_manager_->SetBalloonCollection(balloon_collection_); 120 service_.reset(new DesktopNotificationService(profile(), ui_manager_.get())); 121 log_output_.clear(); 122 } 123 124 void DesktopNotificationsTest::TearDown() { 125 service_.reset(NULL); 126 ui_manager_.reset(NULL); 127 profile_.reset(NULL); 128 #if defined(USE_ASH) 129 ash::Shell::DeleteInstance(); 130 // The message center is notmally shutdown on |g_browser_process| which 131 // is not created for these tests. 132 message_center::MessageCenter::Shutdown(); 133 aura::Env::DeleteInstance(); 134 #endif 135 ui::ShutdownInputMethodForTesting(); 136 } 137 138 content::ShowDesktopNotificationHostMsgParams 139 DesktopNotificationsTest::StandardTestNotification() { 140 content::ShowDesktopNotificationHostMsgParams params; 141 params.notification_id = 0; 142 params.origin = GURL("http://www.google.com"); 143 params.is_html = false; 144 params.icon_url = GURL("/icon.png"); 145 params.title = ASCIIToUTF16("Title"); 146 params.body = ASCIIToUTF16("Text"); 147 params.direction = WebKit::WebTextDirectionDefault; 148 return params; 149 } 150 151 TEST_F(DesktopNotificationsTest, TestShow) { 152 content::ShowDesktopNotificationHostMsgParams params = 153 StandardTestNotification(); 154 params.notification_id = 1; 155 156 EXPECT_TRUE(service_->ShowDesktopNotification( 157 params, 0, 0, DesktopNotificationService::PageNotification)); 158 base::MessageLoopForUI::current()->RunUntilIdle(); 159 EXPECT_EQ(1, balloon_collection_->count()); 160 161 content::ShowDesktopNotificationHostMsgParams params2; 162 params2.origin = GURL("http://www.google.com"); 163 params2.is_html = true; 164 params2.contents_url = GURL("http://www.google.com/notification.html"); 165 params2.notification_id = 2; 166 167 EXPECT_TRUE(service_->ShowDesktopNotification( 168 params2, 0, 0, DesktopNotificationService::PageNotification)); 169 base::MessageLoopForUI::current()->RunUntilIdle(); 170 EXPECT_EQ(2, balloon_collection_->count()); 171 172 EXPECT_EQ("notification displayed\n" 173 "notification displayed\n", 174 log_output_); 175 } 176 177 TEST_F(DesktopNotificationsTest, TestClose) { 178 content::ShowDesktopNotificationHostMsgParams params = 179 StandardTestNotification(); 180 params.notification_id = 1; 181 182 // Request a notification; should open a balloon. 183 EXPECT_TRUE(service_->ShowDesktopNotification( 184 params, 0, 0, DesktopNotificationService::PageNotification)); 185 base::MessageLoopForUI::current()->RunUntilIdle(); 186 EXPECT_EQ(1, balloon_collection_->count()); 187 188 // Close all the open balloons. 189 while (balloon_collection_->count() > 0) { 190 (*(balloon_collection_->GetActiveBalloons().begin()))->OnClose(true); 191 } 192 193 EXPECT_EQ("notification displayed\n" 194 "notification closed by user\n", 195 log_output_); 196 } 197 198 TEST_F(DesktopNotificationsTest, TestCancel) { 199 int process_id = 0; 200 int route_id = 0; 201 int notification_id = 1; 202 203 content::ShowDesktopNotificationHostMsgParams params = 204 StandardTestNotification(); 205 params.notification_id = notification_id; 206 207 // Request a notification; should open a balloon. 208 EXPECT_TRUE(service_->ShowDesktopNotification( 209 params, process_id, route_id, 210 DesktopNotificationService::PageNotification)); 211 base::MessageLoopForUI::current()->RunUntilIdle(); 212 EXPECT_EQ(1, balloon_collection_->count()); 213 214 // Cancel the same notification 215 service_->CancelDesktopNotification(process_id, 216 route_id, 217 notification_id); 218 base::MessageLoopForUI::current()->RunUntilIdle(); 219 // Verify that the balloon collection is now empty. 220 EXPECT_EQ(0, balloon_collection_->count()); 221 222 EXPECT_EQ("notification displayed\n" 223 "notification closed by script\n", 224 log_output_); 225 } 226 227 #if defined(OS_WIN) || defined(TOOLKIT_VIEWS) 228 TEST_F(DesktopNotificationsTest, TestPositioning) { 229 content::ShowDesktopNotificationHostMsgParams params = 230 StandardTestNotification(); 231 std::string expected_log; 232 // Create some toasts. After each but the first, make sure there 233 // is a minimum separation between the toasts. 234 int last_top = 0; 235 for (int id = 0; id <= 3; ++id) { 236 params.notification_id = id; 237 EXPECT_TRUE(service_->ShowDesktopNotification( 238 params, 0, 0, DesktopNotificationService::PageNotification)); 239 expected_log.append("notification displayed\n"); 240 int top = balloon_collection_->UppermostVerticalPosition(); 241 if (id > 0) 242 EXPECT_LE(top, last_top - balloon_collection_->MinHeight()); 243 last_top = top; 244 } 245 246 EXPECT_EQ(expected_log, log_output_); 247 } 248 249 TEST_F(DesktopNotificationsTest, TestVariableSize) { 250 content::ShowDesktopNotificationHostMsgParams params; 251 params.origin = GURL("http://long.google.com"); 252 params.is_html = false; 253 params.icon_url = GURL("/icon.png"); 254 params.title = ASCIIToUTF16("Really Really Really Really Really Really " 255 "Really Really Really Really Really Really " 256 "Really Really Really Really Really Really " 257 "Really Long Title"), 258 params.body = ASCIIToUTF16("Text"); 259 params.notification_id = 0; 260 261 std::string expected_log; 262 // Create some toasts. After each but the first, make sure there 263 // is a minimum separation between the toasts. 264 EXPECT_TRUE(service_->ShowDesktopNotification( 265 params, 0, 0, DesktopNotificationService::PageNotification)); 266 expected_log.append("notification displayed\n"); 267 268 params.origin = GURL("http://short.google.com"); 269 params.title = ASCIIToUTF16("Short title"); 270 params.notification_id = 1; 271 EXPECT_TRUE(service_->ShowDesktopNotification( 272 params, 0, 0, DesktopNotificationService::PageNotification)); 273 expected_log.append("notification displayed\n"); 274 275 std::deque<Balloon*>& balloons = balloon_collection_->balloons(); 276 std::deque<Balloon*>::iterator iter; 277 for (iter = balloons.begin(); iter != balloons.end(); ++iter) { 278 if ((*iter)->notification().origin_url().host() == "long.google.com") { 279 EXPECT_GE((*iter)->GetViewSize().height(), 280 balloon_collection_->MinHeight()); 281 EXPECT_LE((*iter)->GetViewSize().height(), 282 balloon_collection_->MaxHeight()); 283 } else { 284 EXPECT_EQ((*iter)->GetViewSize().height(), 285 balloon_collection_->MinHeight()); 286 } 287 } 288 EXPECT_EQ(expected_log, log_output_); 289 } 290 #endif 291 292 TEST_F(DesktopNotificationsTest, TestCancelByProfile) { 293 int process_id = 0; 294 int route_id = 0; 295 296 TestingBrowserProcess* browser_process = 297 TestingBrowserProcess::GetGlobal(); 298 TestingProfileManager profile_manager(browser_process); 299 ASSERT_TRUE(profile_manager.SetUp()); 300 301 TestingProfile* second_profile = 302 profile_manager.CreateTestingProfile("SecondTestingProfile"); 303 304 scoped_ptr<DesktopNotificationService> second_service( 305 new DesktopNotificationService(second_profile, ui_manager_.get())); 306 307 // Request lots of identical notifications. 308 content::ShowDesktopNotificationHostMsgParams params = 309 StandardTestNotification(); 310 params.notification_id = 1; 311 // Notice that the first one is the only one that doesn't use 312 // the second profile. 313 EXPECT_TRUE(service_->ShowDesktopNotification( 314 params, process_id, route_id, 315 DesktopNotificationService::PageNotification)); 316 317 // |kLotsOfToasts| must be large enough to trigger a resize of the underlying 318 // std::deque while we're clearing it. 319 const int kLotsOfToasts = 20; 320 for (int id = 2; id <= kLotsOfToasts; ++id) { 321 params.notification_id = id; 322 EXPECT_TRUE(second_service->ShowDesktopNotification( 323 params, process_id, route_id, 324 DesktopNotificationService::PageNotification)); 325 } 326 base::MessageLoopForUI::current()->RunUntilIdle(); 327 328 ui_manager_->CancelAllByProfile(second_profile); 329 330 // Verify that the balloon collection only contains the single 331 // notification from the first profile. 332 EXPECT_EQ(1, balloon_collection_->count()); 333 } 334 335 TEST_F(DesktopNotificationsTest, TestCancelBySourceOrigin) { 336 int process_id = 0; 337 int route_id = 0; 338 339 // Request lots of identical notifications. 340 content::ShowDesktopNotificationHostMsgParams params = 341 StandardTestNotification(); 342 343 // After the first, all the notifications are from attacker.com. 344 content::ShowDesktopNotificationHostMsgParams odd_params = 345 StandardTestNotification(); 346 odd_params.origin = GURL("attacker.com"); 347 348 // Show the only non-attacker.com notification. 349 params.notification_id = 1; 350 EXPECT_TRUE(service_->ShowDesktopNotification( 351 params, process_id, route_id, 352 DesktopNotificationService::PageNotification)); 353 354 // |kLotsOfToasts| must be large enough to trigger a resize of the underlying 355 // std::deque while we're clearing it. 356 const int kLotsOfToasts = 20; 357 for (int id = 2; id <= kLotsOfToasts; ++id) { 358 odd_params.notification_id = id; 359 EXPECT_TRUE(service_->ShowDesktopNotification( 360 odd_params, process_id, route_id, 361 DesktopNotificationService::PageNotification)); 362 } 363 base::MessageLoopForUI::current()->RunUntilIdle(); 364 365 ui_manager_->CancelAllBySourceOrigin(odd_params.origin); 366 367 // Verify that the balloon collection only contains the single 368 // notification which is not from the canceled origin. 369 EXPECT_EQ(1, balloon_collection_->count()); 370 } 371 372 TEST_F(DesktopNotificationsTest, TestQueueing) { 373 int process_id = 0; 374 int route_id = 0; 375 376 // Request lots of identical notifications. 377 content::ShowDesktopNotificationHostMsgParams params = 378 StandardTestNotification(); 379 const int kLotsOfToasts = 20; 380 for (int id = 1; id <= kLotsOfToasts; ++id) { 381 params.notification_id = id; 382 EXPECT_TRUE(service_->ShowDesktopNotification( 383 params, process_id, route_id, 384 DesktopNotificationService::PageNotification)); 385 } 386 base::MessageLoopForUI::current()->RunUntilIdle(); 387 388 // Build up an expected log of what should be happening. 389 std::string expected_log; 390 for (int i = 0; i < balloon_collection_->max_balloon_count(); ++i) { 391 expected_log.append("notification displayed\n"); 392 } 393 394 // The max number that our balloon collection can hold should be 395 // shown. 396 EXPECT_EQ(balloon_collection_->max_balloon_count(), 397 balloon_collection_->count()); 398 EXPECT_EQ(expected_log, log_output_); 399 400 // Cancel the notifications from the start; the balloon space should 401 // remain full. 402 { 403 int id; 404 for (id = 1; 405 id <= kLotsOfToasts - balloon_collection_->max_balloon_count(); 406 ++id) { 407 service_->CancelDesktopNotification(process_id, route_id, id); 408 base::MessageLoopForUI::current()->RunUntilIdle(); 409 expected_log.append("notification closed by script\n"); 410 expected_log.append("notification displayed\n"); 411 EXPECT_EQ(balloon_collection_->max_balloon_count(), 412 balloon_collection_->count()); 413 EXPECT_EQ(expected_log, log_output_); 414 } 415 416 // Now cancel the rest. It should empty the balloon space. 417 for (; id <= kLotsOfToasts; ++id) { 418 service_->CancelDesktopNotification(process_id, route_id, id); 419 expected_log.append("notification closed by script\n"); 420 base::MessageLoopForUI::current()->RunUntilIdle(); 421 EXPECT_EQ(expected_log, log_output_); 422 } 423 } 424 425 // Verify that the balloon collection is now empty. 426 EXPECT_EQ(0, balloon_collection_->count()); 427 } 428 429 TEST_F(DesktopNotificationsTest, TestEarlyDestruction) { 430 // Create some toasts and then prematurely delete the notification service, 431 // just to make sure nothing crashes/leaks. 432 content::ShowDesktopNotificationHostMsgParams params = 433 StandardTestNotification(); 434 for (int id = 0; id <= 3; ++id) { 435 params.notification_id = id; 436 EXPECT_TRUE(service_->ShowDesktopNotification( 437 params, 0, 0, DesktopNotificationService::PageNotification)); 438 } 439 service_.reset(NULL); 440 } 441 442 TEST_F(DesktopNotificationsTest, TestUserInputEscaping) { 443 // Create a test script with some HTML; assert that it doesn't get into the 444 // data:// URL that's produced for the balloon. 445 content::ShowDesktopNotificationHostMsgParams params = 446 StandardTestNotification(); 447 params.title = ASCIIToUTF16("<script>window.alert('uh oh');</script>"); 448 params.body = ASCIIToUTF16("<i>this text is in italics</i>"); 449 params.notification_id = 1; 450 EXPECT_TRUE(service_->ShowDesktopNotification( 451 params, 0, 0, DesktopNotificationService::PageNotification)); 452 453 base::MessageLoopForUI::current()->RunUntilIdle(); 454 EXPECT_EQ(1, balloon_collection_->count()); 455 Balloon* balloon = (*balloon_collection_->balloons().begin()); 456 GURL data_url = balloon->notification().content_url(); 457 EXPECT_EQ(std::string::npos, data_url.spec().find("<script>")); 458 EXPECT_EQ(std::string::npos, data_url.spec().find("<i>")); 459 // URL-encoded versions of tags should also not be found. 460 EXPECT_EQ(std::string::npos, data_url.spec().find("%3cscript%3e")); 461 EXPECT_EQ(std::string::npos, data_url.spec().find("%3ci%3e")); 462 } 463 464 TEST_F(DesktopNotificationsTest, TestBoundingBox) { 465 // Create some notifications. 466 content::ShowDesktopNotificationHostMsgParams params = 467 StandardTestNotification(); 468 for (int id = 0; id <= 3; ++id) { 469 params.notification_id = id; 470 EXPECT_TRUE(service_->ShowDesktopNotification( 471 params, 0, 0, DesktopNotificationService::PageNotification)); 472 } 473 474 gfx::Rect box = balloon_collection_->GetBalloonsBoundingBox(); 475 476 // Try this for all positions. 477 BalloonCollection::PositionPreference pref; 478 for (pref = BalloonCollection::UPPER_RIGHT; 479 pref <= BalloonCollection::LOWER_LEFT; 480 pref = static_cast<BalloonCollection::PositionPreference>(pref + 1)) { 481 // Make sure each balloon's 4 corners are inside the box. 482 std::deque<Balloon*>& balloons = balloon_collection_->balloons(); 483 std::deque<Balloon*>::iterator iter; 484 for (iter = balloons.begin(); iter != balloons.end(); ++iter) { 485 int min_x = (*iter)->GetPosition().x(); 486 int max_x = min_x + (*iter)->GetViewSize().width() - 1; 487 int min_y = (*iter)->GetPosition().y(); 488 int max_y = min_y + (*iter)->GetViewSize().height() - 1; 489 490 EXPECT_TRUE(box.Contains(gfx::Point(min_x, min_y))); 491 EXPECT_TRUE(box.Contains(gfx::Point(min_x, max_y))); 492 EXPECT_TRUE(box.Contains(gfx::Point(max_x, min_y))); 493 EXPECT_TRUE(box.Contains(gfx::Point(max_x, max_y))); 494 } 495 } 496 } 497 498 TEST_F(DesktopNotificationsTest, TestPositionPreference) { 499 // Set position preference to lower right. 500 local_state_.SetInteger(prefs::kDesktopNotificationPosition, 501 BalloonCollection::LOWER_RIGHT); 502 503 // Create some notifications. 504 content::ShowDesktopNotificationHostMsgParams params = 505 StandardTestNotification(); 506 for (int id = 0; id <= 3; ++id) { 507 params.notification_id = id; 508 EXPECT_TRUE(service_->ShowDesktopNotification( 509 params, 0, 0, DesktopNotificationService::PageNotification)); 510 } 511 512 std::deque<Balloon*>& balloons = balloon_collection_->balloons(); 513 std::deque<Balloon*>::iterator iter; 514 515 // Check that they decrease in y-position (for MAC, with reversed 516 // coordinates, they should increase). 517 int last_y = -1; 518 int last_x = -1; 519 520 for (iter = balloons.begin(); iter != balloons.end(); ++iter) { 521 int current_x = (*iter)->GetPosition().x(); 522 int current_y = (*iter)->GetPosition().y(); 523 if (last_x > 0) 524 EXPECT_EQ(last_x, current_x); 525 526 if (last_y > 0) { 527 #if defined(OS_MACOSX) 528 EXPECT_GT(current_y, last_y); 529 #else 530 EXPECT_LT(current_y, last_y); 531 #endif 532 } 533 534 last_x = current_x; 535 last_y = current_y; 536 } 537 538 // Now change the position to upper right. This should cause an immediate 539 // repositioning, and we check for the reverse ordering. 540 local_state_.SetInteger(prefs::kDesktopNotificationPosition, 541 BalloonCollection::UPPER_RIGHT); 542 last_x = -1; 543 last_y = -1; 544 545 for (iter = balloons.begin(); iter != balloons.end(); ++iter) { 546 int current_x = (*iter)->GetPosition().x(); 547 int current_y = (*iter)->GetPosition().y(); 548 549 if (last_x > 0) 550 EXPECT_EQ(last_x, current_x); 551 552 if (last_y > 0) { 553 #if defined(OS_MACOSX) 554 EXPECT_LT(current_y, last_y); 555 #else 556 EXPECT_GT(current_y, last_y); 557 #endif 558 } 559 560 last_x = current_x; 561 last_y = current_y; 562 } 563 564 // Now change the position to upper left. Confirm that the X value for the 565 // balloons gets smaller. 566 local_state_.SetInteger(prefs::kDesktopNotificationPosition, 567 BalloonCollection::UPPER_LEFT); 568 569 int current_x = (*balloons.begin())->GetPosition().x(); 570 EXPECT_LT(current_x, last_x); 571 } 572