summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoratwilson@chromium.org <atwilson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-04 01:17:27 +0000
committeratwilson@chromium.org <atwilson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-04 01:17:27 +0000
commit88ea00fc725ef255d550faeb30e268922afa6e24 (patch)
treef7bdb398876faadc985566f0f5df685332ba7be6
parent8cffde0ee3ef1f115d8e37e2c34daba3d6b06f64 (diff)
downloadchromium_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
-rw-r--r--chrome/browser/background/background_application_list_model.cc63
-rw-r--r--chrome/browser/background/background_application_list_model.h8
-rw-r--r--chrome/browser/background/background_application_list_model_unittest.cc49
-rw-r--r--chrome/browser/background/background_contents_service.cc37
-rw-r--r--chrome/browser/background/background_contents_service.h11
-rw-r--r--chrome/browser/background/background_mode_manager.cc18
-rw-r--r--chrome/browser/background/background_mode_manager.h7
-rw-r--r--chrome/browser/extensions/app_background_page_apitest.cc87
-rw-r--r--chrome/common/chrome_notification_types.h11
-rw-r--r--chrome/test/data/extensions/api_test/app_background_page/basic_close/content_script.js17
-rw-r--r--chrome/test/data/extensions/api_test/app_background_page/basic_close/manifest.json17
-rw-r--r--chrome/test/data/extensions/api_test/app_background_page/basic_close/test.html6
-rw-r--r--chrome/test/data/extensions/api_test/app_background_page/basic_close/test.js38
-rw-r--r--chrome/test/data/extensions/api_test/app_background_page/basic_open/content_script.js17
-rw-r--r--chrome/test/data/extensions/api_test/app_background_page/basic_open/manifest.json17
-rw-r--r--chrome/test/data/extensions/api_test/app_background_page/basic_open/test.html6
-rw-r--r--chrome/test/data/extensions/api_test/app_background_page/basic_open/test.js38
-rw-r--r--chrome/test/data/extensions/api_test/app_background_page/common/close.html22
-rw-r--r--chrome/test/data/extensions/api_test/app_background_page/common/common.js8
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);
+}