diff options
25 files changed, 680 insertions, 158 deletions
diff --git a/chrome/browser/apps/ephemeral_app_browsertest.cc b/chrome/browser/apps/ephemeral_app_browsertest.cc index 0ea00bc4..3445281 100644 --- a/chrome/browser/apps/ephemeral_app_browsertest.cc +++ b/chrome/browser/apps/ephemeral_app_browsertest.cc @@ -2,45 +2,60 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "chrome/browser/apps/ephemeral_app_browsertest.h" + +#include <vector> + #include "apps/saved_files_service.h" #include "base/files/scoped_temp_dir.h" +#include "base/scoped_observer.h" #include "base/stl_util.h" #include "chrome/browser/apps/app_browsertest_util.h" #include "chrome/browser/apps/ephemeral_app_service.h" #include "chrome/browser/extensions/api/file_system/file_system_api.h" +#include "chrome/browser/extensions/app_sync_data.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_sync_service.h" #include "chrome/browser/extensions/extension_test_message_listener.h" #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/notifications/desktop_notification_service.h" #include "chrome/browser/notifications/desktop_notification_service_factory.h" +#include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/api/alarms.h" #include "content/public/test/browser_test.h" #include "content/public/test/test_utils.h" +#include "extensions/browser/app_sorting.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_registry_observer.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/extension_util.h" #include "extensions/browser/process_manager.h" +#include "extensions/common/extension.h" #include "extensions/common/switches.h" +#include "sync/api/fake_sync_change_processor.h" +#include "sync/api/sync_change_processor_wrapper_for_test.h" +#include "sync/api/sync_error_factory_mock.h" #include "ui/message_center/message_center.h" #include "ui/message_center/notifier_settings.h" +using extensions::AppSyncData; using extensions::Event; using extensions::EventRouter; using extensions::Extension; using extensions::ExtensionInfo; using extensions::ExtensionPrefs; +using extensions::ExtensionRegistry; +using extensions::ExtensionRegistryObserver; using extensions::ExtensionSystem; using extensions::Manifest; -using extensions::PlatformAppBrowserTest; namespace { namespace alarms = extensions::api::alarms; const char kDispatchEventTestApp[] = "ephemeral_apps/dispatch_event"; -const char kMessagingReceiverApp[] = "ephemeral_apps/messaging_receiver"; -const char kMessagingReceiverAppV2[] = "ephemeral_apps/messaging_receiver2"; const char kNotificationsTestApp[] = "ephemeral_apps/notification_settings"; const char kFileSystemTestApp[] = "ephemeral_apps/filesystem_retain_entries"; const char kRetainDataApp[] = "ephemeral_apps/retain_data"; @@ -70,57 +85,186 @@ bool IsAppInExtensionsInfo(const ExtensionPrefs::ExtensionsInfo& ext_info, return false; } -} // namespace - -class EphemeralAppBrowserTest : public PlatformAppBrowserTest { - protected: - virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { - // Skip PlatformAppBrowserTest, which sets different values for the switches - // below. - ExtensionBrowserTest::SetUpCommandLine(command_line); - - // Make event pages get suspended immediately. - command_line->AppendSwitchASCII( - extensions::switches::kEventPageIdleTime, "10"); - command_line->AppendSwitchASCII( - extensions::switches::kEventPageSuspendingTime, "10"); +// Saves some parameters from the extension installed notification in order +// to verify them in tests. +class InstallObserver : public ExtensionRegistryObserver { + public: + struct InstallParameters { + std::string id; + bool is_update; + bool from_ephemeral; + + InstallParameters( + const std::string& id, + bool is_update, + bool from_ephemeral) + : id(id), is_update(is_update), from_ephemeral(from_ephemeral) {} + }; + + explicit InstallObserver(Profile* profile) : registry_observer_(this) { + registry_observer_.Add(ExtensionRegistry::Get(profile)); } - base::FilePath GetTestPath(const char* test_path) { - return test_data_dir_.AppendASCII("platform_apps").AppendASCII(test_path); - } + virtual ~InstallObserver() {} - const Extension* InstallEphemeralApp(const char* test_path, - Manifest::Location manifest_location) { - const Extension* extension = - InstallEphemeralAppWithSourceAndFlags( - GetTestPath(test_path), - 1, - manifest_location, - Extension::NO_FLAGS); - return extension; + const InstallParameters& Last() { + CHECK(!install_params_.empty()); + return install_params_.back(); } - const Extension* InstallEphemeralApp(const char* test_path) { - return InstallEphemeralApp(test_path, Manifest::INTERNAL); + private: + virtual void OnExtensionWillBeInstalled( + content::BrowserContext* browser_context, + const Extension* extension, + bool is_update, + bool from_ephemeral, + const std::string& old_name) OVERRIDE { + install_params_.push_back( + InstallParameters(extension->id(), is_update, from_ephemeral)); } - const Extension* InstallAndLaunchEphemeralApp(const char* test_path) { - ExtensionTestMessageListener launched_listener("launched", false); - const Extension* extension = InstallEphemeralApp(test_path); - EXPECT_TRUE(extension); - if (!extension) - return NULL; + std::vector<InstallParameters> install_params_; + ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> + registry_observer_; +}; + +} // namespace + + +// EphemeralAppTestBase: + +const char EphemeralAppTestBase::kMessagingReceiverApp[] = + "ephemeral_apps/messaging_receiver"; +const char EphemeralAppTestBase::kMessagingReceiverAppV2[] = + "ephemeral_apps/messaging_receiver2"; + +EphemeralAppTestBase::EphemeralAppTestBase() {} + +EphemeralAppTestBase::~EphemeralAppTestBase() {} + +void EphemeralAppTestBase::SetUpCommandLine(base::CommandLine* command_line) { + // Skip PlatformAppBrowserTest, which sets different values for the switches + // below. + ExtensionBrowserTest::SetUpCommandLine(command_line); + + // Make event pages get suspended immediately. + command_line->AppendSwitchASCII( + extensions::switches::kEventPageIdleTime, "10"); + command_line->AppendSwitchASCII( + extensions::switches::kEventPageSuspendingTime, "10"); + + // Enable ephemeral apps flag. + command_line->AppendSwitch(switches::kEnableEphemeralApps); +} + +base::FilePath EphemeralAppTestBase::GetTestPath(const char* test_path) { + return test_data_dir_.AppendASCII("platform_apps").AppendASCII(test_path); +} - LaunchPlatformApp(extension); - bool wait_result = launched_listener.WaitUntilSatisfied(); - EXPECT_TRUE(wait_result); - if (!wait_result) - return NULL; +const Extension* EphemeralAppTestBase::InstallEphemeralApp( + const char* test_path, Manifest::Location manifest_location) { + const Extension* extension = InstallEphemeralAppWithSourceAndFlags( + GetTestPath(test_path), 1, manifest_location, Extension::NO_FLAGS); + EXPECT_TRUE(extension); + if (extension) + EXPECT_TRUE(extensions::util::IsEphemeralApp(extension->id(), profile())); + return extension; +} + +const Extension* EphemeralAppTestBase::InstallEphemeralApp( + const char* test_path) { + return InstallEphemeralApp(test_path, Manifest::INTERNAL); +} + +const Extension* EphemeralAppTestBase::InstallAndLaunchEphemeralApp( + const char* test_path) { + ExtensionTestMessageListener launched_listener("launched", false); + const Extension* extension = InstallEphemeralApp(test_path); + EXPECT_TRUE(extension); + if (!extension) + return NULL; + + LaunchPlatformApp(extension); + bool wait_result = launched_listener.WaitUntilSatisfied(); + EXPECT_TRUE(wait_result); + if (!wait_result) + return NULL; + + return extension; +} + +const Extension* EphemeralAppTestBase::UpdateEphemeralApp( + const std::string& app_id, + const base::FilePath& test_dir, + const base::FilePath& pem_path) { + // Pack a new version of the app. + base::ScopedTempDir temp_dir; + EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); - return extension; + base::FilePath crx_path = temp_dir.path().AppendASCII("temp.crx"); + if (!base::DeleteFile(crx_path, false)) { + ADD_FAILURE() << "Failed to delete existing crx: " << crx_path.value(); + return NULL; } + base::FilePath app_v2_path = PackExtensionWithOptions( + test_dir, crx_path, pem_path, base::FilePath()); + EXPECT_FALSE(app_v2_path.empty()); + + // Update the ephemeral app and wait for the update to finish. + extensions::CrxInstaller* crx_installer = NULL; + content::WindowedNotificationObserver windowed_observer( + chrome::NOTIFICATION_CRX_INSTALLER_DONE, + content::Source<extensions::CrxInstaller>(crx_installer)); + ExtensionService* service = + ExtensionSystem::Get(profile())->extension_service(); + EXPECT_TRUE(service->UpdateExtension(app_id, app_v2_path, true, + &crx_installer)); + windowed_observer.Wait(); + + return service->GetExtensionById(app_id, false); +} + +void EphemeralAppTestBase::PromoteEphemeralApp( + const extensions::Extension* app) { + ExtensionService* extension_service = + ExtensionSystem::Get(profile())->extension_service(); + ASSERT_TRUE(extension_service); + extension_service->PromoteEphemeralApp(app, false); +} + +void EphemeralAppTestBase::CloseApp(const std::string& app_id) { + content::WindowedNotificationObserver event_page_destroyed_signal( + chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, + content::Source<Profile>(profile())); + + EXPECT_EQ(1U, GetAppWindowCountForApp(app_id)); + apps::AppWindow* app_window = GetFirstAppWindowForApp(app_id); + ASSERT_TRUE(app_window); + CloseAppWindow(app_window); + + event_page_destroyed_signal.Wait(); +} + +void EphemeralAppTestBase::EvictApp(const std::string& app_id) { + // Uninstall the app, which is what happens when ephemeral apps get evicted + // from the cache. + content::WindowedNotificationObserver uninstalled_signal( + chrome::NOTIFICATION_EXTENSION_UNINSTALLED, + content::Source<Profile>(profile())); + + ExtensionService* service = + ExtensionSystem::Get(profile())->extension_service(); + ASSERT_TRUE(service); + service->UninstallExtension(app_id, false, NULL); + + uninstalled_signal.Wait(); +} + +// EphemeralAppBrowserTest: + +class EphemeralAppBrowserTest : public EphemeralAppTestBase { + protected: bool LaunchAppAndRunTest(const Extension* app, const char* test_name) { ExtensionTestMessageListener launched_listener("launched", true); LaunchPlatformApp(app); @@ -139,36 +283,8 @@ class EphemeralAppBrowserTest : public PlatformAppBrowserTest { return result; } - void CloseApp(const std::string& app_id) { - content::WindowedNotificationObserver event_page_destroyed_signal( - chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, - content::Source<Profile>(browser()->profile())); - - EXPECT_EQ(1U, GetAppWindowCountForApp(app_id)); - apps::AppWindow* app_window = GetFirstAppWindowForApp(app_id); - ASSERT_TRUE(app_window); - CloseAppWindow(app_window); - - event_page_destroyed_signal.Wait(); - } - - void EvictApp(const std::string& app_id) { - // Uninstall the app, which is what happens when ephemeral apps get evicted - // from the cache. - content::WindowedNotificationObserver uninstalled_signal( - chrome::NOTIFICATION_EXTENSION_UNINSTALLED, - content::Source<Profile>(browser()->profile())); - - ExtensionService* service = - ExtensionSystem::Get(browser()->profile())->extension_service(); - ASSERT_TRUE(service); - service->UninstallExtension(app_id, false, NULL); - - uninstalled_signal.Wait(); - } - void VerifyAppNotLoaded(const std::string& app_id) { - EXPECT_FALSE(ExtensionSystem::Get(browser()->profile())-> + EXPECT_FALSE(ExtensionSystem::Get(profile())-> process_manager()->GetBackgroundHostForExtension(app_id)); } @@ -187,10 +303,67 @@ class EphemeralAppBrowserTest : public PlatformAppBrowserTest { void GarbageCollectData() { EphemeralAppService* service = - EphemeralAppService::Get(browser()->profile()); + EphemeralAppService::Get(profile()); ASSERT_TRUE(service); service->GarbageCollectData(); } + + const Extension* ReplaceEphemeralApp(const std::string& app_id, + const char* test_path) { + return UpdateExtensionWaitForIdle(app_id, GetTestPath(test_path), 0); + } + + void VerifyPromotedApp(const std::string& app_id, + ExtensionRegistry::IncludeFlag expected_set) { + const Extension* app = ExtensionRegistry::Get(profile())->GetExtensionById( + app_id, expected_set); + ASSERT_TRUE(app); + + // The app should not be ephemeral. + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); + ASSERT_TRUE(prefs); + EXPECT_FALSE(prefs->IsEphemeralApp(app_id)); + + // Check sort ordinals. + extensions::AppSorting* app_sorting = prefs->app_sorting(); + EXPECT_TRUE(app_sorting->GetAppLaunchOrdinal(app_id).IsValid()); + EXPECT_TRUE(app_sorting->GetPageOrdinal(app_id).IsValid()); + } + + void InitSyncService() { + ExtensionSyncService* sync_service = ExtensionSyncService::Get(profile()); + sync_service->MergeDataAndStartSyncing( + syncer::APPS, + syncer::SyncDataList(), + scoped_ptr<syncer::SyncChangeProcessor>( + new syncer::SyncChangeProcessorWrapperForTest( + &mock_sync_processor_)), + scoped_ptr<syncer::SyncErrorFactory>( + new syncer::SyncErrorFactoryMock())); + } + + scoped_ptr<AppSyncData> GetFirstSyncChangeForApp(const std::string& id) { + scoped_ptr<AppSyncData> sync_data; + for (syncer::SyncChangeList::iterator it = + mock_sync_processor_.changes().begin(); + it != mock_sync_processor_.changes().end(); ++it) { + sync_data.reset(new AppSyncData(*it)); + if (sync_data->id() == id) + return sync_data.Pass(); + } + + return scoped_ptr<AppSyncData>(); + } + + void VerifySyncChange(const AppSyncData* sync_change, bool expect_enabled) { + ASSERT_TRUE(sync_change); + EXPECT_TRUE(sync_change->page_ordinal().IsValid()); + EXPECT_TRUE(sync_change->app_launch_ordinal().IsValid()); + EXPECT_FALSE(sync_change->uninstalled()); + EXPECT_EQ(expect_enabled, sync_change->extension_sync_data().enabled()); + } + + syncer::FakeSyncChangeProcessor mock_sync_processor_; }; // Verify that ephemeral apps can be launched and receive system events when @@ -203,7 +376,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, EventDispatchWhenLaunched) { // Send a fake alarm event to the app and verify that a response is // received. - EventRouter* event_router = EventRouter::Get(browser()->profile()); + EventRouter* event_router = EventRouter::Get(profile()); ASSERT_TRUE(event_router); ExtensionTestMessageListener alarm_received_listener("alarm_received", false); @@ -246,41 +419,24 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, ReceiveMessagesWhenLaunched) { IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, UpdateEphemeralApp) { const Extension* app_v1 = InstallEphemeralApp(kMessagingReceiverApp); ASSERT_TRUE(app_v1); - ASSERT_TRUE(extensions::util::IsEphemeralApp(app_v1->id(), profile())); std::string app_id = app_v1->id(); base::Version app_original_version = *app_v1->version(); app_v1 = NULL; // The extension object will be destroyed during update. - // Pack version 2 of the app. - base::ScopedTempDir temp_dir; - ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - - base::FilePath crx_path = temp_dir.path().AppendASCII("temp.crx"); - if (!base::DeleteFile(crx_path, false)) { - ADD_FAILURE() << "Failed to delete crx: " << crx_path.value(); - return; - } - - base::FilePath app_v2_path = PackExtensionWithOptions( - GetTestPath(kMessagingReceiverAppV2), - crx_path, + // Update to version 2 of the app. + InstallObserver installed_observer(profile()); + const Extension* app_v2 = UpdateEphemeralApp( + app_id, GetTestPath(kMessagingReceiverAppV2), GetTestPath(kMessagingReceiverApp).ReplaceExtension( - FILE_PATH_LITERAL(".pem")), - base::FilePath()); - ASSERT_FALSE(app_v2_path.empty()); + FILE_PATH_LITERAL(".pem"))); - // Update the ephemeral app and wait for the update to finish. - extensions::CrxInstaller* crx_installer = NULL; - content::WindowedNotificationObserver windowed_observer( - chrome::NOTIFICATION_CRX_INSTALLER_DONE, - content::Source<extensions::CrxInstaller>(crx_installer)); - ExtensionService* service = - ExtensionSystem::Get(browser()->profile())->extension_service(); - EXPECT_TRUE(service->UpdateExtension(app_id, app_v2_path, true, - &crx_installer)); - windowed_observer.Wait(); + // Check the notification parameters. + const InstallObserver::InstallParameters& params = installed_observer.Last(); + EXPECT_EQ(app_id, params.id); + EXPECT_TRUE(params.is_update); + EXPECT_FALSE(params.from_ephemeral); - const Extension* app_v2 = service->GetExtensionById(app_id, false); + // The ephemeral flag should still be enabled. ASSERT_TRUE(app_v2); EXPECT_TRUE(app_v2->version()->CompareTo(app_original_version) > 0); EXPECT_TRUE(extensions::util::IsEphemeralApp(app_v2->id(), profile())); @@ -294,7 +450,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, StickyNotificationSettings) { // Disable notifications for this app. DesktopNotificationService* notification_service = - DesktopNotificationServiceFactory::GetForProfile(browser()->profile()); + DesktopNotificationServiceFactory::GetForProfile(profile()); ASSERT_TRUE(notification_service); message_center::NotifierId notifier_id( @@ -376,7 +532,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, // Verify that after the app has been closed, all retained entries are // flushed. std::vector<apps::SavedFileEntry> file_entries = - apps::SavedFilesService::Get(browser()->profile()) + apps::SavedFilesService::Get(profile()) ->GetAllFileEntries(app->id()); EXPECT_TRUE(file_entries.empty()); @@ -402,7 +558,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, RetainData) { app = NULL; // The app should be in the list of evicted apps. - ExtensionPrefs* prefs = ExtensionPrefs::Get(browser()->profile()); + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); ASSERT_TRUE(prefs); scoped_ptr<ExtensionPrefs::ExtensionsInfo> extensions_info( prefs->GetEvictedEphemeralAppsInfo()); @@ -414,14 +570,15 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, RetainData) { // The app should not be in the list of installed extensions. extensions_info = prefs->GetInstalledExtensionsInfo(); EXPECT_FALSE(IsAppInExtensionsInfo(*extensions_info, app_id)); + EXPECT_FALSE(prefs->IsEphemeralApp(app_id)); // Ensure the evicted app is considered to have isolated storage. This will // prevent its data from getting garbage collected by // ExtensionService::GarbageCollectIsolatedStorage(). GURL site_url = extensions::util::GetSiteForExtensionId( - app_id, browser()->profile()); + app_id, profile()); EXPECT_TRUE(extensions::util::SiteHasIsolatedStorage( - site_url, browser()->profile())); + site_url, profile())); // Phase 2 - Reinstall the ephemeral app and verify that data still exists // in the storage. @@ -437,6 +594,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, RetainData) { EXPECT_FALSE(IsAppInExtensionsInfo(*extensions_info, app_id)); single_extension_info = prefs->GetEvictedEphemeralAppInfo(app_id); EXPECT_FALSE(single_extension_info.get()); + EXPECT_TRUE(prefs->IsEphemeralApp(app_id)); } // Verify that preferences are updated correctly when an evicted ephemeral app @@ -454,7 +612,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, InstallEvictedEphemeralApp) { ASSERT_TRUE(app); // Verify that preferences are correct. - ExtensionPrefs* prefs = ExtensionPrefs::Get(browser()->profile()); + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); ASSERT_TRUE(prefs); EXPECT_FALSE(prefs->IsEphemeralApp(app->id())); @@ -478,7 +636,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, RemoveInstalledData) { app = NULL; // The app should not be in the preferences. - ExtensionPrefs* prefs = ExtensionPrefs::Get(browser()->profile()); + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); ASSERT_TRUE(prefs); scoped_ptr<ExtensionPrefs::ExtensionsInfo> extensions_info( prefs->GetEvictedEphemeralAppsInfo()); @@ -510,7 +668,7 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, GarbageCollectData) { EvictApp(retain_app_id); retain_app = NULL; - ExtensionPrefs* prefs = ExtensionPrefs::Get(browser()->profile()); + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); ASSERT_TRUE(prefs); // Both apps should be in the list of evicted apps. @@ -539,3 +697,161 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, GarbageCollectData) { evict_app = InstallEphemeralApp(kRetainDataApp); ASSERT_TRUE(LaunchAppAndRunTest(evict_app, "DataReset")) << message_; } + +// Checks the process of installing and then promoting an ephemeral app. +IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteEphemeralApp) { + InitSyncService(); + + const Extension* app = InstallEphemeralApp(kRetainDataApp); + ASSERT_TRUE(app); + + // Ephemeral apps should not be synced. + scoped_ptr<AppSyncData> sync_change = GetFirstSyncChangeForApp(app->id()); + EXPECT_FALSE(sync_change.get()); + + // Promote the app to a regular installed app. + InstallObserver installed_observer(profile()); + PromoteEphemeralApp(app); + VerifyPromotedApp(app->id(), ExtensionRegistry::ENABLED); + + // Check the notification parameters. + const InstallObserver::InstallParameters& params = installed_observer.Last(); + EXPECT_EQ(app->id(), params.id); + EXPECT_TRUE(params.is_update); + EXPECT_TRUE(params.from_ephemeral); + + // The installation should now be synced. + sync_change = GetFirstSyncChangeForApp(app->id()); + VerifySyncChange(sync_change.get(), true); +} + +// Verifies that promoting an ephemeral app will enable it. +IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteEphemeralAppAndEnable) { + InitSyncService(); + + const Extension* app = InstallEphemeralApp(kRetainDataApp); + ASSERT_TRUE(app); + + // Disable the ephemeral app. + ExtensionService* service = + ExtensionSystem::Get(profile())->extension_service(); + service->DisableExtension(app->id(), Extension::DISABLE_PERMISSIONS_INCREASE); + ASSERT_TRUE(ExtensionRegistry::Get(profile())-> + GetExtensionById(app->id(), ExtensionRegistry::DISABLED)); + + // Promote to a regular installed app. It should be enabled. + PromoteEphemeralApp(app); + VerifyPromotedApp(app->id(), ExtensionRegistry::ENABLED); + + scoped_ptr<AppSyncData> sync_change = GetFirstSyncChangeForApp(app->id()); + VerifySyncChange(sync_change.get(), true); +} + +// Verifies that promoting an ephemeral app that has unsupported requirements +// will not enable it. +IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, + PromoteUnsupportedEphemeralApp) { + InitSyncService(); + + const Extension* app = InstallEphemeralApp(kRetainDataApp); + ASSERT_TRUE(app); + + // Disable the ephemeral app. + ExtensionService* service = + ExtensionSystem::Get(profile())->extension_service(); + service->DisableExtension( + app->id(), Extension::DISABLE_UNSUPPORTED_REQUIREMENT); + ASSERT_TRUE(ExtensionRegistry::Get(profile())-> + GetExtensionById(app->id(), ExtensionRegistry::DISABLED)); + + // Promote to a regular installed app. It should remain disabled. + PromoteEphemeralApp(app); + VerifyPromotedApp(app->id(), ExtensionRegistry::DISABLED); + + scoped_ptr<AppSyncData> sync_change = GetFirstSyncChangeForApp(app->id()); + VerifySyncChange(sync_change.get(), false); +} + +// Checks the process of promoting an ephemeral app from sync. +IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, PromoteEphemeralAppFromSync) { + InitSyncService(); + + const Extension* app = InstallEphemeralApp(kRetainDataApp); + ASSERT_TRUE(app); + std::string app_id = app->id(); + + // Simulate an install from sync. + const syncer::StringOrdinal kAppLaunchOrdinal("x"); + const syncer::StringOrdinal kPageOrdinal("y"); + AppSyncData app_sync_data( + *app, + true /* enabled */, + false /* incognito enabled */, + false /* remote install */, + kAppLaunchOrdinal, + kPageOrdinal, + extensions::LAUNCH_TYPE_REGULAR); + + ExtensionSyncService* sync_service = ExtensionSyncService::Get(profile()); + sync_service->ProcessAppSyncData(app_sync_data); + + // Verify the installation. + VerifyPromotedApp(app_id, ExtensionRegistry::ENABLED); + + // The sort ordinals from sync should not be overridden. + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); + extensions::AppSorting* app_sorting = prefs->app_sorting(); + EXPECT_TRUE(app_sorting->GetAppLaunchOrdinal(app_id).Equals( + kAppLaunchOrdinal)); + EXPECT_TRUE(app_sorting->GetPageOrdinal(app_id).Equals(kPageOrdinal)); +} + +// In most cases, ExtensionService::PromoteEphemeralApp() will be called to +// permanently install an ephemeral app. However, there may be cases where an +// install occurs through the usual route of installing from the Web Store (due +// to race conditions). Ensure that the app is still installed correctly. +IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, + ReplaceEphemeralAppWithInstalledApp) { + const Extension* app = InstallEphemeralApp(kRetainDataApp); + ASSERT_TRUE(app); + std::string app_id = app->id(); + app = NULL; + + InstallObserver installed_observer(profile()); + ReplaceEphemeralApp(app_id, kRetainDataApp); + VerifyPromotedApp(app_id, ExtensionRegistry::ENABLED); + + // Check the notification parameters. + const InstallObserver::InstallParameters& params = installed_observer.Last(); + EXPECT_EQ(app_id, params.id); + EXPECT_TRUE(params.is_update); + EXPECT_TRUE(params.from_ephemeral); +} + +// This is similar to ReplaceEphemeralAppWithInstalledApp, but installs will +// be delayed until the app is idle. +IN_PROC_BROWSER_TEST_F(EphemeralAppBrowserTest, + ReplaceEphemeralAppWithDelayedInstalledApp) { + const Extension* app = InstallAndLaunchEphemeralApp(kRetainDataApp); + ASSERT_TRUE(app); + std::string app_id = app->id(); + app = NULL; + + // Initiate install. + ReplaceEphemeralApp(app_id, kRetainDataApp); + + // The delayed installation will occur when the ephemeral app is closed. + content::WindowedNotificationObserver installed_signal( + chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED, + content::Source<Profile>(profile())); + InstallObserver installed_observer(profile()); + CloseApp(app_id); + installed_signal.Wait(); + VerifyPromotedApp(app_id, ExtensionRegistry::ENABLED); + + // Check the notification parameters. + const InstallObserver::InstallParameters& params = installed_observer.Last(); + EXPECT_EQ(app_id, params.id); + EXPECT_TRUE(params.is_update); + EXPECT_TRUE(params.from_ephemeral); +} diff --git a/chrome/browser/apps/ephemeral_app_browsertest.h b/chrome/browser/apps/ephemeral_app_browsertest.h new file mode 100644 index 0000000..21a6d9a --- /dev/null +++ b/chrome/browser/apps/ephemeral_app_browsertest.h @@ -0,0 +1,47 @@ +// Copyright 2014 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_APPS_EPHEMERAL_APP_BROWSERTEST_H_ +#define CHROME_BROWSER_APPS_EPHEMERAL_APP_BROWSERTEST_H_ + +#include <string> + +#include "base/files/file_path.h" +#include "chrome/browser/apps/app_browsertest_util.h" +#include "extensions/common/manifest.h" + +namespace base { +class CommandLine; +} + +// Contains common code for ephemeral app browser tests. +class EphemeralAppTestBase : public extensions::PlatformAppBrowserTest { + public: + static const char kMessagingReceiverApp[]; + static const char kMessagingReceiverAppV2[]; + + EphemeralAppTestBase(); + virtual ~EphemeralAppTestBase(); + + virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE; + + protected: + base::FilePath GetTestPath(const char* test_path); + + const extensions::Extension* InstallEphemeralApp( + const char* test_path, extensions::Manifest::Location manifest_location); + const extensions::Extension* InstallEphemeralApp(const char* test_path); + const extensions::Extension* InstallAndLaunchEphemeralApp( + const char* test_path); + const extensions::Extension* UpdateEphemeralApp( + const std::string& app_id, + const base::FilePath& test_dir, + const base::FilePath& pem_path); + void PromoteEphemeralApp(const extensions::Extension* app); + + void CloseApp(const std::string& app_id); + void EvictApp(const std::string& app_id); +}; + +#endif // CHROME_BROWSER_APPS_EPHEMERAL_APP_BROWSERTEST_H_ diff --git a/chrome/browser/apps/ephemeral_app_service.cc b/chrome/browser/apps/ephemeral_app_service.cc index 9ed8a56..88b8053 100644 --- a/chrome/browser/apps/ephemeral_app_service.cc +++ b/chrome/browser/apps/ephemeral_app_service.cc @@ -102,6 +102,7 @@ void EphemeralAppService::OnExtensionWillBeInstalled( content::BrowserContext* browser_context, const extensions::Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) { if (extensions::util::IsEphemeralApp(extension->id(), profile_)) { ++ephemeral_app_count_; diff --git a/chrome/browser/apps/ephemeral_app_service.h b/chrome/browser/apps/ephemeral_app_service.h index edad28d..c12e97e 100644 --- a/chrome/browser/apps/ephemeral_app_service.h +++ b/chrome/browser/apps/ephemeral_app_service.h @@ -61,6 +61,7 @@ class EphemeralAppService : public KeyedService, content::BrowserContext* browser_context, const extensions::Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) OVERRIDE; virtual void OnExtensionUninstalled( content::BrowserContext* browser_context, diff --git a/chrome/browser/apps/shortcut_manager.cc b/chrome/browser/apps/shortcut_manager.cc index efb54cc..b61ff4a 100644 --- a/chrome/browser/apps/shortcut_manager.cc +++ b/chrome/browser/apps/shortcut_manager.cc @@ -107,6 +107,7 @@ void AppShortcutManager::OnExtensionWillBeInstalled( content::BrowserContext* browser_context, const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) { // If the app is being updated, update any existing shortcuts but do not // create new ones. If it is being installed, automatically create a diff --git a/chrome/browser/apps/shortcut_manager.h b/chrome/browser/apps/shortcut_manager.h index 63e9112e..f5a81a79 100644 --- a/chrome/browser/apps/shortcut_manager.h +++ b/chrome/browser/apps/shortcut_manager.h @@ -50,6 +50,7 @@ class AppShortcutManager : public KeyedService, content::BrowserContext* browser_context, const extensions::Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) OVERRIDE; virtual void OnExtensionUninstalled( content::BrowserContext* browser_context, diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc index 09a13a0..e3acae2 100644 --- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc +++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc @@ -41,6 +41,7 @@ #include "content/public/common/referrer.h" #include "extensions/browser/extension_function_dispatcher.h" #include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/extension_util.h" #include "extensions/common/error_utils.h" @@ -564,8 +565,6 @@ bool WebstorePrivateCompleteInstallFunction::RunAsync() { return false; } - // Balanced in OnExtensionInstallSuccess() or OnExtensionInstallFailure(). - AddRef(); AppListService* app_list_service = AppListService::Get(GetCurrentBrowser()->host_desktop_type()); @@ -583,6 +582,22 @@ bool WebstorePrivateCompleteInstallFunction::RunAsync() { app_list_service->AutoShowForProfile(GetProfile()); } + // If the target extension has already been installed ephemerally, it can + // be promoted to a regular installed extension and downloading from the Web + // Store is not necessary. + const Extension* extension = ExtensionRegistry::Get(GetProfile())-> + GetExtensionById(params->expected_id, ExtensionRegistry::EVERYTHING); + if (extension && util::IsEphemeralApp(extension->id(), GetProfile())) { + ExtensionService* extension_service = + ExtensionSystem::Get(GetProfile())->extension_service(); + extension_service->PromoteEphemeralApp(extension, false); + OnInstallSuccess(extension->id()); + return true; + } + + // Balanced in OnExtensionInstallSuccess() or OnExtensionInstallFailure(). + AddRef(); + // The extension will install through the normal extension install flow, but // the whitelist entry will bypass the normal permissions install dialog. scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller( @@ -599,13 +614,7 @@ bool WebstorePrivateCompleteInstallFunction::RunAsync() { void WebstorePrivateCompleteInstallFunction::OnExtensionInstallSuccess( const std::string& id) { - if (test_webstore_installer_delegate) - test_webstore_installer_delegate->OnExtensionInstallSuccess(id); - - VLOG(1) << "Install success, sending response"; - g_pending_installs.Get().EraseInstall(GetProfile(), id); - SendResponse(true); - + OnInstallSuccess(id); RecordWebstoreExtensionInstallResult(true); // Matches the AddRef in RunAsync(). @@ -632,6 +641,16 @@ void WebstorePrivateCompleteInstallFunction::OnExtensionInstallFailure( Release(); } +void WebstorePrivateCompleteInstallFunction::OnInstallSuccess( + const std::string& id) { + if (test_webstore_installer_delegate) + test_webstore_installer_delegate->OnExtensionInstallSuccess(id); + + VLOG(1) << "Install success, sending response"; + g_pending_installs.Get().EraseInstall(GetProfile(), id); + SendResponse(true); +} + WebstorePrivateEnableAppLauncherFunction:: WebstorePrivateEnableAppLauncherFunction() {} diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.h b/chrome/browser/extensions/api/webstore_private/webstore_private_api.h index 7d56c50..b320360 100644 --- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.h +++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.h @@ -199,6 +199,8 @@ class WebstorePrivateCompleteInstallFunction private: scoped_ptr<WebstoreInstaller::Approval> approval_; + + void OnInstallSuccess(const std::string& id); }; class WebstorePrivateEnableAppLauncherFunction diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 4d64ba8..1a12f92 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -1891,6 +1891,7 @@ void ExtensionService::AddNewOrUpdatedExtension( CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); const bool blacklisted_for_malware = blacklist_state == extensions::BLACKLISTED_MALWARE; + bool was_ephemeral = extension_prefs_->IsEphemeralApp(extension->id()); extension_prefs_->OnExtensionInstalled(extension, initial_state, blacklisted_for_malware, @@ -1900,7 +1901,7 @@ void ExtensionService::AddNewOrUpdatedExtension( delayed_installs_.Remove(extension->id()); if (InstallVerifier::NeedsVerification(*extension)) system_->install_verifier()->VerifyExtension(extension->id()); - FinishInstallation(extension); + FinishInstallation(extension, was_ephemeral); } void ExtensionService::MaybeFinishDelayedInstallation( @@ -1945,13 +1946,15 @@ void ExtensionService::FinishDelayedInstallation( CHECK(extension.get()); delayed_installs_.Remove(extension_id); + bool was_ephemeral = extension_prefs_->IsEphemeralApp(extension->id()); if (!extension_prefs_->FinishDelayedInstallInfo(extension_id)) NOTREACHED(); - FinishInstallation(extension.get()); + FinishInstallation(extension.get(), was_ephemeral); } -void ExtensionService::FinishInstallation(const Extension* extension) { +void ExtensionService::FinishInstallation( + const Extension* extension, bool was_ephemeral) { const extensions::Extension* existing_extension = GetInstalledExtension(extension->id()); bool is_update = false; @@ -1960,14 +1963,17 @@ void ExtensionService::FinishInstallation(const Extension* extension) { is_update = true; old_name = existing_extension->name(); } - extensions::InstalledExtensionInfo details(extension, is_update, old_name); + bool from_ephemeral = + was_ephemeral && !extension_prefs_->IsEphemeralApp(extension->id()); + extensions::InstalledExtensionInfo details( + extension, is_update, from_ephemeral, old_name); content::NotificationService::current()->Notify( chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED, content::Source<Profile>(profile_), content::Details<const extensions::InstalledExtensionInfo>(&details)); - ExtensionRegistry::Get(profile_) - ->TriggerOnWillBeInstalled(extension, is_update, old_name); + ExtensionRegistry::Get(profile_)->TriggerOnWillBeInstalled( + extension, is_update, from_ephemeral, old_name); bool unacknowledged_external = IsUnacknowledgedExternalExtension(extension); @@ -2006,6 +2012,70 @@ void ExtensionService::FinishInstallation(const Extension* extension) { } } +void ExtensionService::PromoteEphemeralApp( + const extensions::Extension* extension, bool is_from_sync) { + DCHECK(GetInstalledExtension(extension->id()) && + extension_prefs_->IsEphemeralApp(extension->id())); + + if (!is_from_sync) { + if (extension->RequiresSortOrdinal()) { + // Reset the sort ordinals of the app to ensure it is added to the default + // position, like newly installed apps would. + extension_prefs_->app_sorting()->ClearOrdinals(extension->id()); + extension_prefs_->app_sorting()->EnsureValidOrdinals( + extension->id(), syncer::StringOrdinal()); + } + + if (extension_prefs_->IsExtensionDisabled(extension->id()) && + !extension_prefs_->IsExtensionBlacklisted(extension->id())) { + // If the extension is not blacklisted and was disabled due to permission + // increase or user action only, we can enable it because the user was + // prompted. + extension_prefs_->RemoveDisableReason( + extension->id(), + Extension::DISABLE_PERMISSIONS_INCREASE); + extension_prefs_->RemoveDisableReason( + extension->id(), + Extension::DISABLE_USER_ACTION); + if (!extension_prefs_->GetDisableReasons(extension->id())) + EnableExtension(extension->id()); + } + } + + // Remove the ephemeral flags from the preferences. + extension_prefs_->OnEphemeralAppPromoted(extension->id()); + + // Fire install-related events to allow observers to handle the promotion + // of the ephemeral app. + extensions::InstalledExtensionInfo details( + extension, + true /* is update */, + true /* from ephemeral */, + extension->name() /* old name */); + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED, + content::Source<Profile>(profile_), + content::Details<const extensions::InstalledExtensionInfo>(&details)); + + registry_->TriggerOnWillBeInstalled( + extension, + true /* is update */, + true /* from ephemeral */, + extension->name() /* old name */); + + if (registry_->enabled_extensions().Contains(extension->id())) { + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, + content::Source<Profile>(profile_), + content::Details<const Extension>(extension)); + + registry_->TriggerOnLoaded(extension); + } + + if (!is_from_sync && extension_sync_service_) + extension_sync_service_->SyncExtensionChangeIfNeeded(*extension); +} + const Extension* ExtensionService::GetPendingExtensionUpdate( const std::string& id) const { return delayed_installs_.GetByID(id); diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h index b0c3844..5688f4c 100644 --- a/chrome/browser/extensions/extension_service.h +++ b/chrome/browser/extensions/extension_service.h @@ -291,7 +291,14 @@ class ExtensionService // when installation of that extension was previously delayed because the // extension was in use. virtual void FinishDelayedInstallation( - const std::string& extension_id) OVERRIDE; + const std::string& extension_id) OVERRIDE; + + // Promotes an ephemeral app to a regular installed app. Ephemeral apps + // are already installed in extension system (albiet transiently) and only + // need to be exposed in the UI. Set |is_from_sync| to true if the + // install was initiated via sync. + void PromoteEphemeralApp( + const extensions::Extension* extension, bool is_from_sync); // Returns an update for an extension with the specified id, if installation // of that update was previously delayed because the extension was in use. If @@ -429,7 +436,7 @@ class ExtensionService } void FinishInstallationForTest(const extensions::Extension* extension) { - FinishInstallation(extension); + FinishInstallation(extension, false); } #endif @@ -501,8 +508,10 @@ class ExtensionService const extensions::Extension* extension, extensions::UnloadedExtensionInfo::Reason reason); - // Common helper to finish installing the given extension. - void FinishInstallation(const extensions::Extension* extension); + // Common helper to finish installing the given extension. |was_ephemeral| + // should be true if the extension was previously installed and ephemeral. + void FinishInstallation(const extensions::Extension* extension, + bool was_ephemeral); // Updates the |extension|'s active permission set to include only permissions // currently requested by the extension and all the permissions required by diff --git a/chrome/browser/extensions/extension_sync_service.cc b/chrome/browser/extensions/extension_sync_service.cc index 483659c..f978667 100644 --- a/chrome/browser/extensions/extension_sync_service.cc +++ b/chrome/browser/extensions/extension_sync_service.cc @@ -490,15 +490,23 @@ bool ExtensionSyncService::ProcessExtensionSyncDataHelper( // incognito flag invalidates the |extension| pointer (it reloads the // extension). bool extension_installed = (extension != NULL); - int result = extension ? + int version_compare_result = extension ? extension->version()->CompareTo(extension_sync_data.version()) : 0; + + // If the target extension has already been installed ephemerally, it can + // be promoted to a regular installed extension and downloading from the Web + // Store is not necessary. + if (extension && extensions::util::IsEphemeralApp(id, profile_)) + extension_service_->PromoteEphemeralApp(extension, true); + + // Update the incognito flag. extensions::util::SetIsIncognitoEnabled( id, profile_, extension_sync_data.incognito_enabled()); extension = NULL; // No longer safe to use. if (extension_installed) { // If the extension is already installed, check if it's outdated. - if (result < 0) { + if (version_compare_result < 0) { // Extension is outdated. return false; } diff --git a/chrome/browser/extensions/install_tracker.cc b/chrome/browser/extensions/install_tracker.cc index 09eec52..be72ff5 100644 --- a/chrome/browser/extensions/install_tracker.cc +++ b/chrome/browser/extensions/install_tracker.cc @@ -145,6 +145,7 @@ void InstallTracker::OnExtensionWillBeInstalled( content::BrowserContext* browser_context, const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) { FOR_EACH_OBSERVER( InstallObserver, observers_, OnExtensionInstalled(extension)); diff --git a/chrome/browser/extensions/install_tracker.h b/chrome/browser/extensions/install_tracker.h index 6728cf6..3834ee9 100644 --- a/chrome/browser/extensions/install_tracker.h +++ b/chrome/browser/extensions/install_tracker.h @@ -69,6 +69,7 @@ class InstallTracker : public KeyedService, content::BrowserContext* browser_context, const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) OVERRIDE; ObserverList<InstallObserver> observers_; diff --git a/chrome/browser/extensions/webstore_installer.cc b/chrome/browser/extensions/webstore_installer.cc index 512d008..7dd6017 100644 --- a/chrome/browser/extensions/webstore_installer.cc +++ b/chrome/browser/extensions/webstore_installer.cc @@ -403,6 +403,7 @@ void WebstoreInstaller::OnExtensionWillBeInstalled( content::BrowserContext* browser_context, const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) { CHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context))); if (pending_modules_.empty()) diff --git a/chrome/browser/extensions/webstore_installer.h b/chrome/browser/extensions/webstore_installer.h index c2d1646..b35d1e1b 100644 --- a/chrome/browser/extensions/webstore_installer.h +++ b/chrome/browser/extensions/webstore_installer.h @@ -203,6 +203,7 @@ class WebstoreInstaller : public content::NotificationObserver, content::BrowserContext* browser_context, const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) OVERRIDE; // Removes the reference to the delegate passed in the constructor. Used when diff --git a/chrome/browser/extensions/webstore_standalone_installer.cc b/chrome/browser/extensions/webstore_standalone_installer.cc index 6fd762b..91662ea 100644 --- a/chrome/browser/extensions/webstore_standalone_installer.cc +++ b/chrome/browser/extensions/webstore_standalone_installer.cc @@ -13,6 +13,7 @@ #include "chrome/browser/profiles/profile.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/extension_util.h" #include "extensions/common/extension.h" @@ -231,32 +232,34 @@ void WebstoreStandaloneInstaller::InstallUIProceed() { return; } + scoped_ptr<WebstoreInstaller::Approval> approval = CreateApproval(); + ExtensionService* extension_service = ExtensionSystem::Get(profile_)->extension_service(); const Extension* extension = extension_service->GetExtensionById(id_, true /* include disabled */); if (extension) { std::string install_result; // Empty string for install success. - if (!extension_service->IsExtensionEnabled(id_)) { - if (!ExtensionPrefs::Get(profile_)->IsExtensionBlacklisted(id_)) { - // If the extension is installed but disabled, and not blacklisted, - // enable it. - extension_service->EnableExtension(id_); - } else { // Don't install a blacklisted extension. - install_result = kExtensionIsBlacklisted; - } - } else if (!util::IsEphemeralApp(extension->id(), profile_)) { - // else extension is installed and enabled; no work to be done. - CompleteInstall(install_result); - return; - } - // TODO(tmdiep): Optimize installation of ephemeral apps. For now we just - // reinstall the app. + if (ExtensionPrefs::Get(profile_)->IsExtensionBlacklisted(id_)) { + // Don't install a blacklisted extension. + install_result = kExtensionIsBlacklisted; + } else if (util::IsEphemeralApp(extension->id(), profile_) && + !approval->is_ephemeral) { + // If the target extension has already been installed ephemerally, it can + // be promoted to a regular installed extension and downloading from the + // Web Store is not necessary. + extension_service->PromoteEphemeralApp(extension, false); + } else if (!extension_service->IsExtensionEnabled(id_)) { + // If the extension is installed but disabled, and not blacklisted, + // enable it. + extension_service->EnableExtension(id_); + } // else extension is installed and enabled; no work to be done. + + CompleteInstall(install_result); + return; } - scoped_ptr<WebstoreInstaller::Approval> approval = CreateApproval(); - scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller( profile_, this, diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 6c0f235..06f3bae 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -879,6 +879,7 @@ 'browser/apps/app_window_browsertest.cc', 'browser/apps/app_url_redirector_browsertest.cc', 'browser/apps/ephemeral_app_browsertest.cc', + 'browser/apps/ephemeral_app_browsertest.h', 'browser/apps/ephemeral_app_service_browsertest.cc', 'browser/apps/event_page_browsertest.cc', 'browser/apps/speech_recognition_browsertest.cc', diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc index 3bbe6b2..2ebbadd 100644 --- a/extensions/browser/extension_prefs.cc +++ b/extensions/browser/extension_prefs.cc @@ -1640,6 +1640,10 @@ void ExtensionPrefs::RemoveEvictedEphemeralApp( } bool ExtensionPrefs::IsEphemeralApp(const std::string& extension_id) const { + // Hide the data of evicted ephemeral apps. + if (ReadPrefAsBooleanAndReturn(extension_id, kPrefEvictedEphemeralApp)) + return false; + if (ReadPrefAsBooleanAndReturn(extension_id, kPrefEphemeralApp)) return true; @@ -1648,6 +1652,16 @@ bool ExtensionPrefs::IsEphemeralApp(const std::string& extension_id) const { return (GetCreationFlags(extension_id) & Extension::IS_EPHEMERAL) != 0; } +void ExtensionPrefs::OnEphemeralAppPromoted(const std::string& extension_id) { + DCHECK(IsEphemeralApp(extension_id)); + + ScopedExtensionPrefUpdate update(prefs_, extension_id); + update->Set(kPrefEphemeralApp, new base::FundamentalValue(false)); + + DCHECK(!IsEvictedEphemeralApp(update.Get())); + update->Remove(kPrefEvictedEphemeralApp, NULL); +} + bool ExtensionPrefs::WasAppDraggedByUser(const std::string& extension_id) { return ReadPrefAsBooleanAndReturn(extension_id, kPrefUserDraggedApp); } @@ -2142,10 +2156,8 @@ void ExtensionPrefs::PopulateExtensionInfoPrefs( if (blacklisted_for_malware) extension_dict->Set(kPrefBlacklist, new base::FundamentalValue(true)); - if (is_ephemeral) - extension_dict->Set(kPrefEphemeralApp, new base::FundamentalValue(true)); - else - extension_dict->Remove(kPrefEphemeralApp, NULL); + extension_dict->Set(kPrefEphemeralApp, + new base::FundamentalValue(is_ephemeral)); base::FilePath::StringType path = MakePathRelative(install_directory_, extension->path()); diff --git a/extensions/browser/extension_prefs.h b/extensions/browser/extension_prefs.h index 9ffcf11..fc65d73 100644 --- a/extensions/browser/extension_prefs.h +++ b/extensions/browser/extension_prefs.h @@ -480,6 +480,9 @@ class ExtensionPrefs : public ExtensionScopedPrefs, public KeyedService { // Returns true if the extension is an ephemeral app. bool IsEphemeralApp(const std::string& extension_id) const; + // Promotes an ephemeral app to a regular installed app. + void OnEphemeralAppPromoted(const std::string& extension_id); + // Returns true if the user repositioned the app on the app launcher via drag // and drop. bool WasAppDraggedByUser(const std::string& extension_id); diff --git a/extensions/browser/extension_registry.cc b/extensions/browser/extension_registry.cc index 2b4d005..959fdc0 100644 --- a/extensions/browser/extension_registry.cc +++ b/extensions/browser/extension_registry.cc @@ -55,14 +55,16 @@ void ExtensionRegistry::TriggerOnUnloaded( void ExtensionRegistry::TriggerOnWillBeInstalled(const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) { DCHECK(is_update == GenerateInstalledExtensionsSet()->Contains(extension->id())); DCHECK(is_update == !old_name.empty()); - FOR_EACH_OBSERVER(ExtensionRegistryObserver, - observers_, - OnExtensionWillBeInstalled( - browser_context_, extension, is_update, old_name)); + FOR_EACH_OBSERVER( + ExtensionRegistryObserver, + observers_, + OnExtensionWillBeInstalled( + browser_context_, extension, is_update, from_ephemeral, old_name)); } void ExtensionRegistry::TriggerOnUninstalled(const Extension* extension) { diff --git a/extensions/browser/extension_registry.h b/extensions/browser/extension_registry.h index 0087645..646863c 100644 --- a/extensions/browser/extension_registry.h +++ b/extensions/browser/extension_registry.h @@ -78,8 +78,12 @@ class ExtensionRegistry : public KeyedService { // any installed extension with |extension|'s ID. If this is an update then // |is_update| is true and must be an installed extension with |extension|'s // ID, and |old_name| must be non-empty. + // If true, |from_ephemeral| indicates that the extension was previously + // installed ephemerally and has been promoted to a regular installed + // extension. |is_update| should also be true. void TriggerOnWillBeInstalled(const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name); // Invokes the observer method OnExtensionUninstalled(). The extension must diff --git a/extensions/browser/extension_registry_observer.h b/extensions/browser/extension_registry_observer.h index b6cf8e0..7cb524e 100644 --- a/extensions/browser/extension_registry_observer.h +++ b/extensions/browser/extension_registry_observer.h @@ -37,16 +37,25 @@ class ExtensionRegistryObserver { // Called when |extension| is about to be installed. |is_update| is true if // the installation is the result of it updating, in which case |old_name| is // the name of the extension's previous version. + // If true, |from_ephemeral| indicates that the extension was previously + // installed ephemerally and has been promoted to a regular installed + // extension. |is_update| will be true, although the version has not + // necessarily changed. // The ExtensionRegistry will not be tracking |extension| at the time this // event is fired, but will be immediately afterwards (note: not necessarily // enabled; it might be installed in the disabled or even blacklisted sets, // for example). // Note that it's much more common to care about extensions being loaded // (OnExtensionLoaded). + // + // TODO(tmdiep): We should stash the state of the previous extension version + // somewhere and have observers retrieve it. |is_update|, |from_ephemeral| + // and |old_name| can be removed when this is done. virtual void OnExtensionWillBeInstalled( content::BrowserContext* browser_context, const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) {} // Called after an extension is uninstalled. The extension no longer exsit in diff --git a/extensions/browser/extension_registry_unittest.cc b/extensions/browser/extension_registry_unittest.cc index 5566fff..36fbfec 100644 --- a/extensions/browser/extension_registry_unittest.cc +++ b/extensions/browser/extension_registry_unittest.cc @@ -64,6 +64,7 @@ class TestObserver : public ExtensionRegistryObserver { content::BrowserContext* browser_context, const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) OVERRIDE { installed_.push_back(extension); } @@ -240,13 +241,14 @@ TEST_F(ExtensionRegistryTest, Observer) { scoped_refptr<const Extension> extension = test_util::CreateExtensionWithID("id"); - registry.TriggerOnWillBeInstalled(extension, false, base::EmptyString()); + registry.TriggerOnWillBeInstalled( + extension, false, false, base::EmptyString()); EXPECT_TRUE(HasSingleExtension(observer.installed(), extension.get())); registry.AddEnabled(extension); registry.TriggerOnLoaded(extension); - registry.TriggerOnWillBeInstalled(extension, true, "foo"); + registry.TriggerOnWillBeInstalled(extension, true, false, "foo"); EXPECT_TRUE(HasSingleExtension(observer.loaded(), extension.get())); EXPECT_TRUE(observer.unloaded().empty()); diff --git a/extensions/common/extension.cc b/extensions/common/extension.cc index 46810ad..ada3b31 100644 --- a/extensions/common/extension.cc +++ b/extensions/common/extension.cc @@ -770,9 +770,11 @@ ExtensionInfo::~ExtensionInfo() {} InstalledExtensionInfo::InstalledExtensionInfo( const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name) : extension(extension), is_update(is_update), + from_ephemeral(from_ephemeral), old_name(old_name) {} UnloadedExtensionInfo::UnloadedExtensionInfo( diff --git a/extensions/common/extension.h b/extensions/common/extension.h index e3d5dfc..797c906 100644 --- a/extensions/common/extension.h +++ b/extensions/common/extension.h @@ -508,12 +508,17 @@ struct InstalledExtensionInfo { // True if the extension is being updated; false if it is being installed. bool is_update; + // True if the extension was previously installed ephemerally and is now + // a regular installed extension. + bool from_ephemeral; + // The name of the extension prior to this update. Will be empty if // |is_update| is false. std::string old_name; InstalledExtensionInfo(const Extension* extension, bool is_update, + bool from_ephemeral, const std::string& old_name); }; |