root/chrome/browser/notifications/desktop_notifications_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. Add
  2. HasSpace
  3. MakeBalloon
  4. OnBalloonClosed
  5. GetActiveBalloons
  6. UppermostVerticalPosition
  7. balloon_collection_
  8. SetUp
  9. TearDown
  10. StandardTestNotification
  11. TEST_F
  12. TEST_F
  13. TEST_F
  14. TEST_F
  15. TEST_F
  16. TEST_F
  17. TEST_F
  18. TEST_F
  19. TEST_F
  20. TEST_F
  21. TEST_F
  22. TEST_F

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/notifications/desktop_notifications_unittest.h"

#include "base/prefs/testing_pref_service.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/notifications/balloon_notification_ui_manager.h"
#include "chrome/browser/notifications/fake_balloon_view.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_unit_test_suite.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "content/public/common/show_desktop_notification_params.h"
#include "ui/base/ime/input_method_initializer.h"
#include "ui/gl/gl_surface.h"
#include "ui/message_center/message_center.h"

#if defined(USE_ASH)
#include "ash/shell.h"
#include "ash/test/test_shell_delegate.h"
#include "ui/aura/env.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/context_factories_for_test.h"
#endif

#if defined(USE_AURA)
#include "ui/wm/core/wm_state.h"
#endif


using content::BrowserThread;

// static
const int MockBalloonCollection::kMockBalloonSpace = 5;

// static
std::string DesktopNotificationsTest::log_output_;

MockBalloonCollection::MockBalloonCollection() {}

MockBalloonCollection::~MockBalloonCollection() {}

void MockBalloonCollection::Add(const Notification& notification,
                                Profile* profile) {
  // Swap in a logging proxy for the purpose of logging calls that
  // would be made into javascript, then pass this down to the
  // balloon collection.
  Notification test_notification(
      notification.origin_url(),
      notification.content_url(),
      notification.display_source(),
      notification.replace_id(),
      new LoggingNotificationProxy(notification.notification_id()));
  BalloonCollectionImpl::Add(test_notification, profile);
}

bool MockBalloonCollection::HasSpace() const {
  return count() < kMockBalloonSpace;
}

Balloon* MockBalloonCollection::MakeBalloon(const Notification& notification,
                                            Profile* profile) {
  // Start with a normal balloon but mock out the view.
  Balloon* balloon = BalloonCollectionImpl::MakeBalloon(notification, profile);
  balloon->set_view(new FakeBalloonView(balloon));
  balloons_.push_back(balloon);
  return balloon;
}

void MockBalloonCollection::OnBalloonClosed(Balloon* source) {
  std::deque<Balloon*>::iterator it;
  for (it = balloons_.begin(); it != balloons_.end(); ++it) {
    if (*it == source) {
      balloons_.erase(it);
      BalloonCollectionImpl::OnBalloonClosed(source);
      break;
    }
  }
}

const BalloonCollection::Balloons& MockBalloonCollection::GetActiveBalloons() {
  return balloons_;
}

int MockBalloonCollection::UppermostVerticalPosition() {
  int min = 0;
  std::deque<Balloon*>::iterator iter;
  for (iter = balloons_.begin(); iter != balloons_.end(); ++iter) {
    int pos = (*iter)->GetPosition().y();
    if (iter == balloons_.begin() || pos < min)
      min = pos;
  }
  return min;
}

DesktopNotificationsTest::DesktopNotificationsTest()
    : ui_thread_(BrowserThread::UI, &message_loop_),
      balloon_collection_(NULL) {
}

DesktopNotificationsTest::~DesktopNotificationsTest() {
}

void DesktopNotificationsTest::SetUp() {
  ChromeUnitTestSuite::InitializeProviders();
  ChromeUnitTestSuite::InitializeResourceBundle();
  ui::InitializeInputMethodForTesting();
#if defined(USE_AURA)
  wm_state_.reset(new wm::WMState);
#endif
#if defined(USE_ASH)
  ui::ScopedAnimationDurationScaleMode normal_duration_mode(
      ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
  // The message center is notmally initialized on |g_browser_process| which
  // is not created for these tests.
  message_center::MessageCenter::Initialize();
  // The ContextFactory must exist before any Compositors are created.
  bool enable_pixel_output = false;
  ui::InitializeContextFactoryForTests(enable_pixel_output);
  // MockBalloonCollection retrieves information about the screen on creation.
  // So it is necessary to make sure the desktop gets created first.
  ash::Shell::CreateInstance(new ash::test::TestShellDelegate);
#endif
  chrome::RegisterLocalState(local_state_.registry());
  profile_.reset(new TestingProfile());
  ui_manager_.reset(new BalloonNotificationUIManager(&local_state_));
  balloon_collection_ = new MockBalloonCollection();
  ui_manager_->SetBalloonCollection(balloon_collection_);
  service_.reset(new DesktopNotificationService(profile(), ui_manager_.get()));
  log_output_.clear();
}

void DesktopNotificationsTest::TearDown() {
  service_.reset(NULL);
  ui_manager_.reset(NULL);
  profile_.reset(NULL);
#if defined(USE_ASH)
  ash::Shell::DeleteInstance();
  // The message center is notmally shutdown on |g_browser_process| which
  // is not created for these tests.
  message_center::MessageCenter::Shutdown();
  aura::Env::DeleteInstance();
  ui::TerminateContextFactoryForTests();
#endif
#if defined(USE_AURA)
  wm_state_.reset();
#endif
  ui::ShutdownInputMethodForTesting();
}

content::ShowDesktopNotificationHostMsgParams
DesktopNotificationsTest::StandardTestNotification() {
  content::ShowDesktopNotificationHostMsgParams params;
  params.notification_id = 0;
  params.origin = GURL("http://www.google.com");
  params.icon_url = GURL("/icon.png");
  params.title = base::ASCIIToUTF16("Title");
  params.body = base::ASCIIToUTF16("Text");
  params.direction = blink::WebTextDirectionDefault;
  return params;
}

TEST_F(DesktopNotificationsTest, TestShow) {
  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();
  params.notification_id = 1;

  EXPECT_TRUE(service_->ShowDesktopNotification(
      params, 0, 0, DesktopNotificationService::PageNotification));
  base::MessageLoopForUI::current()->RunUntilIdle();
  EXPECT_EQ(1, balloon_collection_->count());

  content::ShowDesktopNotificationHostMsgParams params2 =
      StandardTestNotification();
  params2.notification_id = 2;
  params2.origin = GURL("http://www.google.com");
  params2.body = base::ASCIIToUTF16("Text");

  EXPECT_TRUE(service_->ShowDesktopNotification(
      params2, 0, 0, DesktopNotificationService::PageNotification));
  base::MessageLoopForUI::current()->RunUntilIdle();
  EXPECT_EQ(2, balloon_collection_->count());

  EXPECT_EQ("notification displayed\n"
            "notification displayed\n",
            log_output_);
}

TEST_F(DesktopNotificationsTest, TestClose) {
  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();
  params.notification_id = 1;

  // Request a notification; should open a balloon.
  EXPECT_TRUE(service_->ShowDesktopNotification(
      params, 0, 0, DesktopNotificationService::PageNotification));
  base::MessageLoopForUI::current()->RunUntilIdle();
  EXPECT_EQ(1, balloon_collection_->count());

  // Close all the open balloons.
  while (balloon_collection_->count() > 0) {
    (*(balloon_collection_->GetActiveBalloons().begin()))->OnClose(true);
  }

  EXPECT_EQ("notification displayed\n"
            "notification closed by user\n",
            log_output_);
}

TEST_F(DesktopNotificationsTest, TestCancel) {
  int process_id = 0;
  int route_id = 0;
  int notification_id = 1;

  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();
  params.notification_id = notification_id;

  // Request a notification; should open a balloon.
  EXPECT_TRUE(service_->ShowDesktopNotification(
      params, process_id, route_id,
      DesktopNotificationService::PageNotification));
  base::MessageLoopForUI::current()->RunUntilIdle();
  EXPECT_EQ(1, balloon_collection_->count());

  // Cancel the same notification
  service_->CancelDesktopNotification(process_id,
                                      route_id,
                                      notification_id);
  base::MessageLoopForUI::current()->RunUntilIdle();
  // Verify that the balloon collection is now empty.
  EXPECT_EQ(0, balloon_collection_->count());

  EXPECT_EQ("notification displayed\n"
            "notification closed by script\n",
            log_output_);
}

#if defined(OS_WIN) || defined(TOOLKIT_VIEWS)
TEST_F(DesktopNotificationsTest, TestPositioning) {
  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();
  std::string expected_log;
  // Create some toasts.  After each but the first, make sure there
  // is a minimum separation between the toasts.
  int last_top = 0;
  for (int id = 0; id <= 3; ++id) {
    params.notification_id = id;
    EXPECT_TRUE(service_->ShowDesktopNotification(
        params, 0, 0, DesktopNotificationService::PageNotification));
    expected_log.append("notification displayed\n");
    int top = balloon_collection_->UppermostVerticalPosition();
    if (id > 0)
      EXPECT_LE(top, last_top - balloon_collection_->MinHeight());
    last_top = top;
  }

  EXPECT_EQ(expected_log, log_output_);
}

TEST_F(DesktopNotificationsTest, TestVariableSize) {
  content::ShowDesktopNotificationHostMsgParams params;
  params.origin = GURL("http://long.google.com");
  params.icon_url = GURL("/icon.png");
  params.title = base::ASCIIToUTF16("Really Really Really Really Really Really "
                                    "Really Really Really Really Really Really "
                                    "Really Really Really Really Really Really "
                                    "Really Long Title"),
  params.body = base::ASCIIToUTF16("Text");
  params.notification_id = 0;

  std::string expected_log;
  // Create some toasts.  After each but the first, make sure there
  // is a minimum separation between the toasts.
  EXPECT_TRUE(service_->ShowDesktopNotification(
      params, 0, 0, DesktopNotificationService::PageNotification));
  expected_log.append("notification displayed\n");

  params.origin = GURL("http://short.google.com");
  params.title = base::ASCIIToUTF16("Short title");
  params.notification_id = 1;
  EXPECT_TRUE(service_->ShowDesktopNotification(
      params, 0, 0, DesktopNotificationService::PageNotification));
  expected_log.append("notification displayed\n");

  std::deque<Balloon*>& balloons = balloon_collection_->balloons();
  std::deque<Balloon*>::iterator iter;
  for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
    if ((*iter)->notification().origin_url().host() == "long.google.com") {
      EXPECT_GE((*iter)->GetViewSize().height(),
                balloon_collection_->MinHeight());
      EXPECT_LE((*iter)->GetViewSize().height(),
                balloon_collection_->MaxHeight());
    } else {
      EXPECT_EQ((*iter)->GetViewSize().height(),
                balloon_collection_->MinHeight());
    }
  }
  EXPECT_EQ(expected_log, log_output_);
}
#endif

TEST_F(DesktopNotificationsTest, TestCancelByProfile) {
  int process_id = 0;
  int route_id = 0;

  TestingBrowserProcess* browser_process =
      TestingBrowserProcess::GetGlobal();
  TestingProfileManager profile_manager(browser_process);
  ASSERT_TRUE(profile_manager.SetUp());

  TestingProfile* second_profile =
      profile_manager.CreateTestingProfile("SecondTestingProfile");

  scoped_ptr<DesktopNotificationService> second_service(
      new DesktopNotificationService(second_profile, ui_manager_.get()));

  // Request lots of identical notifications.
  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();
  params.notification_id = 1;
  // Notice that the first one is the only one that doesn't use
  // the second profile.
  EXPECT_TRUE(service_->ShowDesktopNotification(
      params, process_id, route_id,
      DesktopNotificationService::PageNotification));

  // |kLotsOfToasts| must be large enough to trigger a resize of the underlying
  // std::deque while we're clearing it.
  const int kLotsOfToasts = 20;
  for (int id = 2; id <= kLotsOfToasts; ++id) {
    params.notification_id = id;
    EXPECT_TRUE(second_service->ShowDesktopNotification(
        params, process_id, route_id,
        DesktopNotificationService::PageNotification));
  }
  base::MessageLoopForUI::current()->RunUntilIdle();

  ui_manager_->CancelAllByProfile(second_profile);

  // Verify that the balloon collection only contains the single
  // notification from the first profile.
  EXPECT_EQ(1, balloon_collection_->count());
}

TEST_F(DesktopNotificationsTest, TestCancelBySourceOrigin) {
  int process_id = 0;
  int route_id = 0;

  // Request lots of identical notifications.
  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();

  // After the first, all the notifications are from attacker.com.
  content::ShowDesktopNotificationHostMsgParams odd_params =
      StandardTestNotification();
  odd_params.origin = GURL("attacker.com");

  // Show the only non-attacker.com notification.
  params.notification_id = 1;
  EXPECT_TRUE(service_->ShowDesktopNotification(
      params, process_id, route_id,
      DesktopNotificationService::PageNotification));

  // |kLotsOfToasts| must be large enough to trigger a resize of the underlying
  // std::deque while we're clearing it.
  const int kLotsOfToasts = 20;
  for (int id = 2; id <= kLotsOfToasts; ++id) {
    odd_params.notification_id = id;
    EXPECT_TRUE(service_->ShowDesktopNotification(
        odd_params, process_id, route_id,
        DesktopNotificationService::PageNotification));
  }
  base::MessageLoopForUI::current()->RunUntilIdle();

  ui_manager_->CancelAllBySourceOrigin(odd_params.origin);

  // Verify that the balloon collection only contains the single
  // notification which is not from the canceled origin.
  EXPECT_EQ(1, balloon_collection_->count());
}

TEST_F(DesktopNotificationsTest, TestQueueing) {
  int process_id = 0;
  int route_id = 0;

  // Request lots of identical notifications.
  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();
  const int kLotsOfToasts = 20;
  for (int id = 1; id <= kLotsOfToasts; ++id) {
    params.notification_id = id;
    EXPECT_TRUE(service_->ShowDesktopNotification(
        params, process_id, route_id,
        DesktopNotificationService::PageNotification));
  }
  base::MessageLoopForUI::current()->RunUntilIdle();

  // Build up an expected log of what should be happening.
  std::string expected_log;
  for (int i = 0; i < balloon_collection_->max_balloon_count(); ++i) {
    expected_log.append("notification displayed\n");
  }

  // The max number that our balloon collection can hold should be
  // shown.
  EXPECT_EQ(balloon_collection_->max_balloon_count(),
            balloon_collection_->count());
  EXPECT_EQ(expected_log, log_output_);

  // Cancel the notifications from the start; the balloon space should
  // remain full.
  {
    int id;
    for (id = 1;
         id <= kLotsOfToasts - balloon_collection_->max_balloon_count();
         ++id) {
      service_->CancelDesktopNotification(process_id, route_id, id);
      base::MessageLoopForUI::current()->RunUntilIdle();
      expected_log.append("notification closed by script\n");
      expected_log.append("notification displayed\n");
      EXPECT_EQ(balloon_collection_->max_balloon_count(),
                balloon_collection_->count());
      EXPECT_EQ(expected_log, log_output_);
    }

    // Now cancel the rest.  It should empty the balloon space.
    for (; id <= kLotsOfToasts; ++id) {
      service_->CancelDesktopNotification(process_id, route_id, id);
      expected_log.append("notification closed by script\n");
      base::MessageLoopForUI::current()->RunUntilIdle();
      EXPECT_EQ(expected_log, log_output_);
    }
  }

  // Verify that the balloon collection is now empty.
  EXPECT_EQ(0, balloon_collection_->count());
}

TEST_F(DesktopNotificationsTest, TestEarlyDestruction) {
  // Create some toasts and then prematurely delete the notification service,
  // just to make sure nothing crashes/leaks.
  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();
  for (int id = 0; id <= 3; ++id) {
    params.notification_id = id;
    EXPECT_TRUE(service_->ShowDesktopNotification(
        params, 0, 0, DesktopNotificationService::PageNotification));
  }
  service_.reset(NULL);
}

TEST_F(DesktopNotificationsTest, TestUserInputEscaping) {
  // Create a test script with some HTML; assert that it doesn't get into the
  // data:// URL that's produced for the balloon.
  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();
  params.title = base::ASCIIToUTF16("<script>window.alert('uh oh');</script>");
  params.body = base::ASCIIToUTF16("<i>this text is in italics</i>");
  params.notification_id = 1;
  EXPECT_TRUE(service_->ShowDesktopNotification(
      params, 0, 0, DesktopNotificationService::PageNotification));

  base::MessageLoopForUI::current()->RunUntilIdle();
  EXPECT_EQ(1, balloon_collection_->count());
  Balloon* balloon = (*balloon_collection_->balloons().begin());
  GURL data_url = balloon->notification().content_url();
  EXPECT_EQ(std::string::npos, data_url.spec().find("<script>"));
  EXPECT_EQ(std::string::npos, data_url.spec().find("<i>"));
  // URL-encoded versions of tags should also not be found.
  EXPECT_EQ(std::string::npos, data_url.spec().find("%3cscript%3e"));
  EXPECT_EQ(std::string::npos, data_url.spec().find("%3ci%3e"));
}

TEST_F(DesktopNotificationsTest, TestBoundingBox) {
  // Create some notifications.
  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();
  for (int id = 0; id <= 3; ++id) {
    params.notification_id = id;
    EXPECT_TRUE(service_->ShowDesktopNotification(
        params, 0, 0, DesktopNotificationService::PageNotification));
  }

  gfx::Rect box = balloon_collection_->GetBalloonsBoundingBox();

  // Try this for all positions.
  BalloonCollection::PositionPreference pref;
  for (pref = BalloonCollection::UPPER_RIGHT;
       pref <= BalloonCollection::LOWER_LEFT;
       pref = static_cast<BalloonCollection::PositionPreference>(pref + 1)) {
    // Make sure each balloon's 4 corners are inside the box.
    std::deque<Balloon*>& balloons = balloon_collection_->balloons();
    std::deque<Balloon*>::iterator iter;
    for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
      int min_x = (*iter)->GetPosition().x();
      int max_x = min_x + (*iter)->GetViewSize().width() - 1;
      int min_y = (*iter)->GetPosition().y();
      int max_y = min_y + (*iter)->GetViewSize().height() - 1;

      EXPECT_TRUE(box.Contains(gfx::Point(min_x, min_y)));
      EXPECT_TRUE(box.Contains(gfx::Point(min_x, max_y)));
      EXPECT_TRUE(box.Contains(gfx::Point(max_x, min_y)));
      EXPECT_TRUE(box.Contains(gfx::Point(max_x, max_y)));
    }
  }
}

TEST_F(DesktopNotificationsTest, TestPositionPreference) {
  // Set position preference to lower right.
  local_state_.SetInteger(prefs::kDesktopNotificationPosition,
                          BalloonCollection::LOWER_RIGHT);

  // Create some notifications.
  content::ShowDesktopNotificationHostMsgParams params =
      StandardTestNotification();
  for (int id = 0; id <= 3; ++id) {
    params.notification_id = id;
    EXPECT_TRUE(service_->ShowDesktopNotification(
        params, 0, 0, DesktopNotificationService::PageNotification));
  }

  std::deque<Balloon*>& balloons = balloon_collection_->balloons();
  std::deque<Balloon*>::iterator iter;

  // Check that they decrease in y-position (for MAC, with reversed
  // coordinates, they should increase).
  int last_y = -1;
  int last_x = -1;

  for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
    int current_x = (*iter)->GetPosition().x();
    int current_y = (*iter)->GetPosition().y();
    if (last_x > 0)
      EXPECT_EQ(last_x, current_x);

    if (last_y > 0) {
#if defined(OS_MACOSX)
      EXPECT_GT(current_y, last_y);
#else
      EXPECT_LT(current_y, last_y);
#endif
    }

    last_x = current_x;
    last_y = current_y;
  }

  // Now change the position to upper right.  This should cause an immediate
  // repositioning, and we check for the reverse ordering.
  local_state_.SetInteger(prefs::kDesktopNotificationPosition,
                          BalloonCollection::UPPER_RIGHT);
  last_x = -1;
  last_y = -1;

  for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
    int current_x = (*iter)->GetPosition().x();
    int current_y = (*iter)->GetPosition().y();

    if (last_x > 0)
      EXPECT_EQ(last_x, current_x);

    if (last_y > 0) {
#if defined(OS_MACOSX)
      EXPECT_LT(current_y, last_y);
#else
      EXPECT_GT(current_y, last_y);
#endif
    }

    last_x = current_x;
    last_y = current_y;
  }

  // Now change the position to upper left.  Confirm that the X value for the
  // balloons gets smaller.
  local_state_.SetInteger(prefs::kDesktopNotificationPosition,
                          BalloonCollection::UPPER_LEFT);

  int current_x = (*balloons.begin())->GetPosition().x();
  EXPECT_LT(current_x, last_x);
}

/* [<][>][^][v][top][bottom][index][help] */