diff options
author | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-02 02:09:43 +0000 |
---|---|---|
committer | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-02 02:09:43 +0000 |
commit | 0d6ec3a7c816498626f48b4f3f8491f9658a8e2e (patch) | |
tree | 043e946873a431fb1d8ec488a820aa37a3a133a4 /chrome | |
parent | 1859248461da934dbc0e70dcedcb65442ec0d9ff (diff) | |
download | chromium_src-0d6ec3a7c816498626f48b4f3f8491f9658a8e2e.zip chromium_src-0d6ec3a7c816498626f48b4f3f8491f9658a8e2e.tar.gz chromium_src-0d6ec3a7c816498626f48b4f3f8491f9658a8e2e.tar.bz2 |
When uninstalling a hosted app, delete data for its launch_url's origin, as
long as no other apps are installed on that origin.
(cloned from matt's OCL http://codereview.chromium.org/7747025/)
TBR=mpcomplete
BUG=87258
Review URL: http://codereview.chromium.org/7807002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@99288 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/extensions/extension_data_deleter.cc | 50 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_data_deleter.h | 29 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_service.cc | 37 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_service.h | 4 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_service_unittest.cc | 138 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_service_unittest.h | 2 |
6 files changed, 230 insertions, 30 deletions
diff --git a/chrome/browser/extensions/extension_data_deleter.cc b/chrome/browser/extensions/extension_data_deleter.cc index e8e5ab4..3812d70 100644 --- a/chrome/browser/extensions/extension_data_deleter.cc +++ b/chrome/browser/extensions/extension_data_deleter.cc @@ -4,9 +4,13 @@ #include "chrome/browser/extensions/extension_data_deleter.h" +#include "base/file_util.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_constants.h" #include "chrome/common/extensions/extension.h" +#include "content/browser/appcache/chrome_appcache_service.h" #include "content/browser/in_process_webkit/webkit_context.h" +#include "content/common/url_constants.h" #include "net/base/cookie_monster.h" #include "net/base/net_errors.h" #include "net/url_request/url_request_context.h" @@ -16,15 +20,29 @@ #include "webkit/fileapi/file_system_context.h" ExtensionDataDeleter::ExtensionDataDeleter(Profile* profile, - const GURL& extension_url) { + const std::string& extension_id, + const GURL& storage_origin, + bool is_storage_isolated) { DCHECK(profile); + appcache_service_ = profile->GetAppCacheService(); webkit_context_ = profile->GetWebKitContext(); database_tracker_ = profile->GetDatabaseTracker(); - extension_request_context_ = profile->GetRequestContextForExtensions(); + // Pick the right request context depending on whether it's an extension, + // isolated app, or regular app. + if (storage_origin.SchemeIs(chrome::kExtensionScheme)) { + extension_request_context_ = profile->GetRequestContextForExtensions(); + } else if (is_storage_isolated) { + extension_request_context_ = + profile->GetRequestContextForIsolatedApp(extension_id); + isolated_app_path_ = profile->GetPath(). + Append(chrome::kIsolatedAppStateDirname).AppendASCII(extension_id); + } else { + extension_request_context_ = profile->GetRequestContext(); + } file_system_context_ = profile->GetFileSystemContext(); - extension_url_ = extension_url; + storage_origin_ = storage_origin; origin_id_ = - webkit_database::DatabaseUtil::GetOriginIdentifier(extension_url_); + webkit_database::DatabaseUtil::GetOriginIdentifier(storage_origin_); } ExtensionDataDeleter::~ExtensionDataDeleter() { @@ -56,16 +74,21 @@ void ExtensionDataDeleter::StartDeleting() { BrowserThread::FILE, FROM_HERE, NewRunnableMethod( this, &ExtensionDataDeleter::DeleteFileSystemOnFileThread)); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod( + this, &ExtensionDataDeleter::DeleteAppcachesOnIOThread)); } void ExtensionDataDeleter::DeleteCookiesOnIOThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); net::CookieMonster* cookie_monster = extension_request_context_->GetURLRequestContext()->cookie_store()-> - GetCookieMonster(); + GetCookieMonster(); if (cookie_monster) cookie_monster->DeleteAllForHostAsync( - extension_url_, net::CookieMonster::DeleteCallback()); + storage_origin_, net::CookieMonster::DeleteCallback()); } void ExtensionDataDeleter::DeleteDatabaseOnFileThread() { @@ -83,10 +106,21 @@ void ExtensionDataDeleter::DeleteLocalStorageOnWebkitThread() { void ExtensionDataDeleter::DeleteIndexedDBOnWebkitThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT)); webkit_context_->indexed_db_context()->DeleteIndexedDBForOrigin( - extension_url_); + storage_origin_); } void ExtensionDataDeleter::DeleteFileSystemOnFileThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - file_system_context_->DeleteDataForOriginOnFileThread(extension_url_); + file_system_context_->DeleteDataForOriginOnFileThread(storage_origin_); + + // TODO(creis): The following call fails because the request context is still + // around, and holding open file handles in this directory. + // See http://crbug.com/85127 + if (!isolated_app_path_.empty()) + file_util::Delete(isolated_app_path_, true); +} + +void ExtensionDataDeleter::DeleteAppcachesOnIOThread() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + appcache_service_->DeleteAppCachesForOrigin(storage_origin_, NULL); } diff --git a/chrome/browser/extensions/extension_data_deleter.h b/chrome/browser/extensions/extension_data_deleter.h index 8085138..e4ee218 100644 --- a/chrome/browser/extensions/extension_data_deleter.h +++ b/chrome/browser/extensions/extension_data_deleter.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_EXTENSIONS_EXTENSION_DATA_DELETER_H_ #pragma once +#include "base/file_path.h" #include "base/memory/ref_counted.h" #include "base/string16.h" #include "content/browser/browser_thread.h" @@ -19,13 +20,14 @@ namespace fileapi { class FileSystemContext; } -class Profile; -class WebKitContext; - namespace net { class URLRequestContextGetter; } +class ChromeAppCacheService; +class Profile; +class WebKitContext; + // A helper class that takes care of removing local storage, databases and // cookies for a given extension. This is used by // ExtensionService::ClearExtensionData() upon uninstalling an extension. @@ -33,7 +35,10 @@ class ExtensionDataDeleter : public base::RefCountedThreadSafe<ExtensionDataDeleter, BrowserThread::DeleteOnUIThread> { public: - ExtensionDataDeleter(Profile* profile, const GURL& extension_url); + ExtensionDataDeleter(Profile* profile, + const std::string& extension_id, + const GURL& storage_origin, + bool is_storage_isolated); // Start removing data. The extension should not be running when this is // called. Cookies are deleted on the current thread, local storage and @@ -67,14 +72,18 @@ class ExtensionDataDeleter // file thread. void DeleteFileSystemOnFileThread(); + // Deletes appcache files for the extension. May only be called on the IO + // thread. + void DeleteAppcachesOnIOThread(); + // The database context for deleting the database. scoped_refptr<webkit_database::DatabaseTracker> database_tracker_; - // Provides access to the extension request context. + // Provides access to the request context. scoped_refptr<net::URLRequestContextGetter> extension_request_context_; - // The URL of the extension we're removing data for. - GURL extension_url_; + // The origin of the extension/app for which we're going to clear data. + GURL storage_origin_; // The security origin identifier for which we're deleting stuff. string16 origin_id_; @@ -84,6 +93,12 @@ class ExtensionDataDeleter scoped_refptr<fileapi::FileSystemContext> file_system_context_; + scoped_refptr<ChromeAppCacheService> appcache_service_; + + // If non-empty, the extension we're deleting is an isolated app, and this + // is its directory which we should delete. + FilePath isolated_app_path_; + DISALLOW_COPY_AND_ASSIGN(ExtensionDataDeleter); }; diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 27128ef..e9143d0 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -865,20 +865,16 @@ bool ExtensionService::UninstallExtension( // this function. std::string extension_id(extension_id_unsafe); - const Extension* extension = GetInstalledExtension(extension_id); + scoped_refptr<const Extension> extension(GetInstalledExtension(extension_id)); // Callers should not send us nonexistent extensions. CHECK(extension); - // Get hold of information we need after unloading, since the extension - // pointer will be invalid then. - GURL extension_url(extension->url()); - Extension::Location location(extension->location()); - // Policy change which triggers an uninstall will always set // |external_uninstall| to true so this is the only way to uninstall // managed extensions. - if (!Extension::UserMayDisable(location) && !external_uninstall) { + if (!Extension::UserMayDisable(extension->location()) && + !external_uninstall) { NotificationService::current()->Notify( chrome::NOTIFICATION_EXTENSION_UNINSTALL_NOT_ALLOWED, Source<Profile>(profile_), @@ -917,11 +913,11 @@ bool ExtensionService::UninstallExtension( // any of these resources. UnloadExtension(extension_id, extension_misc::UNLOAD_REASON_UNINSTALL); - extension_prefs_->OnExtensionUninstalled(extension_id, location, + extension_prefs_->OnExtensionUninstalled(extension_id, extension->location(), external_uninstall); // Tell the backend to start deleting installed extensions on the file thread. - if (Extension::LOAD != location) { + if (Extension::LOAD != extension->location()) { if (!BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, NewRunnableFunction( @@ -931,7 +927,20 @@ bool ExtensionService::UninstallExtension( NOTREACHED(); } - ClearExtensionData(extension_url); + GURL launch_web_url_origin(extension->launch_web_url()); + launch_web_url_origin = launch_web_url_origin.GetOrigin(); + bool is_storage_isolated = + (extension->is_storage_isolated() && + extension->HasAPIPermission(ExtensionAPIPermission::kExperimental)); + + if (extension->is_hosted_app() && + !profile_->GetExtensionSpecialStoragePolicy()-> + IsStorageProtected(launch_web_url_origin)) { + ClearExtensionData(extension_id, launch_web_url_origin, + is_storage_isolated); + } + ClearExtensionData(extension_id, extension->url(), is_storage_isolated); + UntrackTerminatedExtension(extension_id); // Notify interested parties that we've uninstalled this extension. @@ -949,9 +958,11 @@ bool ExtensionService::UninstallExtension( return true; } -void ExtensionService::ClearExtensionData(const GURL& extension_url) { - scoped_refptr<ExtensionDataDeleter> deleter( - new ExtensionDataDeleter(profile_, extension_url)); +void ExtensionService::ClearExtensionData(const std::string& extension_id, + const GURL& storage_url, + bool is_storage_isolated) { + scoped_refptr<ExtensionDataDeleter> deleter(new ExtensionDataDeleter( + profile_, extension_id, storage_url, is_storage_isolated)); deleter->StartDeleting(); } diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h index 4e1f290..276406c 100644 --- a/chrome/browser/extensions/extension_service.h +++ b/chrome/browser/extensions/extension_service.h @@ -609,7 +609,9 @@ class ExtensionService SyncBundle& bundle); // Clear all persistent data that may have been stored by the extension. - void ClearExtensionData(const GURL& extension_url); + void ClearExtensionData(const std::string& extension_id, + const GURL& storage_url, + bool is_storage_isolated); // Look up an extension by ID, optionally including either or both of enabled // and disabled extensions. diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc index a374f1a..3ded8b68 100644 --- a/chrome/browser/extensions/extension_service_unittest.cc +++ b/chrome/browser/extensions/extension_service_unittest.cc @@ -464,6 +464,13 @@ void ExtensionServiceTestBase::InitializeExtensionServiceHelper( autoupdate_enabled); } +void ExtensionServiceTestBase::InitializeRequestContext() { + ASSERT_TRUE(profile_.get()); + ExtensionTestingProfile* profile = + static_cast<ExtensionTestingProfile*>(profile_.get()); + profile->CreateRequestContext(); +} + // static void ExtensionServiceTestBase::SetUpTestCase() { ExtensionErrorReporter::Init(false); // no noisy errors @@ -1792,6 +1799,7 @@ TEST_F(ExtensionServiceTest, UpdateApps) { TEST_F(ExtensionServiceTest, InstallAppsWithUnlimtedStorage) { InitializeEmptyExtensionService(); + InitializeRequestContext(); EXPECT_TRUE(service_->extensions()->empty()); int pref_count = 0; @@ -1843,6 +1851,7 @@ TEST_F(ExtensionServiceTest, InstallAppsWithUnlimtedStorage) { TEST_F(ExtensionServiceTest, InstallAppsAndCheckStorageProtection) { InitializeEmptyExtensionService(); + InitializeRequestContext(); EXPECT_TRUE(service_->extensions()->empty()); int pref_count = 0; @@ -2702,7 +2711,7 @@ class ExtensionCookieCallback { ScopedRunnableMethodFactory<MessageLoop> message_loop_factory_; }; -// Verifies extension state is removed upon uninstall +// Verifies extension state is removed upon uninstall. TEST_F(ExtensionServiceTest, ClearExtensionData) { InitializeEmptyExtensionService(); ExtensionCookieCallback callback; @@ -2790,6 +2799,133 @@ TEST_F(ExtensionServiceTest, ClearExtensionData) { EXPECT_FALSE(file_util::DirectoryExists(idb_path)); } +// Verifies app state is removed upon uninstall. +TEST_F(ExtensionServiceTest, ClearAppData) { + InitializeEmptyExtensionService(); + InitializeRequestContext(); + ExtensionCookieCallback callback; + + int pref_count = 0; + + // Install app1 with unlimited storage. + PackAndInstallCrx(data_dir_.AppendASCII("app1"), true); + ValidatePrefKeyCount(++pref_count); + ASSERT_EQ(1u, service_->extensions()->size()); + const Extension* extension = service_->extensions()->at(0); + const std::string id1 = extension->id(); + EXPECT_TRUE(extension->HasAPIPermission( + ExtensionAPIPermission::kUnlimitedStorage)); + const GURL origin1(extension->GetFullLaunchURL().GetOrigin()); + EXPECT_TRUE(profile_->GetExtensionSpecialStoragePolicy()-> + IsStorageUnlimited(origin1)); + string16 origin_id = + webkit_database::DatabaseUtil::GetOriginIdentifier(origin1); + + // Install app2 from the same origin with unlimited storage. + PackAndInstallCrx(data_dir_.AppendASCII("app2"), true); + ValidatePrefKeyCount(++pref_count); + ASSERT_EQ(2u, service_->extensions()->size()); + extension = service_->extensions()->at(1); + const std::string id2 = extension->id(); + EXPECT_TRUE(extension->HasAPIPermission( + ExtensionAPIPermission::kUnlimitedStorage)); + EXPECT_TRUE(extension->web_extent().MatchesURL( + extension->GetFullLaunchURL())); + const GURL origin2(extension->GetFullLaunchURL().GetOrigin()); + EXPECT_EQ(origin1, origin2); + EXPECT_TRUE(profile_->GetExtensionSpecialStoragePolicy()-> + IsStorageUnlimited(origin2)); + + // Set a cookie for the extension. + net::CookieMonster* cookie_monster = + profile_->GetRequestContext()->GetURLRequestContext()-> + cookie_store()->GetCookieMonster(); + ASSERT_TRUE(cookie_monster); + net::CookieOptions options; + cookie_monster->SetCookieWithOptionsAsync( + origin1, "dummy=value", options, + base::Bind(&ExtensionCookieCallback::SetCookieCallback, + base::Unretained(&callback))); + loop_.RunAllPending(); + EXPECT_TRUE(callback.result_); + + cookie_monster->GetAllCookiesForURLAsync( + origin1, + base::Bind(&ExtensionCookieCallback::GetAllCookiesCallback, + base::Unretained(&callback))); + loop_.RunAllPending(); + EXPECT_EQ(1U, callback.list_.size()); + + // Open a database. + webkit_database::DatabaseTracker* db_tracker = profile_->GetDatabaseTracker(); + string16 db_name = UTF8ToUTF16("db"); + string16 description = UTF8ToUTF16("db_description"); + int64 size; + db_tracker->DatabaseOpened(origin_id, db_name, description, 1, &size); + db_tracker->DatabaseClosed(origin_id, db_name); + std::vector<webkit_database::OriginInfo> origins; + db_tracker->GetAllOriginsInfo(&origins); + EXPECT_EQ(1U, origins.size()); + EXPECT_EQ(origin_id, origins[0].GetOrigin()); + + // Create local storage. We only simulate this by creating the backing file + // since webkit is not initialized. + DOMStorageContext* context = + profile_->GetWebKitContext()->dom_storage_context(); + FilePath lso_path = context->GetLocalStorageFilePath(origin_id); + EXPECT_TRUE(file_util::CreateDirectory(lso_path.DirName())); + EXPECT_EQ(0, file_util::WriteFile(lso_path, NULL, 0)); + EXPECT_TRUE(file_util::PathExists(lso_path)); + + // Create indexed db. Similarly, it is enough to only simulate this by + // creating the directory on the disk. + IndexedDBContext* idb_context = + profile_->GetWebKitContext()->indexed_db_context(); + FilePath idb_path = idb_context->GetIndexedDBFilePath(origin_id); + EXPECT_TRUE(file_util::CreateDirectory(idb_path)); + EXPECT_TRUE(file_util::DirectoryExists(idb_path)); + + // Uninstall one of them, unlimited storage should still be granted + // to the origin. + UninstallExtension(id1, false); + EXPECT_EQ(1u, service_->extensions()->size()); + EXPECT_TRUE(profile_->GetExtensionSpecialStoragePolicy()-> + IsStorageUnlimited(origin1)); + + // Check that the cookie is still there. + cookie_monster->GetAllCookiesForURLAsync( + origin1, + base::Bind(&ExtensionCookieCallback::GetAllCookiesCallback, + base::Unretained(&callback))); + loop_.RunAllPending(); + EXPECT_EQ(1U, callback.list_.size()); + + // Now uninstall the other. Storage should be cleared for the apps. + UninstallExtension(id2, false); + EXPECT_EQ(0u, service_->extensions()->size()); + EXPECT_FALSE(profile_->GetExtensionSpecialStoragePolicy()-> + IsStorageUnlimited(origin1)); + + // Check that the cookie is gone. + cookie_monster->GetAllCookiesForURLAsync( + origin1, + base::Bind(&ExtensionCookieCallback::GetAllCookiesCallback, + base::Unretained(&callback))); + loop_.RunAllPending(); + EXPECT_EQ(0U, callback.list_.size()); + + // The database should have vanished as well. + origins.clear(); + db_tracker->GetAllOriginsInfo(&origins); + EXPECT_EQ(0U, origins.size()); + + // Check that the LSO file has been removed. + EXPECT_FALSE(file_util::PathExists(lso_path)); + + // Check if the indexed db has disappeared too. + EXPECT_FALSE(file_util::DirectoryExists(idb_path)); +} + // Tests loading single extensions (like --load-extension) TEST_F(ExtensionServiceTest, LoadExtension) { InitializeEmptyExtensionService(); diff --git a/chrome/browser/extensions/extension_service_unittest.h b/chrome/browser/extensions/extension_service_unittest.h index 847fda5..daaf6f1 100644 --- a/chrome/browser/extensions/extension_service_unittest.h +++ b/chrome/browser/extensions/extension_service_unittest.h @@ -31,6 +31,8 @@ class ExtensionServiceTestBase : public testing::Test { void InitializeExtensionServiceWithUpdater(); + void InitializeRequestContext(); + static void SetUpTestCase(); virtual void SetUp(); |