summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/apps/ephemeral_app_browsertest.cc536
-rw-r--r--chrome/browser/apps/ephemeral_app_browsertest.h47
-rw-r--r--chrome/browser/apps/ephemeral_app_service.cc1
-rw-r--r--chrome/browser/apps/ephemeral_app_service.h1
-rw-r--r--chrome/browser/apps/shortcut_manager.cc1
-rw-r--r--chrome/browser/apps/shortcut_manager.h1
-rw-r--r--chrome/browser/extensions/api/webstore_private/webstore_private_api.cc37
-rw-r--r--chrome/browser/extensions/api/webstore_private/webstore_private_api.h2
-rw-r--r--chrome/browser/extensions/extension_service.cc82
-rw-r--r--chrome/browser/extensions/extension_service.h17
-rw-r--r--chrome/browser/extensions/extension_sync_service.cc12
-rw-r--r--chrome/browser/extensions/install_tracker.cc1
-rw-r--r--chrome/browser/extensions/install_tracker.h1
-rw-r--r--chrome/browser/extensions/webstore_installer.cc1
-rw-r--r--chrome/browser/extensions/webstore_installer.h1
-rw-r--r--chrome/browser/extensions/webstore_standalone_installer.cc37
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--extensions/browser/extension_prefs.cc20
-rw-r--r--extensions/browser/extension_prefs.h3
-rw-r--r--extensions/browser/extension_registry.cc10
-rw-r--r--extensions/browser/extension_registry.h4
-rw-r--r--extensions/browser/extension_registry_observer.h9
-rw-r--r--extensions/browser/extension_registry_unittest.cc6
-rw-r--r--extensions/common/extension.cc2
-rw-r--r--extensions/common/extension.h5
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);
};