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 "ash/system/web_notification/web_notification_tray.h" 6 7 #include <vector> 8 9 #include "ash/display/display_manager.h" 10 #include "ash/root_window_controller.h" 11 #include "ash/shelf/shelf_layout_manager.h" 12 #include "ash/shelf/shelf_widget.h" 13 #include "ash/shell.h" 14 #include "ash/system/status_area_widget.h" 15 #include "ash/system/tray/system_tray.h" 16 #include "ash/system/tray/system_tray_item.h" 17 #include "ash/test/ash_test_base.h" 18 #include "ash/test/status_area_widget_test_helper.h" 19 #include "ash/test/test_system_tray_delegate.h" 20 #include "ash/wm/window_state.h" 21 #include "base/strings/stringprintf.h" 22 #include "base/strings/utf_string_conversions.h" 23 #include "ui/aura/client/aura_constants.h" 24 #include "ui/aura/test/event_generator.h" 25 #include "ui/aura/window.h" 26 #include "ui/gfx/display.h" 27 #include "ui/gfx/screen.h" 28 #include "ui/message_center/message_center_style.h" 29 #include "ui/message_center/message_center_tray.h" 30 #include "ui/message_center/notification_list.h" 31 #include "ui/message_center/notification_types.h" 32 #include "ui/message_center/views/message_center_bubble.h" 33 #include "ui/message_center/views/message_popup_collection.h" 34 #include "ui/views/controls/label.h" 35 #include "ui/views/layout/fill_layout.h" 36 #include "ui/views/view.h" 37 #include "ui/views/widget/widget.h" 38 39 namespace ash { 40 41 namespace { 42 43 WebNotificationTray* GetTray() { 44 return StatusAreaWidgetTestHelper::GetStatusAreaWidget()-> 45 web_notification_tray(); 46 } 47 48 WebNotificationTray* GetSecondaryTray() { 49 StatusAreaWidget* status_area_widget = 50 StatusAreaWidgetTestHelper::GetSecondaryStatusAreaWidget(); 51 if (status_area_widget) 52 return status_area_widget->web_notification_tray(); 53 return NULL; 54 } 55 56 message_center::MessageCenter* GetMessageCenter() { 57 return GetTray()->message_center(); 58 } 59 60 SystemTray* GetSystemTray() { 61 return StatusAreaWidgetTestHelper::GetStatusAreaWidget()->system_tray(); 62 } 63 64 // Trivial item implementation for testing PopupAndSystemTray test case. 65 class TestItem : public SystemTrayItem { 66 public: 67 TestItem() : SystemTrayItem(GetSystemTray()) {} 68 69 virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE { 70 views::View* default_view = new views::View; 71 default_view->SetLayoutManager(new views::FillLayout); 72 default_view->AddChildView(new views::Label(base::UTF8ToUTF16("Default"))); 73 return default_view; 74 } 75 76 virtual views::View* CreateNotificationView( 77 user::LoginStatus status) OVERRIDE { 78 return new views::View; 79 } 80 81 private: 82 DISALLOW_COPY_AND_ASSIGN(TestItem); 83 }; 84 85 } // namespace 86 87 class WebNotificationTrayTest : public test::AshTestBase { 88 public: 89 WebNotificationTrayTest() {} 90 virtual ~WebNotificationTrayTest() {} 91 92 virtual void TearDown() OVERRIDE { 93 GetMessageCenter()->RemoveAllNotifications(false); 94 test::AshTestBase::TearDown(); 95 } 96 97 protected: 98 void AddNotification(const std::string& id) { 99 scoped_ptr<message_center::Notification> notification; 100 notification.reset(new message_center::Notification( 101 message_center::NOTIFICATION_TYPE_SIMPLE, 102 id, 103 base::ASCIIToUTF16("Test Web Notification"), 104 base::ASCIIToUTF16("Notification message body."), 105 gfx::Image(), 106 base::ASCIIToUTF16("www.test.org"), 107 message_center::NotifierId(), 108 message_center::RichNotificationData(), 109 NULL /* delegate */)); 110 GetMessageCenter()->AddNotification(notification.Pass()); 111 } 112 113 void UpdateNotification(const std::string& old_id, 114 const std::string& new_id) { 115 scoped_ptr<message_center::Notification> notification; 116 notification.reset(new message_center::Notification( 117 message_center::NOTIFICATION_TYPE_SIMPLE, 118 new_id, 119 base::ASCIIToUTF16("Updated Web Notification"), 120 base::ASCIIToUTF16("Updated message body."), 121 gfx::Image(), 122 base::ASCIIToUTF16("www.test.org"), 123 message_center::NotifierId(), 124 message_center::RichNotificationData(), 125 NULL /* delegate */)); 126 GetMessageCenter()->UpdateNotification(old_id, notification.Pass()); 127 } 128 129 void RemoveNotification(const std::string& id) { 130 GetMessageCenter()->RemoveNotification(id, false); 131 } 132 133 views::Widget* GetWidget() { 134 return GetTray()->GetWidget(); 135 } 136 137 gfx::Rect GetPopupWorkArea() { 138 return GetPopupWorkAreaForTray(GetTray()); 139 } 140 141 gfx::Rect GetPopupWorkAreaForTray(WebNotificationTray* tray) { 142 return tray->popup_collection_->work_area_; 143 } 144 145 bool IsPopupVisible() { 146 return GetTray()->IsPopupVisible(); 147 } 148 149 private: 150 DISALLOW_COPY_AND_ASSIGN(WebNotificationTrayTest); 151 }; 152 153 TEST_F(WebNotificationTrayTest, WebNotifications) { 154 // TODO(mukai): move this test case to ui/message_center. 155 ASSERT_TRUE(GetWidget()); 156 157 // Add a notification. 158 AddNotification("test_id1"); 159 EXPECT_EQ(1u, GetMessageCenter()->NotificationCount()); 160 EXPECT_TRUE(GetMessageCenter()->FindVisibleNotificationById("test_id1")); 161 AddNotification("test_id2"); 162 AddNotification("test_id2"); 163 EXPECT_EQ(2u, GetMessageCenter()->NotificationCount()); 164 EXPECT_TRUE(GetMessageCenter()->FindVisibleNotificationById("test_id2")); 165 166 // Ensure that updating a notification does not affect the count. 167 UpdateNotification("test_id2", "test_id3"); 168 UpdateNotification("test_id3", "test_id3"); 169 EXPECT_EQ(2u, GetMessageCenter()->NotificationCount()); 170 EXPECT_FALSE(GetMessageCenter()->FindVisibleNotificationById("test_id2")); 171 172 // Ensure that Removing the first notification removes it from the tray. 173 RemoveNotification("test_id1"); 174 EXPECT_FALSE(GetMessageCenter()->FindVisibleNotificationById("test_id1")); 175 EXPECT_EQ(1u, GetMessageCenter()->NotificationCount()); 176 177 // Remove the remianing notification. 178 RemoveNotification("test_id3"); 179 EXPECT_EQ(0u, GetMessageCenter()->NotificationCount()); 180 EXPECT_FALSE(GetMessageCenter()->FindVisibleNotificationById("test_id3")); 181 } 182 183 TEST_F(WebNotificationTrayTest, WebNotificationPopupBubble) { 184 // TODO(mukai): move this test case to ui/message_center. 185 ASSERT_TRUE(GetWidget()); 186 187 // Adding a notification should show the popup bubble. 188 AddNotification("test_id1"); 189 EXPECT_TRUE(GetTray()->IsPopupVisible()); 190 191 // Updating a notification should not hide the popup bubble. 192 AddNotification("test_id2"); 193 UpdateNotification("test_id2", "test_id3"); 194 EXPECT_TRUE(GetTray()->IsPopupVisible()); 195 196 // Removing the first notification should not hide the popup bubble. 197 RemoveNotification("test_id1"); 198 EXPECT_TRUE(GetTray()->IsPopupVisible()); 199 200 // Removing the visible notification should hide the popup bubble. 201 RemoveNotification("test_id3"); 202 EXPECT_FALSE(GetTray()->IsPopupVisible()); 203 204 // Now test that we can show multiple popups and then show the message center. 205 AddNotification("test_id4"); 206 AddNotification("test_id5"); 207 EXPECT_TRUE(GetTray()->IsPopupVisible()); 208 209 GetTray()->message_center_tray_->ShowMessageCenterBubble(); 210 GetTray()->message_center_tray_->HideMessageCenterBubble(); 211 212 EXPECT_FALSE(GetTray()->IsPopupVisible()); 213 } 214 215 using message_center::NotificationList; 216 217 218 // Flakily fails. http://crbug.com/229791 219 TEST_F(WebNotificationTrayTest, DISABLED_ManyMessageCenterNotifications) { 220 // Add the max visible notifications +1, ensure the correct visible number. 221 size_t notifications_to_add = 222 message_center::kMaxVisibleMessageCenterNotifications + 1; 223 for (size_t i = 0; i < notifications_to_add; ++i) { 224 std::string id = base::StringPrintf("test_id%d", static_cast<int>(i)); 225 AddNotification(id); 226 } 227 bool shown = GetTray()->message_center_tray_->ShowMessageCenterBubble(); 228 EXPECT_TRUE(shown); 229 RunAllPendingInMessageLoop(); 230 EXPECT_TRUE(GetTray()->message_center_bubble() != NULL); 231 EXPECT_EQ(notifications_to_add, 232 GetMessageCenter()->NotificationCount()); 233 EXPECT_EQ(message_center::kMaxVisibleMessageCenterNotifications, 234 GetTray()->GetMessageCenterBubbleForTest()-> 235 NumMessageViewsForTest()); 236 } 237 238 // Flakily times out. http://crbug.com/229792 239 TEST_F(WebNotificationTrayTest, DISABLED_ManyPopupNotifications) { 240 // Add the max visible popup notifications +1, ensure the correct num visible. 241 size_t notifications_to_add = 242 message_center::kMaxVisiblePopupNotifications + 1; 243 for (size_t i = 0; i < notifications_to_add; ++i) { 244 std::string id = base::StringPrintf("test_id%d", static_cast<int>(i)); 245 AddNotification(id); 246 } 247 GetTray()->ShowPopups(); 248 EXPECT_TRUE(GetTray()->IsPopupVisible()); 249 EXPECT_EQ(notifications_to_add, 250 GetMessageCenter()->NotificationCount()); 251 NotificationList::PopupNotifications popups = 252 GetMessageCenter()->GetPopupNotifications(); 253 EXPECT_EQ(message_center::kMaxVisiblePopupNotifications, popups.size()); 254 } 255 256 #if defined(OS_CHROMEOS) 257 // Display notification is ChromeOS only. 258 #define MAYBE_PopupShownOnBothDisplays PopupShownOnBothDisplays 259 #define MAYBE_PopupAndSystemTrayMultiDisplay PopupAndSystemTrayMultiDisplay 260 #else 261 #define MAYBE_PopupShownOnBothDisplays DISABLED_PopupShownOnBothDisplays 262 #define MAYBE_PopupAndSystemTrayMultiDisplay \ 263 DISABLED_PopupAndSystemTrayMultiDisplay 264 #endif 265 266 // Verifies if the notification appears on both displays when extended mode. 267 TEST_F(WebNotificationTrayTest, MAYBE_PopupShownOnBothDisplays) { 268 if (!SupportsMultipleDisplays()) 269 return; 270 271 // Enables to appear the notification for display changes. 272 test::TestSystemTrayDelegate* tray_delegate = 273 static_cast<test::TestSystemTrayDelegate*>( 274 Shell::GetInstance()->system_tray_delegate()); 275 tray_delegate->set_should_show_display_notification(true); 276 277 UpdateDisplay("400x400,200x200"); 278 // UpdateDisplay() creates the display notifications, so popup is visible. 279 EXPECT_TRUE(GetTray()->IsPopupVisible()); 280 WebNotificationTray* secondary_tray = GetSecondaryTray(); 281 ASSERT_TRUE(secondary_tray); 282 EXPECT_TRUE(secondary_tray->IsPopupVisible()); 283 284 // Transition to mirroring and then back to extended display, which recreates 285 // root window controller and shelf with having notifications. This code 286 // verifies it doesn't cause crash and popups are still visible. See 287 // http://crbug.com/263664 288 DisplayManager* display_manager = Shell::GetInstance()->display_manager(); 289 290 display_manager->SetSecondDisplayMode(DisplayManager::MIRRORING); 291 UpdateDisplay("400x400,200x200"); 292 EXPECT_TRUE(GetTray()->IsPopupVisible()); 293 EXPECT_FALSE(GetSecondaryTray()); 294 295 display_manager->SetSecondDisplayMode(DisplayManager::EXTENDED); 296 UpdateDisplay("400x400,200x200"); 297 EXPECT_TRUE(GetTray()->IsPopupVisible()); 298 secondary_tray = GetSecondaryTray(); 299 ASSERT_TRUE(secondary_tray); 300 EXPECT_TRUE(secondary_tray->IsPopupVisible()); 301 } 302 303 #if defined(OS_CHROMEOS) 304 // PopupAndSystemTray may fail in platforms other than ChromeOS because the 305 // RootWindow's bound can be bigger than gfx::Display's work area so that 306 // openingsystem tray doesn't affect at all the work area of popups. 307 #define MAYBE_PopupAndSystemTray PopupAndSystemTray 308 #define MAYBE_PopupAndAutoHideShelf PopupAndAutoHideShelf 309 #define MAYBE_PopupAndFullscreen PopupAndFullscreen 310 #else 311 #define MAYBE_PopupAndSystemTray DISABLED_PopupAndSystemTray 312 #define MAYBE_PopupAndAutoHideShelf DISABLED_PopupAndAutoHideShelf 313 #define MAYBE_PopupAndFullscreen DISABLED_PopupAndFullscreen 314 #endif 315 316 TEST_F(WebNotificationTrayTest, MAYBE_PopupAndSystemTray) { 317 TestItem* test_item = new TestItem; 318 GetSystemTray()->AddTrayItem(test_item); 319 320 AddNotification("test_id"); 321 EXPECT_TRUE(GetTray()->IsPopupVisible()); 322 gfx::Rect work_area = GetPopupWorkArea(); 323 324 // System tray is created, the popup's work area should be narrowed but still 325 // visible. 326 GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW); 327 EXPECT_TRUE(GetTray()->IsPopupVisible()); 328 gfx::Rect work_area_with_tray = GetPopupWorkArea(); 329 EXPECT_GT(work_area.size().GetArea(), work_area_with_tray.size().GetArea()); 330 331 // System tray notification is also created, the popup's work area is narrowed 332 // even more, but still visible. 333 GetSystemTray()->ShowNotificationView(test_item); 334 EXPECT_TRUE(GetTray()->IsPopupVisible()); 335 gfx::Rect work_area_with_tray_notification = GetPopupWorkArea(); 336 EXPECT_GT(work_area.size().GetArea(), 337 work_area_with_tray_notification.size().GetArea()); 338 EXPECT_GT(work_area_with_tray.size().GetArea(), 339 work_area_with_tray_notification.size().GetArea()); 340 341 // Close system tray, only system tray notifications. 342 GetSystemTray()->ClickedOutsideBubble(); 343 EXPECT_TRUE(GetTray()->IsPopupVisible()); 344 gfx::Rect work_area_with_notification = GetPopupWorkArea(); 345 EXPECT_GT(work_area.size().GetArea(), 346 work_area_with_notification.size().GetArea()); 347 EXPECT_LT(work_area_with_tray_notification.size().GetArea(), 348 work_area_with_notification.size().GetArea()); 349 350 // Close the system tray notifications. 351 GetSystemTray()->HideNotificationView(test_item); 352 EXPECT_TRUE(GetTray()->IsPopupVisible()); 353 EXPECT_EQ(work_area.ToString(), GetPopupWorkArea().ToString()); 354 } 355 356 TEST_F(WebNotificationTrayTest, MAYBE_PopupAndAutoHideShelf) { 357 AddNotification("test_id"); 358 EXPECT_TRUE(GetTray()->IsPopupVisible()); 359 gfx::Rect work_area = GetPopupWorkArea(); 360 361 // Shelf's auto-hide state won't be HIDDEN unless window exists. 362 scoped_ptr<aura::Window> window( 363 CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4))); 364 ShelfLayoutManager* shelf = 365 Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager(); 366 shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS); 367 368 EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); 369 gfx::Rect work_area_auto_hidden = GetPopupWorkArea(); 370 EXPECT_LT(work_area.size().GetArea(), work_area_auto_hidden.size().GetArea()); 371 372 // Close the window, which shows the shelf. 373 window.reset(); 374 EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state()); 375 gfx::Rect work_area_auto_shown = GetPopupWorkArea(); 376 EXPECT_EQ(work_area.ToString(), work_area_auto_shown.ToString()); 377 378 // Create the system tray during auto-hide. 379 window.reset(CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4))); 380 TestItem* test_item = new TestItem; 381 GetSystemTray()->AddTrayItem(test_item); 382 GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW); 383 384 EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state()); 385 EXPECT_TRUE(GetTray()->IsPopupVisible()); 386 gfx::Rect work_area_with_tray = GetPopupWorkArea(); 387 EXPECT_GT(work_area_auto_shown.size().GetArea(), 388 work_area_with_tray.size().GetArea()); 389 390 // Create tray notification. 391 GetSystemTray()->ShowNotificationView(test_item); 392 EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state()); 393 gfx::Rect work_area_with_tray_notification = GetPopupWorkArea(); 394 EXPECT_GT(work_area_with_tray.size().GetArea(), 395 work_area_with_tray_notification.size().GetArea()); 396 397 // Close the system tray. 398 GetSystemTray()->ClickedOutsideBubble(); 399 shelf->UpdateAutoHideState(); 400 RunAllPendingInMessageLoop(); 401 EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); 402 gfx::Rect work_area_hidden_with_tray_notification = GetPopupWorkArea(); 403 EXPECT_LT(work_area_with_tray_notification.size().GetArea(), 404 work_area_hidden_with_tray_notification.size().GetArea()); 405 EXPECT_GT(work_area_auto_hidden.size().GetArea(), 406 work_area_hidden_with_tray_notification.size().GetArea()); 407 408 // Close the window again, which shows the shelf. 409 window.reset(); 410 EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state()); 411 gfx::Rect work_area_shown_with_tray_notification = GetPopupWorkArea(); 412 EXPECT_GT(work_area_hidden_with_tray_notification.size().GetArea(), 413 work_area_shown_with_tray_notification.size().GetArea()); 414 EXPECT_GT(work_area_auto_shown.size().GetArea(), 415 work_area_shown_with_tray_notification.size().GetArea()); 416 } 417 418 TEST_F(WebNotificationTrayTest, MAYBE_PopupAndFullscreen) { 419 AddNotification("test_id"); 420 EXPECT_TRUE(IsPopupVisible()); 421 gfx::Rect work_area = GetPopupWorkArea(); 422 423 // Checks the work area for normal auto-hidden state. 424 scoped_ptr<aura::Window> window( 425 CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4))); 426 ShelfLayoutManager* shelf = 427 Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager(); 428 shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS); 429 EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); 430 gfx::Rect work_area_auto_hidden = GetPopupWorkArea(); 431 shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER); 432 433 // Put |window| into fullscreen without forcing the shelf to hide. Currently, 434 // this is used by immersive fullscreen and forces the shelf to be auto 435 // hidden. 436 wm::GetWindowState(window.get())->set_hide_shelf_when_fullscreen(false); 437 window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); 438 RunAllPendingInMessageLoop(); 439 440 // The work area for auto-hidden status of fullscreen is a bit larger 441 // since it doesn't even have the 3-pixel width. 442 EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); 443 gfx::Rect work_area_fullscreen_hidden = GetPopupWorkArea(); 444 EXPECT_EQ(work_area_auto_hidden.ToString(), 445 work_area_fullscreen_hidden.ToString()); 446 447 // Move the mouse cursor at the bottom, which shows the shelf. 448 aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow()); 449 gfx::Point bottom_right = 450 Shell::GetScreen()->GetPrimaryDisplay().bounds().bottom_right(); 451 bottom_right.Offset(-1, -1); 452 generator.MoveMouseTo(bottom_right); 453 shelf->UpdateAutoHideStateNow(); 454 EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state()); 455 EXPECT_EQ(work_area.ToString(), GetPopupWorkArea().ToString()); 456 457 generator.MoveMouseTo(work_area.CenterPoint()); 458 shelf->UpdateAutoHideStateNow(); 459 EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); 460 EXPECT_EQ(work_area_auto_hidden.ToString(), GetPopupWorkArea().ToString()); 461 } 462 463 TEST_F(WebNotificationTrayTest, MAYBE_PopupAndSystemTrayMultiDisplay) { 464 UpdateDisplay("800x600,600x400"); 465 466 AddNotification("test_id"); 467 gfx::Rect work_area = GetPopupWorkArea(); 468 gfx::Rect work_area_second = GetPopupWorkAreaForTray(GetSecondaryTray()); 469 470 // System tray is created on the primary display. The popups in the secondary 471 // tray aren't affected. 472 GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW); 473 EXPECT_GT(work_area.size().GetArea(), GetPopupWorkArea().size().GetArea()); 474 EXPECT_EQ(work_area_second.ToString(), 475 GetPopupWorkAreaForTray(GetSecondaryTray()).ToString()); 476 } 477 478 } // namespace ash 479