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