// 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 #include "base/basictypes.h" #include "base/command_line.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/prefs/pref_service.h" #include "base/prefs/scoped_user_pref_update.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/background/background_contents_service.h" #include "chrome/browser/background/background_contents_service_factory.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/tab_contents/background_contents.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/common/extensions/extension_test_util.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/browser_with_test_window_test.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/browser/notification_service.h" #include "content/public/test/test_browser_thread.h" #include "extensions/common/extension.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" #include "url/gurl.h" #if defined(ENABLE_NOTIFICATIONS) #include "chrome/browser/notifications/message_center_notification_manager.h" #include "chrome/browser/notifications/notification.h" #include "ui/message_center/fake_message_center_tray_delegate.h" #include "ui/message_center/message_center.h" #include "ui/message_center/message_center_observer.h" #endif class BackgroundContentsServiceTest : public testing::Test { public: BackgroundContentsServiceTest() {} virtual ~BackgroundContentsServiceTest() {} virtual void SetUp() { command_line_.reset(new CommandLine(CommandLine::NO_PROGRAM)); } const base::DictionaryValue* GetPrefs(Profile* profile) { return profile->GetPrefs()->GetDictionary( prefs::kRegisteredBackgroundContents); } // Returns the stored pref URL for the passed app id. std::string GetPrefURLForApp(Profile* profile, const base::string16& appid) { const base::DictionaryValue* pref = GetPrefs(profile); EXPECT_TRUE(pref->HasKey(base::UTF16ToUTF8(appid))); const base::DictionaryValue* value; pref->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid), &value); std::string url; value->GetString("url", &url); return url; } scoped_ptr command_line_; }; class MockBackgroundContents : public BackgroundContents { public: explicit MockBackgroundContents(Profile* profile) : appid_(base::ASCIIToUTF16("app_id")), profile_(profile) { } MockBackgroundContents(Profile* profile, const std::string& id) : appid_(base::ASCIIToUTF16(id)), profile_(profile) { } void SendOpenedNotification(BackgroundContentsService* service) { base::string16 frame_name = base::ASCIIToUTF16("background"); BackgroundContentsOpenedDetails details = { this, frame_name, appid_ }; service->BackgroundContentsOpened(&details, profile_); } virtual void Navigate(GURL url) { url_ = url; content::NotificationService::current()->Notify( chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED, content::Source(profile_), content::Details(this)); } const GURL& GetURL() const override { return url_; } void MockClose(Profile* profile) { content::NotificationService::current()->Notify( chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED, content::Source(profile), content::Details(this)); delete this; } ~MockBackgroundContents() override { content::NotificationService::current()->Notify( chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED, content::Source(profile_), content::Details(this)); } const base::string16& appid() { return appid_; } private: GURL url_; // The ID of our parent application base::string16 appid_; // Parent profile Profile* profile_; }; #if defined(ENABLE_NOTIFICATIONS) // Wait for the notification created. class NotificationWaiter : public message_center::MessageCenterObserver { public: explicit NotificationWaiter(const std::string& target_id, Profile* profile) : target_id_(target_id), profile_(profile) {} ~NotificationWaiter() override {} void WaitForNotificationAdded() { DCHECK(!run_loop_.running()); message_center::MessageCenter* message_center = message_center::MessageCenter::Get(); message_center->AddObserver(this); run_loop_.Run(); message_center->RemoveObserver(this); } private: // message_center::MessageCenterObserver overrides: void OnNotificationAdded(const std::string& notification_id) override { if (notification_id == FindNotificationIdFromDelegateId(target_id_)) run_loop_.Quit(); } void OnNotificationUpdated(const std::string& notification_id) override { if (notification_id == FindNotificationIdFromDelegateId(target_id_)) run_loop_.Quit(); } std::string FindNotificationIdFromDelegateId(const std::string& delegate_id) { MessageCenterNotificationManager* manager = static_cast( g_browser_process->notification_ui_manager()); DCHECK(manager); return manager->FindById(delegate_id, profile_)->id(); } std::string target_id_; Profile* profile_; base::RunLoop run_loop_; DISALLOW_COPY_AND_ASSIGN(NotificationWaiter); }; class BackgroundContentsServiceNotificationTest : public BrowserWithTestWindowTest { public: BackgroundContentsServiceNotificationTest() {} virtual ~BackgroundContentsServiceNotificationTest() {} // Overridden from testing::Test virtual void SetUp() { BrowserWithTestWindowTest::SetUp(); // In ChromeOS environment, BrowserWithTestWindowTest initializes // MessageCenter. #if !defined(OS_CHROMEOS) message_center::MessageCenter::Initialize(); #endif profile_manager_.reset(new TestingProfileManager( TestingBrowserProcess::GetGlobal())); ASSERT_TRUE(profile_manager_->SetUp()); MessageCenterNotificationManager* manager = static_cast( g_browser_process->notification_ui_manager()); manager->SetMessageCenterTrayDelegateForTest( new message_center::FakeMessageCenterTrayDelegate( message_center::MessageCenter::Get(), base::Closure())); } virtual void TearDown() { g_browser_process->notification_ui_manager()->CancelAll(); profile_manager_.reset(); #if !defined(OS_CHROMEOS) message_center::MessageCenter::Shutdown(); #endif BrowserWithTestWindowTest::TearDown(); } protected: // Creates crash notification for the specified extension and returns // the created one. const Notification* CreateCrashNotification( scoped_refptr extension) { std::string notification_id = BackgroundContentsService:: GetNotificationDelegateIdForExtensionForTesting(extension->id()); NotificationWaiter waiter(notification_id, profile()); BackgroundContentsService::ShowBalloonForTesting( extension.get(), profile()); waiter.WaitForNotificationAdded(); return g_browser_process->notification_ui_manager()->FindById( notification_id, profile()); } private: scoped_ptr profile_manager_; DISALLOW_COPY_AND_ASSIGN(BackgroundContentsServiceNotificationTest); }; #endif // ENABLE_NOTIFICATIONS TEST_F(BackgroundContentsServiceTest, Create) { // Check for creation and leaks. TestingProfile profile; BackgroundContentsService service(&profile, command_line_.get()); } TEST_F(BackgroundContentsServiceTest, BackgroundContentsCreateDestroy) { TestingProfile profile; BackgroundContentsService service(&profile, command_line_.get()); MockBackgroundContents* contents = new MockBackgroundContents(&profile); EXPECT_FALSE(service.IsTracked(contents)); contents->SendOpenedNotification(&service); EXPECT_TRUE(service.IsTracked(contents)); delete contents; EXPECT_FALSE(service.IsTracked(contents)); } TEST_F(BackgroundContentsServiceTest, BackgroundContentsUrlAdded) { TestingProfile profile; BackgroundContentsService service(&profile, command_line_.get()); BackgroundContentsServiceFactory::GetInstance()-> RegisterUserPrefsOnBrowserContextForTest(&profile); GURL orig_url; GURL url("http://a/"); GURL url2("http://a/"); { scoped_ptr contents( new MockBackgroundContents(&profile)); EXPECT_EQ(0U, GetPrefs(&profile)->size()); contents->SendOpenedNotification(&service); contents->Navigate(url); EXPECT_EQ(1U, GetPrefs(&profile)->size()); EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid())); // Navigate the contents to a new url, should not change url. contents->Navigate(url2); EXPECT_EQ(1U, GetPrefs(&profile)->size()); EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid())); } // Contents are deleted, url should persist. EXPECT_EQ(1U, GetPrefs(&profile)->size()); } TEST_F(BackgroundContentsServiceTest, BackgroundContentsUrlAddedAndClosed) { TestingProfile profile; BackgroundContentsService service(&profile, command_line_.get()); BackgroundContentsServiceFactory::GetInstance()-> RegisterUserPrefsOnBrowserContextForTest(&profile); GURL url("http://a/"); MockBackgroundContents* contents = new MockBackgroundContents(&profile); EXPECT_EQ(0U, GetPrefs(&profile)->size()); contents->SendOpenedNotification(&service); contents->Navigate(url); EXPECT_EQ(1U, GetPrefs(&profile)->size()); EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid())); // Fake a window closed by script. contents->MockClose(&profile); EXPECT_EQ(0U, GetPrefs(&profile)->size()); } // Test what happens if a BackgroundContents shuts down (say, due to a renderer // crash) then is restarted. Should not persist URL twice. TEST_F(BackgroundContentsServiceTest, RestartBackgroundContents) { TestingProfile profile; BackgroundContentsService service(&profile, command_line_.get()); BackgroundContentsServiceFactory::GetInstance()-> RegisterUserPrefsOnBrowserContextForTest(&profile); GURL url("http://a/"); { scoped_ptr contents(new MockBackgroundContents( &profile, "appid")); contents->SendOpenedNotification(&service); contents->Navigate(url); EXPECT_EQ(1U, GetPrefs(&profile)->size()); EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid())); } // Contents deleted, url should be persisted. EXPECT_EQ(1U, GetPrefs(&profile)->size()); { // Reopen the BackgroundContents to the same URL, we should not register the // URL again. scoped_ptr contents(new MockBackgroundContents( &profile, "appid")); contents->SendOpenedNotification(&service); contents->Navigate(url); EXPECT_EQ(1U, GetPrefs(&profile)->size()); } } // Ensures that BackgroundContentsService properly tracks the association // between a BackgroundContents and its parent extension, including // unregistering the BC when the extension is uninstalled. TEST_F(BackgroundContentsServiceTest, TestApplicationIDLinkage) { TestingProfile profile; BackgroundContentsService service(&profile, command_line_.get()); BackgroundContentsServiceFactory::GetInstance()-> RegisterUserPrefsOnBrowserContextForTest(&profile); EXPECT_EQ(NULL, service.GetAppBackgroundContents(base::ASCIIToUTF16("appid"))); MockBackgroundContents* contents = new MockBackgroundContents(&profile, "appid"); scoped_ptr contents2( new MockBackgroundContents(&profile, "appid2")); contents->SendOpenedNotification(&service); EXPECT_EQ(contents, service.GetAppBackgroundContents(contents->appid())); contents2->SendOpenedNotification(&service); EXPECT_EQ(contents2.get(), service.GetAppBackgroundContents( contents2->appid())); EXPECT_EQ(0U, GetPrefs(&profile)->size()); // Navigate the contents, then make sure the one associated with the extension // is unregistered. GURL url("http://a/"); GURL url2("http://b/"); contents->Navigate(url); EXPECT_EQ(1U, GetPrefs(&profile)->size()); contents2->Navigate(url2); EXPECT_EQ(2U, GetPrefs(&profile)->size()); service.ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16("appid")); EXPECT_FALSE(service.IsTracked(contents)); EXPECT_EQ(NULL, service.GetAppBackgroundContents(base::ASCIIToUTF16("appid"))); EXPECT_EQ(1U, GetPrefs(&profile)->size()); EXPECT_EQ(url2.spec(), GetPrefURLForApp(&profile, contents2->appid())); } #if defined(ENABLE_NOTIFICATIONS) TEST_F(BackgroundContentsServiceNotificationTest, TestShowBalloon) { scoped_refptr extension = extension_test_util::LoadManifest("image_loading_tracker", "app.json"); ASSERT_TRUE(extension.get()); ASSERT_TRUE(extension->GetManifestData("icons")); const Notification* notification = CreateCrashNotification(extension); EXPECT_FALSE(notification->icon().IsEmpty()); } // Verify if a test notification can show the default extension icon for // a crash notification for an extension without icon. TEST_F(BackgroundContentsServiceNotificationTest, TestShowBalloonNoIcon) { // Extension manifest file with no 'icon' field. scoped_refptr extension = extension_test_util::LoadManifest("app", "manifest.json"); ASSERT_TRUE(extension.get()); ASSERT_FALSE(extension->GetManifestData("icons")); const Notification* notification = CreateCrashNotification(extension); EXPECT_FALSE(notification->icon().IsEmpty()); } TEST_F(BackgroundContentsServiceNotificationTest, TestShowTwoBalloons) { TestingProfile profile; scoped_refptr extension = extension_test_util::LoadManifest("app", "manifest.json"); ASSERT_TRUE(extension.get()); CreateCrashNotification(extension); CreateCrashNotification(extension); message_center::MessageCenter* message_center = message_center::MessageCenter::Get(); message_center::NotificationList::Notifications notifications = message_center->GetVisibleNotifications(); ASSERT_EQ(1u, notifications.size()); } #endif