summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorTorne (Richard Coles) <torne@google.com>2013-05-29 14:40:03 +0100
committerTorne (Richard Coles) <torne@google.com>2013-05-29 14:40:03 +0100
commit90dce4d38c5ff5333bea97d859d4e484e27edf0c (patch)
tree9c51c7dd97d24b15befa97a3482c51851e5383a1 /apps
parent1515035f5917d10d363b0888a3615d581ad8b83f (diff)
downloadchromium_org-90dce4d38c5ff5333bea97d859d4e484e27edf0c.tar.gz
Merge from Chromium at DEPS revision r202854
This commit was generated by merge_to_master.py. Change-Id: Idca323f71ef844a9e04f454d4f070b1e398f2deb
Diffstat (limited to 'apps')
-rw-r--r--apps/DEPS1
-rw-r--r--apps/app_restore_service.cc83
-rw-r--r--apps/app_restore_service.h32
-rw-r--r--apps/app_restore_service_browsertest.cc38
-rw-r--r--apps/app_restore_service_factory.cc22
-rw-r--r--apps/app_restore_service_factory.h12
-rw-r--r--apps/app_shim/OWNERS2
-rw-r--r--apps/app_shim/app_shim_handler_mac.cc15
-rw-r--r--apps/app_shim/app_shim_handler_mac.h15
-rw-r--r--apps/app_shim/app_shim_host_mac.cc116
-rw-r--r--apps/app_shim/app_shim_host_mac.h20
-rw-r--r--apps/app_shim/app_shim_host_mac_unittest.cc26
-rw-r--r--apps/app_shim/app_shim_host_manager_mac.h5
-rw-r--r--apps/app_shim/app_shim_host_manager_mac.mm3
-rw-r--r--apps/app_shim/extension_app_shim_handler_mac.cc130
-rw-r--r--apps/app_shim/extension_app_shim_handler_mac.h60
-rw-r--r--apps/app_shim/extension_app_shim_handler_mac_unittest.cc139
-rw-r--r--apps/apps.gypi8
-rw-r--r--apps/saved_files_service.cc441
-rw-r--r--apps/saved_files_service.h135
-rw-r--r--apps/saved_files_service_factory.cc36
-rw-r--r--apps/saved_files_service_factory.h35
-rw-r--r--apps/saved_files_service_unittest.cc239
-rw-r--r--apps/shell_window_geometry_cache.cc276
-rw-r--r--apps/shell_window_geometry_cache.h137
-rw-r--r--apps/shell_window_geometry_cache_unittest.cc256
-rw-r--r--apps/shortcut_manager.cc6
-rw-r--r--apps/shortcut_manager.h4
-rw-r--r--apps/shortcut_manager_factory.cc20
-rw-r--r--apps/shortcut_manager_factory.h14
30 files changed, 2088 insertions, 238 deletions
diff --git a/apps/DEPS b/apps/DEPS
index fb10d13439..b7951f84d4 100644
--- a/apps/DEPS
+++ b/apps/DEPS
@@ -1,6 +1,7 @@
include_rules = [
"+base",
"+content",
+ "+components/browser_context_keyed_service",
"+ui",
"+win8",
# Temporary allowed includes.
diff --git a/apps/app_restore_service.cc b/apps/app_restore_service.cc
index f8932fbc01..88ac811840 100644
--- a/apps/app_restore_service.cc
+++ b/apps/app_restore_service.cc
@@ -4,13 +4,15 @@
#include "apps/app_restore_service.h"
+#include "apps/saved_files_service.h"
#include "chrome/browser/extensions/api/app_runtime/app_runtime_api.h"
-#include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_host.h"
+#include "chrome/browser/extensions/extension_prefs.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/platform_app_launcher.h"
+#include "chrome/browser/ui/extensions/shell_window.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_set.h"
@@ -55,6 +57,7 @@ AppRestoreService::AppRestoreService(Profile* profile)
registrar_.Add(
this, chrome::NOTIFICATION_APP_TERMINATING,
content::NotificationService::AllSources());
+ StartObservingShellWindows();
}
void AppRestoreService::HandleStartup(bool should_restore_apps) {
@@ -67,13 +70,16 @@ void AppRestoreService::HandleStartup(bool should_restore_apps) {
it != extensions->end(); ++it) {
const Extension* extension = *it;
if (extension_prefs->IsExtensionRunning(extension->id())) {
- std::vector<SavedFileEntry> file_entries;
- extensions::app_file_handler_util::GetSavedFileEntries(extension_prefs,
- extension->id(),
- &file_entries);
RecordAppStop(extension->id());
- if (should_restore_apps)
- RestoreApp(*it, file_entries);
+ // If we are not restoring apps (e.g., because it is a clean restart), and
+ // the app does not have retain permission, explicitly clear the retained
+ // entries queue.
+ if (should_restore_apps) {
+ RestoreApp(*it);
+ } else {
+ SavedFilesService::Get(profile_)->ClearQueueIfNoRetainPermission(
+ extension);
+ }
}
}
}
@@ -102,11 +108,28 @@ void AppRestoreService::Observe(int type,
// Stop listening to NOTIFICATION_EXTENSION_HOST_DESTROYED in particular
// as all extension hosts will be destroyed as a result of shutdown.
registrar_.RemoveAll();
+ // Stop listening to the ShellWindowRegistry for window closes, because
+ // all windows will be closed as a result of shutdown.
+ StopObservingShellWindows();
break;
}
}
}
+void AppRestoreService::OnShellWindowAdded(ShellWindow* shell_window) {
+ RecordIfAppHasWindows(shell_window->extension_id());
+}
+
+void AppRestoreService::OnShellWindowIconChanged(ShellWindow* shell_window) {
+}
+
+void AppRestoreService::OnShellWindowRemoved(ShellWindow* shell_window) {
+ RecordIfAppHasWindows(shell_window->extension_id());
+}
+
+void AppRestoreService::Shutdown() {
+ StopObservingShellWindows();
+}
void AppRestoreService::RecordAppStart(const std::string& extension_id) {
ExtensionPrefs* extension_prefs =
@@ -118,16 +141,46 @@ void AppRestoreService::RecordAppStop(const std::string& extension_id) {
ExtensionPrefs* extension_prefs =
ExtensionSystem::Get(profile_)->extension_service()->extension_prefs();
extension_prefs->SetExtensionRunning(extension_id, false);
- extensions::app_file_handler_util::ClearSavedFileEntries(
- extension_prefs, extension_id);
}
-void AppRestoreService::RestoreApp(
- const Extension* extension,
- const std::vector<SavedFileEntry>& file_entries) {
- extensions::RestartPlatformAppWithFileEntries(profile_,
- extension,
- file_entries);
+void AppRestoreService::RecordIfAppHasWindows(
+ const std::string& id) {
+ ExtensionService* extension_service =
+ ExtensionSystem::Get(profile_)->extension_service();
+ ExtensionPrefs* extension_prefs = extension_service->extension_prefs();
+
+ // If the extension isn't running then we will already have recorded whether
+ // it had windows or not.
+ if (!extension_prefs->IsExtensionRunning(id))
+ return;
+
+ extensions::ShellWindowRegistry* shell_window_registry =
+ extensions::ShellWindowRegistry::Factory::GetForProfile(
+ profile_, false /* create */);
+ if (!shell_window_registry)
+ return;
+ bool has_windows = !shell_window_registry->GetShellWindowsForApp(id).empty();
+ extension_prefs->SetHasWindows(id, has_windows);
+}
+
+void AppRestoreService::RestoreApp(const Extension* extension) {
+ extensions::RestartPlatformApp(profile_, extension);
+}
+
+void AppRestoreService::StartObservingShellWindows() {
+ extensions::ShellWindowRegistry* shell_window_registry =
+ extensions::ShellWindowRegistry::Factory::GetForProfile(
+ profile_, false /* create */);
+ if (shell_window_registry)
+ shell_window_registry->AddObserver(this);
+}
+
+void AppRestoreService::StopObservingShellWindows() {
+ extensions::ShellWindowRegistry* shell_window_registry =
+ extensions::ShellWindowRegistry::Factory::GetForProfile(
+ profile_, false /* create */);
+ if (shell_window_registry)
+ shell_window_registry->RemoveObserver(this);
}
} // namespace apps
diff --git a/apps/app_restore_service.h b/apps/app_restore_service.h
index f27c83cade..65d3de2e58 100644
--- a/apps/app_restore_service.h
+++ b/apps/app_restore_service.h
@@ -8,28 +8,23 @@
#include <string>
#include <vector>
-#include "chrome/browser/profiles/profile_keyed_service.h"
+#include "chrome/browser/extensions/shell_window_registry.h"
+#include "components/browser_context_keyed_service/browser_context_keyed_service.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
namespace extensions {
class Extension;
-
-namespace app_file_handler_util {
-struct SavedFileEntry;
-}
-
}
class Profile;
-using extensions::app_file_handler_util::SavedFileEntry;
-
namespace apps {
// Tracks what apps need to be restarted when the browser restarts.
-class AppRestoreService : public ProfileKeyedService,
- public content::NotificationObserver {
+class AppRestoreService : public BrowserContextKeyedService,
+ public content::NotificationObserver,
+ public extensions::ShellWindowRegistry::Observer {
public:
// Returns true if apps should be restored on the current platform, given
// whether this new browser process launched due to a restart.
@@ -47,11 +42,22 @@ class AppRestoreService : public ProfileKeyedService,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
+ // extensions::ShellWindowRegistry::Observer.
+ virtual void OnShellWindowAdded(ShellWindow* shell_window) OVERRIDE;
+ virtual void OnShellWindowIconChanged(ShellWindow* shell_window) OVERRIDE;
+ virtual void OnShellWindowRemoved(ShellWindow* shell_window) OVERRIDE;
+
+ // BrowserContextKeyedService.
+ virtual void Shutdown() OVERRIDE;
+
void RecordAppStart(const std::string& extension_id);
void RecordAppStop(const std::string& extension_id);
- void RestoreApp(
- const extensions::Extension* extension,
- const std::vector<SavedFileEntry>& file_entries);
+ void RecordIfAppHasWindows(const std::string& id);
+
+ void RestoreApp(const extensions::Extension* extension);
+
+ void StartObservingShellWindows();
+ void StopObservingShellWindows();
content::NotificationRegistrar registrar_;
Profile* profile_;
diff --git a/apps/app_restore_service_browsertest.cc b/apps/app_restore_service_browsertest.cc
index a335617e75..cf832fedf6 100644
--- a/apps/app_restore_service_browsertest.cc
+++ b/apps/app_restore_service_browsertest.cc
@@ -4,17 +4,14 @@
#include "apps/app_restore_service.h"
#include "apps/app_restore_service_factory.h"
-#include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
+#include "apps/saved_files_service.h"
#include "chrome/browser/extensions/api/file_system/file_system_api.h"
#include "chrome/browser/extensions/extension_prefs.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/extension_test_message_listener.h"
#include "chrome/browser/extensions/platform_app_browsertest_util.h"
#include "chrome/common/extensions/extension.h"
#include "content/public/test/test_utils.h"
-using extensions::app_file_handler_util::SavedFileEntry;
using extensions::Extension;
using extensions::ExtensionPrefs;
using extensions::ExtensionSystem;
@@ -35,9 +32,7 @@ IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, RunningAppsAreRecorded) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("platform_apps/restart_test"));
ASSERT_TRUE(extension);
- ExtensionService* extension_service =
- ExtensionSystem::Get(browser()->profile())->extension_service();
- ExtensionPrefs* extension_prefs = extension_service->extension_prefs();
+ ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(browser()->profile());
// App is running.
ASSERT_TRUE(extension_prefs->IsExtensionRunning(extension->id()));
@@ -82,21 +77,15 @@ IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, FileAccessIsSavedToPrefs) {
ASSERT_TRUE(extension);
file_written_listener.WaitUntilSatisfied();
- ExtensionPrefs* extension_prefs =
- ExtensionSystem::Get(browser()->profile())->extension_prefs();
+ SavedFilesService* saved_files_service = SavedFilesService::Get(profile());
- // Record the file entries in prefs because when the app gets suspended it
- // will have them all cleared.
- std::vector<SavedFileEntry> file_entries;
- extensions::app_file_handler_util::GetSavedFileEntries(
- extension_prefs, extension->id(), &file_entries);
+ std::vector<SavedFileEntry> file_entries =
+ saved_files_service->GetAllFileEntries(extension->id());
// One for the read-only file entry and one for the writable file entry.
ASSERT_EQ(2u, file_entries.size());
extension_suspended.Wait();
- file_entries.clear();
- extensions::app_file_handler_util::GetSavedFileEntries(
- extension_prefs, extension->id(), &file_entries);
+ file_entries = saved_files_service->GetAllFileEntries(extension->id());
// File entries should be cleared when the extension is suspended.
ASSERT_TRUE(file_entries.empty());
}
@@ -127,13 +116,10 @@ IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, FileAccessIsRestored) {
file_written_listener.WaitUntilSatisfied();
ExtensionPrefs* extension_prefs =
- ExtensionSystem::Get(browser()->profile())->extension_prefs();
- // Record the file entries in prefs because when the app gets suspended it
- // will have them all cleared.
- std::vector<SavedFileEntry> file_entries;
- extensions::app_file_handler_util::GetSavedFileEntries(extension_prefs,
- extension->id(),
- &file_entries);
+ ExtensionPrefs::Get(browser()->profile());
+ SavedFilesService* saved_files_service = SavedFilesService::Get(profile());
+ std::vector<SavedFileEntry> file_entries =
+ saved_files_service->GetAllFileEntries(extension->id());
extension_suspended.Wait();
// Simulate a restart by populating the preferences as if the browser didn't
@@ -141,8 +127,8 @@ IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, FileAccessIsRestored) {
extension_prefs->SetExtensionRunning(extension->id(), true);
for (std::vector<SavedFileEntry>::const_iterator it = file_entries.begin();
it != file_entries.end(); ++it) {
- extensions::app_file_handler_util::AddSavedFileEntry(
- extension_prefs, extension->id(), it->id, it->path, it->writable);
+ saved_files_service->RegisterFileEntry(
+ extension->id(), it->id, it->path, it->writable);
}
apps::AppRestoreServiceFactory::GetForProfile(browser()->profile())->
diff --git a/apps/app_restore_service_factory.cc b/apps/app_restore_service_factory.cc
index 71dd80b582..ffd31a701c 100644
--- a/apps/app_restore_service_factory.cc
+++ b/apps/app_restore_service_factory.cc
@@ -5,22 +5,16 @@
#include "apps/app_restore_service_factory.h"
#include "apps/app_restore_service.h"
+#include "chrome/browser/extensions/shell_window_registry.h"
#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_dependency_manager.h"
+#include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
namespace apps {
// static
AppRestoreService* AppRestoreServiceFactory::GetForProfile(Profile* profile) {
return static_cast<AppRestoreService*>(
- GetInstance()->GetServiceForProfile(profile, true));
-}
-
-// static
-void AppRestoreServiceFactory::ResetForProfile(Profile* profile) {
- AppRestoreServiceFactory* factory = GetInstance();
- factory->ProfileShutdown(profile);
- factory->ProfileDestroyed(profile);
+ GetInstance()->GetServiceForBrowserContext(profile, true));
}
AppRestoreServiceFactory* AppRestoreServiceFactory::GetInstance() {
@@ -28,19 +22,21 @@ AppRestoreServiceFactory* AppRestoreServiceFactory::GetInstance() {
}
AppRestoreServiceFactory::AppRestoreServiceFactory()
- : ProfileKeyedServiceFactory("AppRestoreService",
- ProfileDependencyManager::GetInstance()) {
+ : BrowserContextKeyedServiceFactory(
+ "AppRestoreService",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(extensions::ShellWindowRegistry::Factory::GetInstance());
}
AppRestoreServiceFactory::~AppRestoreServiceFactory() {
}
-ProfileKeyedService* AppRestoreServiceFactory::BuildServiceInstanceFor(
+BrowserContextKeyedService* AppRestoreServiceFactory::BuildServiceInstanceFor(
content::BrowserContext* profile) const {
return new AppRestoreService(static_cast<Profile*>(profile));
}
-bool AppRestoreServiceFactory::ServiceIsCreatedWithProfile() const {
+bool AppRestoreServiceFactory::ServiceIsCreatedWithBrowserContext() const {
return true;
}
diff --git a/apps/app_restore_service_factory.h b/apps/app_restore_service_factory.h
index b84c585a64..1c0d438905 100644
--- a/apps/app_restore_service_factory.h
+++ b/apps/app_restore_service_factory.h
@@ -6,7 +6,7 @@
#define APPS_APP_RESTORE_SERVICE_FACTORY_H_
#include "base/memory/singleton.h"
-#include "chrome/browser/profiles/profile_keyed_service_factory.h"
+#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h"
class Profile;
@@ -17,12 +17,10 @@ class AppRestoreService;
// Singleton that owns all AppRestoreServices and associates them with
// Profiles. Listens for the Profile's destruction notification and cleans up
// the associated AppRestoreService.
-class AppRestoreServiceFactory : public ProfileKeyedServiceFactory {
+class AppRestoreServiceFactory : public BrowserContextKeyedServiceFactory {
public:
static AppRestoreService* GetForProfile(Profile* profile);
- static void ResetForProfile(Profile* profile);
-
static AppRestoreServiceFactory* GetInstance();
private:
@@ -31,10 +29,10 @@ class AppRestoreServiceFactory : public ProfileKeyedServiceFactory {
AppRestoreServiceFactory();
virtual ~AppRestoreServiceFactory();
- // ProfileKeyedServiceFactory:
- virtual ProfileKeyedService* BuildServiceInstanceFor(
+ // BrowserContextKeyedServiceFactory:
+ virtual BrowserContextKeyedService* BuildServiceInstanceFor(
content::BrowserContext* profile) const OVERRIDE;
- virtual bool ServiceIsCreatedWithProfile() const OVERRIDE;
+ virtual bool ServiceIsCreatedWithBrowserContext() const OVERRIDE;
};
} // namespace apps
diff --git a/apps/app_shim/OWNERS b/apps/app_shim/OWNERS
index c046a3267d..13c6f49f97 100644
--- a/apps/app_shim/OWNERS
+++ b/apps/app_shim/OWNERS
@@ -1 +1 @@
-jeremya@chromium.org
+tapted@chromium.org
diff --git a/apps/app_shim/app_shim_handler_mac.cc b/apps/app_shim/app_shim_handler_mac.cc
index 18cca92426..6076557710 100644
--- a/apps/app_shim/app_shim_handler_mac.cc
+++ b/apps/app_shim/app_shim_handler_mac.cc
@@ -25,7 +25,7 @@ class AppShimHandlerRegistry {
if (it != handlers_.end())
return it->second;
- return NULL;
+ return default_handler_;
}
bool SetForAppMode(const std::string& app_mode_id, AppShimHandler* handler) {
@@ -36,14 +36,20 @@ class AppShimHandlerRegistry {
return inserted_or_removed;
}
+ void SetDefaultHandler(AppShimHandler* handler) {
+ DCHECK_NE(default_handler_ == NULL, handler == NULL);
+ default_handler_ = handler;
+ }
+
private:
friend struct DefaultSingletonTraits<AppShimHandlerRegistry>;
typedef std::map<std::string, AppShimHandler*> HandlerMap;
- AppShimHandlerRegistry() {}
+ AppShimHandlerRegistry() : default_handler_(NULL) {}
~AppShimHandlerRegistry() {}
HandlerMap handlers_;
+ AppShimHandler* default_handler_;
DISALLOW_COPY_AND_ASSIGN(AppShimHandlerRegistry);
};
@@ -67,4 +73,9 @@ AppShimHandler* AppShimHandler::GetForAppMode(const std::string& app_mode_id) {
return AppShimHandlerRegistry::GetInstance()->GetForAppMode(app_mode_id);
}
+// static
+void AppShimHandler::SetDefaultHandler(AppShimHandler* handler) {
+ AppShimHandlerRegistry::GetInstance()->SetDefaultHandler(handler);
+}
+
} // namespace apps
diff --git a/apps/app_shim/app_shim_handler_mac.h b/apps/app_shim/app_shim_handler_mac.h
index 6a694c0a0a..507fe5f841 100644
--- a/apps/app_shim/app_shim_handler_mac.h
+++ b/apps/app_shim/app_shim_handler_mac.h
@@ -7,6 +7,8 @@
#include <string>
+class Profile;
+
namespace apps {
// Registrar, and interface for services that can handle interactions with OSX
@@ -18,6 +20,10 @@ class AppShimHandler {
// Invoked when the app is closed in the browser process.
virtual void OnAppClosed() = 0;
+ // Allows the handler to determine which app this host corresponds to.
+ virtual Profile* GetProfile() const = 0;
+ virtual std::string GetAppId() const = 0;
+
protected:
virtual ~Host() {}
};
@@ -29,10 +35,15 @@ class AppShimHandler {
// Remove a handler for an |app_mode_id|.
static void RemoveHandler(const std::string& app_mode_id);
- // Returns the handler registered for the given |app_mode_id|, or NULL if none
- // is registered.
+ // Returns the handler registered for the given |app_mode_id|. If there is
+ // none registered, it returns the default handler or NULL if there is no
+ // default handler.
static AppShimHandler* GetForAppMode(const std::string& app_mode_id);
+ // Sets the default handler to return when there is no app-specific handler.
+ // Setting this to NULL removes the default handler.
+ static void SetDefaultHandler(AppShimHandler* handler);
+
// Invoked by the shim host when the shim process is launched. The handler
// must return true if successful, or false to indicate back to the shim
// process that it should close.
diff --git a/apps/app_shim/app_shim_host_mac.cc b/apps/app_shim/app_shim_host_mac.cc
index bfe28f9ac4..ad63e93038 100644
--- a/apps/app_shim/app_shim_host_mac.cc
+++ b/apps/app_shim/app_shim_host_mac.cc
@@ -10,16 +10,9 @@
#include "base/files/file_path.h"
#include "base/logging.h"
#include "chrome/browser/browser_process.h"
-#include "chrome/browser/extensions/extension_host.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_system.h"
-#include "chrome/browser/extensions/shell_window_registry.h"
#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/extensions/application_launch.h"
-#include "chrome/browser/ui/extensions/shell_window.h"
-#include "chrome/common/extensions/extension_constants.h"
+#include "content/public/browser/browser_thread.h"
#include "ipc/ipc_channel_proxy.h"
-#include "ui/base/cocoa/focus_window_set.h"
AppShimHost::AppShimHost()
: channel_(NULL), profile_(NULL) {
@@ -36,7 +29,16 @@ void AppShimHost::ServeChannel(const IPC::ChannelHandle& handle) {
DCHECK(CalledOnValidThread());
DCHECK(!channel_.get());
channel_.reset(new IPC::ChannelProxy(handle, IPC::Channel::MODE_SERVER, this,
- BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)));
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::IO)));
+}
+
+Profile* AppShimHost::GetProfile() const {
+ return profile_;
+}
+
+std::string AppShimHost::GetAppId() const {
+ return app_id_;
}
bool AppShimHost::OnMessageReceived(const IPC::Message& message) {
@@ -62,57 +64,25 @@ bool AppShimHost::Send(IPC::Message* message) {
void AppShimHost::OnLaunchApp(std::string profile_dir, std::string app_id) {
DCHECK(CalledOnValidThread());
+ DCHECK(!profile_);
+ if (profile_) {
+ // Only one app launch message per channel.
+ Send(new AppShimMsg_LaunchApp_Done(false));
+ return;
+ }
+
+ profile_ = FetchProfileForDirectory(profile_dir);
app_id_ = app_id;
apps::AppShimHandler* handler = apps::AppShimHandler::GetForAppMode(app_id_);
- bool success =
- handler ? handler->OnShimLaunch(this) : LaunchAppImpl(profile_dir);
+ bool success = handler && handler->OnShimLaunch(this);
Send(new AppShimMsg_LaunchApp_Done(success));
}
void AppShimHost::OnFocus() {
DCHECK(CalledOnValidThread());
apps::AppShimHandler* handler = apps::AppShimHandler::GetForAppMode(app_id_);
- if (handler) {
+ if (handler)
handler->OnShimFocus(this);
- return;
- }
-
- if (!profile_)
- return;
- extensions::ShellWindowRegistry* registry =
- extensions::ShellWindowRegistry::Get(profile_);
- const std::set<ShellWindow*> windows =
- registry->GetShellWindowsForApp(app_id_);
- std::set<gfx::NativeWindow> native_windows;
- for (std::set<ShellWindow*>::const_iterator i = windows.begin();
- i != windows.end();
- ++i) {
- native_windows.insert((*i)->GetNativeWindow());
- }
- ui::FocusWindowSet(native_windows);
-}
-
-bool AppShimHost::LaunchAppImpl(const std::string& profile_dir) {
- DCHECK(CalledOnValidThread());
- if (profile_) {
- // Only one app launch message per channel.
- return false;
- }
- if (!extensions::Extension::IdIsValid(app_id_)) {
- LOG(ERROR) << "Bad app ID from app shim launch message.";
- return false;
- }
- Profile* profile = FetchProfileForDirectory(profile_dir);
- if (!profile)
- return false;
-
- if (!LaunchApp(profile))
- return false;
-
- profile_ = profile;
- registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
- content::Source<Profile>(profile_));
- return true;
}
Profile* AppShimHost::FetchProfileForDirectory(const std::string& profile_dir) {
@@ -138,50 +108,6 @@ Profile* AppShimHost::FetchProfileForDirectory(const std::string& profile_dir) {
return profile;
}
-bool AppShimHost::LaunchApp(Profile* profile) {
- extensions::ExtensionSystem* extension_system =
- extensions::ExtensionSystem::Get(profile);
- ExtensionServiceInterface* extension_service =
- extension_system->extension_service();
- const extensions::Extension* extension =
- extension_service->GetExtensionById(
- app_id_, false);
- if (!extension) {
- LOG(ERROR) << "Attempted to launch nonexistent app with id '"
- << app_id_ << "'.";
- return false;
- }
- // TODO(jeremya): Handle the case that launching the app fails. Probably we
- // need to watch for 'app successfully launched' or at least 'background page
- // exists/was created' and time out with failure if we don't see that sign of
- // life within a certain window.
- chrome::AppLaunchParams params(profile,
- extension,
- extension_misc::LAUNCH_NONE,
- NEW_WINDOW);
- chrome::OpenApplication(params);
- return true;
-}
-
-void AppShimHost::Observe(int type,
- const content::NotificationSource& source,
- const content::NotificationDetails& details) {
- DCHECK(CalledOnValidThread());
- DCHECK(content::Source<Profile>(source).ptr() == profile_);
- switch (type) {
- case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
- extensions::ExtensionHost* extension_host =
- content::Details<extensions::ExtensionHost>(details).ptr();
- if (app_id_ == extension_host->extension_id())
- Close();
- break;
- }
- default:
- NOTREACHED() << "Unexpected notification sent.";
- break;
- }
-}
-
void AppShimHost::OnAppClosed() {
Close();
}
diff --git a/apps/app_shim/app_shim_host_mac.h b/apps/app_shim/app_shim_host_mac.h
index 438832ce22..e68380c314 100644
--- a/apps/app_shim/app_shim_host_mac.h
+++ b/apps/app_shim/app_shim_host_mac.h
@@ -10,8 +10,6 @@
#include "apps/app_shim/app_shim_handler_mac.h"
#include "base/memory/scoped_ptr.h"
#include "base/threading/non_thread_safe.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
#include "ipc/ipc_listener.h"
#include "ipc/ipc_sender.h"
@@ -30,7 +28,6 @@ class Message;
class AppShimHost : public IPC::Listener,
public IPC::Sender,
public apps::AppShimHandler::Host,
- public content::NotificationObserver,
public base::NonThreadSafe {
public:
AppShimHost();
@@ -42,12 +39,9 @@ class AppShimHost : public IPC::Listener,
void ServeChannel(const IPC::ChannelHandle& handle);
protected:
- const std::string& app_id() const { return app_id_; }
- const Profile* profile() const { return profile_; }
// Used internally; virtual so they can be mocked for testing.
virtual Profile* FetchProfileForDirectory(const std::string& profile_dir);
- virtual bool LaunchApp(Profile* profile);
// IPC::Listener implementation.
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
@@ -68,17 +62,10 @@ class AppShimHost : public IPC::Listener,
// Cmd+Tabbed to it.)
void OnFocus();
- bool LaunchAppImpl(const std::string& profile_dir);
-
- // The AppShimHost listens to the NOTIFICATION_EXTENSION_HOST_DESTROYED
- // message to detect when the app closes. When that happens, the AppShimHost
- // closes the channel, which causes the app shim process to quit.
- virtual void Observe(int type,
- const content::NotificationSource& source,
- const content::NotificationDetails& details) OVERRIDE;
-
- // apps::AppShimHandler::Host override:
+ // apps::AppShimHandler::Host overrides:
virtual void OnAppClosed() OVERRIDE;
+ virtual Profile* GetProfile() const OVERRIDE;
+ virtual std::string GetAppId() const OVERRIDE;
// Closes the channel and destroys the AppShimHost.
void Close();
@@ -86,7 +73,6 @@ class AppShimHost : public IPC::Listener,
scoped_ptr<IPC::ChannelProxy> channel_;
std::string app_id_;
Profile* profile_;
- content::NotificationRegistrar registrar_;
};
#endif // CHROME_BROWSER_WEB_APPLICATIONS_APP_SHIM_HOST_MAC_H_
diff --git a/apps/app_shim/app_shim_host_mac_unittest.cc b/apps/app_shim/app_shim_host_mac_unittest.cc
index 476b2ceb43..f9a8e4dcc7 100644
--- a/apps/app_shim/app_shim_host_mac_unittest.cc
+++ b/apps/app_shim/app_shim_host_mac_unittest.cc
@@ -5,6 +5,7 @@
#include "apps/app_shim/app_shim_host_mac.h"
#include "apps/app_shim/app_shim_messages.h"
+#include "base/basictypes.h"
#include "base/memory/scoped_vector.h"
#include "chrome/test/base/testing_profile.h"
#include "ipc/ipc_message.h"
@@ -31,18 +32,9 @@ class TestingAppShimHost : public AppShimHost {
fails_launch_ = fails_launch;
}
- const std::string& GetAppId() const {
- return app_id();
- }
-
- const Profile* GetProfile() const {
- return profile();
- }
-
protected:
virtual Profile* FetchProfileForDirectory(const std::string& profile_dir)
OVERRIDE;
- virtual bool LaunchApp(Profile* profile) OVERRIDE;
virtual bool Send(IPC::Message* message) OVERRIDE;
private:
@@ -77,10 +69,6 @@ Profile* TestingAppShimHost::FetchProfileForDirectory(
return fails_profile_ ? NULL : test_profile_;
}
-bool TestingAppShimHost::LaunchApp(Profile* profile) {
- return !fails_launch_;
-}
-
class AppShimHostTest : public testing::Test,
public apps::AppShimHandler {
public:
@@ -117,6 +105,7 @@ class AppShimHostTest : public testing::Test,
private:
virtual void SetUp() OVERRIDE {
+ testing::Test::SetUp();
profile_.reset(new TestingProfile);
host_.reset(new TestingAppShimHost(profile()));
}
@@ -132,19 +121,12 @@ const char kTestProfileDir[] = "Default";
} // namespace
-TEST_F(AppShimHostTest, TestLaunchApp) {
- host()->ReceiveMessage(
- new AppShimHostMsg_LaunchApp(kTestProfileDir, kTestAppId));
- ASSERT_EQ(kTestAppId, host()->GetAppId());
- ASSERT_EQ(profile(), host()->GetProfile());
- ASSERT_TRUE(LaunchWasSuccessful());
-}
-
TEST_F(AppShimHostTest, TestLaunchAppWithHandler) {
apps::AppShimHandler::RegisterHandler(kTestAppId, this);
EXPECT_TRUE(host()->ReceiveMessage(
new AppShimHostMsg_LaunchApp(kTestProfileDir, kTestAppId)));
- EXPECT_EQ(kTestAppId, host()->GetAppId());
+ EXPECT_EQ(kTestAppId,
+ implicit_cast<apps::AppShimHandler::Host*>(host())->GetAppId());
EXPECT_TRUE(LaunchWasSuccessful());
EXPECT_EQ(1, launch_count_);
EXPECT_EQ(0, focus_count_);
diff --git a/apps/app_shim/app_shim_host_manager_mac.h b/apps/app_shim/app_shim_host_manager_mac.h
index 13283ee980..45d881c56e 100644
--- a/apps/app_shim/app_shim_host_manager_mac.h
+++ b/apps/app_shim/app_shim_host_manager_mac.h
@@ -5,9 +5,8 @@
#ifndef CHROME_BROWSER_WEB_APPLICATIONS_APP_SHIM_HOST_MANAGER_MAC_H_
#define CHROME_BROWSER_WEB_APPLICATIONS_APP_SHIM_HOST_MANAGER_MAC_H_
-#include "base/mac/scoped_cftyperef.h"
+#include "apps/app_shim/extension_app_shim_handler_mac.h"
#include "base/memory/weak_ptr.h"
-#include "base/threading/thread.h"
#include "ipc/ipc_channel_factory.h"
// The AppShimHostManager receives connections from app shims on a UNIX
@@ -34,6 +33,8 @@ class AppShimHostManager
scoped_ptr<IPC::ChannelFactory> factory_;
+ apps::ExtensionAppShimHandler extension_app_shim_handler_;
+
DISALLOW_COPY_AND_ASSIGN(AppShimHostManager);
};
diff --git a/apps/app_shim/app_shim_host_manager_mac.mm b/apps/app_shim/app_shim_host_manager_mac.mm
index 6133b9be38..049ba5b4fe 100644
--- a/apps/app_shim/app_shim_host_manager_mac.mm
+++ b/apps/app_shim/app_shim_host_manager_mac.mm
@@ -4,6 +4,7 @@
#include "apps/app_shim/app_shim_host_manager_mac.h"
+#include "apps/app_shim/app_shim_handler_mac.h"
#include "apps/app_shim/app_shim_host_mac.h"
#include "base/bind.h"
#include "base/files/file_path.h"
@@ -27,6 +28,7 @@ void CreateAppShimHost(const IPC::ChannelHandle& handle) {
AppShimHostManager::AppShimHostManager() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ apps::AppShimHandler::SetDefaultHandler(&extension_app_shim_handler_);
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&AppShimHostManager::InitOnFileThread,
@@ -34,6 +36,7 @@ AppShimHostManager::AppShimHostManager() {
}
AppShimHostManager::~AppShimHostManager() {
+ apps::AppShimHandler::SetDefaultHandler(NULL);
}
void AppShimHostManager::InitOnFileThread() {
diff --git a/apps/app_shim/extension_app_shim_handler_mac.cc b/apps/app_shim/extension_app_shim_handler_mac.cc
new file mode 100644
index 0000000000..9d31d694c0
--- /dev/null
+++ b/apps/app_shim/extension_app_shim_handler_mac.cc
@@ -0,0 +1,130 @@
+// Copyright 2013 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 "apps/app_shim/extension_app_shim_handler_mac.h"
+
+#include "apps/app_shim/app_shim_messages.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "chrome/browser/extensions/extension_host.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/extensions/shell_window_registry.h"
+#include "chrome/browser/ui/extensions/application_launch.h"
+#include "chrome/browser/ui/extensions/shell_window.h"
+#include "ui/base/cocoa/focus_window_set.h"
+
+namespace apps {
+
+ExtensionAppShimHandler::ExtensionAppShimHandler() {}
+
+ExtensionAppShimHandler::~ExtensionAppShimHandler() {
+ for (HostMap::iterator it = hosts_.begin(); it != hosts_.end(); ) {
+ // Increment the iterator first as OnAppClosed may call back to OnShimClose
+ // and invalidate the iterator.
+ it++->second->OnAppClosed();
+ }
+}
+
+bool ExtensionAppShimHandler::OnShimLaunch(Host* host) {
+ Profile* profile = host->GetProfile();
+ DCHECK(profile);
+
+ const std::string& app_id = host->GetAppId();
+ if (!extensions::Extension::IdIsValid(app_id)) {
+ LOG(ERROR) << "Bad app ID from app shim launch message.";
+ return false;
+ }
+
+ if (!LaunchApp(profile, app_id))
+ return false;
+
+ // The first host to claim this (profile, app_id) becomes the main host.
+ // For any others, we launch the app but return false.
+ if (!hosts_.insert(make_pair(make_pair(profile, app_id), host)).second)
+ return false;
+
+ if (!registrar_.IsRegistered(
+ this, chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::Source<Profile>(profile))) {
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::Source<Profile>(profile));
+ }
+
+ return true;
+}
+
+void ExtensionAppShimHandler::OnShimClose(Host* host) {
+ HostMap::iterator it = hosts_.find(make_pair(host->GetProfile(),
+ host->GetAppId()));
+ // Any hosts other than the main host will still call OnShimClose, so ignore
+ // them.
+ if (it != hosts_.end() && it->second == host)
+ hosts_.erase(it);
+}
+
+void ExtensionAppShimHandler::OnShimFocus(Host* host) {
+ if (!host->GetProfile())
+ return;
+
+ extensions::ShellWindowRegistry* registry =
+ extensions::ShellWindowRegistry::Get(host->GetProfile());
+ const extensions::ShellWindowRegistry::ShellWindowList windows =
+ registry->GetShellWindowsForApp(host->GetAppId());
+ std::set<gfx::NativeWindow> native_windows;
+ for (extensions::ShellWindowRegistry::const_iterator i = windows.begin();
+ i != windows.end(); ++i) {
+ native_windows.insert((*i)->GetNativeWindow());
+ }
+ ui::FocusWindowSet(native_windows);
+}
+
+bool ExtensionAppShimHandler::LaunchApp(Profile* profile,
+ const std::string& app_id) {
+ extensions::ExtensionSystem* extension_system =
+ extensions::ExtensionSystem::Get(profile);
+ ExtensionServiceInterface* extension_service =
+ extension_system->extension_service();
+ const extensions::Extension* extension =
+ extension_service->GetExtensionById(app_id, false);
+ if (!extension) {
+ LOG(ERROR) << "Attempted to launch nonexistent app with id '"
+ << app_id << "'.";
+ return false;
+ }
+ // TODO(jeremya): Handle the case that launching the app fails. Probably we
+ // need to watch for 'app successfully launched' or at least 'background page
+ // exists/was created' and time out with failure if we don't see that sign of
+ // life within a certain window.
+ chrome::OpenApplication(chrome::AppLaunchParams(
+ profile, extension, NEW_FOREGROUND_TAB));
+ return true;
+}
+
+void ExtensionAppShimHandler::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ Profile* profile = content::Source<Profile>(source).ptr();
+ switch (type) {
+ case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
+ extensions::ExtensionHost* extension_host =
+ content::Details<extensions::ExtensionHost>(details).ptr();
+ CloseShim(profile, extension_host->extension_id());
+ break;
+ }
+ default:
+ NOTREACHED(); // Unexpected notification.
+ break;
+ }
+}
+
+void ExtensionAppShimHandler::CloseShim(Profile* profile,
+ const std::string& app_id) {
+ HostMap::const_iterator it = hosts_.find(make_pair(profile, app_id));
+ if (it != hosts_.end())
+ it->second->OnAppClosed();
+}
+
+} // namespace apps
diff --git a/apps/app_shim/extension_app_shim_handler_mac.h b/apps/app_shim/extension_app_shim_handler_mac.h
new file mode 100644
index 0000000000..ea9a8c7eee
--- /dev/null
+++ b/apps/app_shim/extension_app_shim_handler_mac.h
@@ -0,0 +1,60 @@
+// Copyright 2013 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.
+
+#ifndef APPS_APP_SHIM_EXTENSION_APP_SHIM_HANDLER_H_
+#define APPS_APP_SHIM_EXTENSION_APP_SHIM_HANDLER_H_
+
+#include <map>
+#include <string>
+
+#include "apps/app_shim/app_shim_handler_mac.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+class Profile;
+
+namespace apps {
+
+// This app shim handler that handles events for app shims that correspond to an
+// extension.
+class ExtensionAppShimHandler : public AppShimHandler,
+ public content::NotificationObserver {
+ public:
+ ExtensionAppShimHandler();
+ virtual ~ExtensionAppShimHandler();
+
+ // AppShimHandler overrides:
+ virtual bool OnShimLaunch(Host* host) OVERRIDE;
+ virtual void OnShimClose(Host* host) OVERRIDE;
+ virtual void OnShimFocus(Host* host) OVERRIDE;
+
+ protected:
+ typedef std::map<std::pair<Profile*, std::string>, AppShimHandler::Host*>
+ HostMap;
+
+ // Exposed for testing.
+ HostMap& hosts() { return hosts_; }
+ content::NotificationRegistrar& registrar() { return registrar_; }
+
+ private:
+ virtual bool LaunchApp(Profile* profile, const std::string& app_id);
+
+ // Listen to the NOTIFICATION_EXTENSION_HOST_DESTROYED message to detect when
+ // an app closes. When that happens, call OnAppClosed on the relevant
+ // AppShimHandler::Host which causes the app shim process to quit.
+ // The host will call OnShimClose on this.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ void CloseShim(Profile* profile, const std::string& app_id);
+
+ HostMap hosts_;
+
+ content::NotificationRegistrar registrar_;
+};
+
+} // namespace apps
+
+#endif // APPS_APP_SHIM_EXTENSION_APP_SHIM_HANDLER_H_
diff --git a/apps/app_shim/extension_app_shim_handler_mac_unittest.cc b/apps/app_shim/extension_app_shim_handler_mac_unittest.cc
new file mode 100644
index 0000000000..dbbbd13c80
--- /dev/null
+++ b/apps/app_shim/extension_app_shim_handler_mac_unittest.cc
@@ -0,0 +1,139 @@
+// Copyright 2013 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 "apps/app_shim/extension_app_shim_handler_mac.h"
+
+#include "apps/app_shim/app_shim_host_mac.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/browser/notification_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace apps {
+
+class TestingExtensionAppShimHandler : public ExtensionAppShimHandler {
+ public:
+ explicit TestingExtensionAppShimHandler() : fails_launch_(false) {}
+ virtual ~TestingExtensionAppShimHandler() {}
+
+ void set_fails_launch(bool fails_launch) {
+ fails_launch_ = fails_launch;
+ }
+
+ AppShimHandler::Host* FindHost(Profile* profile,
+ const std::string& app_id) {
+ HostMap::const_iterator it = hosts().find(make_pair(profile, app_id));
+ return it == hosts().end() ? NULL : it->second;
+ }
+
+ content::NotificationRegistrar& GetRegistrar() { return registrar(); }
+
+ protected:
+ virtual bool LaunchApp(Profile* profile, const std::string& app_id) OVERRIDE {
+ return !fails_launch_;
+ }
+
+ private:
+ bool fails_launch_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestingExtensionAppShimHandler);
+};
+
+const char kTestAppIdA[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const char kTestAppIdB[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+
+class FakeHost : public apps::AppShimHandler::Host {
+ public:
+ FakeHost(Profile* profile,
+ std::string app_id,
+ TestingExtensionAppShimHandler* handler)
+ : profile_(profile),
+ app_id_(app_id),
+ handler_(handler),
+ close_count_(0) {}
+
+ virtual void OnAppClosed() OVERRIDE {
+ handler_->OnShimClose(this);
+ ++close_count_;
+ }
+ virtual Profile* GetProfile() const OVERRIDE { return profile_; }
+ virtual std::string GetAppId() const OVERRIDE { return app_id_; }
+
+ int close_count() { return close_count_; }
+
+ private:
+ Profile* profile_;
+ std::string app_id_;
+ TestingExtensionAppShimHandler* handler_;
+ int close_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeHost);
+};
+
+class ExtensionAppShimHandlerTest : public testing::Test {
+ protected:
+ ExtensionAppShimHandlerTest()
+ : handler_(new TestingExtensionAppShimHandler),
+ host_aa_(&profile_a_, kTestAppIdA, handler_.get()),
+ host_ab_(&profile_a_, kTestAppIdB, handler_.get()),
+ host_bb_(&profile_b_, kTestAppIdB, handler_.get()),
+ host_aa_duplicate_(&profile_a_, kTestAppIdA, handler_.get()) {}
+
+ scoped_ptr<TestingExtensionAppShimHandler> handler_;
+ TestingProfile profile_a_;
+ TestingProfile profile_b_;
+ FakeHost host_aa_;
+ FakeHost host_ab_;
+ FakeHost host_bb_;
+ FakeHost host_aa_duplicate_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ExtensionAppShimHandlerTest);
+};
+
+TEST_F(ExtensionAppShimHandlerTest, LaunchAndCloseShim) {
+ // If launch fails, the host is not added to the map.
+ handler_->set_fails_launch(true);
+ EXPECT_EQ(false, handler_->OnShimLaunch(&host_aa_));
+ EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA));
+
+ // Normal startup.
+ handler_->set_fails_launch(false);
+ EXPECT_EQ(true, handler_->OnShimLaunch(&host_aa_));
+ EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA));
+ EXPECT_TRUE(handler_->GetRegistrar().IsRegistered(
+ handler_.get(),
+ chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::Source<Profile>(&profile_a_)));
+ EXPECT_EQ(true, handler_->OnShimLaunch(&host_ab_));
+ EXPECT_EQ(&host_ab_, handler_->FindHost(&profile_a_, kTestAppIdB));
+ EXPECT_EQ(true, handler_->OnShimLaunch(&host_bb_));
+ EXPECT_EQ(&host_bb_, handler_->FindHost(&profile_b_, kTestAppIdB));
+ EXPECT_TRUE(handler_->GetRegistrar().IsRegistered(
+ handler_.get(),
+ chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::Source<Profile>(&profile_b_)));
+
+ // Starting and closing a second host does nothing.
+ EXPECT_EQ(false, handler_->OnShimLaunch(&host_aa_duplicate_));
+ EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA));
+ handler_->OnShimClose(&host_aa_duplicate_);
+ EXPECT_EQ(&host_aa_, handler_->FindHost(&profile_a_, kTestAppIdA));
+
+ // Normal close.
+ handler_->OnShimClose(&host_aa_);
+ EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA));
+
+ // Closing the second host afterward does nothing.
+ handler_->OnShimClose(&host_aa_duplicate_);
+ EXPECT_FALSE(handler_->FindHost(&profile_a_, kTestAppIdA));
+
+ // Destruction sends OnAppClose to remaining hosts.
+ handler_.reset();
+ EXPECT_EQ(1, host_ab_.close_count());
+ EXPECT_EQ(1, host_bb_.close_count());
+}
+
+} // namespace apps
diff --git a/apps/apps.gypi b/apps/apps.gypi
index 4867f73349..92a677e29f 100644
--- a/apps/apps.gypi
+++ b/apps/apps.gypi
@@ -36,12 +36,20 @@
'app_shim/app_shim_host_mac.h',
'app_shim/app_shim_host_manager_mac.h',
'app_shim/app_shim_host_manager_mac.mm',
+ 'app_shim/extension_app_shim_handler_mac.h',
+ 'app_shim/extension_app_shim_handler_mac.cc',
'field_trial_names.cc',
'field_trial_names.h',
'pref_names.cc',
'pref_names.h',
'prefs.cc',
'prefs.h',
+ 'saved_files_service.cc',
+ 'saved_files_service.h',
+ 'saved_files_service_factory.cc',
+ 'saved_files_service_factory.h',
+ 'shell_window_geometry_cache.cc',
+ 'shell_window_geometry_cache.h',
'shortcut_manager.cc',
'shortcut_manager.h',
'shortcut_manager_factory.cc',
diff --git a/apps/saved_files_service.cc b/apps/saved_files_service.cc
new file mode 100644
index 0000000000..c509c083ea
--- /dev/null
+++ b/apps/saved_files_service.cc
@@ -0,0 +1,441 @@
+// Copyright 2013 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 "apps/saved_files_service.h"
+
+#include <algorithm>
+
+#include "apps/saved_files_service_factory.h"
+#include "base/basictypes.h"
+#include "base/hash_tables.h"
+#include "base/value_conversions.h"
+#include "chrome/browser/extensions/extension_host.h"
+#include "chrome/browser/extensions/extension_prefs.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/common/extensions/permissions/api_permission.h"
+#include "chrome/common/extensions/permissions/permission_set.h"
+
+namespace apps {
+
+using extensions::APIPermission;
+using extensions::Extension;
+using extensions::ExtensionHost;
+using extensions::ExtensionPrefs;
+
+namespace {
+
+// Preference keys
+
+// The file entries that the app has permission to access.
+const char kFileEntries[] = "file_entries";
+
+// The path to a file entry that the app had permission to access.
+const char kFileEntryPath[] = "path";
+
+// Whether or not the app had write access to a file entry.
+const char kFileEntryWritable[] = "writable";
+
+// The sequence number in the LRU of the file entry.
+const char kFileEntrySequenceNumber[] = "sequence_number";
+
+const size_t kMaxSavedFileEntries = 500;
+const int kMaxSequenceNumber = kint32max;
+
+// These might be different to the constant values in tests.
+size_t g_max_saved_file_entries = kMaxSavedFileEntries;
+int g_max_sequence_number = kMaxSequenceNumber;
+
+// Persists a SavedFileEntry in ExtensionPrefs.
+void AddSavedFileEntry(ExtensionPrefs* prefs,
+ const std::string& extension_id,
+ const SavedFileEntry& file_entry) {
+ ExtensionPrefs::ScopedDictionaryUpdate update(
+ prefs, extension_id, kFileEntries);
+ DictionaryValue* file_entries = update.Get();
+ if (!file_entries)
+ file_entries = update.Create();
+ DCHECK(!file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, NULL));
+
+ DictionaryValue* file_entry_dict = new DictionaryValue();
+ file_entry_dict->Set(kFileEntryPath, CreateFilePathValue(file_entry.path));
+ file_entry_dict->SetBoolean(kFileEntryWritable, file_entry.writable);
+ file_entry_dict->SetInteger(kFileEntrySequenceNumber,
+ file_entry.sequence_number);
+ file_entries->SetWithoutPathExpansion(file_entry.id, file_entry_dict);
+}
+
+// Updates the sequence_number of a SavedFileEntry persisted in ExtensionPrefs.
+void UpdateSavedFileEntry(ExtensionPrefs* prefs,
+ const std::string& extension_id,
+ const SavedFileEntry& file_entry) {
+ ExtensionPrefs::ScopedDictionaryUpdate update(
+ prefs, extension_id, kFileEntries);
+ DictionaryValue* file_entries = update.Get();
+ DCHECK(file_entries);
+ DictionaryValue* file_entry_dict = NULL;
+ file_entries->GetDictionaryWithoutPathExpansion(file_entry.id,
+ &file_entry_dict);
+ DCHECK(file_entry_dict);
+ file_entry_dict->SetInteger(kFileEntrySequenceNumber,
+ file_entry.sequence_number);
+}
+
+// Removes a SavedFileEntry from ExtensionPrefs.
+void RemoveSavedFileEntry(ExtensionPrefs* prefs,
+ const std::string& extension_id,
+ const std::string& file_entry_id) {
+ ExtensionPrefs::ScopedDictionaryUpdate update(
+ prefs, extension_id, kFileEntries);
+ DictionaryValue* file_entries = update.Get();
+ if (!file_entries)
+ file_entries = update.Create();
+ file_entries->RemoveWithoutPathExpansion(file_entry_id, NULL);
+}
+
+// Clears all SavedFileEntry for the app from ExtensionPrefs.
+void ClearSavedFileEntries(ExtensionPrefs* prefs,
+ const std::string& extension_id) {
+ prefs->UpdateExtensionPref(extension_id, kFileEntries, NULL);
+}
+
+// Returns all SavedFileEntries for the app.
+std::vector<SavedFileEntry> GetSavedFileEntries(
+ ExtensionPrefs* prefs,
+ const std::string& extension_id) {
+ std::vector<SavedFileEntry> result;
+ const DictionaryValue* file_entries = NULL;
+ if (!prefs->ReadPrefAsDictionary(extension_id, kFileEntries, &file_entries))
+ return result;
+
+ for (DictionaryValue::Iterator it(*file_entries); !it.IsAtEnd();
+ it.Advance()) {
+ const DictionaryValue* file_entry = NULL;
+ if (!it.value().GetAsDictionary(&file_entry))
+ continue;
+ const base::Value* path_value;
+ if (!file_entry->Get(kFileEntryPath, &path_value))
+ continue;
+ base::FilePath file_path;
+ if (!GetValueAsFilePath(*path_value, &file_path))
+ continue;
+ bool writable = false;
+ if (!file_entry->GetBoolean(kFileEntryWritable, &writable))
+ continue;
+ int sequence_number = 0;
+ if (!file_entry->GetInteger(kFileEntrySequenceNumber, &sequence_number))
+ continue;
+ if (!sequence_number)
+ continue;
+ result.push_back(
+ SavedFileEntry(it.key(), file_path, writable, sequence_number));
+ }
+ return result;
+}
+
+} // namespace
+
+SavedFileEntry::SavedFileEntry() : writable(false), sequence_number(0) {}
+
+SavedFileEntry::SavedFileEntry(const std::string& id,
+ const base::FilePath& path,
+ bool writable,
+ int sequence_number)
+ : id(id),
+ path(path),
+ writable(writable),
+ sequence_number(sequence_number) {}
+
+class SavedFilesService::SavedFiles {
+ public:
+ SavedFiles(Profile* profile, const std::string& extension_id);
+ ~SavedFiles();
+
+ void RegisterFileEntry(const std::string& id,
+ const base::FilePath& file_path,
+ bool writable);
+ void EnqueueFileEntry(const std::string& id);
+ bool IsRegistered(const std::string& id) const;
+ const SavedFileEntry* GetFileEntry(const std::string& id) const;
+ std::vector<SavedFileEntry> GetAllFileEntries() const;
+
+ private:
+ // Compacts sequence numbers if the largest sequence number is
+ // g_max_sequence_number. Outside of testing, it is set to kint32max, so this
+ // will almost never do any real work.
+ void MaybeCompactSequenceNumbers();
+
+ void LoadSavedFileEntriesFromPreferences();
+
+ Profile* profile_;
+ const std::string extension_id_;
+
+ // Contains all file entries that have been registered, keyed by ID. Owns
+ // values.
+ base::hash_map<std::string, SavedFileEntry*> registered_file_entries_;
+ STLValueDeleter<base::hash_map<std::string, SavedFileEntry*> >
+ registered_file_entries_deleter_;
+
+ // The queue of file entries that have been retained, keyed by
+ // sequence_number. Values are a subset of values in registered_file_entries_.
+ // This should be kept in sync with file entries stored in extension prefs.
+ std::map<int, SavedFileEntry*> saved_file_lru_;
+
+ DISALLOW_COPY_AND_ASSIGN(SavedFiles);
+};
+
+// static
+SavedFilesService* SavedFilesService::Get(Profile* profile) {
+ return SavedFilesServiceFactory::GetForProfile(profile);
+}
+
+SavedFilesService::SavedFilesService(Profile* profile)
+ : extension_id_to_saved_files_deleter_(&extension_id_to_saved_files_),
+ profile_(profile) {
+ registrar_.Add(this,
+ chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::NotificationService::AllSources());
+ registrar_.Add(this,
+ chrome::NOTIFICATION_APP_TERMINATING,
+ content::NotificationService::AllSources());
+}
+
+SavedFilesService::~SavedFilesService() {}
+
+void SavedFilesService::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
+ ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
+ const Extension* extension = host->extension();
+ if (extension) {
+ ClearQueueIfNoRetainPermission(extension);
+ Clear(extension->id());
+ }
+ break;
+ }
+
+ case chrome::NOTIFICATION_APP_TERMINATING: {
+ // Stop listening to NOTIFICATION_EXTENSION_HOST_DESTROYED in particular
+ // as all extension hosts will be destroyed as a result of shutdown.
+ registrar_.RemoveAll();
+ break;
+ }
+ }
+}
+
+void SavedFilesService::RegisterFileEntry(const std::string& extension_id,
+ const std::string& id,
+ const base::FilePath& file_path,
+ bool writable) {
+ GetOrInsert(extension_id)->RegisterFileEntry(id, file_path, writable);
+}
+
+void SavedFilesService::EnqueueFileEntry(const std::string& extension_id,
+ const std::string& id) {
+ GetOrInsert(extension_id)->EnqueueFileEntry(id);
+}
+
+std::vector<SavedFileEntry> SavedFilesService::GetAllFileEntries(
+ const std::string& extension_id) {
+ return GetOrInsert(extension_id)->GetAllFileEntries();
+}
+
+bool SavedFilesService::IsRegistered(const std::string& extension_id,
+ const std::string& id) {
+ return GetOrInsert(extension_id)->IsRegistered(id);
+}
+
+const SavedFileEntry* SavedFilesService::GetFileEntry(
+ const std::string& extension_id,
+ const std::string& id) {
+ return GetOrInsert(extension_id)->GetFileEntry(id);
+}
+
+void SavedFilesService::ClearQueueIfNoRetainPermission(
+ const Extension* extension) {
+ if (!extension->GetActivePermissions()->HasAPIPermission(
+ APIPermission::kFileSystemRetainFiles)) {
+ ClearSavedFileEntries(ExtensionPrefs::Get(profile_), extension->id());
+ Clear(extension->id());
+ }
+}
+
+SavedFilesService::SavedFiles* SavedFilesService::GetOrInsert(
+ const std::string& extension_id) {
+ std::map<std::string, SavedFiles*>::iterator it =
+ extension_id_to_saved_files_.find(extension_id);
+ if (it != extension_id_to_saved_files_.end())
+ return it->second;
+
+ SavedFiles* saved_files = new SavedFiles(profile_, extension_id);
+ extension_id_to_saved_files_.insert(
+ std::make_pair(extension_id, saved_files));
+ return saved_files;
+}
+
+void SavedFilesService::Clear(const std::string& extension_id) {
+ std::map<std::string, SavedFiles*>::iterator it =
+ extension_id_to_saved_files_.find(extension_id);
+ if (it != extension_id_to_saved_files_.end()) {
+ delete it->second;
+ extension_id_to_saved_files_.erase(it);
+ }
+}
+
+SavedFilesService::SavedFiles::SavedFiles(Profile* profile,
+ const std::string& extension_id)
+ : profile_(profile),
+ extension_id_(extension_id),
+ registered_file_entries_deleter_(&registered_file_entries_) {
+ LoadSavedFileEntriesFromPreferences();
+}
+
+SavedFilesService::SavedFiles::~SavedFiles() {}
+
+void SavedFilesService::SavedFiles::RegisterFileEntry(
+ const std::string& id,
+ const base::FilePath& file_path,
+ bool writable) {
+ if (ContainsKey(registered_file_entries_, id))
+ return;
+
+ registered_file_entries_.insert(
+ std::make_pair(id, new SavedFileEntry(id, file_path, writable, 0)));
+}
+
+void SavedFilesService::SavedFiles::EnqueueFileEntry(const std::string& id) {
+ base::hash_map<std::string, SavedFileEntry*>::iterator it =
+ registered_file_entries_.find(id);
+ DCHECK(it != registered_file_entries_.end());
+
+ SavedFileEntry* file_entry = it->second;
+ int old_sequence_number = file_entry->sequence_number;
+ if (!saved_file_lru_.empty()) {
+ // Get the sequence number after the last file entry in the LRU.
+ std::map<int, SavedFileEntry*>::reverse_iterator it =
+ saved_file_lru_.rbegin();
+ if (it->second == file_entry)
+ return;
+
+ file_entry->sequence_number = it->first + 1;
+ } else {
+ // The first sequence number is 1, as 0 means the entry is not in the LRU.
+ file_entry->sequence_number = 1;
+ }
+ saved_file_lru_.insert(
+ std::make_pair(file_entry->sequence_number, file_entry));
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
+ if (old_sequence_number) {
+ saved_file_lru_.erase(old_sequence_number);
+ UpdateSavedFileEntry(prefs, extension_id_, *file_entry);
+ } else {
+ AddSavedFileEntry(prefs, extension_id_, *file_entry);
+ if (saved_file_lru_.size() > g_max_saved_file_entries) {
+ std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin();
+ it->second->sequence_number = 0;
+ RemoveSavedFileEntry(prefs, extension_id_, it->second->id);
+ saved_file_lru_.erase(it);
+ }
+ }
+ MaybeCompactSequenceNumbers();
+}
+
+bool SavedFilesService::SavedFiles::IsRegistered(const std::string& id) const {
+ return ContainsKey(registered_file_entries_, id);
+}
+
+const SavedFileEntry* SavedFilesService::SavedFiles::GetFileEntry(
+ const std::string& id) const {
+ base::hash_map<std::string, SavedFileEntry*>::const_iterator it =
+ registered_file_entries_.find(id);
+ if (it == registered_file_entries_.end())
+ return NULL;
+
+ return it->second;
+}
+
+std::vector<SavedFileEntry> SavedFilesService::SavedFiles::GetAllFileEntries()
+ const {
+ std::vector<SavedFileEntry> result;
+ for (base::hash_map<std::string, SavedFileEntry*>::const_iterator it =
+ registered_file_entries_.begin();
+ it != registered_file_entries_.end();
+ ++it) {
+ result.push_back(*it->second);
+ }
+ return result;
+}
+
+void SavedFilesService::SavedFiles::MaybeCompactSequenceNumbers() {
+ DCHECK_GE(g_max_sequence_number, 0);
+ DCHECK_GE(static_cast<size_t>(g_max_sequence_number),
+ g_max_saved_file_entries);
+ std::map<int, SavedFileEntry*>::reverse_iterator it =
+ saved_file_lru_.rbegin();
+ if (it == saved_file_lru_.rend())
+ return;
+
+ // Only compact sequence numbers if the last entry's sequence number is the
+ // maximum value. This should almost never be the case.
+ if (it->first < g_max_sequence_number)
+ return;
+
+ int sequence_number = 0;
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
+ for (std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin();
+ it != saved_file_lru_.end();
+ ++it) {
+ sequence_number++;
+ if (it->second->sequence_number == sequence_number)
+ continue;
+
+ SavedFileEntry* file_entry = it->second;
+ file_entry->sequence_number = sequence_number;
+ UpdateSavedFileEntry(prefs, extension_id_, *file_entry);
+ saved_file_lru_.erase(it++);
+ // Provide the following element as an insert hint. While optimized
+ // insertion time with the following element as a hint is only supported by
+ // the spec in C++11, the implementations do support this.
+ it = saved_file_lru_.insert(
+ it, std::make_pair(file_entry->sequence_number, file_entry));
+ }
+}
+
+void SavedFilesService::SavedFiles::LoadSavedFileEntriesFromPreferences() {
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
+ std::vector<SavedFileEntry> saved_entries =
+ GetSavedFileEntries(prefs, extension_id_);
+ for (std::vector<SavedFileEntry>::iterator it = saved_entries.begin();
+ it != saved_entries.end();
+ ++it) {
+ SavedFileEntry* file_entry = new SavedFileEntry(*it);
+ registered_file_entries_.insert(std::make_pair(file_entry->id, file_entry));
+ saved_file_lru_.insert(
+ std::make_pair(file_entry->sequence_number, file_entry));
+ }
+}
+
+// static
+void SavedFilesService::SetMaxSequenceNumberForTest(int max_value) {
+ g_max_sequence_number = max_value;
+}
+
+// static
+void SavedFilesService::ClearMaxSequenceNumberForTest() {
+ g_max_sequence_number = kMaxSequenceNumber;
+}
+
+// static
+void SavedFilesService::SetLruSizeForTest(int size) {
+ g_max_saved_file_entries = size;
+}
+
+// static
+void SavedFilesService::ClearLruSizeForTest() {
+ g_max_saved_file_entries = kMaxSavedFileEntries;
+}
+
+} // namespace apps
diff --git a/apps/saved_files_service.h b/apps/saved_files_service.h
new file mode 100644
index 0000000000..613764aaa8
--- /dev/null
+++ b/apps/saved_files_service.h
@@ -0,0 +1,135 @@
+// Copyright 2013 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.
+
+#ifndef APPS_SAVED_FILES_SERVICE_H_
+#define APPS_SAVED_FILES_SERVICE_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/stl_util.h"
+#include "components/browser_context_keyed_service/browser_context_keyed_service.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+class Profile;
+class SavedFilesServiceUnitTest;
+FORWARD_DECLARE_TEST(SavedFilesServiceUnitTest, RetainTwoFilesTest);
+FORWARD_DECLARE_TEST(SavedFilesServiceUnitTest, EvictionTest);
+FORWARD_DECLARE_TEST(SavedFilesServiceUnitTest, SequenceNumberCompactionTest);
+
+namespace extensions {
+class Extension;
+}
+
+namespace apps {
+
+// Represents a file entry that a user has given an app permission to
+// access. Will be persisted to disk (in the Preferences file), so should remain
+// serializable.
+struct SavedFileEntry {
+ SavedFileEntry();
+
+ SavedFileEntry(const std::string& id,
+ const base::FilePath& path,
+ bool writable,
+ int sequence_number);
+
+ // The opaque id of this file entry.
+ std::string id;
+
+ // The path to a file entry that the app had permission to access.
+ base::FilePath path;
+
+ // Whether or not the app had write access to a file entry.
+ bool writable;
+
+ // The sequence number in the LRU of the file entry. The value 0 indicates
+ // that the entry is not in the LRU.
+ int sequence_number;
+};
+
+// Tracks the files that apps have retained access to both while running and
+// when suspended.
+class SavedFilesService : public BrowserContextKeyedService,
+ public content::NotificationObserver {
+ public:
+ explicit SavedFilesService(Profile* profile);
+ virtual ~SavedFilesService();
+
+ static SavedFilesService* Get(Profile* profile);
+
+ // Registers a file entry with the saved files service, making it eligible to
+ // be put into the queue. File entries that are in the retained files queue at
+ // object construction are automatically registered.
+ void RegisterFileEntry(const std::string& extension_id,
+ const std::string& id,
+ const base::FilePath& file_path,
+ bool writable);
+
+ // If the file with |id| is not in the queue of files to be retained
+ // permanently, adds the file to the back of the queue, evicting the least
+ // recently used entry at the front of the queue if it is full. If it is
+ // already present, moves it to the back of the queue. The |id| must have been
+ // registered.
+ void EnqueueFileEntry(const std::string& extension_id, const std::string& id);
+
+ // Returns whether the file entry with the given |id| has been registered.
+ bool IsRegistered(const std::string& extension_id, const std::string& id);
+
+ // Gets a borrowed pointer to the file entry with the specified |id|. Returns
+ // NULL if the file entry has not been registered.
+ const SavedFileEntry* GetFileEntry(const std::string& extension_id,
+ const std::string& id);
+
+ // Returns all registered file entries.
+ std::vector<SavedFileEntry> GetAllFileEntries(
+ const std::string& extension_id);
+
+ // Clears all retained files if the app does not have the
+ // fileSystem.retainFiles permission.
+ void ClearQueueIfNoRetainPermission(const extensions::Extension* extension);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(::SavedFilesServiceUnitTest, RetainTwoFilesTest);
+ FRIEND_TEST_ALL_PREFIXES(::SavedFilesServiceUnitTest, EvictionTest);
+ FRIEND_TEST_ALL_PREFIXES(::SavedFilesServiceUnitTest,
+ SequenceNumberCompactionTest);
+ friend class ::SavedFilesServiceUnitTest;
+
+ // A container for the registered files for an app.
+ class SavedFiles;
+
+ // content::NotificationObserver.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // Returns the SavedFiles for |extension_id|, creating it if necessary.
+ SavedFiles* GetOrInsert(const std::string& extension_id);
+
+ // Clears the SavedFiles for |extension_id|.
+ void Clear(const std::string& extension_id);
+
+ static void SetMaxSequenceNumberForTest(int max_value);
+ static void ClearMaxSequenceNumberForTest();
+ static void SetLruSizeForTest(int size);
+ static void ClearLruSizeForTest();
+
+ std::map<std::string, SavedFiles*> extension_id_to_saved_files_;
+ STLValueDeleter<std::map<std::string, SavedFiles*> >
+ extension_id_to_saved_files_deleter_;
+ content::NotificationRegistrar registrar_;
+ Profile* profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(SavedFilesService);
+};
+
+} // namespace apps
+
+#endif // APPS_SAVED_FILES_SERVICE_H_
diff --git a/apps/saved_files_service_factory.cc b/apps/saved_files_service_factory.cc
new file mode 100644
index 0000000000..c9047702d5
--- /dev/null
+++ b/apps/saved_files_service_factory.cc
@@ -0,0 +1,36 @@
+// Copyright 2013 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 "apps/saved_files_service_factory.h"
+
+#include "apps/saved_files_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
+
+namespace apps {
+
+// static
+SavedFilesService* SavedFilesServiceFactory::GetForProfile(Profile* profile) {
+ return static_cast<SavedFilesService*>(
+ GetInstance()->GetServiceForBrowserContext(profile, true));
+}
+
+// static
+SavedFilesServiceFactory* SavedFilesServiceFactory::GetInstance() {
+ return Singleton<SavedFilesServiceFactory>::get();
+}
+
+SavedFilesServiceFactory::SavedFilesServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "SavedFilesService",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+SavedFilesServiceFactory::~SavedFilesServiceFactory() {}
+
+BrowserContextKeyedService* SavedFilesServiceFactory::BuildServiceInstanceFor(
+ content::BrowserContext* profile) const {
+ return new SavedFilesService(static_cast<Profile*>(profile));
+}
+
+} // namespace apps
diff --git a/apps/saved_files_service_factory.h b/apps/saved_files_service_factory.h
new file mode 100644
index 0000000000..502010f091
--- /dev/null
+++ b/apps/saved_files_service_factory.h
@@ -0,0 +1,35 @@
+// Copyright 2013 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.
+
+#ifndef APPS_SAVED_FILES_SERVICE_FACTORY_H_
+#define APPS_SAVED_FILES_SERVICE_FACTORY_H_
+
+#include "base/memory/singleton.h"
+#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h"
+
+class Profile;
+
+namespace apps {
+
+class SavedFilesService;
+
+// BrowserContextKeyedServiceFactory for SavedFilesService.
+class SavedFilesServiceFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static SavedFilesService* GetForProfile(Profile* profile);
+
+ static SavedFilesServiceFactory* GetInstance();
+
+ private:
+ SavedFilesServiceFactory();
+ virtual ~SavedFilesServiceFactory();
+ friend struct DefaultSingletonTraits<SavedFilesServiceFactory>;
+
+ virtual BrowserContextKeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* profile) const OVERRIDE;
+};
+
+} // namespace apps
+
+#endif // APPS_SAVED_FILES_SERVICE_FACTORY_H_
diff --git a/apps/saved_files_service_unittest.cc b/apps/saved_files_service_unittest.cc
new file mode 100644
index 0000000000..4d31485426
--- /dev/null
+++ b/apps/saved_files_service_unittest.cc
@@ -0,0 +1,239 @@
+// Copyright 2013 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 <algorithm>
+
+#include "apps/saved_files_service.h"
+#include "base/files/file_path.h"
+#include "base/string_number_conversions.h"
+#include "base/test/values_test_util.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_prefs.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/extensions/test_extension_environment.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/test/base/testing_profile.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if !defined(OS_ANDROID)
+
+#define TRACE_CALL(expression) \
+ do { \
+ SCOPED_TRACE(#expression); \
+ expression; \
+ } while (0)
+
+using apps::SavedFileEntry;
+using apps::SavedFilesService;
+
+namespace {
+
+std::string GenerateId(int i) {
+ return base::IntToString(i) + ":filename.ext";
+}
+
+} // namespace
+
+class SavedFilesServiceUnitTest : public testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ testing::Test::SetUp();
+ extension_ = env_.MakeExtension(*base::test::ParseJson(
+ "{"
+ " \"app\": {"
+ " \"background\": {"
+ " \"scripts\": [\"background.js\"]"
+ " }"
+ " },"
+ " \"permissions\": ["
+ " {\"fileSystem\": [\"retainFiles\"]}"
+ " ]"
+ "}"));
+ service_ = SavedFilesService::Get(env_.profile());
+ path_ = base::FilePath(FILE_PATH_LITERAL("filename.ext"));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ SavedFilesService::ClearMaxSequenceNumberForTest();
+ SavedFilesService::ClearLruSizeForTest();
+ testing::Test::TearDown();
+ }
+
+ // Check that a registered file entry has the correct value.
+ void CheckEntrySequenceNumber(int id, int sequence_number) {
+ std::string id_string = GenerateId(id);
+ SCOPED_TRACE(id_string);
+ EXPECT_TRUE(service_->IsRegistered(extension_->id(), id_string));
+ const SavedFileEntry* entry =
+ service_->GetFileEntry(extension_->id(), id_string);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(id_string, entry->id);
+ EXPECT_EQ(path_, entry->path);
+ EXPECT_TRUE(entry->writable);
+ EXPECT_EQ(sequence_number, entry->sequence_number);
+ }
+
+ // Check that a range of registered file entries have the correct values.
+ void CheckRangeEnqueuedInOrder(int start, int end) {
+ SavedFileEntry entry;
+ for (int i = start; i < end; i++) {
+ CheckEntrySequenceNumber(i, i + 1);
+ }
+ }
+
+ extensions::TestExtensionEnvironment env_;
+ const extensions::Extension* extension_;
+ SavedFilesService* service_;
+ base::FilePath path_;
+};
+
+TEST_F(SavedFilesServiceUnitTest, RetainTwoFilesTest) {
+ service_->RegisterFileEntry(extension_->id(), GenerateId(1), path_, true);
+ service_->RegisterFileEntry(extension_->id(), GenerateId(2), path_, true);
+ service_->RegisterFileEntry(extension_->id(), GenerateId(3), path_, true);
+
+ // Test that no entry has a sequence number.
+ TRACE_CALL(CheckEntrySequenceNumber(1, 0));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 0));
+ TRACE_CALL(CheckEntrySequenceNumber(3, 0));
+
+ // Test that only entry #1 has a sequence number.
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(1));
+ TRACE_CALL(CheckEntrySequenceNumber(1, 1));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 0));
+
+ // Test that entry #1 has not changed sequence number because it is the most
+ // recently enqueued entry.
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(1));
+ TRACE_CALL(CheckEntrySequenceNumber(1, 1));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 0));
+
+ // Test that entry #1 is unchanged and entry #2 has been assigned the next
+ // sequence number.
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(2));
+ TRACE_CALL(CheckEntrySequenceNumber(1, 1));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 2));
+
+ // Test that both entries #1 and #2 are unchanged because #2 is the most
+ // recently enqueued entry.
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(2));
+ TRACE_CALL(CheckEntrySequenceNumber(1, 1));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 2));
+
+ // Test that entry #1 has been assigned the next sequence number.
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(1));
+ TRACE_CALL(CheckEntrySequenceNumber(1, 3));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 2));
+ TRACE_CALL(CheckEntrySequenceNumber(3, 0));
+
+ EXPECT_FALSE(service_->IsRegistered(extension_->id(), "another id"));
+ SavedFileEntry entry;
+ EXPECT_FALSE(service_->GetFileEntry(extension_->id(), "another id"));
+
+ // ClearQueueIfNoRetainPermission should be a no-op because the app has the
+ // fileSystem.retainFiles permission.
+ service_->ClearQueueIfNoRetainPermission(extension_);
+ TRACE_CALL(CheckEntrySequenceNumber(1, 3));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 2));
+ TRACE_CALL(CheckEntrySequenceNumber(3, 0));
+
+ // Test that after a clear, retained file entries are unchanged, but file
+ // entries that have been registered but not retained are no longer
+ // registered.
+ service_->Clear(extension_->id());
+ TRACE_CALL(CheckEntrySequenceNumber(1, 3));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 2));
+ EXPECT_FALSE(service_->IsRegistered(extension_->id(), GenerateId(3)));
+}
+
+TEST_F(SavedFilesServiceUnitTest, NoRetainFilesPermissionTest) {
+ extension_ = env_.MakeExtension(*base::test::ParseJson(
+ "{\"app\": {\"background\": {\"scripts\": [\"background.js\"]}},"
+ "\"permissions\": [\"fileSystem\"]}"));
+ service_->RegisterFileEntry(extension_->id(), GenerateId(1), path_, true);
+ TRACE_CALL(CheckEntrySequenceNumber(1, 0));
+ SavedFileEntry entry;
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(1));
+ TRACE_CALL(CheckEntrySequenceNumber(1, 1));
+ EXPECT_FALSE(service_->IsRegistered(extension_->id(), "another id"));
+ EXPECT_FALSE(service_->GetFileEntry(extension_->id(), "another id"));
+
+ // ClearQueueIfNoRetainPermission should clear the queue, since the app does
+ // not have the "retainFiles" permission.
+ service_->ClearQueueIfNoRetainPermission(extension_);
+ std::vector<SavedFileEntry> entries =
+ service_->GetAllFileEntries(extension_->id());
+ EXPECT_TRUE(entries.empty());
+}
+
+TEST_F(SavedFilesServiceUnitTest, EvictionTest) {
+ SavedFilesService::SetLruSizeForTest(10);
+ for (int i = 0; i < 10; i++) {
+ service_->RegisterFileEntry(extension_->id(), GenerateId(i), path_, true);
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(i));
+ }
+ service_->RegisterFileEntry(extension_->id(), GenerateId(10), path_, true);
+
+ // Expect that entries 0 to 9 are in the queue, but 10 is not.
+ TRACE_CALL(CheckRangeEnqueuedInOrder(0, 10));
+ TRACE_CALL(CheckEntrySequenceNumber(10, 0));
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(10));
+
+ // Expect that entries 1 to 10 are in the queue, but entry 0 is not.
+ TRACE_CALL(CheckEntrySequenceNumber(0, 0));
+ TRACE_CALL(CheckRangeEnqueuedInOrder(1, 11));
+
+ // Check that retained entries are unchanged after a clear.
+ service_->Clear(extension_->id());
+ SavedFileEntry entry;
+ EXPECT_FALSE(service_->GetFileEntry(extension_->id(), GenerateId(0)));
+ TRACE_CALL(CheckRangeEnqueuedInOrder(1, 11));
+
+ // Expect that entry 2 is now at the back of the queue, and no further entries
+ // have been evicted.
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(2));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 12));
+ TRACE_CALL(CheckRangeEnqueuedInOrder(1, 1));
+ TRACE_CALL(CheckRangeEnqueuedInOrder(3, 11));
+
+ // Check that retained entries are unchanged after a clear.
+ service_->Clear(extension_->id());
+ TRACE_CALL(CheckEntrySequenceNumber(2, 12));
+ TRACE_CALL(CheckRangeEnqueuedInOrder(1, 1));
+ TRACE_CALL(CheckRangeEnqueuedInOrder(3, 11));
+}
+
+TEST_F(SavedFilesServiceUnitTest, SequenceNumberCompactionTest) {
+ SavedFilesService::SetMaxSequenceNumberForTest(8);
+ SavedFilesService::SetLruSizeForTest(8);
+ for (int i = 0; i < 4; i++) {
+ service_->RegisterFileEntry(extension_->id(), GenerateId(i), path_, true);
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(i));
+ }
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(2));
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(3));
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(2));
+
+ // The sequence numbers should be sparse, as they have not gone over the
+ // limit.
+ TRACE_CALL(CheckEntrySequenceNumber(0, 1));
+ TRACE_CALL(CheckEntrySequenceNumber(1, 2));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 7));
+ TRACE_CALL(CheckEntrySequenceNumber(3, 6));
+ service_->Clear(extension_->id());
+ TRACE_CALL(CheckEntrySequenceNumber(0, 1));
+ TRACE_CALL(CheckEntrySequenceNumber(1, 2));
+ TRACE_CALL(CheckEntrySequenceNumber(2, 7));
+ TRACE_CALL(CheckEntrySequenceNumber(3, 6));
+
+ // This should push the sequence number to the limit of 8, and trigger a
+ // sequence number compaction. Expect that the sequence numbers are
+ // contiguous from 1 to 4.
+ service_->EnqueueFileEntry(extension_->id(), GenerateId(3));
+ TRACE_CALL(CheckRangeEnqueuedInOrder(0, 4));
+ service_->Clear(extension_->id());
+ TRACE_CALL(CheckRangeEnqueuedInOrder(0, 4));
+}
+#endif
diff --git a/apps/shell_window_geometry_cache.cc b/apps/shell_window_geometry_cache.cc
new file mode 100644
index 0000000000..19901d9b93
--- /dev/null
+++ b/apps/shell_window_geometry_cache.cc
@@ -0,0 +1,276 @@
+// 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 "apps/shell_window_geometry_cache.h"
+
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/browser/extensions/extension_prefs.h"
+#include "chrome/browser/extensions/extension_prefs_factory.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/extensions/extension.h"
+#include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+
+namespace {
+
+// The timeout in milliseconds before we'll persist window geometry to the
+// StateStore.
+const int kSyncTimeoutMilliseconds = 1000;
+
+} // namespace
+
+namespace apps {
+
+ShellWindowGeometryCache::ShellWindowGeometryCache(
+ Profile* profile, extensions::ExtensionPrefs* prefs)
+ : prefs_(prefs),
+ sync_delay_(base::TimeDelta::FromMilliseconds(kSyncTimeoutMilliseconds)) {
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
+ content::Source<Profile>(profile));
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
+ content::Source<Profile>(profile));
+}
+
+ShellWindowGeometryCache::~ShellWindowGeometryCache() {
+}
+
+// static
+ShellWindowGeometryCache* ShellWindowGeometryCache::Get(
+ content::BrowserContext* context) {
+ return Factory::GetForContext(context, true /* create */);
+}
+
+void ShellWindowGeometryCache::SaveGeometry(
+ const std::string& extension_id,
+ const std::string& window_id,
+ const gfx::Rect& bounds,
+ ui::WindowShowState window_state) {
+ ExtensionData& extension_data = cache_[extension_id];
+
+ // If we don't have any unsynced changes and this is a duplicate of what's
+ // already in the cache, just ignore it.
+ if (extension_data[window_id].bounds == bounds &&
+ extension_data[window_id].window_state == window_state &&
+ !ContainsKey(unsynced_extensions_, extension_id))
+ return;
+
+ base::Time now = base::Time::Now();
+
+ extension_data[window_id].bounds = bounds;
+ extension_data[window_id].window_state = window_state;
+ extension_data[window_id].last_change = now;
+
+ if (extension_data.size() > kMaxCachedWindows) {
+ ExtensionData::iterator oldest = extension_data.end();
+ // Too many windows in the cache, find the oldest one to remove.
+ for (ExtensionData::iterator it = extension_data.begin();
+ it != extension_data.end(); ++it) {
+ // Don't expunge the window that was just added.
+ if (it->first == window_id) continue;
+
+ // If time is in the future, reset it to now to minimize weirdness.
+ if (it->second.last_change > now)
+ it->second.last_change = now;
+
+ if (oldest == extension_data.end() ||
+ it->second.last_change < oldest->second.last_change)
+ oldest = it;
+ }
+ extension_data.erase(oldest);
+ }
+
+ unsynced_extensions_.insert(extension_id);
+
+ // We don't use Reset() because the timer may not yet be running.
+ // (In that case Stop() is a no-op.)
+ sync_timer_.Stop();
+ sync_timer_.Start(FROM_HERE, sync_delay_, this,
+ &ShellWindowGeometryCache::SyncToStorage);
+}
+
+void ShellWindowGeometryCache::SyncToStorage() {
+ std::set<std::string> tosync;
+ tosync.swap(unsynced_extensions_);
+ for (std::set<std::string>::const_iterator it = tosync.begin(),
+ eit = tosync.end(); it != eit; ++it) {
+ const std::string& extension_id = *it;
+ const ExtensionData& extension_data = cache_[extension_id];
+
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ for (ExtensionData::const_iterator it = extension_data.begin(),
+ eit = extension_data.end(); it != eit; ++it) {
+ base::DictionaryValue* value = new base::DictionaryValue;
+ const gfx::Rect& bounds = it->second.bounds;
+ value->SetInteger("x", bounds.x());
+ value->SetInteger("y", bounds.y());
+ value->SetInteger("w", bounds.width());
+ value->SetInteger("h", bounds.height());
+ value->SetInteger("state", it->second.window_state);
+ value->SetString(
+ "ts", base::Int64ToString(it->second.last_change.ToInternalValue()));
+ dict->SetWithoutPathExpansion(it->first, value);
+ }
+ prefs_->SetGeometryCache(extension_id, dict.Pass());
+ }
+}
+
+bool ShellWindowGeometryCache::GetGeometry(
+ const std::string& extension_id,
+ const std::string& window_id,
+ gfx::Rect* bounds,
+ ui::WindowShowState* window_state) const {
+
+ std::map<std::string, ExtensionData>::const_iterator
+ extension_data_it = cache_.find(extension_id);
+
+ // Not in the map means loading data for the extension didn't finish yet.
+ if (extension_data_it == cache_.end())
+ return false;
+
+ ExtensionData::const_iterator window_data = extension_data_it->second.find(
+ window_id);
+
+ if (window_data == extension_data_it->second.end())
+ return false;
+
+ if (bounds)
+ *bounds = window_data->second.bounds;
+ if (window_state)
+ *window_state = window_data->second.window_state;
+ return true;
+}
+
+void ShellWindowGeometryCache::Shutdown() {
+ SyncToStorage();
+}
+
+void ShellWindowGeometryCache::Observe(
+ int type, const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case chrome::NOTIFICATION_EXTENSION_LOADED: {
+ std::string extension_id =
+ content::Details<const extensions::Extension>(details).ptr()->id();
+ OnExtensionLoaded(extension_id);
+ break;
+ }
+ case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
+ std::string extension_id =
+ content::Details<const extensions::UnloadedExtensionInfo>(details).
+ ptr()->extension->id();
+ OnExtensionUnloaded(extension_id);
+ break;
+ }
+ default:
+ NOTREACHED();
+ return;
+ }
+}
+
+void ShellWindowGeometryCache::SetSyncDelayForTests(int timeout_ms) {
+ sync_delay_ = base::TimeDelta::FromMilliseconds(timeout_ms);
+}
+
+void ShellWindowGeometryCache::OnExtensionLoaded(
+ const std::string& extension_id) {
+ ExtensionData& extension_data = cache_[extension_id];
+
+ const base::DictionaryValue* stored_windows =
+ prefs_->GetGeometryCache(extension_id);
+ if (!stored_windows)
+ return;
+
+ for (base::DictionaryValue::Iterator it(*stored_windows); !it.IsAtEnd();
+ it.Advance()) {
+ // If the cache already contains geometry for this window, don't
+ // overwrite that information since it is probably the result of an
+ // application starting up very quickly.
+ const std::string& window_id = it.key();
+ ExtensionData::iterator cached_window = extension_data.find(window_id);
+ if (cached_window == extension_data.end()) {
+ const base::DictionaryValue* stored_window;
+ if (it.value().GetAsDictionary(&stored_window)) {
+ WindowData& window_data = extension_data[it.key()];
+
+ int i;
+ if (stored_window->GetInteger("x", &i))
+ window_data.bounds.set_x(i);
+ if (stored_window->GetInteger("y", &i))
+ window_data.bounds.set_y(i);
+ if (stored_window->GetInteger("w", &i))
+ window_data.bounds.set_width(i);
+ if (stored_window->GetInteger("h", &i))
+ window_data.bounds.set_height(i);
+ if (stored_window->GetInteger("state", &i)) {
+ window_data.window_state =
+ static_cast<ui::WindowShowState>(i);
+ }
+ std::string ts_as_string;
+ if (stored_window->GetString("ts", &ts_as_string)) {
+ int64 ts;
+ if (base::StringToInt64(ts_as_string, &ts)) {
+ window_data.last_change = base::Time::FromInternalValue(ts);
+ }
+ }
+ }
+ }
+ }
+}
+
+void ShellWindowGeometryCache::OnExtensionUnloaded(
+ const std::string& extension_id) {
+ SyncToStorage();
+ cache_.erase(extension_id);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Factory boilerplate
+
+// static
+ShellWindowGeometryCache* ShellWindowGeometryCache::Factory::GetForContext(
+ content::BrowserContext* context, bool create) {
+ return static_cast<ShellWindowGeometryCache*>(
+ GetInstance()->GetServiceForBrowserContext(context, create));
+}
+
+ShellWindowGeometryCache::Factory*
+ShellWindowGeometryCache::Factory::GetInstance() {
+ return Singleton<ShellWindowGeometryCache::Factory>::get();
+}
+
+ShellWindowGeometryCache::Factory::Factory()
+ : BrowserContextKeyedServiceFactory(
+ "ShellWindowGeometryCache",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(extensions::ExtensionPrefsFactory::GetInstance());
+}
+
+ShellWindowGeometryCache::Factory::~Factory() {
+}
+
+BrowserContextKeyedService*
+ShellWindowGeometryCache::Factory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ Profile* profile = Profile::FromBrowserContext(context);
+ return new ShellWindowGeometryCache(
+ profile,
+ extensions::ExtensionPrefs::Get(profile));
+}
+
+bool ShellWindowGeometryCache::Factory::ServiceIsNULLWhileTesting() const {
+ return false;
+}
+
+content::BrowserContext*
+ShellWindowGeometryCache::Factory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ return chrome::GetBrowserContextRedirectedInIncognito(context);
+}
+
+} // namespace apps
diff --git a/apps/shell_window_geometry_cache.h b/apps/shell_window_geometry_cache.h
new file mode 100644
index 0000000000..91ea132a5b
--- /dev/null
+++ b/apps/shell_window_geometry_cache.h
@@ -0,0 +1,137 @@
+// 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_SHELL_WINDOW_GEOMETRY_CACHE_H_
+#define CHROME_BROWSER_EXTENSIONS_SHELL_WINDOW_GEOMETRY_CACHE_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/time.h"
+#include "base/timer.h"
+#include "base/values.h"
+#include "components/browser_context_keyed_service/browser_context_keyed_service.h"
+#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/rect.h"
+
+class Profile;
+
+namespace extensions {
+class ExtensionPrefs;
+}
+
+namespace apps {
+
+// A cache for persisted geometry of shell windows, both to not have to wait
+// for IO when creating a new window, and to not cause IO on every window
+// geometry change.
+class ShellWindowGeometryCache
+ : public BrowserContextKeyedService,
+ public content::NotificationObserver {
+ public:
+ class Factory : public BrowserContextKeyedServiceFactory {
+ public:
+ static ShellWindowGeometryCache* GetForContext(
+ content::BrowserContext* context,
+ bool create);
+
+ static Factory* GetInstance();
+ private:
+ friend struct DefaultSingletonTraits<Factory>;
+
+ Factory();
+ virtual ~Factory();
+
+ // BrowserContextKeyedServiceFactory
+ virtual BrowserContextKeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const OVERRIDE;
+ virtual bool ServiceIsNULLWhileTesting() const OVERRIDE;
+ virtual content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const OVERRIDE;
+ };
+
+ ShellWindowGeometryCache(Profile* profile,
+ extensions::ExtensionPrefs* prefs);
+
+ virtual ~ShellWindowGeometryCache();
+
+ // Returns the instance for the given browsing context.
+ static ShellWindowGeometryCache* Get(content::BrowserContext* context);
+
+ // Save the geometry and state associated with |extension_id| and |window_id|.
+ void SaveGeometry(const std::string& extension_id,
+ const std::string& window_id,
+ const gfx::Rect& bounds,
+ ui::WindowShowState state);
+
+ // Get any saved geometry and state associated with |extension_id| and
+ // |window_id|. If saved data exists, sets |bounds| and |state| if not NULL
+ // and returns true.
+ bool GetGeometry(const std::string& extension_id,
+ const std::string& window_id,
+ gfx::Rect* bounds,
+ ui::WindowShowState* state) const;
+
+ // BrowserContextKeyedService
+ virtual void Shutdown() OVERRIDE;
+
+ // Maximum number of windows we'll cache the geometry for per app.
+ static const size_t kMaxCachedWindows = 100;
+
+ protected:
+ friend class ShellWindowGeometryCacheTest;
+
+ // For tests, this modifies the timeout delay for saving changes from calls
+ // to SaveGeometry. (Note that even if this is set to 0, you still need to
+ // run the message loop to see the results of any SyncToStorage call).
+ void SetSyncDelayForTests(int timeout_ms);
+
+ private:
+ // Data stored for each window.
+ struct WindowData {
+ WindowData() : window_state(ui::SHOW_STATE_DEFAULT) {}
+ gfx::Rect bounds;
+ ui::WindowShowState window_state;
+ base::Time last_change;
+ };
+
+ // Data stored for each extension.
+ typedef std::map<std::string, WindowData> ExtensionData;
+
+ // content::NotificationObserver
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ void OnExtensionLoaded(const std::string& extension_id);
+ void OnExtensionUnloaded(const std::string& extension_id);
+ void SyncToStorage();
+
+ // Preferences storage.
+ extensions::ExtensionPrefs* prefs_;
+
+ // Cached data
+ std::map<std::string, ExtensionData> cache_;
+
+ // Data that still needs saving
+ std::set<std::string> unsynced_extensions_;
+
+ // The timer used to save the data
+ base::OneShotTimer<ShellWindowGeometryCache> sync_timer_;
+
+ // The timeout value we'll use for |sync_timer_|.
+ base::TimeDelta sync_delay_;
+
+ content::NotificationRegistrar registrar_;
+};
+
+} // namespace apps
+
+#endif // CHROME_BROWSER_EXTENSIONS_SHELL_WINDOW_GEOMETRY_CACHE_H_
diff --git a/apps/shell_window_geometry_cache_unittest.cc b/apps/shell_window_geometry_cache_unittest.cc
new file mode 100644
index 0000000000..255c78c535
--- /dev/null
+++ b/apps/shell_window_geometry_cache_unittest.cc
@@ -0,0 +1,256 @@
+// 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 "apps/shell_window_geometry_cache.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/mock_pref_change_callback.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/browser/extensions/extension_prefs.h"
+#include "chrome/browser/extensions/test_extension_prefs.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+ static const char kWindowId[] = "windowid";
+ static const char kWindowId2[] = "windowid2";
+
+ const char kWindowGeometryKey[] = "window_geometry";
+} // namespace
+
+using content::BrowserThread;
+
+namespace apps {
+
+// Base class for tests.
+class ShellWindowGeometryCacheTest : public testing::Test {
+ public:
+ ShellWindowGeometryCacheTest() :
+ ui_thread_(BrowserThread::UI, &ui_message_loop_) {
+ prefs_.reset(new extensions::TestExtensionPrefs(
+ ui_message_loop_.message_loop_proxy()));
+ cache_.reset(
+ new ShellWindowGeometryCache(&profile_, prefs_->prefs()));
+ cache_->SetSyncDelayForTests(0);
+ }
+
+ void AddGeometryAndLoadExtension(
+ const std::string& extension_id,
+ const std::string& window_id,
+ const gfx::Rect& bounds,
+ ui::WindowShowState state);
+
+ // Spins the UI threads' message loops to make sure any task
+ // posted to sync the geometry to the value store gets a chance to run.
+ void WaitForSync();
+
+ void LoadExtension(const std::string& extension_id);
+ void UnloadExtension(const std::string& extension_id);
+
+ protected:
+ TestingProfile profile_;
+ MessageLoopForUI ui_message_loop_;
+ content::TestBrowserThread ui_thread_;
+ scoped_ptr<extensions::TestExtensionPrefs> prefs_;
+ scoped_ptr<ShellWindowGeometryCache> cache_;
+};
+
+void ShellWindowGeometryCacheTest::AddGeometryAndLoadExtension(
+ const std::string& extension_id,
+ const std::string& window_id,
+ const gfx::Rect& bounds,
+ ui::WindowShowState state) {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ base::DictionaryValue* value = new base::DictionaryValue;
+ value->SetInteger("x", bounds.x());
+ value->SetInteger("y", bounds.y());
+ value->SetInteger("w", bounds.width());
+ value->SetInteger("h", bounds.height());
+ value->SetInteger("state", state);
+ dict->SetWithoutPathExpansion(window_id, value);
+ prefs_->prefs()->SetGeometryCache(extension_id, dict.Pass());
+ LoadExtension(extension_id);
+}
+
+void ShellWindowGeometryCacheTest::WaitForSync() {
+ content::RunAllPendingInMessageLoop();
+}
+
+void ShellWindowGeometryCacheTest::LoadExtension(
+ const std::string& extension_id) {
+ cache_->OnExtensionLoaded(extension_id);
+ WaitForSync();
+}
+
+void ShellWindowGeometryCacheTest::UnloadExtension(
+ const std::string& extension_id) {
+ cache_->OnExtensionUnloaded(extension_id);
+ WaitForSync();
+}
+
+// Test getting geometry from an empty store.
+TEST_F(ShellWindowGeometryCacheTest, GetGeometryEmptyStore) {
+ const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1");
+ ASSERT_FALSE(cache_->GetGeometry(extension_id, kWindowId, NULL, NULL));
+}
+
+// Test getting geometry for an unknown extension.
+TEST_F(ShellWindowGeometryCacheTest, GetGeometryUnkownExtension) {
+ const std::string extension_id1 = prefs_->AddExtensionAndReturnId("ext1");
+ const std::string extension_id2 = prefs_->AddExtensionAndReturnId("ext2");
+ AddGeometryAndLoadExtension(extension_id1, kWindowId,
+ gfx::Rect(4, 5, 31, 43),
+ ui::SHOW_STATE_DEFAULT);
+ ASSERT_FALSE(cache_->GetGeometry(extension_id2, kWindowId, NULL, NULL));
+}
+
+// Test getting geometry for an unknown window in a known extension.
+TEST_F(ShellWindowGeometryCacheTest, GetGeometryUnkownWindow) {
+ const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1");
+ AddGeometryAndLoadExtension(extension_id, kWindowId,
+ gfx::Rect(4, 5, 31, 43),
+ ui::SHOW_STATE_DEFAULT);
+ ASSERT_FALSE(cache_->GetGeometry(extension_id, kWindowId2, NULL, NULL));
+}
+
+// Test that loading geometry and state from the store works correctly.
+TEST_F(ShellWindowGeometryCacheTest, GetGeometryAndStateFromStore) {
+ const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1");
+ gfx::Rect bounds(4, 5, 31, 43);
+ ui::WindowShowState state = ui::SHOW_STATE_NORMAL;
+ AddGeometryAndLoadExtension(extension_id, kWindowId, bounds, state);
+ gfx::Rect new_bounds;
+ ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT;
+ ASSERT_TRUE(cache_->GetGeometry(
+ extension_id, kWindowId, &new_bounds, &new_state));
+ ASSERT_EQ(bounds, new_bounds);
+ ASSERT_EQ(state, new_state);
+}
+
+// Test saving geometry and state to the cache and state store, and reading
+// it back.
+TEST_F(ShellWindowGeometryCacheTest, SaveGeometryAndStateToStore) {
+ const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1");
+ const std::string window_id(kWindowId);
+
+ // inform cache of extension
+ LoadExtension(extension_id);
+
+ // update geometry stored in cache
+ gfx::Rect bounds(4, 5, 31, 43);
+ ui::WindowShowState state = ui::SHOW_STATE_NORMAL;
+ cache_->SaveGeometry(extension_id, window_id, bounds, state);
+
+ // make sure that immediately reading back geometry works
+ gfx::Rect new_bounds;
+ ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT;
+ ASSERT_TRUE(cache_->GetGeometry(
+ extension_id, window_id, &new_bounds, &new_state));
+ ASSERT_EQ(bounds, new_bounds);
+ ASSERT_EQ(state, new_state);
+
+ // unload extension to force cache to save data to the state store
+ UnloadExtension(extension_id);
+
+ // check if geometry got stored correctly in the state store
+ const base::DictionaryValue* dict =
+ prefs_->prefs()->GetGeometryCache(extension_id);
+ ASSERT_TRUE(dict);
+
+ ASSERT_TRUE(dict->HasKey(window_id));
+ int v;
+ ASSERT_TRUE(dict->GetInteger(window_id + ".x", &v));
+ ASSERT_EQ(bounds.x(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".y", &v));
+ ASSERT_EQ(bounds.y(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".w", &v));
+ ASSERT_EQ(bounds.width(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".h", &v));
+ ASSERT_EQ(bounds.height(), v);
+ ASSERT_TRUE(dict->GetInteger(window_id + ".state", &v));
+ ASSERT_EQ(state, v);
+
+ // check to make sure cache indeed doesn't know about this extension anymore
+ ASSERT_FALSE(cache_->GetGeometry(
+ extension_id, window_id, &new_bounds, &new_state));
+
+ // reload extension
+ LoadExtension(extension_id);
+ // and make sure the geometry got reloaded properly too
+ ASSERT_TRUE(cache_->GetGeometry(
+ extension_id, window_id, &new_bounds, &new_state));
+ ASSERT_EQ(bounds, new_bounds);
+ ASSERT_EQ(state, new_state);
+}
+
+// Tests that we won't do writes to the state store for SaveGeometry calls
+// which don't change the state we already have.
+TEST_F(ShellWindowGeometryCacheTest, NoDuplicateWrites) {
+ using testing::_;
+ using testing::Mock;
+
+ const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1");
+ gfx::Rect bounds1(100, 200, 300, 400);
+ gfx::Rect bounds2(200, 400, 600, 800);
+ gfx::Rect bounds2_duplicate(200, 400, 600, 800);
+
+ MockPrefChangeCallback observer(prefs_->pref_service());
+ PrefChangeRegistrar registrar;
+ registrar.Init(prefs_->pref_service());
+ registrar.Add("extensions.settings", observer.GetCallback());
+
+ // Write the first bounds - it should do > 0 writes.
+ EXPECT_CALL(observer, OnPreferenceChanged(_));
+ cache_->SaveGeometry(extension_id, kWindowId, bounds1,
+ ui::SHOW_STATE_DEFAULT);
+ WaitForSync();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Write a different bounds - it should also do > 0 writes.
+ EXPECT_CALL(observer, OnPreferenceChanged(_));
+ cache_->SaveGeometry(extension_id, kWindowId, bounds2,
+ ui::SHOW_STATE_DEFAULT);
+ WaitForSync();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Write a different state - it should also do > 0 writes.
+ EXPECT_CALL(observer, OnPreferenceChanged(_));
+ cache_->SaveGeometry(extension_id, kWindowId, bounds2,
+ ui::SHOW_STATE_NORMAL);
+ WaitForSync();
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Write a bounds and state that's a duplicate of what we already have.
+ // This should not do any writes.
+ EXPECT_CALL(observer, OnPreferenceChanged(_)).Times(0);
+ cache_->SaveGeometry(extension_id, kWindowId, bounds2_duplicate,
+ ui::SHOW_STATE_NORMAL);
+ WaitForSync();
+ Mock::VerifyAndClearExpectations(&observer);
+}
+
+// Tests that no more than kMaxCachedWindows windows will be cached.
+TEST_F(ShellWindowGeometryCacheTest, MaxWindows) {
+ const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1");
+ // inform cache of extension
+ LoadExtension(extension_id);
+
+ gfx::Rect bounds(4, 5, 31, 43);
+ for (size_t i = 0; i < ShellWindowGeometryCache::kMaxCachedWindows + 1; ++i) {
+ std::string window_id = "window_" + base::IntToString(i);
+ cache_->SaveGeometry(extension_id, window_id, bounds,
+ ui::SHOW_STATE_DEFAULT);
+ }
+
+ // The first added window should no longer have cached geometry.
+ EXPECT_FALSE(cache_->GetGeometry(extension_id, "window_0", NULL, NULL));
+ // All other windows should still exist.
+ for (size_t i = 1; i < ShellWindowGeometryCache::kMaxCachedWindows + 1; ++i) {
+ std::string window_id = "window_" + base::IntToString(i);
+ EXPECT_TRUE(cache_->GetGeometry(extension_id, window_id, NULL, NULL));
+ }
+}
+
+} // namespace extensions
diff --git a/apps/shortcut_manager.cc b/apps/shortcut_manager.cc
index df31a797a3..2bdccb5fc2 100644
--- a/apps/shortcut_manager.cc
+++ b/apps/shortcut_manager.cc
@@ -6,6 +6,8 @@
#include "base/bind.h"
#include "base/compiler_specific.h"
+#include "base/string16.h"
+#include "base/utf_string_conversions.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/ui/web_applications/web_app_ui.h"
#include "chrome/browser/web_applications/web_app.h"
@@ -61,7 +63,9 @@ void ShortcutManager::Observe(int type,
base::Callback<void(const ShellIntegration::ShortcutInfo&)>
create_or_update;
if (installed_info->is_update) {
- create_or_update = base::Bind(&web_app::UpdateAllShortcuts);
+ string16 old_title = UTF8ToUTF16(installed_info->old_name);
+ create_or_update = base::Bind(&web_app::UpdateAllShortcuts,
+ old_title);
} else {
create_or_update = base::Bind(&CreateShortcutsInApplicationsMenu);
}
diff --git a/apps/shortcut_manager.h b/apps/shortcut_manager.h
index a1efa01a79..a0efb822c1 100644
--- a/apps/shortcut_manager.h
+++ b/apps/shortcut_manager.h
@@ -6,8 +6,8 @@
#define APPS_SHORTCUT_MANAGER_H_
#include "base/memory/weak_ptr.h"
-#include "chrome/browser/profiles/profile_keyed_service.h"
#include "chrome/common/extensions/extension.h"
+#include "components/browser_context_keyed_service/browser_context_keyed_service.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
@@ -16,7 +16,7 @@ class Profile;
namespace apps {
// This class manages the installation of shortcuts for platform apps.
-class ShortcutManager : public ProfileKeyedService,
+class ShortcutManager : public BrowserContextKeyedService,
public content::NotificationObserver {
public:
explicit ShortcutManager(Profile* profile);
diff --git a/apps/shortcut_manager_factory.cc b/apps/shortcut_manager_factory.cc
index b76b16dfba..f59e57584a 100644
--- a/apps/shortcut_manager_factory.cc
+++ b/apps/shortcut_manager_factory.cc
@@ -6,21 +6,14 @@
#include "apps/shortcut_manager.h"
#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_dependency_manager.h"
+#include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
namespace apps {
// static
ShortcutManager* ShortcutManagerFactory::GetForProfile(Profile* profile) {
return static_cast<ShortcutManager*>(
- GetInstance()->GetServiceForProfile(profile, true));
-}
-
-// static
-void ShortcutManagerFactory::ResetForProfile(Profile* profile) {
- ShortcutManagerFactory* factory = GetInstance();
- factory->ProfileShutdown(profile);
- factory->ProfileDestroyed(profile);
+ GetInstance()->GetServiceForBrowserContext(profile, true));
}
ShortcutManagerFactory* ShortcutManagerFactory::GetInstance() {
@@ -28,19 +21,20 @@ ShortcutManagerFactory* ShortcutManagerFactory::GetInstance() {
}
ShortcutManagerFactory::ShortcutManagerFactory()
- : ProfileKeyedServiceFactory("ShortcutManager",
- ProfileDependencyManager::GetInstance()) {
+ : BrowserContextKeyedServiceFactory(
+ "ShortcutManager",
+ BrowserContextDependencyManager::GetInstance()) {
}
ShortcutManagerFactory::~ShortcutManagerFactory() {
}
-ProfileKeyedService* ShortcutManagerFactory::BuildServiceInstanceFor(
+BrowserContextKeyedService* ShortcutManagerFactory::BuildServiceInstanceFor(
content::BrowserContext* profile) const {
return new ShortcutManager(static_cast<Profile*>(profile));
}
-bool ShortcutManagerFactory::ServiceIsCreatedWithProfile() const {
+bool ShortcutManagerFactory::ServiceIsCreatedWithBrowserContext() const {
return true;
}
diff --git a/apps/shortcut_manager_factory.h b/apps/shortcut_manager_factory.h
index bed2136c5d..8b41f062d2 100644
--- a/apps/shortcut_manager_factory.h
+++ b/apps/shortcut_manager_factory.h
@@ -5,10 +5,12 @@
#ifndef APPS_SHORTCUT_MANAGER_FACTORY_H_
#define APPS_SHORTCUT_MANAGER_FACTORY_H_
-#include "chrome/browser/profiles/profile_keyed_service_factory.h"
+#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h"
template<typename Type> struct DefaultSingletonTraits;
+class Profile;
+
namespace apps {
class ShortcutManager;
@@ -17,12 +19,10 @@ class ShortcutManager;
// Profiles. Listens for the Profile's destruction notification and cleans up
// the associated ShortcutManager.
// ShortcutManagers should not exist in incognito profiles.
-class ShortcutManagerFactory : public ProfileKeyedServiceFactory {
+class ShortcutManagerFactory : public BrowserContextKeyedServiceFactory {
public:
static ShortcutManager* GetForProfile(Profile* profile);
- static void ResetForProfile(Profile* profile);
-
static ShortcutManagerFactory* GetInstance();
private:
@@ -31,10 +31,10 @@ class ShortcutManagerFactory : public ProfileKeyedServiceFactory {
ShortcutManagerFactory();
virtual ~ShortcutManagerFactory();
- // ProfileKeyedServiceFactory:
- virtual ProfileKeyedService* BuildServiceInstanceFor(
+ // BrowserContextKeyedServiceFactory:
+ virtual BrowserContextKeyedService* BuildServiceInstanceFor(
content::BrowserContext* profile) const OVERRIDE;
- virtual bool ServiceIsCreatedWithProfile() const OVERRIDE;
+ virtual bool ServiceIsCreatedWithBrowserContext() const OVERRIDE;
};
} // namespace apps