summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorandybons@chromium.org <andybons@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-01 02:09:42 +0000
committerandybons@chromium.org <andybons@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-01 02:09:42 +0000
commit654512b4c7e02401af2ce207d678c683841eedb6 (patch)
treed856c557204c2ebbc94a5f4c7d8106b1ab5ffc41
parent07598d6736b8d612b1f68eaf2066ab15776293da (diff)
downloadchromium_src-654512b4c7e02401af2ce207d678c683841eedb6.zip
chromium_src-654512b4c7e02401af2ce207d678c683841eedb6.tar.gz
chromium_src-654512b4c7e02401af2ce207d678c683841eedb6.tar.bz2
Protect local storage created by extension apps.
BUG=49228 TEST=ExtensionsServiceTest.InstallAppsAndCheckStorageProtection TEST=DatabaseTrackerTest.* Review URL: http://codereview.chromium.org/3256003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@58108 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/browsing_data_remover.cc48
-rw-r--r--chrome/browser/browsing_data_remover.h15
-rw-r--r--chrome/browser/extensions/extensions_service.cc47
-rw-r--r--chrome/browser/extensions/extensions_service.h14
-rw-r--r--chrome/browser/extensions/extensions_service_unittest.cc43
-rw-r--r--chrome/browser/in_process_webkit/dom_storage_context.cc14
-rw-r--r--chrome/browser/in_process_webkit/dom_storage_context.h3
-rw-r--r--chrome/browser/in_process_webkit/webkit_context.cc9
-rw-r--r--chrome/browser/in_process_webkit/webkit_context.h5
-rw-r--r--chrome/test/data/extensions/app4/manifest.json9
-rw-r--r--webkit/database/database_tracker.cc8
-rw-r--r--webkit/database/database_tracker.h8
-rw-r--r--webkit/database/database_tracker_unittest.cc9
13 files changed, 205 insertions, 27 deletions
diff --git a/chrome/browser/browsing_data_remover.cc b/chrome/browser/browsing_data_remover.cc
index c20644f..8413b9e 100644
--- a/chrome/browser/browsing_data_remover.cc
+++ b/chrome/browser/browsing_data_remover.cc
@@ -10,6 +10,7 @@
#include "base/callback.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/download/download_manager.h"
+#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/history/history.h"
#include "chrome/browser/in_process_webkit/webkit_context.h"
#include "chrome/browser/profile.h"
@@ -30,6 +31,7 @@
#include "net/http/http_cache.h"
#include "net/url_request/url_request_context.h"
#include "webkit/database/database_tracker.h"
+#include "webkit/database/database_util.h"
// Done so that we can use PostTask on BrowsingDataRemovers and not have
// BrowsingDataRemover implement RefCounted.
@@ -95,6 +97,25 @@ void BrowsingDataRemover::Remove(int remove_mask) {
DCHECK(!removing_);
removing_ = true;
+ std::vector<GURL> origin_whitelist;
+ ExtensionsService* extensions_service = profile_->GetExtensionsService();
+ if (extensions_service && extensions_service->HasInstalledExtensions()) {
+ std::map<GURL, int> whitelist_map =
+ extensions_service->protected_storage_map();
+ for (std::map<GURL, int>::const_iterator iter = whitelist_map.begin();
+ iter != whitelist_map.end(); ++iter) {
+ origin_whitelist.push_back(iter->first);
+ }
+ }
+
+ std::vector<string16> webkit_db_whitelist;
+ for (size_t i = 0; i < origin_whitelist.size(); ++i) {
+ webkit_db_whitelist.push_back(
+ webkit_database::DatabaseUtil::GetOriginIdentifier(
+ origin_whitelist[i]));
+ }
+
+
if (remove_mask & REMOVE_HISTORY) {
HistoryService* history_service =
profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
@@ -153,7 +174,7 @@ void BrowsingDataRemover::Remove(int remove_mask) {
// REMOVE_COOKIES is actually "cookies and other site data" so we make sure
// to remove other data such local databases, STS state, etc.
profile_->GetWebKitContext()->DeleteDataModifiedSince(
- delete_begin_, chrome::kExtensionScheme);
+ delete_begin_, chrome::kExtensionScheme, webkit_db_whitelist);
database_tracker_ = profile_->GetDatabaseTracker();
if (database_tracker_.get()) {
@@ -163,7 +184,8 @@ void BrowsingDataRemover::Remove(int remove_mask) {
NewRunnableMethod(
this,
&BrowsingDataRemover::ClearDatabasesOnFILEThread,
- delete_begin_));
+ delete_begin_,
+ webkit_db_whitelist));
}
ChromeThread::PostTask(
@@ -179,7 +201,8 @@ void BrowsingDataRemover::Remove(int remove_mask) {
NewRunnableMethod(
this,
&BrowsingDataRemover::ClearAppCacheOnIOThread,
- delete_begin_)); // we assume end time == now
+ delete_begin_, // we assume end time == now
+ origin_whitelist));
}
if (remove_mask & REMOVE_PASSWORDS) {
@@ -381,12 +404,13 @@ void BrowsingDataRemover::OnClearedDatabases(int rv) {
NotifyAndDeleteIfDone();
}
-void BrowsingDataRemover::ClearDatabasesOnFILEThread(base::Time delete_begin) {
+void BrowsingDataRemover::ClearDatabasesOnFILEThread(base::Time delete_begin,
+ const std::vector<string16>& webkit_db_whitelist) {
// This function should be called on the FILE thread.
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
int rv = database_tracker_->DeleteDataModifiedSince(
- delete_begin, &database_cleared_callback_);
+ delete_begin, webkit_db_whitelist, &database_cleared_callback_);
if (rv != net::ERR_IO_PENDING)
OnClearedDatabases(rv);
}
@@ -399,14 +423,17 @@ void BrowsingDataRemover::OnClearedAppCache() {
DCHECK(result);
return;
}
+ appcache_whitelist_.clear();
waiting_for_clear_appcache_ = false;
NotifyAndDeleteIfDone();
}
-void BrowsingDataRemover::ClearAppCacheOnIOThread(base::Time delete_begin) {
+void BrowsingDataRemover::ClearAppCacheOnIOThread(base::Time delete_begin,
+ const std::vector<GURL>& origin_whitelist) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
DCHECK(waiting_for_clear_appcache_);
+ appcache_whitelist_ = origin_whitelist;
appcache_info_ = new appcache::AppCacheInfoCollection;
GetAppCacheService()->GetAllAppCacheInfo(
appcache_info_, &appcache_got_info_callback_);
@@ -420,6 +447,15 @@ void BrowsingDataRemover::OnGotAppCacheInfo(int rv) {
for (InfoByOrigin::const_iterator origin =
appcache_info_->infos_by_origin.begin();
origin != appcache_info_->infos_by_origin.end(); ++origin) {
+
+ bool found_in_whitelist = false;
+ for (size_t i = 0; i < appcache_whitelist_.size(); ++i) {
+ if (appcache_whitelist_[i] == origin->first)
+ found_in_whitelist = true;
+ }
+ if (found_in_whitelist)
+ continue;
+
for (AppCacheInfoVector::const_iterator info = origin->second.begin();
info != origin->second.end(); ++info) {
if (info->creation_time > delete_begin_) {
diff --git a/chrome/browser/browsing_data_remover.h b/chrome/browser/browsing_data_remover.h
index 9260ce9..a160198 100644
--- a/chrome/browser/browsing_data_remover.h
+++ b/chrome/browser/browsing_data_remover.h
@@ -6,6 +6,8 @@
#define CHROME_BROWSER_BROWSING_DATA_REMOVER_H_
#pragma once
+#include <vector>
+
#include "base/observer_list.h"
#include "base/ref_counted.h"
#include "base/time.h"
@@ -116,15 +118,19 @@ class BrowsingDataRemover : public NotificationObserver {
// NotifyAndDeleteIfDone.
void OnClearedDatabases(int rv);
- // Invoked on the FILE thread to delete HTML5 databases.
- void ClearDatabasesOnFILEThread(base::Time delete_begin);
+ // Invoked on the FILE thread to delete HTML5 databases. Ignores any within
+ // the |webkit_db_whitelist|.
+ void ClearDatabasesOnFILEThread(base::Time delete_begin,
+ const std::vector<string16>& webkit_db_whitelist);
// Callback when the appcache has been cleared. Invokes
// NotifyAndDeleteIfDone.
void OnClearedAppCache();
- // Invoked on the IO thread to delete from the AppCache.
- void ClearAppCacheOnIOThread(base::Time delete_begin);
+ // Invoked on the IO thread to delete from the AppCache, ignoring data from
+ // any origins within the |origin_whitelist|.
+ void ClearAppCacheOnIOThread(base::Time delete_begin,
+ const std::vector<GURL>& origin_whitelist);
// Lower level helpers.
void OnGotAppCacheInfo(int rv);
@@ -166,6 +172,7 @@ class BrowsingDataRemover : public NotificationObserver {
net::CompletionCallbackImpl<BrowsingDataRemover> appcache_deleted_callback_;
scoped_refptr<appcache::AppCacheInfoCollection> appcache_info_;
scoped_refptr<URLRequestContextGetter> request_context_getter_;
+ std::vector<GURL> appcache_whitelist_;
int appcaches_to_be_deleted_count_;
// Used to delete data from the HTTP caches.
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 7ba0f42..4d1ce3b 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -4,6 +4,8 @@
#include "chrome/browser/extensions/extensions_service.h"
+#include <algorithm>
+
#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/file_util.h"
@@ -85,9 +87,18 @@ void GetExplicitOriginsInExtent(Extension* extension,
pattern != patterns.end(); ++pattern) {
if (pattern->match_subdomains() || pattern->match_all_urls())
continue;
- GURL origin = GURL(pattern->GetAsString()).GetOrigin();
- if (origin.is_valid())
- set.insert(origin);
+ // Wildcard URL schemes won't parse into a valid GURL, so explicit schemes
+ // must be used.
+ PatternList explicit_patterns = pattern->ConvertToExplicitSchemes();
+ for (PatternList::const_iterator explicit_p = explicit_patterns.begin();
+ explicit_p != explicit_patterns.end(); ++explicit_p) {
+ GURL origin = GURL(explicit_p->GetAsString()).GetOrigin();
+ if (origin.is_valid()) {
+ set.insert(origin);
+ } else {
+ NOTREACHED();
+ }
+ }
}
for (std::set<GURL>::const_iterator unique = set.begin();
@@ -700,6 +711,11 @@ void ExtensionsService::NotifyExtensionLoaded(Extension* extension) {
// Check if this permission requires unlimited storage quota
if (extension->HasApiPermission(Extension::kUnlimitedStoragePermission))
GrantUnlimitedStorage(extension);
+
+ // If the extension is an app, protect its local storage from
+ // "Clear browsing data."
+ if (extension->is_app())
+ GrantProtectedStorage(extension);
}
LOG(INFO) << "Sending EXTENSION_LOADED";
@@ -725,6 +741,31 @@ void ExtensionsService::NotifyExtensionUnloaded(Extension* extension) {
// in-memory quota.
if (extension->HasApiPermission(Extension::kUnlimitedStoragePermission))
RevokeUnlimitedStorage(extension);
+
+ // If this is an app, then stop protecting its storage so it can be deleted.
+ if (extension->is_app())
+ RevokeProtectedStorage(extension);
+ }
+}
+
+void ExtensionsService::GrantProtectedStorage(Extension* extension) {
+ DCHECK(extension->is_app()) << "Only Apps are allowed protected storage.";
+ std::vector<GURL> origins;
+ GetExplicitOriginsInExtent(extension, &origins);
+ for (size_t i = 0; i < origins.size(); ++i)
+ ++protected_storage_map_[origins[i]];
+}
+
+void ExtensionsService::RevokeProtectedStorage(Extension* extension) {
+ DCHECK(extension->is_app()) << "Attempting to revoke protected storage from "
+ << " a non-app extension.";
+ std::vector<GURL> origins;
+ GetExplicitOriginsInExtent(extension, &origins);
+ for (size_t i = 0; i < origins.size(); ++i) {
+ const GURL& origin = origins[i];
+ DCHECK(protected_storage_map_[origin] > 0);
+ if (--protected_storage_map_[origin] <= 0)
+ protected_storage_map_.erase(origin);
}
}
diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h
index efdc17b..f158303 100644
--- a/chrome/browser/extensions/extensions_service.h
+++ b/chrome/browser/extensions/extensions_service.h
@@ -362,6 +362,10 @@ class ExtensionsService
ExtensionMenuManager* menu_manager() { return &menu_manager_; }
+ const std::map<GURL, int>& protected_storage_map() const {
+ return protected_storage_map_;
+ }
+
// Notify the frontend that there was an error loading an extension.
// This method is public because ExtensionsServiceBackend can post to here.
void ReportExtensionLoadError(const FilePath& extension_path,
@@ -412,6 +416,8 @@ class ExtensionsService
void LoadInstalledExtension(const ExtensionInfo& info, bool write_to_prefs);
// Helper methods to configure the storage services accordingly.
+ void GrantProtectedStorage(Extension* extension);
+ void RevokeProtectedStorage(Extension* extension);
void GrantUnlimitedStorage(Extension* extension);
void RevokeUnlimitedStorage(Extension* extension);
@@ -489,10 +495,18 @@ class ExtensionsService
typedef std::map<GURL, int> UnlimitedStorageMap;
UnlimitedStorageMap unlimited_storage_map_;
+ // Collection of origins whose storage is protected by "Clear browsing data."
+ // A map from origin to the number of Apps currently installed and therefore
+ // intrinsically protected.
+ typedef std::map<GURL, int> ProtectedStorageMap;
+ ProtectedStorageMap protected_storage_map_;
+
FRIEND_TEST_ALL_PREFIXES(ExtensionsServiceTest,
UpdatePendingExtensionAlreadyInstalled);
FRIEND_TEST_ALL_PREFIXES(ExtensionsServiceTest,
InstallAppsWithUnlimtedStorage);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionsServiceTest,
+ InstallAppsAndCheckStorageProtection);
DISALLOW_COPY_AND_ASSIGN(ExtensionsService);
};
diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc
index 819a6a2..bd92b0c 100644
--- a/chrome/browser/extensions/extensions_service_unittest.cc
+++ b/chrome/browser/extensions/extensions_service_unittest.cc
@@ -1267,6 +1267,49 @@ TEST_F(ExtensionsServiceTest, InstallAppsWithUnlimtedStorage) {
EXPECT_TRUE(service_->unlimited_storage_map_.empty());
}
+TEST_F(ExtensionsServiceTest, InstallAppsAndCheckStorageProtection) {
+ InitializeEmptyExtensionsService();
+ EXPECT_TRUE(service_->extensions()->empty());
+ EXPECT_TRUE(service_->protected_storage_map_.empty());
+
+ FilePath extensions_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
+ extensions_path = extensions_path.AppendASCII("extensions");
+ int pref_count = 0;
+
+ PackAndInstallExtension(extensions_path.AppendASCII("app1"), true);
+ ValidatePrefKeyCount(++pref_count);
+ ASSERT_EQ(1u, service_->extensions()->size());
+ Extension* extension = service_->extensions()->at(0);
+ EXPECT_TRUE(extension->is_app());
+ const std::string id1 = extension->id();
+ EXPECT_FALSE(service_->protected_storage_map_.empty());
+ const GURL origin1(extension->GetFullLaunchURL().GetOrigin());
+ ASSERT_EQ(1, service_->protected_storage_map_[origin1]);
+
+ // App 4 has a different origin (maps.google.com).
+ PackAndInstallExtension(extensions_path.AppendASCII("app4"), true);
+ ValidatePrefKeyCount(++pref_count);
+ ASSERT_EQ(2u, service_->extensions()->size());
+ extension = service_->extensions()->at(1);
+ const std::string id2 = extension->id();
+ EXPECT_FALSE(service_->protected_storage_map_.empty());
+ const GURL origin2(extension->GetFullLaunchURL().GetOrigin());
+ ASSERT_NE(origin1, origin2);
+ ASSERT_EQ(1, service_->protected_storage_map_[origin2]);
+
+ service_->UninstallExtension(id1, false);
+ loop_.RunAllPending();
+ EXPECT_EQ(1u, service_->extensions()->size());
+ EXPECT_FALSE(service_->protected_storage_map_.empty());
+
+ service_->UninstallExtension(id2, false);
+ loop_.RunAllPending();
+
+ EXPECT_TRUE(service_->extensions()->empty());
+ EXPECT_TRUE(service_->protected_storage_map_.empty());
+}
+
// Test that when an extension version is reinstalled, nothing happens.
TEST_F(ExtensionsServiceTest, Reinstall) {
InitializeEmptyExtensionsService();
diff --git a/chrome/browser/in_process_webkit/dom_storage_context.cc b/chrome/browser/in_process_webkit/dom_storage_context.cc
index a4cba5f..e13e203 100644
--- a/chrome/browser/in_process_webkit/dom_storage_context.cc
+++ b/chrome/browser/in_process_webkit/dom_storage_context.cc
@@ -4,6 +4,8 @@
#include "chrome/browser/in_process_webkit/dom_storage_context.h"
+#include <algorithm>
+
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/string_util.h"
@@ -156,7 +158,8 @@ void DOMStorageContext::PurgeMemory() {
void DOMStorageContext::DeleteDataModifiedSince(
const base::Time& cutoff,
- const char* url_scheme_to_be_skipped) {
+ const char* url_scheme_to_be_skipped,
+ const std::vector<string16>& protected_origins) {
// Make sure that we don't delete a database that's currently being accessed
// by unloading all of the databases temporarily.
PurgeMemory();
@@ -171,6 +174,13 @@ void DOMStorageContext::DeleteDataModifiedSince(
webkit_glue::FilePathToWebString(path.BaseName()));
if (EqualsASCII(web_security_origin.protocol(), url_scheme_to_be_skipped))
continue;
+
+ std::vector<string16>::const_iterator find_iter =
+ std::find(protected_origins.begin(), protected_origins.end(),
+ web_security_origin.databaseIdentifier());
+ if (find_iter != protected_origins.end())
+ continue;
+
file_util::FileEnumerator::FindInfo find_info;
file_enumerator.GetFindInfo(&find_info);
if (file_util::HasFileBeenModifiedSince(find_info, cutoff))
@@ -185,7 +195,7 @@ void DOMStorageContext::DeleteLocalStorageFile(const FilePath& file_path) {
// by unloading all of the databases temporarily.
// TODO(bulach): both this method and DeleteDataModifiedSince could purge
// only the memory used by the specific file instead of all memory at once.
- // See http://code.google.com/p/chromium/issues/detail?id=32000
+ // See http://crbug.com/32000
PurgeMemory();
file_util::Delete(file_path, false);
}
diff --git a/chrome/browser/in_process_webkit/dom_storage_context.h b/chrome/browser/in_process_webkit/dom_storage_context.h
index b082779..1552a918 100644
--- a/chrome/browser/in_process_webkit/dom_storage_context.h
+++ b/chrome/browser/in_process_webkit/dom_storage_context.h
@@ -67,7 +67,8 @@ class DOMStorageContext {
// Delete any local storage files that have been touched since the cutoff
// date that's supplied.
void DeleteDataModifiedSince(const base::Time& cutoff,
- const char* url_scheme_to_be_skipped);
+ const char* url_scheme_to_be_skipped,
+ const std::vector<string16>& protected_origins);
// Deletes a single local storage file.
void DeleteLocalStorageFile(const FilePath& file_path);
diff --git a/chrome/browser/in_process_webkit/webkit_context.cc b/chrome/browser/in_process_webkit/webkit_context.cc
index a5db03f..ae1665a 100644
--- a/chrome/browser/in_process_webkit/webkit_context.cc
+++ b/chrome/browser/in_process_webkit/webkit_context.cc
@@ -48,18 +48,19 @@ void WebKitContext::PurgeMemory() {
void WebKitContext::DeleteDataModifiedSince(
const base::Time& cutoff,
- const char* url_scheme_to_be_skipped) {
+ const char* url_scheme_to_be_skipped,
+ const std::vector<string16>& protected_origins) {
if (!ChromeThread::CurrentlyOn(ChromeThread::WEBKIT)) {
bool result = ChromeThread::PostTask(
ChromeThread::WEBKIT, FROM_HERE,
NewRunnableMethod(this, &WebKitContext::DeleteDataModifiedSince,
- cutoff, url_scheme_to_be_skipped));
+ cutoff, url_scheme_to_be_skipped, protected_origins));
DCHECK(result);
return;
}
- dom_storage_context_->DeleteDataModifiedSince(cutoff,
- url_scheme_to_be_skipped);
+ dom_storage_context_->DeleteDataModifiedSince(
+ cutoff, url_scheme_to_be_skipped, protected_origins);
}
diff --git a/chrome/browser/in_process_webkit/webkit_context.h b/chrome/browser/in_process_webkit/webkit_context.h
index 5434984..26e0621 100644
--- a/chrome/browser/in_process_webkit/webkit_context.h
+++ b/chrome/browser/in_process_webkit/webkit_context.h
@@ -6,6 +6,8 @@
#define CHROME_BROWSER_IN_PROCESS_WEBKIT_WEBKIT_CONTEXT_H_
#pragma once
+#include <vector>
+
#include "base/file_path.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
@@ -51,7 +53,8 @@ class WebKitContext : public base::RefCountedThreadSafe<WebKitContext> {
// Tell all children (where applicable) to delete any objects that were
// last modified on or after the following time.
void DeleteDataModifiedSince(const base::Time& cutoff,
- const char* url_scheme_to_be_skipped);
+ const char* url_scheme_to_be_skipped,
+ const std::vector<string16>& protected_origins);
// Delete the session storage namespace associated with this id. Called from
// the UI thread.
diff --git a/chrome/test/data/extensions/app4/manifest.json b/chrome/test/data/extensions/app4/manifest.json
new file mode 100644
index 0000000..f951051
--- /dev/null
+++ b/chrome/test/data/extensions/app4/manifest.json
@@ -0,0 +1,9 @@
+{
+ "name": "Test App 4",
+ "version": "1",
+ "app": {
+ "launch": {
+ "web_url": "http://maps.google.com/path5/foo.html"
+ }
+ }
+}
diff --git a/webkit/database/database_tracker.cc b/webkit/database/database_tracker.cc
index a37c2a0..8a8a21d 100644
--- a/webkit/database/database_tracker.cc
+++ b/webkit/database/database_tracker.cc
@@ -4,6 +4,7 @@
#include "webkit/database/database_tracker.h"
+#include <algorithm>
#include <vector>
#include "app/sql/connection.h"
@@ -520,6 +521,7 @@ int DatabaseTracker::DeleteDatabase(const string16& origin_identifier,
int DatabaseTracker::DeleteDataModifiedSince(
const base::Time& cutoff,
+ const std::vector<string16>& protected_origins,
net::CompletionCallback* callback) {
if (!LazyInit())
return net::ERR_FAILED;
@@ -536,6 +538,12 @@ int DatabaseTracker::DeleteDataModifiedSince(
ori != origins.end(); ++ori) {
if (StartsWith(*ori, ASCIIToUTF16(kExtensionOriginIdentifierPrefix), true))
continue;
+
+ std::vector<string16>::const_iterator find_iter =
+ std::find(protected_origins.begin(), protected_origins.end(), *ori);
+ if (find_iter != protected_origins.end())
+ continue;
+
std::vector<DatabaseDetails> details;
if (!databases_table_->GetAllDatabaseDetailsForOrigin(*ori, &details))
rv = net::ERR_FAILED;
diff --git a/webkit/database/database_tracker.h b/webkit/database/database_tracker.h
index e4c8183..4976a91 100644
--- a/webkit/database/database_tracker.h
+++ b/webkit/database/database_tracker.h
@@ -145,10 +145,12 @@ class DatabaseTracker
net::CompletionCallback* callback);
// Delete any databases that have been touched since the cutoff date that's
- // supplied. Returns net::OK on success, net::FAILED if not all databases
- // could be deleted, and net::ERR_IO_PENDING and |callback| is invoked upon
- // completion, if non-NULL.
+ // supplied, omitting any that match IDs within |protected_origins|.
+ // Returns net::OK on success, net::FAILED if not all databases could be
+ // deleted, and net::ERR_IO_PENDING and |callback| is invoked upon completion,
+ // if non-NULL.
int DeleteDataModifiedSince(const base::Time& cutoff,
+ const std::vector<string16>& protected_origins,
net::CompletionCallback* callback);
// Delete all databases that belong to the given origin. Returns net::OK on
diff --git a/webkit/database/database_tracker_unittest.cc b/webkit/database/database_tracker_unittest.cc
index bc255dd..2afdf5e 100644
--- a/webkit/database/database_tracker_unittest.cc
+++ b/webkit/database/database_tracker_unittest.cc
@@ -156,10 +156,13 @@ class DatabaseTracker_TestHelper_Test {
EXPECT_TRUE(file_util::SetLastModifiedTime(
tracker->GetFullDBFilePath(kOrigin2, kDB3), three_days_ago));
- // Delete databases modified since yesterday.
+ // Delete databases modified since yesterday. db2 is whitelisted.
base::Time yesterday = base::Time::Now();
yesterday -= base::TimeDelta::FromDays(1);
- result = tracker->DeleteDataModifiedSince(yesterday, &callback);
+ std::vector<string16> protected_origins;
+ protected_origins.push_back(kOrigin2);
+ result = tracker->DeleteDataModifiedSince(
+ yesterday, protected_origins, &callback);
EXPECT_EQ(net::ERR_IO_PENDING, result);
ASSERT_FALSE(callback.have_result());
EXPECT_TRUE(observer.DidReceiveNewNotification());
@@ -169,7 +172,7 @@ class DatabaseTracker_TestHelper_Test {
EXPECT_EQ(net::OK, result);
EXPECT_FALSE(file_util::PathExists(tracker->DatabaseDirectory().Append(
FilePath::FromWStringHack(UTF16ToWide(kOrigin1)))));
- EXPECT_FALSE(
+ EXPECT_TRUE(
file_util::PathExists(tracker->GetFullDBFilePath(kOrigin2, kDB2)));
EXPECT_TRUE(
file_util::PathExists(tracker->GetFullDBFilePath(kOrigin2, kDB3)));