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/chromeos/notifications/desktop_notifications_unittest.h" 6 7 #include "base/stringprintf.h" 8 #include "base/utf_string_conversions.h" 9 #include "chrome/browser/prefs/browser_prefs.h" 10 #include "chrome/browser/prefs/pref_service.h" 11 #include "content/common/desktop_notification_messages.h" 12 13 namespace chromeos { 14 15 // static 16 std::string DesktopNotificationsTest::log_output_; 17 18 class MockNotificationUI : public BalloonCollectionImpl::NotificationUI { 19 public: 20 virtual void Add(Balloon* balloon) {} 21 virtual bool Update(Balloon* balloon) { return false; } 22 virtual void Remove(Balloon* balloon) {} 23 virtual void Show(Balloon* balloon) {} 24 virtual void ResizeNotification(Balloon* balloon, 25 const gfx::Size& size) {} 26 virtual void SetActiveView(BalloonViewImpl* view) {} 27 }; 28 29 MockBalloonCollection::MockBalloonCollection() { 30 set_notification_ui(new MockNotificationUI()); 31 } 32 33 void MockBalloonCollection::Add(const Notification& notification, 34 Profile* profile) { 35 // Swap in a logging proxy for the purpose of logging calls that 36 // would be made into javascript, then pass this down to the 37 // balloon collection. 38 Notification test_notification( 39 notification.origin_url(), 40 notification.content_url(), 41 notification.display_source(), 42 notification.replace_id(), 43 new LoggingNotificationProxy(notification.notification_id())); 44 BalloonCollectionImpl::Add(test_notification, profile); 45 } 46 47 Balloon* MockBalloonCollection::MakeBalloon(const Notification& notification, 48 Profile* profile) { 49 // Start with a normal balloon but mock out the view. 50 Balloon* balloon = BalloonCollectionImpl::MakeBalloon(notification, profile); 51 balloon->set_view(new MockBalloonView(balloon)); 52 balloons_.insert(balloon); 53 return balloon; 54 } 55 56 void MockBalloonCollection::OnBalloonClosed(Balloon* source) { 57 balloons_.erase(source); 58 BalloonCollectionImpl::OnBalloonClosed(source); 59 } 60 61 int MockBalloonCollection::UppermostVerticalPosition() { 62 int min = 0; 63 std::set<Balloon*>::iterator iter; 64 for (iter = balloons_.begin(); iter != balloons_.end(); ++iter) { 65 int pos = (*iter)->GetPosition().y(); 66 if (iter == balloons_.begin() || pos < min) 67 min = pos; 68 } 69 return min; 70 } 71 72 DesktopNotificationsTest::DesktopNotificationsTest() 73 : ui_thread_(BrowserThread::UI, &message_loop_) { 74 } 75 76 DesktopNotificationsTest::~DesktopNotificationsTest() { 77 } 78 79 void DesktopNotificationsTest::SetUp() { 80 browser::RegisterLocalState(&local_state_); 81 profile_.reset(new TestingProfile()); 82 balloon_collection_ = new MockBalloonCollection(); 83 ui_manager_.reset(new NotificationUIManager(&local_state_)); 84 ui_manager_->Initialize(balloon_collection_); 85 balloon_collection_->set_space_change_listener(ui_manager_.get()); 86 service_.reset(new DesktopNotificationService(profile(), ui_manager_.get())); 87 log_output_.clear(); 88 } 89 90 void DesktopNotificationsTest::TearDown() { 91 service_.reset(NULL); 92 ui_manager_.reset(NULL); 93 profile_.reset(NULL); 94 } 95 96 DesktopNotificationHostMsg_Show_Params 97 DesktopNotificationsTest::StandardTestNotification() { 98 DesktopNotificationHostMsg_Show_Params params; 99 params.notification_id = 0; 100 params.origin = GURL("http://www.google.com"); 101 params.is_html = false; 102 params.icon_url = GURL("/icon.png"); 103 params.title = ASCIIToUTF16("Title"); 104 params.body = ASCIIToUTF16("Text"); 105 params.direction = WebKit::WebTextDirectionDefault; 106 return params; 107 } 108 109 TEST_F(DesktopNotificationsTest, TestShow) { 110 DesktopNotificationHostMsg_Show_Params params = StandardTestNotification(); 111 params.notification_id = 1; 112 EXPECT_TRUE(service_->ShowDesktopNotification( 113 params, 0, 0, DesktopNotificationService::PageNotification)); 114 115 MessageLoopForUI::current()->RunAllPending(); 116 EXPECT_EQ(1, balloon_collection_->count()); 117 118 DesktopNotificationHostMsg_Show_Params params2; 119 params2.origin = GURL("http://www.google.com"); 120 params2.is_html = true; 121 params2.contents_url = GURL("http://www.google.com/notification.html"); 122 params2.notification_id = 2; 123 124 EXPECT_TRUE(service_->ShowDesktopNotification( 125 params2, 0, 0, DesktopNotificationService::PageNotification)); 126 MessageLoopForUI::current()->RunAllPending(); 127 EXPECT_EQ(2, balloon_collection_->count()); 128 129 EXPECT_EQ("notification displayed\n" 130 "notification displayed\n", 131 log_output_); 132 } 133 134 TEST_F(DesktopNotificationsTest, TestClose) { 135 DesktopNotificationHostMsg_Show_Params params = StandardTestNotification(); 136 params.notification_id = 1; 137 138 // Request a notification; should open a balloon. 139 EXPECT_TRUE(service_->ShowDesktopNotification( 140 params, 0, 0, DesktopNotificationService::PageNotification)); 141 MessageLoopForUI::current()->RunAllPending(); 142 EXPECT_EQ(1, balloon_collection_->count()); 143 144 // Close all the open balloons. 145 std::set<Balloon*> balloons = balloon_collection_->balloons(); 146 std::set<Balloon*>::iterator iter; 147 for (iter = balloons.begin(); iter != balloons.end(); ++iter) { 148 (*iter)->OnClose(true); 149 } 150 151 // Verify that the balloon collection is now empty. 152 EXPECT_EQ(0, balloon_collection_->count()); 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 TEST_F(DesktopNotificationsTest, TestManyNotifications) { 188 int process_id = 0; 189 int route_id = 0; 190 191 // Request lots of identical notifications. 192 const int kLotsOfToasts = 20; 193 for (int id = 1; id <= kLotsOfToasts; ++id) { 194 SCOPED_TRACE(base::StringPrintf("Creation loop: id=%d", id)); 195 DesktopNotificationHostMsg_Show_Params params = StandardTestNotification(); 196 params.notification_id = id; 197 EXPECT_TRUE(service_->ShowDesktopNotification( 198 params, process_id, route_id, 199 DesktopNotificationService::PageNotification)); 200 } 201 MessageLoopForUI::current()->RunAllPending(); 202 203 // Build up an expected log of what should be happening. 204 std::string expected_log; 205 for (int i = 0; i < kLotsOfToasts; ++i) { 206 expected_log.append("notification displayed\n"); 207 } 208 209 EXPECT_EQ(kLotsOfToasts, balloon_collection_->count()); 210 EXPECT_EQ(expected_log, log_output_); 211 212 // Cancel half of the notifications from the start 213 int id; 214 int cancelled = kLotsOfToasts / 2; 215 for (id = 1; 216 id <= cancelled; 217 ++id) { 218 SCOPED_TRACE(base::StringPrintf("Cancel half of notifications: id=%d", id)); 219 service_->CancelDesktopNotification(process_id, route_id, id); 220 MessageLoopForUI::current()->RunAllPending(); 221 expected_log.append("notification closed by script\n"); 222 EXPECT_EQ(kLotsOfToasts - id, 223 balloon_collection_->count()); 224 EXPECT_EQ(expected_log, log_output_); 225 } 226 227 // Now cancel the rest. It should empty the balloon space. 228 for (; id <= kLotsOfToasts; ++id) { 229 SCOPED_TRACE(base::StringPrintf("Cancel loop: id=%d", id)); 230 service_->CancelDesktopNotification(process_id, route_id, id); 231 expected_log.append("notification closed by script\n"); 232 MessageLoopForUI::current()->RunAllPending(); 233 EXPECT_EQ(expected_log, log_output_); 234 } 235 236 // Verify that the balloon collection is now empty. 237 EXPECT_EQ(0, balloon_collection_->count()); 238 } 239 240 TEST_F(DesktopNotificationsTest, TestEarlyDestruction) { 241 // Create some toasts and then prematurely delete the notification service, 242 // just to make sure nothing crashes/leaks. 243 for (int id = 0; id <= 3; ++id) { 244 SCOPED_TRACE(base::StringPrintf("Show Text loop: id=%d", id)); 245 246 EXPECT_TRUE(service_->ShowDesktopNotification( 247 StandardTestNotification(), 0, 0, 248 DesktopNotificationService::PageNotification)); 249 } 250 service_.reset(NULL); 251 } 252 253 TEST_F(DesktopNotificationsTest, TestUserInputEscaping) { 254 // Create a test script with some HTML; assert that it doesn't get into the 255 // data:// URL that's produced for the balloon. 256 DesktopNotificationHostMsg_Show_Params params = StandardTestNotification(); 257 params.title = ASCIIToUTF16("<script>window.alert('uh oh');</script>"); 258 params.body = ASCIIToUTF16("<i>this text is in italics</i>"); 259 params.notification_id = 1; 260 EXPECT_TRUE(service_->ShowDesktopNotification( 261 params, 0, 0, DesktopNotificationService::PageNotification)); 262 263 MessageLoopForUI::current()->RunAllPending(); 264 EXPECT_EQ(1, balloon_collection_->count()); 265 Balloon* balloon = (*balloon_collection_->balloons().begin()); 266 GURL data_url = balloon->notification().content_url(); 267 EXPECT_EQ(std::string::npos, data_url.spec().find("<script>")); 268 EXPECT_EQ(std::string::npos, data_url.spec().find("<i>")); 269 } 270 271 } // namespace chromeos 272