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