diff options
author | atwilson@chromium.org <atwilson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-04 01:17:27 +0000 |
---|---|---|
committer | atwilson@chromium.org <atwilson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-04 01:17:27 +0000 |
commit | 88ea00fc725ef255d550faeb30e268922afa6e24 (patch) | |
tree | f7bdb398876faadc985566f0f5df685332ba7be6 | |
parent | 8cffde0ee3ef1f115d8e37e2c34daba3d6b06f64 (diff) | |
download | chromium_src-88ea00fc725ef255d550faeb30e268922afa6e24.zip chromium_src-88ea00fc725ef255d550faeb30e268922afa6e24.tar.gz chromium_src-88ea00fc725ef255d550faeb30e268922afa6e24.tar.bz2 |
No longer start BG mode until a BackgroundContents is loaded
BUG=120448
TEST=Install hosted app, ensure it does not kick off background mode until it opens a background contents via JS.
Review URL: http://codereview.chromium.org/10298002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@135264 0039d316-1c4b-4281-b951-d872f2087c98
19 files changed, 440 insertions, 37 deletions
diff --git a/chrome/browser/background/background_application_list_model.cc b/chrome/browser/background/background_application_list_model.cc index dfb4a2d..a414cf1 100644 --- a/chrome/browser/background/background_application_list_model.cc +++ b/chrome/browser/background/background_application_list_model.cc @@ -11,6 +11,8 @@ #include "base/utf_string_conversions.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/background/background_mode_manager.h" +#include "chrome/browser/background/background_contents_service.h" +#include "chrome/browser/background/background_contents_service_factory.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_service.h" @@ -79,8 +81,10 @@ void GetServiceApplications(ExtensionService* service, cursor != extensions->end(); ++cursor) { const Extension* extension = *cursor; - if (BackgroundApplicationListModel::IsBackgroundApp(*extension)) + if (BackgroundApplicationListModel::IsBackgroundApp(*extension, + service->profile())) { applications_result->push_back(extension); + } } // Walk the list of terminated extensions also (just because an extension @@ -90,8 +94,10 @@ void GetServiceApplications(ExtensionService* service, cursor != extensions->end(); ++cursor) { const Extension* extension = *cursor; - if (BackgroundApplicationListModel::IsBackgroundApp(*extension)) + if (BackgroundApplicationListModel::IsBackgroundApp(*extension, + service->profile())) { applications_result->push_back(extension); + } } std::string locale = g_browser_process->GetApplicationLocale(); @@ -167,6 +173,9 @@ BackgroundApplicationListModel::BackgroundApplicationListModel(Profile* profile) registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED, content::Source<Profile>(profile)); + registrar_.Add(this, + chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED, + content::Source<Profile>(profile)); ExtensionService* service = profile->GetExtensionService(); if (service && service->is_ready()) Update(); @@ -178,7 +187,7 @@ void BackgroundApplicationListModel::AddObserver(Observer* observer) { void BackgroundApplicationListModel::AssociateApplicationData( const Extension* extension) { - DCHECK(IsBackgroundApp(*extension)); + DCHECK(IsBackgroundApp(*extension, profile_)); Application* application = FindApplication(extension); if (!application) { // App position is used as a dynamic command and so must be less than any @@ -250,8 +259,41 @@ int BackgroundApplicationListModel::GetPosition( // static bool BackgroundApplicationListModel::IsBackgroundApp( - const Extension& extension) { - return extension.HasAPIPermission(ExtensionAPIPermission::kBackground); + const Extension& extension, Profile* profile) { + // An extension is a "background app" if it has the "background API" + // permission, and meets one of the following criteria: + // 1) It is an extension (not a hosted app). + // 2) It is a hosted app, and has a background contents registered or in the + // manifest. + + // Not a background app if we don't have the background permission. + if (!extension.HasAPIPermission(ExtensionAPIPermission::kBackground)) + return false; + + // Extensions and packaged apps with background permission are always treated + // as background apps. + if (!extension.is_hosted_app()) + return true; + + // Hosted apps with manifest-provided background pages are background apps. + if (extension.has_background_page()) + return true; + + BackgroundContentsService* service = + BackgroundContentsServiceFactory::GetForProfile(profile); + string16 app_id = ASCIIToUTF16(extension.id()); + // If we have an active or registered background contents for this app, then + // it's a background app. This covers the cases where the app has created its + // background contents, but it hasn't navigated yet, or the background + // contents crashed and hasn't yet been restarted - in both cases we still + // want to treat the app as a background app. + if (service->GetAppBackgroundContents(app_id) || + service->HasRegisteredBackgroundContents(app_id)) { + return true; + } + + // Doesn't meet our criteria, so it's not a background app. + return false; } void BackgroundApplicationListModel::Observe( @@ -281,6 +323,9 @@ void BackgroundApplicationListModel::Observe( content::Details<UpdatedExtensionPermissionsInfo>(details)-> permissions); break; + case chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED: + Update(); + break; default: NOTREACHED() << "Received unexpected notification"; } @@ -295,14 +340,14 @@ void BackgroundApplicationListModel::SendApplicationDataChangedNotifications( void BackgroundApplicationListModel::OnExtensionLoaded( const Extension* extension) { // We only care about extensions that are background applications - if (!IsBackgroundApp(*extension)) + if (!IsBackgroundApp(*extension, profile_)) return; AssociateApplicationData(extension); } void BackgroundApplicationListModel::OnExtensionUnloaded( const Extension* extension) { - if (!IsBackgroundApp(*extension)) + if (!IsBackgroundApp(*extension, profile_)) return; Update(); DissociateApplicationData(extension); @@ -315,11 +360,11 @@ void BackgroundApplicationListModel::OnExtensionPermissionsUpdated( if (permissions->HasAPIPermission(ExtensionAPIPermission::kBackground)) { switch (reason) { case UpdatedExtensionPermissionsInfo::ADDED: - DCHECK(IsBackgroundApp(*extension)); + DCHECK(IsBackgroundApp(*extension, profile_)); OnExtensionLoaded(extension); break; case UpdatedExtensionPermissionsInfo::REMOVED: - DCHECK(!IsBackgroundApp(*extension)); + DCHECK(!IsBackgroundApp(*extension, profile_)); Update(); DissociateApplicationData(extension); break; diff --git a/chrome/browser/background/background_application_list_model.h b/chrome/browser/background/background_application_list_model.h index 8e7b798..8722bc8a 100644 --- a/chrome/browser/background/background_application_list_model.h +++ b/chrome/browser/background/background_application_list_model.h @@ -17,8 +17,9 @@ class Profile; -// Model for list of Background Applications, that is, Extensions with -// kBackgroundPermission set, associated with a Profile. +// Model for list of Background Applications associated with a Profile (i.e. +// extensions with kBackgroundPermission set, or hosted apps with a +// BackgroundContents). class BackgroundApplicationListModel : public content::NotificationObserver { public: // Observer is informed of changes to the model. Users of the @@ -66,7 +67,8 @@ class BackgroundApplicationListModel : public content::NotificationObserver { const Extension* GetExtension(int position) const; // Returns true if the passed extension is a background app. - static bool IsBackgroundApp(const Extension& extension); + static bool IsBackgroundApp(const Extension& extension, + Profile* profile); // Dissociate observer from this model. void RemoveObserver(Observer* observer); diff --git a/chrome/browser/background/background_application_list_model_unittest.cc b/chrome/browser/background/background_application_list_model_unittest.cc index 2e8690e..fea9d20 100644 --- a/chrome/browser/background/background_application_list_model_unittest.cc +++ b/chrome/browser/background/background_application_list_model_unittest.cc @@ -45,6 +45,11 @@ class BackgroundApplicationListModelTest : public ExtensionServiceTestBase { InitializeEmptyExtensionService(); service_->OnLoadedInstalledExtensions(); /* Sends EXTENSIONS_READY */ } + + bool IsBackgroundApp(const Extension& app) { + return BackgroundApplicationListModel::IsBackgroundApp(app, + profile_.get()); + } }; // Returns a barebones test Extension object with the specified |name|. The @@ -84,7 +89,10 @@ std::string GenerateUniqueExtensionName() { void AddBackgroundPermission(ExtensionService* service, Extension* extension) { - if (BackgroundApplicationListModel::IsBackgroundApp(*extension)) return; + if (BackgroundApplicationListModel::IsBackgroundApp(*extension, + service->profile())) { + return; + } static scoped_refptr<Extension> temporary = CreateExtension(GenerateUniqueExtensionName(), true); @@ -96,7 +104,10 @@ void AddBackgroundPermission(ExtensionService* service, void RemoveBackgroundPermission(ExtensionService* service, Extension* extension) { - if (!BackgroundApplicationListModel::IsBackgroundApp(*extension)) return; + if (!BackgroundApplicationListModel::IsBackgroundApp(*extension, + service->profile())) { + return; + } extensions::PermissionsUpdater(service->profile()).RemovePermissions( extension, extension->GetActivePermissions()); } @@ -125,45 +136,45 @@ TEST_F(BackgroundApplicationListModelTest, ExplicitTest) { ASSERT_EQ(0U, model->size()); // Add alternating Extensions and Background Apps - ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext1)); + ASSERT_FALSE(IsBackgroundApp(*ext1)); service->AddExtension(ext1); ASSERT_EQ(1U, service->extensions()->size()); ASSERT_EQ(0U, model->size()); - ASSERT_TRUE(BackgroundApplicationListModel::IsBackgroundApp(*bgapp1)); + ASSERT_TRUE(IsBackgroundApp(*bgapp1)); service->AddExtension(bgapp1); ASSERT_EQ(2U, service->extensions()->size()); ASSERT_EQ(1U, model->size()); - ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext2)); + ASSERT_FALSE(IsBackgroundApp(*ext2)); service->AddExtension(ext2); ASSERT_EQ(3U, service->extensions()->size()); ASSERT_EQ(1U, model->size()); - ASSERT_TRUE(BackgroundApplicationListModel::IsBackgroundApp(*bgapp2)); + ASSERT_TRUE(IsBackgroundApp(*bgapp2)); service->AddExtension(bgapp2); ASSERT_EQ(4U, service->extensions()->size()); ASSERT_EQ(2U, model->size()); - ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext3)); + ASSERT_FALSE(IsBackgroundApp(*ext3)); service->AddExtension(ext3); ASSERT_EQ(5U, service->extensions()->size()); ASSERT_EQ(2U, model->size()); // Remove in FIFO order. - ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext1)); + ASSERT_FALSE(IsBackgroundApp(*ext1)); service->UninstallExtension(ext1->id(), false, NULL); ASSERT_EQ(4U, service->extensions()->size()); ASSERT_EQ(2U, model->size()); - ASSERT_TRUE(BackgroundApplicationListModel::IsBackgroundApp(*bgapp1)); + ASSERT_TRUE(IsBackgroundApp(*bgapp1)); service->UninstallExtension(bgapp1->id(), false, NULL); ASSERT_EQ(3U, service->extensions()->size()); ASSERT_EQ(1U, model->size()); - ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext2)); + ASSERT_FALSE(IsBackgroundApp(*ext2)); service->UninstallExtension(ext2->id(), false, NULL); ASSERT_EQ(2U, service->extensions()->size()); ASSERT_EQ(1U, model->size()); - ASSERT_TRUE(BackgroundApplicationListModel::IsBackgroundApp(*bgapp2)); + ASSERT_TRUE(IsBackgroundApp(*bgapp2)); service->UninstallExtension(bgapp2->id(), false, NULL); ASSERT_EQ(1U, service->extensions()->size()); ASSERT_EQ(0U, model->size()); - ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext3)); + ASSERT_FALSE(IsBackgroundApp(*ext3)); service->UninstallExtension(ext3->id(), false, NULL); ASSERT_EQ(0U, service->extensions()->size()); ASSERT_EQ(0U, model->size()); @@ -188,11 +199,11 @@ TEST_F(BackgroundApplicationListModelTest, AddRemovePermissionsTest) { ASSERT_EQ(0U, model->size()); // Add one (non-background) extension and one background application - ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext)); + ASSERT_FALSE(IsBackgroundApp(*ext)); service->AddExtension(ext); ASSERT_EQ(1U, service->extensions()->size()); ASSERT_EQ(0U, model->size()); - ASSERT_TRUE(BackgroundApplicationListModel::IsBackgroundApp(*bgapp)); + ASSERT_TRUE(IsBackgroundApp(*bgapp)); service->AddExtension(bgapp); ASSERT_EQ(2U, service->extensions()->size()); ASSERT_EQ(1U, model->size()); @@ -227,7 +238,8 @@ void AddExtension(ExtensionService* service, } scoped_refptr<Extension> extension = CreateExtension(GenerateUniqueExtensionName(), create_background); - ASSERT_EQ(BackgroundApplicationListModel::IsBackgroundApp(*extension), + ASSERT_EQ(BackgroundApplicationListModel::IsBackgroundApp(*extension, + service->profile()), create_background); extensions->insert(extension); ++*count; @@ -258,8 +270,10 @@ void RemoveExtension(ExtensionService* service, } scoped_refptr<Extension> extension = cursor->get(); std::string id = extension->id(); - if (BackgroundApplicationListModel::IsBackgroundApp(*extension)) + if (BackgroundApplicationListModel::IsBackgroundApp(*extension, + service->profile())) { --*expected; + } extensions->erase(cursor); --*count; ASSERT_EQ(*count, extensions->size()); @@ -290,7 +304,8 @@ void TogglePermission(ExtensionService* service, } scoped_refptr<Extension> extension = cursor->get(); std::string id = extension->id(); - if (BackgroundApplicationListModel::IsBackgroundApp(*extension)) { + if (BackgroundApplicationListModel::IsBackgroundApp(*extension, + service->profile())) { --*expected; ASSERT_EQ(*count, extensions->size()); RemoveBackgroundPermission(service, extension); diff --git a/chrome/browser/background/background_contents_service.cc b/chrome/browser/background/background_contents_service.cc index d6dc9a7..b98f264 100644 --- a/chrome/browser/background/background_contents_service.cc +++ b/chrome/browser/background/background_contents_service.cc @@ -215,19 +215,24 @@ void BackgroundContentsService::Observe( const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { - case chrome::NOTIFICATION_EXTENSIONS_READY: - LoadBackgroundContentsFromManifests( - content::Source<Profile>(source).ptr()); - LoadBackgroundContentsFromPrefs(content::Source<Profile>(source).ptr()); + case chrome::NOTIFICATION_EXTENSIONS_READY: { + Profile* profile = content::Source<Profile>(source).ptr(); + LoadBackgroundContentsFromManifests(profile); + LoadBackgroundContentsFromPrefs(profile); + SendChangeNotification(profile); break; + } case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED: BackgroundContentsShutdown( content::Details<BackgroundContents>(details).ptr()); + SendChangeNotification(content::Source<Profile>(source).ptr()); break; case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED: DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr())); UnregisterBackgroundContents( content::Details<BackgroundContents>(details).ptr()); + // CLOSED is always followed by a DELETED notification so we'll send our + // change notification there. break; case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: { DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr())); @@ -271,6 +276,7 @@ void BackgroundContentsService::Observe( // Remove any "This extension has crashed" balloons. ScheduleCloseBalloon(extension->id()); + SendChangeNotification(profile); break; } case chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED: @@ -310,12 +316,15 @@ void BackgroundContentsService::Observe( ShutdownAssociatedBackgroundContents( ASCIIToUTF16(content::Details<UnloadedExtensionInfo>(details)-> extension->id())); + SendChangeNotification(content::Source<Profile>(source).ptr()); break; case extension_misc::UNLOAD_REASON_UPDATE: { // If there is a manifest specified background page, then shut it down // here, since if the updated extension still has the background page, // then it will be loaded from LOADED callback. Otherwise, leave // BackgroundContents in place. + // We don't call SendChangeNotification here - it will be generated + // from the LOADED callback. const Extension* extension = content::Details<UnloadedExtensionInfo>(details)->extension; if (extension->has_background_page()) @@ -375,6 +384,13 @@ void BackgroundContentsService::LoadBackgroundContentsFromPrefs( } } +void BackgroundContentsService::SendChangeNotification(Profile* profile) { + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED, + content::Source<Profile>(profile), + content::Details<BackgroundContentsService>(this)); +} + void BackgroundContentsService::LoadBackgroundContentsForExtension( Profile* profile, const std::string& extension_id) { @@ -482,6 +498,9 @@ BackgroundContents* BackgroundContentsService::CreateBackgroundContents( chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED, content::Source<Profile>(profile), content::Details<BackgroundContentsOpenedDetails>(&details)); + + // A new background contents has been created - notify our listeners. + SendChangeNotification(profile); return contents; } @@ -509,6 +528,16 @@ void BackgroundContentsService::RegisterBackgroundContents( pref->SetWithoutPathExpansion(UTF16ToUTF8(appid), dict); } +bool BackgroundContentsService::HasRegisteredBackgroundContents( + const string16& app_id) { + if (!prefs_) + return false; + std::string app = UTF16ToUTF8(app_id); + const DictionaryValue* contents = + prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); + return contents->HasKey(app); +} + void BackgroundContentsService::UnregisterBackgroundContents( BackgroundContents* background_contents) { if (!prefs_) diff --git a/chrome/browser/background/background_contents_service.h b/chrome/browser/background/background_contents_service.h index 10b7677..7232cce 100644 --- a/chrome/browser/background/background_contents_service.h +++ b/chrome/browser/background/background_contents_service.h @@ -53,6 +53,12 @@ class BackgroundContentsService : private content::NotificationObserver, // or NULL if none. BackgroundContents* GetAppBackgroundContents(const string16& appid); + // Returns true if there's a registered BackgroundContents for this app. It + // is possible for this routine to return true when GetAppBackgroundContents() + // returns false, if the BackgroundContents closed due to the render process + // crashing. + bool HasRegisteredBackgroundContents(const string16& appid); + // Returns all currently opened BackgroundContents (used by the task manager). std::vector<BackgroundContents*> GetBackgroundContents() const; @@ -152,6 +158,11 @@ class BackgroundContentsService : private content::NotificationObserver, // Returns true if this BackgroundContents is in the contents_list_. bool IsTracked(BackgroundContents* contents) const; + // Sends out a notification when our association of background contents with + // apps may have changed (used by BackgroundApplicationListModel to update the + // set of background apps as new background contents are opened/closed). + void SendChangeNotification(Profile* profile); + // PrefService used to store list of background pages (or NULL if this is // running under an incognito profile). PrefService* prefs_; diff --git a/chrome/browser/background/background_mode_manager.cc b/chrome/browser/background/background_mode_manager.cc index e21c572..0a3bdd8 100644 --- a/chrome/browser/background/background_mode_manager.cc +++ b/chrome/browser/background/background_mode_manager.cc @@ -263,6 +263,10 @@ void BackgroundModeManager::LaunchBackgroundApplication( NEW_FOREGROUND_TAB); } +bool BackgroundModeManager::IsBackgroundModeActiveForTest() { + return in_background_mode_; +} + int BackgroundModeManager::NumberOfBackgroundModeData() { return background_mode_data_.size(); } @@ -290,10 +294,11 @@ void BackgroundModeManager::Observe( case chrome::NOTIFICATION_EXTENSION_LOADED: { Extension* extension = content::Details<Extension>(details).ptr(); - if (BackgroundApplicationListModel::IsBackgroundApp(*extension)) { + Profile* profile = content::Source<Profile>(source).ptr(); + if (BackgroundApplicationListModel::IsBackgroundApp( + *extension, profile)) { // Extensions loaded after the ExtensionsService is ready should be // treated as new installs. - Profile* profile = content::Source<Profile>(source).ptr(); if (profile->GetExtensionService()->is_ready()) OnBackgroundAppInstalled(extension); } @@ -537,6 +542,11 @@ void BackgroundModeManager::StartBackgroundMode() { // Display a status icon to exit Chrome. InitStatusTrayIcon(); + + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED, + content::Source<BackgroundModeManager>(this), + content::Details<bool>(&in_background_mode_)); } void BackgroundModeManager::InitStatusTrayIcon() { @@ -556,6 +566,10 @@ void BackgroundModeManager::EndBackgroundMode() { BrowserList::EndKeepAlive(); RemoveStatusTrayIcon(); + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED, + content::Source<BackgroundModeManager>(this), + content::Details<bool>(&in_background_mode_)); } void BackgroundModeManager::EnableBackgroundMode() { diff --git a/chrome/browser/background/background_mode_manager.h b/chrome/browser/background/background_mode_manager.h index 633c92b..c972048 100644 --- a/chrome/browser/background/background_mode_manager.h +++ b/chrome/browser/background/background_mode_manager.h @@ -45,7 +45,6 @@ class BackgroundModeManager : public content::NotificationObserver, public BackgroundApplicationListModel::Observer, public ProfileInfoCacheObserver, - public ProfileKeyedService, public ui::SimpleMenuModel::Delegate { public: BackgroundModeManager(CommandLine* command_line, @@ -63,8 +62,9 @@ class BackgroundModeManager int NumberOfBackgroundModeData(); private: - friend class TestBackgroundModeManager; + friend class AppBackgroundPageApiTest; friend class BackgroundModeManagerTest; + friend class TestBackgroundModeManager; FRIEND_TEST_ALL_PREFIXES(BackgroundModeManagerTest, BackgroundAppLoadUnload); FRIEND_TEST_ALL_PREFIXES(BackgroundModeManagerTest, @@ -238,6 +238,9 @@ class BackgroundModeManager // (virtual to allow overriding in tests). virtual bool IsBackgroundModePrefEnabled() const; + // Returns true if background mode is active. Used only by tests. + bool IsBackgroundModeActiveForTest(); + // Turns off background mode if it's currently enabled. void DisableBackgroundMode(); diff --git a/chrome/browser/extensions/app_background_page_apitest.cc b/chrome/browser/extensions/app_background_page_apitest.cc index 8b4a7b3..69cbc95 100644 --- a/chrome/browser/extensions/app_background_page_apitest.cc +++ b/chrome/browser/extensions/app_background_page_apitest.cc @@ -6,6 +6,8 @@ #include "base/utf_string_conversions.h" #include "chrome/browser/background/background_contents_service.h" #include "chrome/browser/background/background_contents_service_factory.h" +#include "chrome/browser/background/background_mode_manager.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/profiles/profile.h" @@ -14,6 +16,7 @@ #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" #include "chrome/test/base/ui_test_utils.h" +#include "content/public/browser/notification_service.h" #include "content/test/test_notification_tracker.h" #include "net/base/mock_host_resolver.h" @@ -44,6 +47,33 @@ class AppBackgroundPageApiTest : public ExtensionApiTest { return true; } + bool WaitForBackgroundMode(bool expected_background_mode) { +#if defined(OS_CHROMEOS) + // BackgroundMode is not supported on chromeos, so we should test the + // behavior of BackgroundContents, but not the background mode state itself. + return true; +#else + BackgroundModeManager* manager = + g_browser_process->background_mode_manager(); + // If background mode is disabled on this platform (e.g. cros), then skip + // this check. + if (!manager || !manager->IsBackgroundModePrefEnabled()) { + DLOG(WARNING) << "Skipping check - background mode disabled"; + return true; + } + if (manager->IsBackgroundModeActiveForTest() == expected_background_mode) + return true; + + // We are not currently in the expected state - wait for the state to + // change. + ui_test_utils::WindowedNotificationObserver watcher( + chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED, + content::NotificationService::AllSources()); + watcher.Wait(); + return manager->IsBackgroundModeActiveForTest() == expected_background_mode; +#endif + } + private: ScopedTempDir app_dir_; }; @@ -79,7 +109,12 @@ IN_PROC_BROWSER_TEST_F(AppBackgroundPageApiTest, MAYBE_Basic) { FilePath app_dir; ASSERT_TRUE(CreateApp(app_manifest, &app_dir)); ASSERT_TRUE(LoadExtension(app_dir)); + // Background mode should not be active until a background page is created. + ASSERT_TRUE(WaitForBackgroundMode(false)); ASSERT_TRUE(RunExtensionTest("app_background_page/basic")) << message_; + // The test closes the background contents, so we should fall back to no + // background mode at the end. + ASSERT_TRUE(WaitForBackgroundMode(false)); } // Crashy, http://crbug.com/69215. @@ -108,6 +143,7 @@ IN_PROC_BROWSER_TEST_F(AppBackgroundPageApiTest, DISABLED_LacksPermission) { ASSERT_TRUE(LoadExtension(app_dir)); ASSERT_TRUE(RunExtensionTest("app_background_page/lacks_permission")) << message_; + ASSERT_TRUE(WaitForBackgroundMode(false)); } IN_PROC_BROWSER_TEST_F(AppBackgroundPageApiTest, ManifestBackgroundPage) { @@ -137,7 +173,12 @@ IN_PROC_BROWSER_TEST_F(AppBackgroundPageApiTest, ManifestBackgroundPage) { FilePath app_dir; ASSERT_TRUE(CreateApp(app_manifest, &app_dir)); + // Background mode should not be active now because no background app was + // loaded. ASSERT_TRUE(LoadExtension(app_dir)); + // Background mode be active now because a background page was created when + // the app was loaded. + ASSERT_TRUE(WaitForBackgroundMode(true)); const Extension* extension = GetSingleLoadedExtension(); ASSERT_TRUE( @@ -323,3 +364,49 @@ IN_PROC_BROWSER_TEST_F(AppBackgroundPageApiTest, DISABLED_OpenPopupFromBGPage) { ASSERT_TRUE(LoadExtension(app_dir)); ASSERT_TRUE(RunExtensionTest("app_background_page/bg_open")) << message_; } + +IN_PROC_BROWSER_TEST_F(AppBackgroundPageApiTest, OpenThenClose) { + host_resolver()->AddRule("a.com", "127.0.0.1"); + ASSERT_TRUE(StartTestServer()); + + std::string app_manifest = base::StringPrintf( + "{" + " \"name\": \"App\"," + " \"version\": \"0.1\"," + " \"manifest_version\": 2," + " \"app\": {" + " \"urls\": [" + " \"http://a.com/\"" + " ]," + " \"launch\": {" + " \"web_url\": \"http://a.com:%d/\"" + " }" + " }," + " \"permissions\": [\"background\"]" + "}", + test_server()->host_port_pair().port()); + + FilePath app_dir; + ASSERT_TRUE(CreateApp(app_manifest, &app_dir)); + ASSERT_TRUE(LoadExtension(app_dir)); + // There isn't a background page loaded initially. + const Extension* extension = GetSingleLoadedExtension(); + ASSERT_FALSE( + BackgroundContentsServiceFactory::GetForProfile(browser()->profile())-> + GetAppBackgroundContents(ASCIIToUTF16(extension->id()))); + // Background mode should not be active until a background page is created. + ASSERT_TRUE(WaitForBackgroundMode(false)); + ASSERT_TRUE(RunExtensionTest("app_background_page/basic_open")) << message_; + // Background mode should be active now because a background page was created. + ASSERT_TRUE(WaitForBackgroundMode(true)); + ASSERT_TRUE( + BackgroundContentsServiceFactory::GetForProfile(browser()->profile())-> + GetAppBackgroundContents(ASCIIToUTF16(extension->id()))); + // Now close the BackgroundContents. + ASSERT_TRUE(RunExtensionTest("app_background_page/basic_close")) << message_; + // Background mode should no longer be active. + ASSERT_TRUE(WaitForBackgroundMode(false)); + ASSERT_FALSE( + BackgroundContentsServiceFactory::GetForProfile(browser()->profile())-> + GetAppBackgroundContents(ASCIIToUTF16(extension->id()))); +} diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h index 7d92902..0a41389 100644 --- a/chrome/common/chrome_notification_types.h +++ b/chrome/common/chrome_notification_types.h @@ -200,6 +200,17 @@ enum NotificationType { // and the details are the BackgroundContents. NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED, + // The background contents associated with a hosted app has changed (either + // a new background contents has been created, or an existing background + // contents has closed). The source is the parent Profile, and the details + // are the BackgroundContentsService. + NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED, + + // Chrome has entered/exited background mode. The source is the + // BackgroundModeManager and the details are a boolean value which is set to + // true if Chrome is now in background mode. + NOTIFICATION_BACKGROUND_MODE_CHANGED, + // This is sent when a login prompt is shown. The source is the // Source<NavigationController> for the tab in which the prompt is shown. // Details are a LoginNotificationDetails which provide the LoginHandler diff --git a/chrome/test/data/extensions/api_test/app_background_page/basic_close/content_script.js b/chrome/test/data/extensions/api_test/app_background_page/basic_close/content_script.js new file mode 100644 index 0000000..477f83a --- /dev/null +++ b/chrome/test/data/extensions/api_test/app_background_page/basic_close/content_script.js @@ -0,0 +1,17 @@ +// 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. +var scriptMessageEvent = document.createEvent("Event"); +scriptMessageEvent.initEvent('scriptMessage', true, true); + +var pageToScriptTunnel = document.getElementById("pageToScriptTunnel"); +pageToScriptTunnel.addEventListener("scriptMessage", function() { + var data = JSON.parse(pageToScriptTunnel.innerText); + chrome.extension.sendRequest(data); +}); + +chrome.extension.onRequest.addListener(function(request) { + var scriptToPageTunnel = document.getElementById("scriptToPageTunnel"); + scriptToPageTunnel.innerText = JSON.stringify(request); + scriptToPageTunnel.dispatchEvent(scriptMessageEvent); +}); diff --git a/chrome/test/data/extensions/api_test/app_background_page/basic_close/manifest.json b/chrome/test/data/extensions/api_test/app_background_page/basic_close/manifest.json new file mode 100644 index 0000000..c1568cc --- /dev/null +++ b/chrome/test/data/extensions/api_test/app_background_page/basic_close/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "app_background_page/basic_close", + "version": "0.1", + "manifest_version": 2, + "description": "Tests that web-extent background pages can be closed.", + "background": { + "page": "test.html" + }, + "permissions": ["tabs", "http://a.com/*"], + "content_scripts": [ + { + "matches": ["http://a.com/*"], + "js": ["content_script.js"], + "run_at": "document_end" + } + ] +} diff --git a/chrome/test/data/extensions/api_test/app_background_page/basic_close/test.html b/chrome/test/data/extensions/api_test/app_background_page/basic_close/test.html new file mode 100644 index 0000000..3efb342 --- /dev/null +++ b/chrome/test/data/extensions/api_test/app_background_page/basic_close/test.html @@ -0,0 +1,6 @@ +<!-- + * Copyright (c) 2011 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. +--> +<script src="test.js"></script> diff --git a/chrome/test/data/extensions/api_test/app_background_page/basic_close/test.js b/chrome/test/data/extensions/api_test/app_background_page/basic_close/test.js new file mode 100644 index 0000000..2690d42 --- /dev/null +++ b/chrome/test/data/extensions/api_test/app_background_page/basic_close/test.js @@ -0,0 +1,38 @@ +// 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. + +// This test closes the existing background page opened by a.html. + +var pagePrefix = + 'http://a.com:PORT/files/extensions/api_test/app_background_page/common'; + +// Dispatch "tunneled" functions from the live web pages to this testing page. +chrome.extension.onRequest.addListener(function(request) { + window[request.name](request.args); +}); + +// At no point should a window be created that contains the background page +// (bg.html). +chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { + if (tab.url.match("bg\.html$")) { + chrome.test.notifyFail("popup opened instead of background page"); + } +}); + +// Start the test by opening the first page in the app. +window.onload = function() { + // We wait for window.onload before getting the test config. If the + // config is requested before onload, then sometimes onload has already + // fired by the time chrome.test.getConfig()'s callback runs. + chrome.test.getConfig(function(config) { + var closeUrl = + pagePrefix.replace(/PORT/, config.testServer.port) + '/close.html'; + chrome.tabs.create({ 'url': closeUrl }, function(tab) {}); + }); +}; + +// Background page is closed now. +function onBackgroundPageClosed() { + chrome.test.notifyPass(); +}; diff --git a/chrome/test/data/extensions/api_test/app_background_page/basic_open/content_script.js b/chrome/test/data/extensions/api_test/app_background_page/basic_open/content_script.js new file mode 100644 index 0000000..477f83a --- /dev/null +++ b/chrome/test/data/extensions/api_test/app_background_page/basic_open/content_script.js @@ -0,0 +1,17 @@ +// 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. +var scriptMessageEvent = document.createEvent("Event"); +scriptMessageEvent.initEvent('scriptMessage', true, true); + +var pageToScriptTunnel = document.getElementById("pageToScriptTunnel"); +pageToScriptTunnel.addEventListener("scriptMessage", function() { + var data = JSON.parse(pageToScriptTunnel.innerText); + chrome.extension.sendRequest(data); +}); + +chrome.extension.onRequest.addListener(function(request) { + var scriptToPageTunnel = document.getElementById("scriptToPageTunnel"); + scriptToPageTunnel.innerText = JSON.stringify(request); + scriptToPageTunnel.dispatchEvent(scriptMessageEvent); +}); diff --git a/chrome/test/data/extensions/api_test/app_background_page/basic_open/manifest.json b/chrome/test/data/extensions/api_test/app_background_page/basic_open/manifest.json new file mode 100644 index 0000000..e97e05a --- /dev/null +++ b/chrome/test/data/extensions/api_test/app_background_page/basic_open/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "app_background_page/basic_open", + "version": "0.1", + "manifest_version": 2, + "description": "Tests that web-extent background pages can be opened.", + "background": { + "page": "test.html" + }, + "permissions": ["tabs", "http://a.com/*"], + "content_scripts": [ + { + "matches": ["http://a.com/*"], + "js": ["content_script.js"], + "run_at": "document_end" + } + ] +} diff --git a/chrome/test/data/extensions/api_test/app_background_page/basic_open/test.html b/chrome/test/data/extensions/api_test/app_background_page/basic_open/test.html new file mode 100644 index 0000000..3efb342 --- /dev/null +++ b/chrome/test/data/extensions/api_test/app_background_page/basic_open/test.html @@ -0,0 +1,6 @@ +<!-- + * Copyright (c) 2011 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. +--> +<script src="test.js"></script> diff --git a/chrome/test/data/extensions/api_test/app_background_page/basic_open/test.js b/chrome/test/data/extensions/api_test/app_background_page/basic_open/test.js new file mode 100644 index 0000000..25363fd --- /dev/null +++ b/chrome/test/data/extensions/api_test/app_background_page/basic_open/test.js @@ -0,0 +1,38 @@ +// 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. + +// This test opens a single background page opened by a.html. + +var pagePrefix = + 'http://a.com:PORT/files/extensions/api_test/app_background_page/common'; + +// Dispatch "tunneled" functions from the live web pages to this testing page. +chrome.extension.onRequest.addListener(function(request) { + window[request.name](request.args); +}); + +// At no point should a window be created that contains the background page +// (bg.html). +chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { + if (tab.url.match("bg\.html$")) { + chrome.test.notifyFail("popup opened instead of background page"); + } +}); + +// Start the test by opening the first page in the app. +window.onload = function() { + // We wait for window.onload before getting the test config. If the + // config is requested before onload, then sometimes onload has already + // fired by the time chrome.test.getConfig()'s callback runs. + chrome.test.getConfig(function(config) { + var closeUrl = + pagePrefix.replace(/PORT/, config.testServer.port) + '/a.html'; + chrome.tabs.create({ 'url': closeUrl }, function(tab) {}); + }); +}; + +// Background page is open now. +function onBackgroundPageLoaded() { + chrome.test.notifyPass(); +}; diff --git a/chrome/test/data/extensions/api_test/app_background_page/common/close.html b/chrome/test/data/extensions/api_test/app_background_page/common/close.html new file mode 100644 index 0000000..23093e9 --- /dev/null +++ b/chrome/test/data/extensions/api_test/app_background_page/common/close.html @@ -0,0 +1,22 @@ +<script src="common.js"></script> +<h1 id="status">Close BG Page</h1> +<div style="display:none" id="pageToScriptTunnel"></div> +<div style="display:none" id="scriptToPageTunnel"></div> + +<script> +var backgroundWindow; + +window.onload = function() { + // This page just closes an existing BG window. + setupScriptTunnel(); + backgroundWindow = window.open('bg.html', 'bg', 'background'); + if (backgroundWindow) { + setStatus('closing background page'); + backgroundWindow.close(); + notifyBackgroundPageClosed(); + } else { + notifyBackgroundPagePermissionDenied(); + setStatus('background page permission denied'); + } +} +</script> diff --git a/chrome/test/data/extensions/api_test/app_background_page/common/common.js b/chrome/test/data/extensions/api_test/app_background_page/common/common.js index b3d3458..987e746 100644 --- a/chrome/test/data/extensions/api_test/app_background_page/common/common.js +++ b/chrome/test/data/extensions/api_test/app_background_page/common/common.js @@ -1,3 +1,6 @@ +// 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. var scriptMessageEvent; var pageToScriptTunnel; var scriptToPageTunnel; @@ -54,3 +57,8 @@ function notifyBackgroundPageClosing() { pageToScriptTunnel.innerText = JSON.stringify(messageData(arguments)); pageToScriptTunnel.dispatchEvent(scriptMessageEvent); } + +function notifyBackgroundPageClosed() { + pageToScriptTunnel.innerText = JSON.stringify(messageData(arguments)); + pageToScriptTunnel.dispatchEvent(scriptMessageEvent); +} |