Home | History | Annotate | Download | only in notifications
      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/notifications/desktop_notifications_unittest.h"
      6 
      7 #include "base/string_util.h"
      8 #include "base/utf_string_conversions.h"
      9 #include "chrome/browser/prefs/browser_prefs.h"
     10 #include "chrome/common/pref_names.h"
     11 #include "chrome/test/testing_pref_service.h"
     12 #include "content/common/desktop_notification_messages.h"
     13 
     14 // static
     15 const int MockBalloonCollection::kMockBalloonSpace = 5;
     16 
     17 // static
     18 std::string DesktopNotificationsTest::log_output_;
     19 
     20 MockBalloonCollection::MockBalloonCollection() {}
     21 
     22 MockBalloonCollection::~MockBalloonCollection() {}
     23 
     24 void MockBalloonCollection::Add(const Notification& notification,
     25                                 Profile* profile) {
     26   // Swap in a logging proxy for the purpose of logging calls that
     27   // would be made into javascript, then pass this down to the
     28   // balloon collection.
     29   Notification test_notification(
     30       notification.origin_url(),
     31       notification.content_url(),
     32       notification.display_source(),
     33       notification.replace_id(),
     34       new LoggingNotificationProxy(notification.notification_id()));
     35   BalloonCollectionImpl::Add(test_notification, profile);
     36 }
     37 
     38 bool MockBalloonCollection::HasSpace() const {
     39   return count() < kMockBalloonSpace;
     40 }
     41 
     42 Balloon* MockBalloonCollection::MakeBalloon(const Notification& notification,
     43                                             Profile* profile) {
     44   // Start with a normal balloon but mock out the view.
     45   Balloon* balloon = BalloonCollectionImpl::MakeBalloon(notification, profile);
     46   balloon->set_view(new MockBalloonView(balloon));
     47   balloons_.push_back(balloon);
     48   return balloon;
     49 }
     50 
     51 void MockBalloonCollection::OnBalloonClosed(Balloon* source) {
     52   std::deque<Balloon*>::iterator it;
     53   for (it = balloons_.begin(); it != balloons_.end(); ++it) {
     54     if (*it == source) {
     55       balloons_.erase(it);
     56       BalloonCollectionImpl::OnBalloonClosed(source);
     57       break;
     58     }
     59   }
     60 }
     61 
     62 const BalloonCollection::Balloons& MockBalloonCollection::GetActiveBalloons() {
     63   return balloons_;
     64 }
     65 
     66 int MockBalloonCollection::UppermostVerticalPosition() {
     67   int min = 0;
     68   std::deque<Balloon*>::iterator iter;
     69   for (iter = balloons_.begin(); iter != balloons_.end(); ++iter) {
     70     int pos = (*iter)->GetPosition().y();
     71     if (iter == balloons_.begin() || pos < min)
     72       min = pos;
     73   }
     74   return min;
     75 }
     76 
     77 DesktopNotificationsTest::DesktopNotificationsTest()
     78     : ui_thread_(BrowserThread::UI, &message_loop_) {
     79 }
     80 
     81 DesktopNotificationsTest::~DesktopNotificationsTest() {
     82 }
     83 
     84 void DesktopNotificationsTest::SetUp() {
     85   browser::RegisterLocalState(&local_state_);
     86   profile_.reset(new TestingProfile());
     87   balloon_collection_ = new MockBalloonCollection();
     88   ui_manager_.reset(new NotificationUIManager(&local_state_));
     89   ui_manager_->Initialize(balloon_collection_);
     90   balloon_collection_->set_space_change_listener(ui_manager_.get());
     91   service_.reset(new DesktopNotificationService(profile(), ui_manager_.get()));
     92   log_output_.clear();
     93 }
     94 
     95 void DesktopNotificationsTest::TearDown() {
     96   service_.reset(NULL);
     97   ui_manager_.reset(NULL);
     98   profile_.reset(NULL);
     99 }
    100 
    101 DesktopNotificationHostMsg_Show_Params
    102 DesktopNotificationsTest::StandardTestNotification() {
    103   DesktopNotificationHostMsg_Show_Params params;
    104   params.notification_id = 0;
    105   params.origin = GURL("http://www.google.com");
    106   params.is_html = false;
    107   params.icon_url = GURL("/icon.png");
    108   params.title = ASCIIToUTF16("Title");
    109   params.body = ASCIIToUTF16("Text");
    110   params.direction = WebKit::WebTextDirectionDefault;
    111   return params;
    112 }
    113 
    114 TEST_F(DesktopNotificationsTest, TestShow) {
    115   DesktopNotificationHostMsg_Show_Params params = StandardTestNotification();
    116   params.notification_id = 1;
    117 
    118   EXPECT_TRUE(service_->ShowDesktopNotification(
    119       params, 0, 0, DesktopNotificationService::PageNotification));
    120   MessageLoopForUI::current()->RunAllPending();
    121   EXPECT_EQ(1, balloon_collection_->count());
    122 
    123   DesktopNotificationHostMsg_Show_Params params2;
    124   params2.origin = GURL("http://www.google.com");
    125   params2.is_html = true;
    126   params2.contents_url = GURL("http://www.google.com/notification.html");
    127   params2.notification_id = 2;
    128 
    129   EXPECT_TRUE(service_->ShowDesktopNotification(
    130       params2, 0, 0, DesktopNotificationService::PageNotification));
    131   MessageLoopForUI::current()->RunAllPending();
    132   EXPECT_EQ(2, balloon_collection_->count());
    133 
    134   EXPECT_EQ("notification displayed\n"
    135             "notification displayed\n",
    136             log_output_);
    137 }
    138 
    139 TEST_F(DesktopNotificationsTest, TestClose) {
    140   DesktopNotificationHostMsg_Show_Params params = StandardTestNotification();
    141   params.notification_id = 1;
    142 
    143   // Request a notification; should open a balloon.
    144   EXPECT_TRUE(service_->ShowDesktopNotification(
    145       params, 0, 0, DesktopNotificationService::PageNotification));
    146   MessageLoopForUI::current()->RunAllPending();
    147   EXPECT_EQ(1, balloon_collection_->count());
    148 
    149   // Close all the open balloons.
    150   while (balloon_collection_->count() > 0) {
    151     (*(balloon_collection_->GetActiveBalloons().begin()))->OnClose(true);
    152   }
    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 #if defined(OS_WIN) || defined(TOOLKIT_VIEWS)
    188 TEST_F(DesktopNotificationsTest, TestPositioning) {
    189   DesktopNotificationHostMsg_Show_Params params = StandardTestNotification();
    190   std::string expected_log;
    191   // Create some toasts.  After each but the first, make sure there
    192   // is a minimum separation between the toasts.
    193   int last_top = 0;
    194   for (int id = 0; id <= 3; ++id) {
    195     params.notification_id = id;
    196     EXPECT_TRUE(service_->ShowDesktopNotification(
    197         params, 0, 0, DesktopNotificationService::PageNotification));
    198     expected_log.append("notification displayed\n");
    199     int top = balloon_collection_->UppermostVerticalPosition();
    200     if (id > 0)
    201       EXPECT_LE(top, last_top - balloon_collection_->MinHeight());
    202     last_top = top;
    203   }
    204 
    205   EXPECT_EQ(expected_log, log_output_);
    206 }
    207 
    208 TEST_F(DesktopNotificationsTest, TestVariableSize) {
    209   DesktopNotificationHostMsg_Show_Params params;
    210   params.origin = GURL("http://long.google.com");
    211   params.is_html = false;
    212   params.icon_url = GURL("/icon.png");
    213   params.title = ASCIIToUTF16("Really Really Really Really Really Really "
    214                               "Really Really Really Really Really Really "
    215                               "Really Really Really Really Really Really "
    216                               "Really Long Title"),
    217   params.body = ASCIIToUTF16("Text");
    218   params.notification_id = 0;
    219 
    220   std::string expected_log;
    221   // Create some toasts.  After each but the first, make sure there
    222   // is a minimum separation between the toasts.
    223   EXPECT_TRUE(service_->ShowDesktopNotification(
    224       params, 0, 0, DesktopNotificationService::PageNotification));
    225   expected_log.append("notification displayed\n");
    226 
    227   params.origin = GURL("http://short.google.com");
    228   params.title = ASCIIToUTF16("Short title");
    229   params.notification_id = 1;
    230   EXPECT_TRUE(service_->ShowDesktopNotification(
    231       params, 0, 0, DesktopNotificationService::PageNotification));
    232   expected_log.append("notification displayed\n");
    233 
    234   std::deque<Balloon*>& balloons = balloon_collection_->balloons();
    235   std::deque<Balloon*>::iterator iter;
    236   for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
    237     if ((*iter)->notification().origin_url().host() == "long.google.com") {
    238       EXPECT_GE((*iter)->GetViewSize().height(),
    239                 balloon_collection_->MinHeight());
    240       EXPECT_LE((*iter)->GetViewSize().height(),
    241                 balloon_collection_->MaxHeight());
    242     } else {
    243       EXPECT_EQ((*iter)->GetViewSize().height(),
    244                 balloon_collection_->MinHeight());
    245     }
    246   }
    247   EXPECT_EQ(expected_log, log_output_);
    248 }
    249 #endif
    250 
    251 TEST_F(DesktopNotificationsTest, TestQueueing) {
    252   int process_id = 0;
    253   int route_id = 0;
    254 
    255   // Request lots of identical notifications.
    256   DesktopNotificationHostMsg_Show_Params params = StandardTestNotification();
    257   const int kLotsOfToasts = 20;
    258   for (int id = 1; id <= kLotsOfToasts; ++id) {
    259     params.notification_id = id;
    260     EXPECT_TRUE(service_->ShowDesktopNotification(
    261         params, process_id, route_id,
    262         DesktopNotificationService::PageNotification));
    263   }
    264   MessageLoopForUI::current()->RunAllPending();
    265 
    266   // Build up an expected log of what should be happening.
    267   std::string expected_log;
    268   for (int i = 0; i < balloon_collection_->max_balloon_count(); ++i) {
    269     expected_log.append("notification displayed\n");
    270   }
    271 
    272   // The max number that our balloon collection can hold should be
    273   // shown.
    274   EXPECT_EQ(balloon_collection_->max_balloon_count(),
    275             balloon_collection_->count());
    276   EXPECT_EQ(expected_log, log_output_);
    277 
    278   // Cancel the notifications from the start; the balloon space should
    279   // remain full.
    280   {
    281     int id;
    282     for (id = 1;
    283          id <= kLotsOfToasts - balloon_collection_->max_balloon_count();
    284          ++id) {
    285       service_->CancelDesktopNotification(process_id, route_id, id);
    286       MessageLoopForUI::current()->RunAllPending();
    287       expected_log.append("notification closed by script\n");
    288       expected_log.append("notification displayed\n");
    289       EXPECT_EQ(balloon_collection_->max_balloon_count(),
    290                 balloon_collection_->count());
    291       EXPECT_EQ(expected_log, log_output_);
    292     }
    293 
    294     // Now cancel the rest.  It should empty the balloon space.
    295     for (; id <= kLotsOfToasts; ++id) {
    296       service_->CancelDesktopNotification(process_id, route_id, id);
    297       expected_log.append("notification closed by script\n");
    298       MessageLoopForUI::current()->RunAllPending();
    299       EXPECT_EQ(expected_log, log_output_);
    300     }
    301   }
    302 
    303   // Verify that the balloon collection is now empty.
    304   EXPECT_EQ(0, balloon_collection_->count());
    305 }
    306 
    307 TEST_F(DesktopNotificationsTest, TestEarlyDestruction) {
    308   // Create some toasts and then prematurely delete the notification service,
    309   // just to make sure nothing crashes/leaks.
    310   DesktopNotificationHostMsg_Show_Params params = StandardTestNotification();
    311   for (int id = 0; id <= 3; ++id) {
    312     params.notification_id = id;
    313     EXPECT_TRUE(service_->ShowDesktopNotification(
    314         params, 0, 0, DesktopNotificationService::PageNotification));
    315   }
    316   service_.reset(NULL);
    317 }
    318 
    319 TEST_F(DesktopNotificationsTest, TestUserInputEscaping) {
    320   // Create a test script with some HTML; assert that it doesn't get into the
    321   // data:// URL that's produced for the balloon.
    322   DesktopNotificationHostMsg_Show_Params params = StandardTestNotification();
    323   params.title = ASCIIToUTF16("<script>window.alert('uh oh');</script>");
    324   params.body = ASCIIToUTF16("<i>this text is in italics</i>");
    325   params.notification_id = 1;
    326   EXPECT_TRUE(service_->ShowDesktopNotification(
    327       params, 0, 0, DesktopNotificationService::PageNotification));
    328 
    329   MessageLoopForUI::current()->RunAllPending();
    330   EXPECT_EQ(1, balloon_collection_->count());
    331   Balloon* balloon = (*balloon_collection_->balloons().begin());
    332   GURL data_url = balloon->notification().content_url();
    333   EXPECT_EQ(std::string::npos, data_url.spec().find("<script>"));
    334   EXPECT_EQ(std::string::npos, data_url.spec().find("<i>"));
    335   // URL-encoded versions of tags should also not be found.
    336   EXPECT_EQ(std::string::npos, data_url.spec().find("%3cscript%3e"));
    337   EXPECT_EQ(std::string::npos, data_url.spec().find("%3ci%3e"));
    338 }
    339 
    340 TEST_F(DesktopNotificationsTest, TestBoundingBox) {
    341   // Create some notifications.
    342   DesktopNotificationHostMsg_Show_Params params = StandardTestNotification();
    343   for (int id = 0; id <= 3; ++id) {
    344     params.notification_id = id;
    345     EXPECT_TRUE(service_->ShowDesktopNotification(
    346         params, 0, 0, DesktopNotificationService::PageNotification));
    347   }
    348 
    349   gfx::Rect box = balloon_collection_->GetBalloonsBoundingBox();
    350 
    351   // Try this for all positions.
    352   BalloonCollection::PositionPreference pref;
    353   for (pref = BalloonCollection::UPPER_RIGHT;
    354        pref <= BalloonCollection::LOWER_LEFT;
    355        pref = static_cast<BalloonCollection::PositionPreference>(pref + 1)) {
    356     // Make sure each balloon's 4 corners are inside the box.
    357     std::deque<Balloon*>& balloons = balloon_collection_->balloons();
    358     std::deque<Balloon*>::iterator iter;
    359     for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
    360       int min_x = (*iter)->GetPosition().x();
    361       int max_x = min_x + (*iter)->GetViewSize().width() - 1;
    362       int min_y = (*iter)->GetPosition().y();
    363       int max_y = min_y + (*iter)->GetViewSize().height() - 1;
    364 
    365       EXPECT_TRUE(box.Contains(gfx::Point(min_x, min_y)));
    366       EXPECT_TRUE(box.Contains(gfx::Point(min_x, max_y)));
    367       EXPECT_TRUE(box.Contains(gfx::Point(max_x, min_y)));
    368       EXPECT_TRUE(box.Contains(gfx::Point(max_x, max_y)));
    369     }
    370   }
    371 }
    372 
    373 TEST_F(DesktopNotificationsTest, TestPositionPreference) {
    374   // Set position preference to lower right.
    375   local_state_.SetInteger(prefs::kDesktopNotificationPosition,
    376                           BalloonCollection::LOWER_RIGHT);
    377 
    378   // Create some notifications.
    379   DesktopNotificationHostMsg_Show_Params params = StandardTestNotification();
    380   for (int id = 0; id <= 3; ++id) {
    381     params.notification_id = id;
    382     EXPECT_TRUE(service_->ShowDesktopNotification(
    383         params, 0, 0, DesktopNotificationService::PageNotification));
    384   }
    385 
    386   std::deque<Balloon*>& balloons = balloon_collection_->balloons();
    387   std::deque<Balloon*>::iterator iter;
    388 
    389   // Check that they decrease in y-position (for MAC, with reversed
    390   // coordinates, they should increase).
    391   int last_y = -1;
    392   int last_x = -1;
    393 
    394   for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
    395     int current_x = (*iter)->GetPosition().x();
    396     int current_y = (*iter)->GetPosition().y();
    397     if (last_x > 0)
    398       EXPECT_EQ(last_x, current_x);
    399 
    400     if (last_y > 0) {
    401 #if defined(OS_MACOSX)
    402       EXPECT_GT(current_y, last_y);
    403 #else
    404       EXPECT_LT(current_y, last_y);
    405 #endif
    406     }
    407 
    408     last_x = current_x;
    409     last_y = current_y;
    410   }
    411 
    412   // Now change the position to upper right.  This should cause an immediate
    413   // repositioning, and we check for the reverse ordering.
    414   local_state_.SetInteger(prefs::kDesktopNotificationPosition,
    415                           BalloonCollection::UPPER_RIGHT);
    416   last_x = -1;
    417   last_y = -1;
    418 
    419   for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
    420     int current_x = (*iter)->GetPosition().x();
    421     int current_y = (*iter)->GetPosition().y();
    422 
    423     if (last_x > 0)
    424       EXPECT_EQ(last_x, current_x);
    425 
    426     if (last_y > 0) {
    427 #if defined(OS_MACOSX)
    428       EXPECT_LT(current_y, last_y);
    429 #else
    430       EXPECT_GT(current_y, last_y);
    431 #endif
    432     }
    433 
    434     last_x = current_x;
    435     last_y = current_y;
    436   }
    437 
    438   // Now change the position to upper left.  Confirm that the X value for the
    439   // balloons gets smaller.
    440   local_state_.SetInteger(prefs::kDesktopNotificationPosition,
    441                           BalloonCollection::UPPER_LEFT);
    442 
    443   int current_x = (*balloons.begin())->GetPosition().x();
    444   EXPECT_LT(current_x, last_x);
    445 }
    446