blob: 8efb8de809e23e5ec696010850e237bad38dd420 [file] [log] [blame]
// 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/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/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/root_window.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/context_factories_for_test.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() {
ui::InitializeInputMethodForTesting();
#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 allow_test_contexts = true;
ui::InitializeContextFactoryForTests(allow_test_contexts);
// 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
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 = ASCIIToUTF16("Title");
params.body = 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 = 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 = ASCIIToUTF16("Really Really Really Really Really Really "
"Really Really Really Really Really Really "
"Really Really Really Really Really Really "
"Really Long Title"),
params.body = 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 = 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 = ASCIIToUTF16("<script>window.alert('uh oh');</script>");
params.body = 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);
}