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.
5 #include "chrome/browser/notifications/desktop_notifications_unittest.h"
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"
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 #include "ui/compositor/test/context_factories_for_test.h"
31 #include "ui/views/corewm/wm_state.h"
35 using content::BrowserThread
;
38 const int MockBalloonCollection::kMockBalloonSpace
= 5;
41 std::string
DesktopNotificationsTest::log_output_
;
43 MockBalloonCollection::MockBalloonCollection() {}
45 MockBalloonCollection::~MockBalloonCollection() {}
47 void MockBalloonCollection::Add(const Notification
& notification
,
49 // Swap in a logging proxy for the purpose of logging calls that
50 // would be made into javascript, then pass this down to the
51 // balloon collection.
52 Notification
test_notification(
53 notification
.origin_url(),
54 notification
.content_url(),
55 notification
.display_source(),
56 notification
.replace_id(),
57 new LoggingNotificationProxy(notification
.notification_id()));
58 BalloonCollectionImpl::Add(test_notification
, profile
);
61 bool MockBalloonCollection::HasSpace() const {
62 return count() < kMockBalloonSpace
;
65 Balloon
* MockBalloonCollection::MakeBalloon(const Notification
& notification
,
67 // Start with a normal balloon but mock out the view.
68 Balloon
* balloon
= BalloonCollectionImpl::MakeBalloon(notification
, profile
);
69 balloon
->set_view(new FakeBalloonView(balloon
));
70 balloons_
.push_back(balloon
);
74 void MockBalloonCollection::OnBalloonClosed(Balloon
* source
) {
75 std::deque
<Balloon
*>::iterator it
;
76 for (it
= balloons_
.begin(); it
!= balloons_
.end(); ++it
) {
79 BalloonCollectionImpl::OnBalloonClosed(source
);
85 const BalloonCollection::Balloons
& MockBalloonCollection::GetActiveBalloons() {
89 int MockBalloonCollection::UppermostVerticalPosition() {
91 std::deque
<Balloon
*>::iterator iter
;
92 for (iter
= balloons_
.begin(); iter
!= balloons_
.end(); ++iter
) {
93 int pos
= (*iter
)->GetPosition().y();
94 if (iter
== balloons_
.begin() || pos
< min
)
100 DesktopNotificationsTest::DesktopNotificationsTest()
101 : ui_thread_(BrowserThread::UI
, &message_loop_
),
102 balloon_collection_(NULL
) {
105 DesktopNotificationsTest::~DesktopNotificationsTest() {
108 void DesktopNotificationsTest::SetUp() {
109 ui::InitializeInputMethodForTesting();
110 #if defined(USE_AURA)
111 wm_state_
.reset(new views::corewm::WMState
);
114 ui::ScopedAnimationDurationScaleMode
normal_duration_mode(
115 ui::ScopedAnimationDurationScaleMode::ZERO_DURATION
);
116 // The message center is notmally initialized on |g_browser_process| which
117 // is not created for these tests.
118 message_center::MessageCenter::Initialize();
119 // The ContextFactory must exist before any Compositors are created.
120 bool allow_test_contexts
= true;
121 ui::InitializeContextFactoryForTests(allow_test_contexts
);
122 // MockBalloonCollection retrieves information about the screen on creation.
123 // So it is necessary to make sure the desktop gets created first.
124 ash::Shell::CreateInstance(new ash::test::TestShellDelegate
);
126 chrome::RegisterLocalState(local_state_
.registry());
127 profile_
.reset(new TestingProfile());
128 ui_manager_
.reset(new BalloonNotificationUIManager(&local_state_
));
129 balloon_collection_
= new MockBalloonCollection();
130 ui_manager_
->SetBalloonCollection(balloon_collection_
);
131 service_
.reset(new DesktopNotificationService(profile(), ui_manager_
.get()));
135 void DesktopNotificationsTest::TearDown() {
136 service_
.reset(NULL
);
137 ui_manager_
.reset(NULL
);
138 profile_
.reset(NULL
);
140 ash::Shell::DeleteInstance();
141 // The message center is notmally shutdown on |g_browser_process| which
142 // is not created for these tests.
143 message_center::MessageCenter::Shutdown();
144 aura::Env::DeleteInstance();
145 ui::TerminateContextFactoryForTests();
147 #if defined(USE_AURA)
150 ui::ShutdownInputMethodForTesting();
153 content::ShowDesktopNotificationHostMsgParams
154 DesktopNotificationsTest::StandardTestNotification() {
155 content::ShowDesktopNotificationHostMsgParams params
;
156 params
.notification_id
= 0;
157 params
.origin
= GURL("http://www.google.com");
158 params
.icon_url
= GURL("/icon.png");
159 params
.title
= base::ASCIIToUTF16("Title");
160 params
.body
= base::ASCIIToUTF16("Text");
161 params
.direction
= blink::WebTextDirectionDefault
;
165 TEST_F(DesktopNotificationsTest
, TestShow
) {
166 content::ShowDesktopNotificationHostMsgParams params
=
167 StandardTestNotification();
168 params
.notification_id
= 1;
170 EXPECT_TRUE(service_
->ShowDesktopNotification(
171 params
, 0, 0, DesktopNotificationService::PageNotification
));
172 base::MessageLoopForUI::current()->RunUntilIdle();
173 EXPECT_EQ(1, balloon_collection_
->count());
175 content::ShowDesktopNotificationHostMsgParams params2
=
176 StandardTestNotification();
177 params2
.notification_id
= 2;
178 params2
.origin
= GURL("http://www.google.com");
179 params2
.body
= base::ASCIIToUTF16("Text");
181 EXPECT_TRUE(service_
->ShowDesktopNotification(
182 params2
, 0, 0, DesktopNotificationService::PageNotification
));
183 base::MessageLoopForUI::current()->RunUntilIdle();
184 EXPECT_EQ(2, balloon_collection_
->count());
186 EXPECT_EQ("notification displayed\n"
187 "notification displayed\n",
191 TEST_F(DesktopNotificationsTest
, TestClose
) {
192 content::ShowDesktopNotificationHostMsgParams params
=
193 StandardTestNotification();
194 params
.notification_id
= 1;
196 // Request a notification; should open a balloon.
197 EXPECT_TRUE(service_
->ShowDesktopNotification(
198 params
, 0, 0, DesktopNotificationService::PageNotification
));
199 base::MessageLoopForUI::current()->RunUntilIdle();
200 EXPECT_EQ(1, balloon_collection_
->count());
202 // Close all the open balloons.
203 while (balloon_collection_
->count() > 0) {
204 (*(balloon_collection_
->GetActiveBalloons().begin()))->OnClose(true);
207 EXPECT_EQ("notification displayed\n"
208 "notification closed by user\n",
212 TEST_F(DesktopNotificationsTest
, TestCancel
) {
215 int notification_id
= 1;
217 content::ShowDesktopNotificationHostMsgParams params
=
218 StandardTestNotification();
219 params
.notification_id
= notification_id
;
221 // Request a notification; should open a balloon.
222 EXPECT_TRUE(service_
->ShowDesktopNotification(
223 params
, process_id
, route_id
,
224 DesktopNotificationService::PageNotification
));
225 base::MessageLoopForUI::current()->RunUntilIdle();
226 EXPECT_EQ(1, balloon_collection_
->count());
228 // Cancel the same notification
229 service_
->CancelDesktopNotification(process_id
,
232 base::MessageLoopForUI::current()->RunUntilIdle();
233 // Verify that the balloon collection is now empty.
234 EXPECT_EQ(0, balloon_collection_
->count());
236 EXPECT_EQ("notification displayed\n"
237 "notification closed by script\n",
241 #if defined(OS_WIN) || defined(TOOLKIT_VIEWS)
242 TEST_F(DesktopNotificationsTest
, TestPositioning
) {
243 content::ShowDesktopNotificationHostMsgParams params
=
244 StandardTestNotification();
245 std::string expected_log
;
246 // Create some toasts. After each but the first, make sure there
247 // is a minimum separation between the toasts.
249 for (int id
= 0; id
<= 3; ++id
) {
250 params
.notification_id
= id
;
251 EXPECT_TRUE(service_
->ShowDesktopNotification(
252 params
, 0, 0, DesktopNotificationService::PageNotification
));
253 expected_log
.append("notification displayed\n");
254 int top
= balloon_collection_
->UppermostVerticalPosition();
256 EXPECT_LE(top
, last_top
- balloon_collection_
->MinHeight());
260 EXPECT_EQ(expected_log
, log_output_
);
263 TEST_F(DesktopNotificationsTest
, TestVariableSize
) {
264 content::ShowDesktopNotificationHostMsgParams params
;
265 params
.origin
= GURL("http://long.google.com");
266 params
.icon_url
= GURL("/icon.png");
267 params
.title
= base::ASCIIToUTF16("Really Really Really Really Really Really "
268 "Really Really Really Really Really Really "
269 "Really Really Really Really Really Really "
270 "Really Long Title"),
271 params
.body
= base::ASCIIToUTF16("Text");
272 params
.notification_id
= 0;
274 std::string expected_log
;
275 // Create some toasts. After each but the first, make sure there
276 // is a minimum separation between the toasts.
277 EXPECT_TRUE(service_
->ShowDesktopNotification(
278 params
, 0, 0, DesktopNotificationService::PageNotification
));
279 expected_log
.append("notification displayed\n");
281 params
.origin
= GURL("http://short.google.com");
282 params
.title
= base::ASCIIToUTF16("Short title");
283 params
.notification_id
= 1;
284 EXPECT_TRUE(service_
->ShowDesktopNotification(
285 params
, 0, 0, DesktopNotificationService::PageNotification
));
286 expected_log
.append("notification displayed\n");
288 std::deque
<Balloon
*>& balloons
= balloon_collection_
->balloons();
289 std::deque
<Balloon
*>::iterator iter
;
290 for (iter
= balloons
.begin(); iter
!= balloons
.end(); ++iter
) {
291 if ((*iter
)->notification().origin_url().host() == "long.google.com") {
292 EXPECT_GE((*iter
)->GetViewSize().height(),
293 balloon_collection_
->MinHeight());
294 EXPECT_LE((*iter
)->GetViewSize().height(),
295 balloon_collection_
->MaxHeight());
297 EXPECT_EQ((*iter
)->GetViewSize().height(),
298 balloon_collection_
->MinHeight());
301 EXPECT_EQ(expected_log
, log_output_
);
305 TEST_F(DesktopNotificationsTest
, TestCancelByProfile
) {
309 TestingBrowserProcess
* browser_process
=
310 TestingBrowserProcess::GetGlobal();
311 TestingProfileManager
profile_manager(browser_process
);
312 ASSERT_TRUE(profile_manager
.SetUp());
314 TestingProfile
* second_profile
=
315 profile_manager
.CreateTestingProfile("SecondTestingProfile");
317 scoped_ptr
<DesktopNotificationService
> second_service(
318 new DesktopNotificationService(second_profile
, ui_manager_
.get()));
320 // Request lots of identical notifications.
321 content::ShowDesktopNotificationHostMsgParams params
=
322 StandardTestNotification();
323 params
.notification_id
= 1;
324 // Notice that the first one is the only one that doesn't use
325 // the second profile.
326 EXPECT_TRUE(service_
->ShowDesktopNotification(
327 params
, process_id
, route_id
,
328 DesktopNotificationService::PageNotification
));
330 // |kLotsOfToasts| must be large enough to trigger a resize of the underlying
331 // std::deque while we're clearing it.
332 const int kLotsOfToasts
= 20;
333 for (int id
= 2; id
<= kLotsOfToasts
; ++id
) {
334 params
.notification_id
= id
;
335 EXPECT_TRUE(second_service
->ShowDesktopNotification(
336 params
, process_id
, route_id
,
337 DesktopNotificationService::PageNotification
));
339 base::MessageLoopForUI::current()->RunUntilIdle();
341 ui_manager_
->CancelAllByProfile(second_profile
);
343 // Verify that the balloon collection only contains the single
344 // notification from the first profile.
345 EXPECT_EQ(1, balloon_collection_
->count());
348 TEST_F(DesktopNotificationsTest
, TestCancelBySourceOrigin
) {
352 // Request lots of identical notifications.
353 content::ShowDesktopNotificationHostMsgParams params
=
354 StandardTestNotification();
356 // After the first, all the notifications are from attacker.com.
357 content::ShowDesktopNotificationHostMsgParams odd_params
=
358 StandardTestNotification();
359 odd_params
.origin
= GURL("attacker.com");
361 // Show the only non-attacker.com notification.
362 params
.notification_id
= 1;
363 EXPECT_TRUE(service_
->ShowDesktopNotification(
364 params
, process_id
, route_id
,
365 DesktopNotificationService::PageNotification
));
367 // |kLotsOfToasts| must be large enough to trigger a resize of the underlying
368 // std::deque while we're clearing it.
369 const int kLotsOfToasts
= 20;
370 for (int id
= 2; id
<= kLotsOfToasts
; ++id
) {
371 odd_params
.notification_id
= id
;
372 EXPECT_TRUE(service_
->ShowDesktopNotification(
373 odd_params
, process_id
, route_id
,
374 DesktopNotificationService::PageNotification
));
376 base::MessageLoopForUI::current()->RunUntilIdle();
378 ui_manager_
->CancelAllBySourceOrigin(odd_params
.origin
);
380 // Verify that the balloon collection only contains the single
381 // notification which is not from the canceled origin.
382 EXPECT_EQ(1, balloon_collection_
->count());
385 TEST_F(DesktopNotificationsTest
, TestQueueing
) {
389 // Request lots of identical notifications.
390 content::ShowDesktopNotificationHostMsgParams params
=
391 StandardTestNotification();
392 const int kLotsOfToasts
= 20;
393 for (int id
= 1; id
<= kLotsOfToasts
; ++id
) {
394 params
.notification_id
= id
;
395 EXPECT_TRUE(service_
->ShowDesktopNotification(
396 params
, process_id
, route_id
,
397 DesktopNotificationService::PageNotification
));
399 base::MessageLoopForUI::current()->RunUntilIdle();
401 // Build up an expected log of what should be happening.
402 std::string expected_log
;
403 for (int i
= 0; i
< balloon_collection_
->max_balloon_count(); ++i
) {
404 expected_log
.append("notification displayed\n");
407 // The max number that our balloon collection can hold should be
409 EXPECT_EQ(balloon_collection_
->max_balloon_count(),
410 balloon_collection_
->count());
411 EXPECT_EQ(expected_log
, log_output_
);
413 // Cancel the notifications from the start; the balloon space should
418 id
<= kLotsOfToasts
- balloon_collection_
->max_balloon_count();
420 service_
->CancelDesktopNotification(process_id
, route_id
, id
);
421 base::MessageLoopForUI::current()->RunUntilIdle();
422 expected_log
.append("notification closed by script\n");
423 expected_log
.append("notification displayed\n");
424 EXPECT_EQ(balloon_collection_
->max_balloon_count(),
425 balloon_collection_
->count());
426 EXPECT_EQ(expected_log
, log_output_
);
429 // Now cancel the rest. It should empty the balloon space.
430 for (; id
<= kLotsOfToasts
; ++id
) {
431 service_
->CancelDesktopNotification(process_id
, route_id
, id
);
432 expected_log
.append("notification closed by script\n");
433 base::MessageLoopForUI::current()->RunUntilIdle();
434 EXPECT_EQ(expected_log
, log_output_
);
438 // Verify that the balloon collection is now empty.
439 EXPECT_EQ(0, balloon_collection_
->count());
442 TEST_F(DesktopNotificationsTest
, TestEarlyDestruction
) {
443 // Create some toasts and then prematurely delete the notification service,
444 // just to make sure nothing crashes/leaks.
445 content::ShowDesktopNotificationHostMsgParams params
=
446 StandardTestNotification();
447 for (int id
= 0; id
<= 3; ++id
) {
448 params
.notification_id
= id
;
449 EXPECT_TRUE(service_
->ShowDesktopNotification(
450 params
, 0, 0, DesktopNotificationService::PageNotification
));
452 service_
.reset(NULL
);
455 TEST_F(DesktopNotificationsTest
, TestUserInputEscaping
) {
456 // Create a test script with some HTML; assert that it doesn't get into the
457 // data:// URL that's produced for the balloon.
458 content::ShowDesktopNotificationHostMsgParams params
=
459 StandardTestNotification();
460 params
.title
= base::ASCIIToUTF16("<script>window.alert('uh oh');</script>");
461 params
.body
= base::ASCIIToUTF16("<i>this text is in italics</i>");
462 params
.notification_id
= 1;
463 EXPECT_TRUE(service_
->ShowDesktopNotification(
464 params
, 0, 0, DesktopNotificationService::PageNotification
));
466 base::MessageLoopForUI::current()->RunUntilIdle();
467 EXPECT_EQ(1, balloon_collection_
->count());
468 Balloon
* balloon
= (*balloon_collection_
->balloons().begin());
469 GURL data_url
= balloon
->notification().content_url();
470 EXPECT_EQ(std::string::npos
, data_url
.spec().find("<script>"));
471 EXPECT_EQ(std::string::npos
, data_url
.spec().find("<i>"));
472 // URL-encoded versions of tags should also not be found.
473 EXPECT_EQ(std::string::npos
, data_url
.spec().find("%3cscript%3e"));
474 EXPECT_EQ(std::string::npos
, data_url
.spec().find("%3ci%3e"));
477 TEST_F(DesktopNotificationsTest
, TestBoundingBox
) {
478 // Create some notifications.
479 content::ShowDesktopNotificationHostMsgParams params
=
480 StandardTestNotification();
481 for (int id
= 0; id
<= 3; ++id
) {
482 params
.notification_id
= id
;
483 EXPECT_TRUE(service_
->ShowDesktopNotification(
484 params
, 0, 0, DesktopNotificationService::PageNotification
));
487 gfx::Rect box
= balloon_collection_
->GetBalloonsBoundingBox();
489 // Try this for all positions.
490 BalloonCollection::PositionPreference pref
;
491 for (pref
= BalloonCollection::UPPER_RIGHT
;
492 pref
<= BalloonCollection::LOWER_LEFT
;
493 pref
= static_cast<BalloonCollection::PositionPreference
>(pref
+ 1)) {
494 // Make sure each balloon's 4 corners are inside the box.
495 std::deque
<Balloon
*>& balloons
= balloon_collection_
->balloons();
496 std::deque
<Balloon
*>::iterator iter
;
497 for (iter
= balloons
.begin(); iter
!= balloons
.end(); ++iter
) {
498 int min_x
= (*iter
)->GetPosition().x();
499 int max_x
= min_x
+ (*iter
)->GetViewSize().width() - 1;
500 int min_y
= (*iter
)->GetPosition().y();
501 int max_y
= min_y
+ (*iter
)->GetViewSize().height() - 1;
503 EXPECT_TRUE(box
.Contains(gfx::Point(min_x
, min_y
)));
504 EXPECT_TRUE(box
.Contains(gfx::Point(min_x
, max_y
)));
505 EXPECT_TRUE(box
.Contains(gfx::Point(max_x
, min_y
)));
506 EXPECT_TRUE(box
.Contains(gfx::Point(max_x
, max_y
)));
511 TEST_F(DesktopNotificationsTest
, TestPositionPreference
) {
512 // Set position preference to lower right.
513 local_state_
.SetInteger(prefs::kDesktopNotificationPosition
,
514 BalloonCollection::LOWER_RIGHT
);
516 // Create some notifications.
517 content::ShowDesktopNotificationHostMsgParams params
=
518 StandardTestNotification();
519 for (int id
= 0; id
<= 3; ++id
) {
520 params
.notification_id
= id
;
521 EXPECT_TRUE(service_
->ShowDesktopNotification(
522 params
, 0, 0, DesktopNotificationService::PageNotification
));
525 std::deque
<Balloon
*>& balloons
= balloon_collection_
->balloons();
526 std::deque
<Balloon
*>::iterator iter
;
528 // Check that they decrease in y-position (for MAC, with reversed
529 // coordinates, they should increase).
533 for (iter
= balloons
.begin(); iter
!= balloons
.end(); ++iter
) {
534 int current_x
= (*iter
)->GetPosition().x();
535 int current_y
= (*iter
)->GetPosition().y();
537 EXPECT_EQ(last_x
, current_x
);
540 #if defined(OS_MACOSX)
541 EXPECT_GT(current_y
, last_y
);
543 EXPECT_LT(current_y
, last_y
);
551 // Now change the position to upper right. This should cause an immediate
552 // repositioning, and we check for the reverse ordering.
553 local_state_
.SetInteger(prefs::kDesktopNotificationPosition
,
554 BalloonCollection::UPPER_RIGHT
);
558 for (iter
= balloons
.begin(); iter
!= balloons
.end(); ++iter
) {
559 int current_x
= (*iter
)->GetPosition().x();
560 int current_y
= (*iter
)->GetPosition().y();
563 EXPECT_EQ(last_x
, current_x
);
566 #if defined(OS_MACOSX)
567 EXPECT_LT(current_y
, last_y
);
569 EXPECT_GT(current_y
, last_y
);
577 // Now change the position to upper left. Confirm that the X value for the
578 // balloons gets smaller.
579 local_state_
.SetInteger(prefs::kDesktopNotificationPosition
,
580 BalloonCollection::UPPER_LEFT
);
582 int current_x
= (*balloons
.begin())->GetPosition().x();
583 EXPECT_LT(current_x
, last_x
);