Commit 4d63bbd8 authored by marja@chromium.org's avatar marja@chromium.org

Better session restore: Persist sessionStorage on disk.

Write sessionStorage on disk, and restore it after
relaunching, when recovering from a crash, and when the
startup option "continue where I left off" is selected.

BUG=104292
TEST=Manual; SessionStorageDatabaseTest.ReadOriginsInNamespace


Review URL: https://chromiumcodereview.appspot.com/9963107

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@150314 0039d316-1c4b-4281-b951-d872f2087c98
parent aed59fd7
...@@ -84,6 +84,7 @@ ...@@ -84,6 +84,7 @@
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h" #include "chrome/common/url_constants.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/browser/dom_storage_context.h"
#include "content/public/browser/host_zoom_map.h" #include "content/public/browser/host_zoom_map.h"
#include "content/public/browser/notification_service.h" #include "content/public/browser/notification_service.h"
#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_process_host.h"
...@@ -313,6 +314,11 @@ ProfileImpl::ProfileImpl(const FilePath& path, ...@@ -313,6 +314,11 @@ ProfileImpl::ProfileImpl(const FilePath& path,
} else { } else {
NOTREACHED(); NOTREACHED();
} }
if (command_line->HasSwitch(switches::kEnableRestoreSessionState)) {
content::BrowserContext::GetDefaultDOMStorageContext(this)->
SetSaveSessionStorageOnDisk();
}
} }
void ProfileImpl::DoFinalInit(bool is_new_profile) { void ProfileImpl::DoFinalInit(bool is_new_profile) {
......
...@@ -733,7 +733,10 @@ class SessionRestoreImpl : public content::NotificationObserver { ...@@ -733,7 +733,10 @@ class SessionRestoreImpl : public content::NotificationObserver {
100); 100);
if (windows->empty()) { if (windows->empty()) {
// Restore was unsuccessful. // Restore was unsuccessful. The DOM storage system can also delete its
// data, since no session restore will happen at a later point in time.
content::BrowserContext::GetDefaultDOMStorageContext(profile_)->
StartScavengingUnusedSessionStorage();
return FinishedTabCreation(false, false); return FinishedTabCreation(false, false);
} }
...@@ -822,6 +825,12 @@ class SessionRestoreImpl : public content::NotificationObserver { ...@@ -822,6 +825,12 @@ class SessionRestoreImpl : public content::NotificationObserver {
Browser* finished_browser = FinishedTabCreation(true, has_tabbed_browser); Browser* finished_browser = FinishedTabCreation(true, has_tabbed_browser);
if (finished_browser) if (finished_browser)
last_browser = finished_browser; last_browser = finished_browser;
// sessionStorages needed for the session restore have now been recreated
// by RestoreTab. Now it's safe for the DOM storage system to start
// deleting leftover data.
content::BrowserContext::GetDefaultDOMStorageContext(profile_)->
StartScavengingUnusedSessionStorage();
return last_browser; return last_browser;
} }
......
...@@ -12,7 +12,10 @@ ...@@ -12,7 +12,10 @@
#include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h" #include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/url_constants.h" #include "chrome/common/url_constants.h"
#include "content/public/browser/dom_storage_context.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "grit/chromium_strings.h" #include "grit/chromium_strings.h"
#include "grit/generated_resources.h" #include "grit/generated_resources.h"
...@@ -23,7 +26,8 @@ ...@@ -23,7 +26,8 @@
namespace { namespace {
// A delegate for the InfoBar shown when the previous session has crashed. // A delegate for the InfoBar shown when the previous session has crashed.
class SessionCrashedInfoBarDelegate : public ConfirmInfoBarDelegate { class SessionCrashedInfoBarDelegate : public ConfirmInfoBarDelegate,
public content::NotificationObserver {
public: public:
explicit SessionCrashedInfoBarDelegate(InfoBarTabHelper* infobar_helper); explicit SessionCrashedInfoBarDelegate(InfoBarTabHelper* infobar_helper);
...@@ -37,15 +41,40 @@ class SessionCrashedInfoBarDelegate : public ConfirmInfoBarDelegate { ...@@ -37,15 +41,40 @@ class SessionCrashedInfoBarDelegate : public ConfirmInfoBarDelegate {
virtual string16 GetButtonLabel(InfoBarButton button) const OVERRIDE; virtual string16 GetButtonLabel(InfoBarButton button) const OVERRIDE;
virtual bool Accept() OVERRIDE; virtual bool Accept() OVERRIDE;
// content::NotificationObserver:
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
content::NotificationRegistrar registrar_;
bool accepted_;
bool removed_notification_received_;
Browser* browser_;
DISALLOW_COPY_AND_ASSIGN(SessionCrashedInfoBarDelegate); DISALLOW_COPY_AND_ASSIGN(SessionCrashedInfoBarDelegate);
}; };
SessionCrashedInfoBarDelegate::SessionCrashedInfoBarDelegate( SessionCrashedInfoBarDelegate::SessionCrashedInfoBarDelegate(
InfoBarTabHelper* infobar_helper) InfoBarTabHelper* infobar_helper)
: ConfirmInfoBarDelegate(infobar_helper) { : ConfirmInfoBarDelegate(infobar_helper),
accepted_(false),
removed_notification_received_(false),
browser_(browser::FindBrowserWithWebContents(owner()->web_contents())) {
// TODO(pkasting,marja): Once InfoBars own they delegates, this is not needed
// any more. Then we can rely on delegates getting destroyed, and we can
// initiate the session storage scavenging only in the destructor. (Currently,
// info bars are leaked if they get closed while they're in background tabs.)
registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
content::NotificationService::AllSources());
} }
SessionCrashedInfoBarDelegate::~SessionCrashedInfoBarDelegate() { SessionCrashedInfoBarDelegate::~SessionCrashedInfoBarDelegate() {
// If the info bar wasn't accepted, it was either dismissed or expired. In
// that case, session restore won't happen.
if (!accepted_ && !removed_notification_received_) {
content::BrowserContext::GetDefaultDOMStorageContext(
browser_->profile())->StartScavengingUnusedSessionStorage();
}
} }
gfx::Image* SessionCrashedInfoBarDelegate::GetIcon() const { gfx::Image* SessionCrashedInfoBarDelegate::GetIcon() const {
...@@ -69,20 +98,34 @@ string16 SessionCrashedInfoBarDelegate::GetButtonLabel( ...@@ -69,20 +98,34 @@ string16 SessionCrashedInfoBarDelegate::GetButtonLabel(
bool SessionCrashedInfoBarDelegate::Accept() { bool SessionCrashedInfoBarDelegate::Accept() {
uint32 behavior = 0; uint32 behavior = 0;
Browser* browser = if (browser_->tab_count() == 1 &&
browser::FindBrowserWithWebContents(owner()->web_contents()); chrome::GetWebContentsAt(browser_, 0)->GetURL() ==
if (browser->tab_count() == 1 &&
chrome::GetWebContentsAt(browser, 0)->GetURL() ==
GURL(chrome::kChromeUINewTabURL)) { GURL(chrome::kChromeUINewTabURL)) {
// There is only one tab and its the new tab page, make session restore // There is only one tab and its the new tab page, make session restore
// clobber it. // clobber it.
behavior = SessionRestore::CLOBBER_CURRENT_TAB; behavior = SessionRestore::CLOBBER_CURRENT_TAB;
} }
SessionRestore::RestoreSession( SessionRestore::RestoreSession(
browser->profile(), browser, behavior, std::vector<GURL>()); browser_->profile(), browser_, behavior, std::vector<GURL>());
accepted_ = true;
return true; return true;
} }
void SessionCrashedInfoBarDelegate::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(type == chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED);
if (content::Details<std::pair<InfoBarDelegate*, bool> >(details)->first !=
this)
return;
if (!accepted_) {
content::BrowserContext::GetDefaultDOMStorageContext(
browser_->profile())->StartScavengingUnusedSessionStorage();
removed_notification_received_ = true;
}
}
} // namespace } // namespace
namespace chrome { namespace chrome {
......
...@@ -73,6 +73,7 @@ ...@@ -73,6 +73,7 @@
#include "chrome/common/url_constants.h" #include "chrome/common/url_constants.h"
#include "chrome/installer/util/browser_distribution.h" #include "chrome/installer/util/browser_distribution.h"
#include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/dom_storage_context.h"
#include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_registrar.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
...@@ -630,6 +631,16 @@ bool StartupBrowserCreatorImpl::ProcessStartupURLs( ...@@ -630,6 +631,16 @@ bool StartupBrowserCreatorImpl::ProcessStartupURLs(
return false; return false;
AddInfoBarsIfNecessary(browser, chrome::startup::IS_PROCESS_STARTUP); AddInfoBarsIfNecessary(browser, chrome::startup::IS_PROCESS_STARTUP);
// Session restore may occur if the startup preference is "last" or if the
// crash infobar is displayed. Otherwise, it's safe for the DOM storage system
// to start deleting leftover data.
if (pref.type != SessionStartupPref::LAST &&
!HasPendingUncleanExit(profile_)) {
content::BrowserContext::GetDefaultDOMStorageContext(profile_)->
StartScavengingUnusedSessionStorage();
}
return true; return true;
} }
......
...@@ -605,6 +605,11 @@ const char kEnablePasswordGeneration[] = "enable-password-generation"; ...@@ -605,6 +605,11 @@ const char kEnablePasswordGeneration[] = "enable-password-generation";
const char kEnableResourceContentSettings[] = const char kEnableResourceContentSettings[] =
"enable-resource-content-settings"; "enable-resource-content-settings";
// Enables experimental features of better session restore (backing up
// sessionStorage on disk). See also kDisableRestoreSessionState which disables
// the less exprimental features which are on by default.
const char kEnableRestoreSessionState[] = "enable-restore-session-state";
// Enables the installation and usage of Portable Native Client. // Enables the installation and usage of Portable Native Client.
const char kEnablePnacl[] = "enable-pnacl"; const char kEnablePnacl[] = "enable-pnacl";
......
...@@ -173,6 +173,7 @@ extern const char kEnablePasswordGeneration[]; ...@@ -173,6 +173,7 @@ extern const char kEnablePasswordGeneration[];
extern const char kEnablePnacl[]; extern const char kEnablePnacl[];
extern const char kEnableProfiling[]; extern const char kEnableProfiling[];
extern const char kEnableResourceContentSettings[]; extern const char kEnableResourceContentSettings[];
extern const char kEnableRestoreSessionState[];
extern const char kEnableScriptBadges[]; extern const char kEnableScriptBadges[];
extern const char kEnableSdch[]; extern const char kEnableSdch[];
extern const char kEnableSpdy3[]; extern const char kEnableSpdy3[];
......
...@@ -24,6 +24,7 @@ using dom_storage::DomStorageWorkerPoolTaskRunner; ...@@ -24,6 +24,7 @@ using dom_storage::DomStorageWorkerPoolTaskRunner;
namespace { namespace {
const char kLocalStorageDirectory[] = "Local Storage"; const char kLocalStorageDirectory[] = "Local Storage";
const char kSessionStorageDirectory[] = "Session Storage";
void InvokeUsageInfoCallbackHelper( void InvokeUsageInfoCallbackHelper(
const DOMStorageContext::GetUsageInfoCallback& callback, const DOMStorageContext::GetUsageInfoCallback& callback,
...@@ -50,12 +51,11 @@ DOMStorageContextImpl::DOMStorageContextImpl( ...@@ -50,12 +51,11 @@ DOMStorageContextImpl::DOMStorageContextImpl(
const FilePath& data_path, const FilePath& data_path,
quota::SpecialStoragePolicy* special_storage_policy) { quota::SpecialStoragePolicy* special_storage_policy) {
base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool(); base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool();
// TODO(marja): Pass a nonempty session storage directory when session storage
// is backed on disk.
context_ = new dom_storage::DomStorageContext( context_ = new dom_storage::DomStorageContext(
data_path.empty() ? data_path.empty() ?
data_path : data_path.AppendASCII(kLocalStorageDirectory), data_path : data_path.AppendASCII(kLocalStorageDirectory),
FilePath(), // Empty session storage directory. data_path.empty() ?
data_path : data_path.AppendASCII(kSessionStorageDirectory),
special_storage_policy, special_storage_policy,
new DomStorageWorkerPoolTaskRunner( new DomStorageWorkerPoolTaskRunner(
worker_pool, worker_pool,
...@@ -85,6 +85,11 @@ void DOMStorageContextImpl::DeleteOrigin(const GURL& origin) { ...@@ -85,6 +85,11 @@ void DOMStorageContextImpl::DeleteOrigin(const GURL& origin) {
base::Bind(&DomStorageContext::DeleteOrigin, context_, origin)); base::Bind(&DomStorageContext::DeleteOrigin, context_, origin));
} }
void DOMStorageContextImpl::SetSaveSessionStorageOnDisk() {
DCHECK(context_);
context_->SetSaveSessionStorageOnDisk();
}
scoped_refptr<content::SessionStorageNamespace> scoped_refptr<content::SessionStorageNamespace>
DOMStorageContextImpl::RecreateSessionStorage( DOMStorageContextImpl::RecreateSessionStorage(
const std::string& persistent_id) { const std::string& persistent_id) {
...@@ -92,6 +97,15 @@ DOMStorageContextImpl::RecreateSessionStorage( ...@@ -92,6 +97,15 @@ DOMStorageContextImpl::RecreateSessionStorage(
new SessionStorageNamespaceImpl(this, persistent_id)); new SessionStorageNamespaceImpl(this, persistent_id));
} }
void DOMStorageContextImpl::StartScavengingUnusedSessionStorage() {
DCHECK(context_);
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE,
DomStorageTaskRunner::PRIMARY_SEQUENCE,
base::Bind(&DomStorageContext::StartScavengingUnusedSessionStorage,
context_));
}
void DOMStorageContextImpl::PurgeMemory() { void DOMStorageContextImpl::PurgeMemory() {
DCHECK(context_); DCHECK(context_);
context_->task_runner()->PostShutdownBlockingTask( context_->task_runner()->PostShutdownBlockingTask(
......
...@@ -27,8 +27,10 @@ class CONTENT_EXPORT DOMStorageContextImpl : ...@@ -27,8 +27,10 @@ class CONTENT_EXPORT DOMStorageContextImpl :
// DOMStorageContext implementation. // DOMStorageContext implementation.
virtual void GetUsageInfo(const GetUsageInfoCallback& callback) OVERRIDE; virtual void GetUsageInfo(const GetUsageInfoCallback& callback) OVERRIDE;
virtual void DeleteOrigin(const GURL& origin) OVERRIDE; virtual void DeleteOrigin(const GURL& origin) OVERRIDE;
virtual void SetSaveSessionStorageOnDisk() OVERRIDE;
virtual scoped_refptr<content::SessionStorageNamespace> virtual scoped_refptr<content::SessionStorageNamespace>
RecreateSessionStorage(const std::string& persistent_id) OVERRIDE; RecreateSessionStorage(const std::string& persistent_id) OVERRIDE;
virtual void StartScavengingUnusedSessionStorage() OVERRIDE;
// Called to free up memory that's not strictly needed. // Called to free up memory that's not strictly needed.
void PurgeMemory(); void PurgeMemory();
......
...@@ -31,6 +31,12 @@ class DOMStorageContext { ...@@ -31,6 +31,12 @@ class DOMStorageContext {
// Deletes the local storage data for the given origin. // Deletes the local storage data for the given origin.
virtual void DeleteOrigin(const GURL& origin) = 0; virtual void DeleteOrigin(const GURL& origin) = 0;
// If this is called, sessionStorage data will be stored on disk, and can be
// restored after a browser restart (with RecreateSessionStorage). This
// function must be called right after DOMStorageContextImpl is created, and
// before it's used.
virtual void SetSaveSessionStorageOnDisk() = 0;
// Creates a SessionStorageNamespace with the given |persistent_id|. Used // Creates a SessionStorageNamespace with the given |persistent_id|. Used
// after tabs are restored by session restore. When created, the // after tabs are restored by session restore. When created, the
// SessionStorageNamespace with the correct |persistent_id| will be // SessionStorageNamespace with the correct |persistent_id| will be
...@@ -38,6 +44,11 @@ class DOMStorageContext { ...@@ -38,6 +44,11 @@ class DOMStorageContext {
virtual scoped_refptr<SessionStorageNamespace> RecreateSessionStorage( virtual scoped_refptr<SessionStorageNamespace> RecreateSessionStorage(
const std::string& persistent_id) = 0; const std::string& persistent_id) = 0;
// Starts deleting sessionStorages which don't have an associated
// SessionStorageNamespace alive. Called when SessionStorageNamespaces have
// been created after a session restore, or a session restore won't happen.
virtual void StartScavengingUnusedSessionStorage() = 0;
protected: protected:
virtual ~DOMStorageContext() {} virtual ~DOMStorageContext() {}
}; };
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
#include "webkit/dom_storage/dom_storage_task_runner.h" #include "webkit/dom_storage/dom_storage_task_runner.h"
#include "webkit/dom_storage/dom_storage_types.h" #include "webkit/dom_storage/dom_storage_types.h"
#include "webkit/dom_storage/local_storage_database_adapter.h" #include "webkit/dom_storage/local_storage_database_adapter.h"
#include "webkit/dom_storage/session_storage_database.h"
#include "webkit/dom_storage/session_storage_database_adapter.h"
#include "webkit/fileapi/file_system_util.h" #include "webkit/fileapi/file_system_util.h"
#include "webkit/glue/webkit_glue.h" #include "webkit/glue/webkit_glue.h"
...@@ -72,16 +74,23 @@ DomStorageArea::DomStorageArea( ...@@ -72,16 +74,23 @@ DomStorageArea::DomStorageArea(
int64 namespace_id, int64 namespace_id,
const std::string& persistent_namespace_id, const std::string& persistent_namespace_id,
const GURL& origin, const GURL& origin,
SessionStorageDatabase* session_storage_backing,
DomStorageTaskRunner* task_runner) DomStorageTaskRunner* task_runner)
: namespace_id_(namespace_id), : namespace_id_(namespace_id),
persistent_namespace_id_(persistent_namespace_id), persistent_namespace_id_(persistent_namespace_id),
origin_(origin), origin_(origin),
task_runner_(task_runner), task_runner_(task_runner),
map_(new DomStorageMap(kPerAreaQuota + kPerAreaOverQuotaAllowance)), map_(new DomStorageMap(kPerAreaQuota + kPerAreaOverQuotaAllowance)),
session_storage_backing_(session_storage_backing),
is_initial_import_done_(true), is_initial_import_done_(true),
is_shutdown_(false), is_shutdown_(false),
commit_batches_in_flight_(0) { commit_batches_in_flight_(0) {
DCHECK(namespace_id != kLocalStorageNamespaceId); DCHECK(namespace_id != kLocalStorageNamespaceId);
if (session_storage_backing) {
backing_.reset(new SessionStorageDatabaseAdapter(
session_storage_backing, persistent_namespace_id, origin));
is_initial_import_done_ = false;
}
} }
DomStorageArea::~DomStorageArea() { DomStorageArea::~DomStorageArea() {
...@@ -168,13 +177,20 @@ DomStorageArea* DomStorageArea::ShallowCopy( ...@@ -168,13 +177,20 @@ DomStorageArea* DomStorageArea::ShallowCopy(
const std::string& destination_persistent_namespace_id) { const std::string& destination_persistent_namespace_id) {
DCHECK_NE(kLocalStorageNamespaceId, namespace_id_); DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id); DCHECK_NE(kLocalStorageNamespaceId, destination_namespace_id);
DCHECK(!backing_.get()); // SessionNamespaces aren't stored on disk.
DomStorageArea* copy = new DomStorageArea( DomStorageArea* copy = new DomStorageArea(
destination_namespace_id, destination_persistent_namespace_id, origin_, destination_namespace_id, destination_persistent_namespace_id, origin_,
task_runner_); session_storage_backing_, task_runner_);
copy->map_ = map_; copy->map_ = map_;
copy->is_shutdown_ = is_shutdown_; copy->is_shutdown_ = is_shutdown_;
copy->is_initial_import_done_ = true;
// All the uncommitted changes to this area need to happen before the actual
// shallow copy is made (scheduled by the upper layer). Another OnCommitTimer
// call might be in the event queue at this point, but it's handled gracefully
// when it fires.
if (commit_batch_.get())
OnCommitTimer();
return copy; return copy;
} }
...@@ -185,6 +201,8 @@ bool DomStorageArea::HasUncommittedChanges() const { ...@@ -185,6 +201,8 @@ bool DomStorageArea::HasUncommittedChanges() const {
void DomStorageArea::DeleteOrigin() { void DomStorageArea::DeleteOrigin() {
DCHECK(!is_shutdown_); DCHECK(!is_shutdown_);
// This function shouldn't be called for sessionStorage.
DCHECK(!session_storage_backing_.get());
if (HasUncommittedChanges()) { if (HasUncommittedChanges()) {
// TODO(michaeln): This logically deletes the data immediately, // TODO(michaeln): This logically deletes the data immediately,
// and in a matter of a second, deletes the rows from the backing // and in a matter of a second, deletes the rows from the backing
...@@ -236,7 +254,6 @@ void DomStorageArea::InitialImportIfNeeded() { ...@@ -236,7 +254,6 @@ void DomStorageArea::InitialImportIfNeeded() {
if (is_initial_import_done_) if (is_initial_import_done_)
return; return;
DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_);
DCHECK(backing_.get()); DCHECK(backing_.get());
ValuesMap initial_values; ValuesMap initial_values;
...@@ -264,13 +281,15 @@ DomStorageArea::CommitBatch* DomStorageArea::CreateCommitBatchIfNeeded() { ...@@ -264,13 +281,15 @@ DomStorageArea::CommitBatch* DomStorageArea::CreateCommitBatchIfNeeded() {
} }
void DomStorageArea::OnCommitTimer() { void DomStorageArea::OnCommitTimer() {
DCHECK_EQ(kLocalStorageNamespaceId, namespace_id_);
if (is_shutdown_) if (is_shutdown_)
return; return;
DCHECK(backing_.get()); DCHECK(backing_.get());
DCHECK(commit_batch_.get());
DCHECK(!commit_batches_in_flight_); // It's possible that there is nothing to commit, since a shallow copy occured
// before the timer fired.
if (!commit_batch_.get())
return;
// This method executes on the primary sequence, we schedule // This method executes on the primary sequence, we schedule
// a task for immediate execution on the commit sequence. // a task for immediate execution on the commit sequence.
...@@ -298,9 +317,9 @@ void DomStorageArea::CommitChanges(const CommitBatch* commit_batch) { ...@@ -298,9 +317,9 @@ void DomStorageArea::CommitChanges(const CommitBatch* commit_batch) {
void DomStorageArea::OnCommitComplete() { void DomStorageArea::OnCommitComplete() {
// We're back on the primary sequence in this method. // We're back on the primary sequence in this method.
DCHECK(task_runner_->IsRunningOnPrimarySequence()); DCHECK(task_runner_->IsRunningOnPrimarySequence());
--commit_batches_in_flight_;
if (is_shutdown_) if (is_shutdown_)
return; return;
--commit_batches_in_flight_;
if (commit_batch_.get() && !commit_batches_in_flight_) { if (commit_batch_.get() && !commit_batches_in_flight_) {
// More changes have accrued, restart the timer. // More changes have accrued, restart the timer.
task_runner_->PostDelayedTask( task_runner_->PostDelayedTask(
...@@ -323,6 +342,7 @@ void DomStorageArea::ShutdownInCommitSequence() { ...@@ -323,6 +342,7 @@ void DomStorageArea::ShutdownInCommitSequence() {
} }
commit_batch_.reset(); commit_batch_.reset();
backing_.reset(); backing_.reset();
session_storage_backing_ = NULL;
} }
} // namespace dom_storage } // namespace dom_storage
...@@ -19,6 +19,7 @@ namespace dom_storage { ...@@ -19,6 +19,7 @@ namespace dom_storage {
class DomStorageDatabaseAdapter; class DomStorageDatabaseAdapter;
class DomStorageMap; class DomStorageMap;
class DomStorageTaskRunner; class DomStorageTaskRunner;
class SessionStorageDatabase;
// Container for a per-origin Map of key/value pairs potentially // Container for a per-origin Map of key/value pairs potentially
// backed by storage on disk and lazily commits changes to disk. // backed by storage on disk and lazily commits changes to disk.
...@@ -36,10 +37,11 @@ class DomStorageArea ...@@ -36,10 +37,11 @@ class DomStorageArea
const FilePath& directory, const FilePath& directory,
DomStorageTaskRunner* task_runner); DomStorageTaskRunner* task_runner);
// Session storage. // Session storage. Backed on disk if |session_storage_backing| is not NULL.
DomStorageArea(int64 namespace_id, DomStorageArea(int64 namespace_id,
const std::string& persistent_namespace_id, const std::string& persistent_namespace_id,
const GURL& origin, const GURL& origin,
SessionStorageDatabase* session_storage_backing,
DomStorageTaskRunner* task_runner); DomStorageTaskRunner* task_runner);
const GURL& origin() const { return origin_; } const GURL& origin() const { return origin_; }
...@@ -119,6 +121,7 @@ class DomStorageArea ...@@ -119,6 +121,7 @@ class DomStorageArea
scoped_refptr<DomStorageTaskRunner> task_runner_; scoped_refptr<DomStorageTaskRunner> task_runner_;
scoped_refptr<DomStorageMap> map_; scoped_refptr<DomStorageMap> map_;
scoped_ptr<DomStorageDatabaseAdapter> backing_; scoped_ptr<DomStorageDatabaseAdapter> backing_;
scoped_refptr<SessionStorageDatabase> session_storage_backing_;
bool is_initial_import_done_; bool is_initial_import_done_;
bool is_shutdown_; bool is_shutdown_;
scoped_ptr<CommitBatch> commit_batch_; scoped_ptr<CommitBatch> commit_batch_;
......
...@@ -73,7 +73,7 @@ class DomStorageAreaTest : public testing::Test { ...@@ -73,7 +73,7 @@ class DomStorageAreaTest : public testing::Test {
TEST_F(DomStorageAreaTest, DomStorageAreaBasics) { TEST_F(DomStorageAreaTest, DomStorageAreaBasics) {
scoped_refptr<DomStorageArea> area( scoped_refptr<DomStorageArea> area(
new DomStorageArea(1, std::string(), kOrigin, NULL)); new DomStorageArea(1, std::string(), kOrigin, NULL, NULL));
string16 old_value; string16 old_value;
NullableString16 old_nullable_value; NullableString16 old_nullable_value;
scoped_refptr<DomStorageArea> copy; scoped_refptr<DomStorageArea> copy;
...@@ -138,12 +138,12 @@ TEST_F(DomStorageAreaTest, BackingDatabaseOpened) { ...@@ -138,12 +138,12 @@ TEST_F(DomStorageAreaTest, BackingDatabaseOpened) {
EXPECT_FALSE(file_util::PathExists(kExpectedOriginFilePath)); EXPECT_FALSE(file_util::PathExists(kExpectedOriginFilePath));
} }
// Valid directory and origin but non-local namespace id. Backing should // Valid directory and origin but no session storage backing. Backing should
// be null. // be null.
{ {
scoped_refptr<DomStorageArea> area( scoped_refptr<DomStorageArea> area(
new DomStorageArea(kSessionStorageNamespaceId, std::string(), kOrigin, new DomStorageArea(kSessionStorageNamespaceId, std::string(), kOrigin,
NULL)); NULL, NULL));
EXPECT_EQ(NULL, area->backing_.get()); EXPECT_EQ(NULL, area->backing_.get());
EXPECT_TRUE(area->is_initial_import_done_); EXPECT_TRUE(area->is_initial_import_done_);
......
...@@ -15,12 +15,15 @@ ...@@ -15,12 +15,15 @@
#include "webkit/dom_storage/dom_storage_namespace.h" #include "webkit/dom_storage/dom_storage_namespace.h"
#include "webkit/dom_storage/dom_storage_task_runner.h" #include "webkit/dom_storage/dom_storage_task_runner.h"
#include "webkit/dom_storage/dom_storage_types.h" #include "webkit/dom_storage/dom_storage_types.h"
#include "webkit/dom_storage/session_storage_database.h"
#include "webkit/quota/special_storage_policy.h" #include "webkit/quota/special_storage_policy.h"
using file_util::FileEnumerator; using file_util::FileEnumerator;
namespace dom_storage { namespace dom_storage {
static const int kSessionStoraceScavengingSeconds = 60;
DomStorageContext::UsageInfo::UsageInfo() : data_size(0) {} DomStorageContext::UsageInfo::UsageInfo() : data_size(0) {}
DomStorageContext::UsageInfo::~UsageInfo() {} DomStorageContext::UsageInfo::~UsageInfo() {}
...@@ -34,7 +37,8 @@ DomStorageContext::DomStorageContext( ...@@ -34,7 +37,8 @@ DomStorageContext::DomStorageContext(
task_runner_(task_runner), task_runner_(task_runner),
is_shutdown_(false), is_shutdown_(false),
force_keep_session_state_(false), force_keep_session_state_(false),
special_storage_policy_(special_storage_policy) { special_storage_policy_(special_storage_policy),
scavenging_started_(false) {
// AtomicSequenceNum starts at 0 but we want to start session // AtomicSequenceNum starts at 0 but we want to start session
// namespace ids at one since zero is reserved for the // namespace ids at one since zero is reserved for the
// kLocalStorageNamespaceId. // kLocalStorageNamespaceId.
...@@ -88,6 +92,7 @@ void DomStorageContext::GetUsageInfo(std::vector<UsageInfo>* infos, ...@@ -88,6 +92,7 @@ void DomStorageContext::GetUsageInfo(std::vector<UsageInfo>* infos,
infos->push_back(info); infos->push_back(info);
} }
} }
// TODO(marja): Get usage infos for sessionStorage (crbug.com/123599).
} }
void DomStorageContext::DeleteOrigin(const GURL& origin) { void DomStorageContext::DeleteOrigin(const GURL& origin) {
...@@ -111,7 +116,7 @@ void DomStorageContext::Shutdown() { ...@@ -111,7 +116,7 @@ void DomStorageContext::Shutdown() {
for (; it != namespaces_.end(); ++it) for (; it != namespaces_.end(); ++it)
it->second->Shutdown(); it->second->Shutdown();
if (localstorage_directory_.empty()) if (localstorage_directory_.empty() && !session_storage_database_.get())
return; return;
// Respect the content policy settings about what to // Respect the content policy settings about what to
...@@ -186,13 +191,31 @@ void DomStorageContext::CreateSessionNamespace( ...@@ -186,13 +191,31 @@ void DomStorageContext::CreateSessionNamespace(
DCHECK(namespace_id != kLocalStorageNamespaceId); DCHECK(namespace_id != kLocalStorageNamespaceId);
DCHECK(namespaces_.find(namespace_id) == namespaces_.end()); DCHECK(namespaces_.find(namespace_id) == namespaces_.end());
namespaces_[namespace_id] = new DomStorageNamespace( namespaces_[namespace_id] = new DomStorageNamespace(
namespace_id, persistent_namespace_id, task_runner_); namespace_id, persistent_namespace_id, session_storage_database_.get(),
task_runner_);
} }
void DomStorageContext::DeleteSessionNamespace( void DomStorageContext::DeleteSessionNamespace(
int64 namespace_id, bool should_persist_data) { int64 namespace_id, bool should_persist_data) {
DCHECK_NE(kLocalStorageNamespaceId, namespace_id); DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
// TODO(marja): Protect the sessionStorage data (once it's written on disk). StorageNamespaceMap::const_iterator it = namespaces_.find(namespace_id);
if (it == namespaces_.end())
return;
if (session_storage_database_.get()) {
std::string persistent_namespace_id = it->second->persistent_namespace_id();
if (!should_persist_data) {
task_runner_->PostShutdownBlockingTask(
FROM_HERE,
DomStorageTaskRunner::COMMIT_SEQUENCE,
base::Bind(
base::IgnoreResult(&SessionStorageDatabase::DeleteNamespace),
session_storage_database_,
persistent_namespace_id));
} else if (!scavenging_started_) {
// Protect the persistent namespace ID from scavenging.
protected_persistent_session_ids_.insert(persistent_namespace_id);
}
}
namespaces_.erase(namespace_id); namespaces_.erase(namespace_id);
} }
...@@ -211,23 +234,124 @@ void DomStorageContext::CloneSessionNamespace( ...@@ -211,23 +234,124 @@ void DomStorageContext::CloneSessionNamespace(
} }
void DomStorageContext::ClearSessionOnlyOrigins() { void DomStorageContext::ClearSessionOnlyOrigins() {
std::vector<UsageInfo> infos; if (!localstorage_directory_.empty()) {
const bool kDontIncludeFileInfo = false; std::vector<UsageInfo> infos;
GetUsageInfo(&infos, kDontIncludeFileInfo); const bool kDontIncludeFileInfo = false;
for (size_t i = 0; i < infos.size(); ++i) { GetUsageInfo(&infos, kDontIncludeFileInfo);
const GURL& origin = infos[i].origin; for (size_t i = 0; i < infos.size(); ++i) {
if (special_storage_policy_->IsStorageProtected(origin)) const GURL& origin = infos[i].origin;
continue; if (special_storage_policy_->IsStorageProtected(origin))
if (!special_storage_policy_->IsStorageSessionOnly(origin)) continue;
continue; if (!special_storage_policy_->IsStorageSessionOnly(origin))
continue;
const bool kNotRecursive = false;
FilePath database_file_path = localstorage_directory_.Append( const bool kNotRecursive = false;
DomStorageArea::DatabaseFileNameFromOrigin(origin)); FilePath database_file_path = localstorage_directory_.Append(
file_util::Delete(database_file_path, kNotRecursive); DomStorageArea::DatabaseFileNameFromOrigin(origin));
file_util::Delete( file_util::Delete(database_file_path, kNotRecursive);
DomStorageDatabase::GetJournalFilePath(database_file_path), file_util::Delete(
kNotRecursive); DomStorageDatabase::GetJournalFilePath(database_file_path),
kNotRecursive);
}
}
if (session_storage_database_.get()) {
std::vector<std::string> namespace_ids;
session_storage_database_->ReadNamespaceIds(&namespace_ids);
for (std::vector<std::string>::const_iterator it = namespace_ids.begin();
it != namespace_ids.end(); ++it) {
std::vector<GURL> origins;
session_storage_database_->ReadOriginsInNamespace(*it, &origins);
for (std::vector<GURL>::const_iterator origin_it = origins.begin();
origin_it != origins.end(); ++origin_it) {
if (special_storage_policy_->IsStorageProtected(*origin_it))
continue;
if (!special_storage_policy_->IsStorageSessionOnly(*origin_it))
continue;
session_storage_database_->DeleteArea(*it, *origin_it);
}
}
}
}
void DomStorageContext::SetSaveSessionStorageOnDisk() {
DCHECK(namespaces_.empty());
if (!sessionstorage_directory_.empty()) {
session_storage_database_ = new SessionStorageDatabase(
sessionstorage_directory_);
}
}
void DomStorageContext::StartScavengingUnusedSessionStorage() {
if (session_storage_database_.get()) {
task_runner_->PostDelayedTask(
FROM_HERE, base::Bind(&DomStorageContext::FindUnusedNamespaces, this),
base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
}
}
void DomStorageContext::FindUnusedNamespaces() {
DCHECK(session_storage_database_.get());
DCHECK(!scavenging_started_);
scavenging_started_ = true;
std::set<std::string> namespace_ids_in_use;
for (StorageNamespaceMap::const_iterator it = namespaces_.begin();
it != namespaces_.end(); ++it)
namespace_ids_in_use.insert(it->second->persistent_namespace_id());
std::set<std::string> protected_persistent_session_ids;
protected_persistent_session_ids.swap(protected_persistent_session_ids_);
task_runner_->PostShutdownBlockingTask(
FROM_HERE, DomStorageTaskRunner::COMMIT_SEQUENCE,
base::Bind(
&DomStorageContext::FindUnusedNamespacesInCommitSequence,
this, namespace_ids_in_use, protected_persistent_session_ids));
}
void DomStorageContext::FindUnusedNamespacesInCommitSequence(
const std::set<std::string>& namespace_ids_in_use,
const std::set<std::string>& protected_persistent_session_ids) {
DCHECK(session_storage_database_.get());
// Delete all namespaces which don't have an associated DomStorageNamespace
// alive.
std::vector<std::string> namespace_ids;
session_storage_database_->ReadNamespaceIds(&namespace_ids);
for (std::vector<std::string>::const_iterator it = namespace_ids.begin();
it != namespace_ids.end(); ++it) {
if (namespace_ids_in_use.find(*it) == namespace_ids_in_use.end() &&
protected_persistent_session_ids.find(*it) ==
protected_persistent_session_ids.end()) {
deletable_persistent_namespace_ids_.push_back(*it);
}
}
if (!deletable_persistent_namespace_ids_.empty()) {
task_runner_->PostDelayedTask(
FROM_HERE, base::Bind(
&DomStorageContext::DeleteNextUnusedNamespace,
this),
base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
}
}
void DomStorageContext::DeleteNextUnusedNamespace() {
if (is_shutdown_)
return;
task_runner_->PostShutdownBlockingTask(
FROM_HERE, DomStorageTaskRunner::COMMIT_SEQUENCE,
base::Bind(
&DomStorageContext::DeleteNextUnusedNamespaceInCommitSequence,
this));
}
void DomStorageContext::DeleteNextUnusedNamespaceInCommitSequence() {
const std::string& persistent_id = deletable_persistent_namespace_ids_.back();
session_storage_database_->DeleteNamespace(persistent_id);
deletable_persistent_namespace_ids_.pop_back();
if (!deletable_persistent_namespace_ids_.empty()) {
task_runner_->PostDelayedTask(
FROM_HERE, base::Bind(
&DomStorageContext::DeleteNextUnusedNamespace,
this),
base::TimeDelta::FromSeconds(kSessionStoraceScavengingSeconds));
} }
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define WEBKIT_DOM_STORAGE_DOM_STORAGE_CONTEXT_H_ #define WEBKIT_DOM_STORAGE_DOM_STORAGE_CONTEXT_H_
#include <map> #include <map>
#include <set>
#include <vector> #include <vector>
#include "base/atomic_sequence_num.h" #include "base/atomic_sequence_num.h"
...@@ -34,6 +35,7 @@ class DomStorageArea; ...@@ -34,6 +35,7 @@ class DomStorageArea;
class DomStorageNamespace; class DomStorageNamespace;
class DomStorageSession; class DomStorageSession;
class DomStorageTaskRunner; class DomStorageTaskRunner;
class SessionStorageDatabase;
// The Context is the root of an object containment hierachy for // The Context is the root of an object containment hierachy for
// Namespaces and Areas related to the owning profile. // Namespaces and Areas related to the owning profile.
...@@ -159,6 +161,15 @@ class DomStorageContext ...@@ -159,6 +161,15 @@ class DomStorageContext
void CloneSessionNamespace(int64 existing_id, int64 new_id, void CloneSessionNamespace(int64 existing_id, int64 new_id,
const std::string& new_persistent_id); const std::string& new_persistent_id);
// Starts backing sessionStorage on disk. This function must be called right
// after DomStorageContext is created, before it's used.
void SetSaveSessionStorageOnDisk();
// Deletes all namespaces which don't have an associated DomStorageNamespace
// alive. This function is used for deleting possible leftover data after an
// unclean exit.
void StartScavengingUnusedSessionStorage();
private: private:
friend class DomStorageContextTest; friend class DomStorageContextTest;
FRIEND_TEST_ALL_PREFIXES(DomStorageContextTest, Basics); FRIEND_TEST_ALL_PREFIXES(DomStorageContextTest, Basics);
...@@ -170,6 +181,14 @@ class DomStorageContext ...@@ -170,6 +181,14 @@ class DomStorageContext
void ClearSessionOnlyOrigins(); void ClearSessionOnlyOrigins();
// For scavenging unused sessionStorages.
void FindUnusedNamespaces();
void FindUnusedNamespacesInCommitSequence(
const std::set<std::string>& namespace_ids_in_use,
const std::set<std::string>& protected_persistent_session_ids);
void DeleteNextUnusedNamespace();
void DeleteNextUnusedNamespaceInCommitSequence();
// Collection of namespaces keyed by id. // Collection of namespaces keyed by id.
StorageNamespaceMap namespaces_; StorageNamespaceMap namespaces_;
...@@ -194,6 +213,15 @@ class DomStorageContext ...@@ -194,6 +213,15 @@ class DomStorageContext
bool is_shutdown_; bool is_shutdown_;
bool force_keep_session_state_; bool force_keep_session_state_;
scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_; scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
scoped_refptr<SessionStorageDatabase> session_storage_database_;
// For cleaning up unused namespaces gradually.
bool scavenging_started_;
std::vector<std::string> deletable_persistent_namespace_ids_;
// Persistent namespace IDs to protect from gradual deletion (they will
// be needed for session restore).
std::set<std::string> protected_persistent_session_ids_;
}; };
} // namespace dom_storage } // namespace dom_storage
......
...@@ -5,10 +5,13 @@ ...@@ -5,10 +5,13 @@
#include "webkit/dom_storage/dom_storage_namespace.h" #include "webkit/dom_storage/dom_storage_namespace.h"
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h" #include "base/logging.h"
#include "webkit/dom_storage/dom_storage_area.h" #include "webkit/dom_storage/dom_storage_area.h"
#include "webkit/dom_storage/dom_storage_task_runner.h" #include "webkit/dom_storage/dom_storage_task_runner.h"
#include "webkit/dom_storage/dom_storage_types.h" #include "webkit/dom_storage/dom_storage_types.h"
#include "webkit/dom_storage/session_storage_database.h"
namespace dom_storage { namespace dom_storage {
...@@ -23,10 +26,12 @@ DomStorageNamespace::DomStorageNamespace( ...@@ -23,10 +26,12 @@ DomStorageNamespace::DomStorageNamespace(
DomStorageNamespace::DomStorageNamespace( DomStorageNamespace::DomStorageNamespace(
int64 namespace_id, int64 namespace_id,
const std::string& persistent_namespace_id, const std::string& persistent_namespace_id,
SessionStorageDatabase* session_storage_database,
DomStorageTaskRunner* task_runner) DomStorageTaskRunner* task_runner)
: namespace_id_(namespace_id), : namespace_id_(namespace_id),
persistent_namespace_id_(persistent_namespace_id), persistent_namespace_id_(persistent_namespace_id),
task_runner_(task_runner) { task_runner_(task_runner),
session_storage_database_(session_storage_database) {
DCHECK_NE(kLocalStorageNamespaceId, namespace_id); DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
} }
...@@ -43,7 +48,7 @@ DomStorageArea* DomStorageNamespace::OpenStorageArea(const GURL& origin) { ...@@ -43,7 +48,7 @@ DomStorageArea* DomStorageNamespace::OpenStorageArea(const GURL& origin) {
area = new DomStorageArea(origin, directory_, task_runner_); area = new DomStorageArea(origin, directory_, task_runner_);
} else { } else {
area = new DomStorageArea(namespace_id_, persistent_namespace_id_, origin, area = new DomStorageArea(namespace_id_, persistent_namespace_id_, origin,
task_runner_); session_storage_database_, task_runner_);
} }
areas_[origin] = AreaHolder(area, 1); areas_[origin] = AreaHolder(area, 1);
return area; return area;
...@@ -64,17 +69,29 @@ DomStorageNamespace* DomStorageNamespace::Clone( ...@@ -64,17 +69,29 @@ DomStorageNamespace* DomStorageNamespace::Clone(
DCHECK_NE(kLocalStorageNamespaceId, namespace_id_); DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
DCHECK_NE(kLocalStorageNamespaceId, clone_namespace_id); DCHECK_NE(kLocalStorageNamespaceId, clone_namespace_id);
DomStorageNamespace* clone = new DomStorageNamespace( DomStorageNamespace* clone = new DomStorageNamespace(
clone_namespace_id, clone_persistent_namespace_id, task_runner_); clone_namespace_id, clone_persistent_namespace_id,
session_storage_database_, task_runner_);
AreaMap::const_iterator it = areas_.begin(); AreaMap::const_iterator it = areas_.begin();
// Clone the in-memory structures.
for (; it != areas_.end(); ++it) { for (; it != areas_.end(); ++it) {
DomStorageArea* area = it->second.area_->ShallowCopy( DomStorageArea* area = it->second.area_->ShallowCopy(
clone_namespace_id, clone_persistent_namespace_id); clone_namespace_id, clone_persistent_namespace_id);
clone->areas_[it->first] = AreaHolder(area, 0); clone->areas_[it->first] = AreaHolder(area, 0);
} }
// And clone the on-disk structures, too.
if (session_storage_database_.get()) {
task_runner_->PostShutdownBlockingTask(
FROM_HERE,
DomStorageTaskRunner::COMMIT_SEQUENCE,
base::Bind(base::IgnoreResult(&SessionStorageDatabase::CloneNamespace),
session_storage_database_.get(), persistent_namespace_id_,
clone_persistent_namespace_id));
}
return clone; return clone;
} }
void DomStorageNamespace::DeleteOrigin(const GURL& origin) { void DomStorageNamespace::DeleteOrigin(const GURL& origin) {
DCHECK(!session_storage_database_.get());
AreaHolder* holder = GetAreaHolder(origin); AreaHolder* holder = GetAreaHolder(origin);
if (holder) { if (holder) {
holder->area_->DeleteOrigin(); holder->area_->DeleteOrigin();
......
...@@ -17,6 +17,7 @@ namespace dom_storage { ...@@ -17,6 +17,7 @@ namespace dom_storage {
class DomStorageArea; class DomStorageArea;
class DomStorageTaskRunner; class DomStorageTaskRunner;
class SessionStorageDatabase;
// Container for the set of per-origin Areas. // Container for the set of per-origin Areas.
// See class comments for DomStorageContext for a larger overview. // See class comments for DomStorageContext for a larger overview.
...@@ -28,10 +29,11 @@ class DomStorageNamespace ...@@ -28,10 +29,11 @@ class DomStorageNamespace
DomStorageNamespace(const FilePath& directory, // may be empty DomStorageNamespace(const FilePath& directory, // may be empty
DomStorageTaskRunner* task_runner); DomStorageTaskRunner* task_runner);
// Constructor for a SessionStorage namespace with a non-zero id // Constructor for a SessionStorage namespace with a non-zero id and an
// and no backing directory on disk. // optional backing on disk via |session_storage_database| (may be NULL).
DomStorageNamespace(int64 namespace_id, DomStorageNamespace(int64 namespace_id,
const std::string& persistent_namespace_id, const std::string& persistent_namespace_id,
SessionStorageDatabase* session_storage_database,
DomStorageTaskRunner* task_runner); DomStorageTaskRunner* task_runner);
int64 namespace_id() const { return namespace_id_; } int64 namespace_id() const { return namespace_id_; }
...@@ -79,6 +81,7 @@ class DomStorageNamespace ...@@ -79,6 +81,7 @@ class DomStorageNamespace
FilePath directory_; FilePath directory_;
AreaMap areas_; AreaMap areas_;
scoped_refptr<DomStorageTaskRunner> task_runner_; scoped_refptr<DomStorageTaskRunner> task_runner_;
scoped_refptr<SessionStorageDatabase> session_storage_database_;
}; };
} // namespace dom_storage } // namespace dom_storage
......
...@@ -52,12 +52,20 @@ void SessionStorageDatabase::ReadAreaValues(const std::string& namespace_id, ...@@ -52,12 +52,20 @@ void SessionStorageDatabase::ReadAreaValues(const std::string& namespace_id,
// nothing to be added to the result. // nothing to be added to the result.
if (!LazyOpen(false)) if (!LazyOpen(false))
return; return;
// While ReadAreaValues is in progress, another thread can call
// CommitAreaChanges. CommitAreaChanges might update map ref count key while
// this thread is iterating over the map ref count key. To protect the reading
// operation, create a snapshot and read from it.
leveldb::ReadOptions options;
options.snapshot = db_->GetSnapshot();
std::string map_id; std::string map_id;
bool exists; bool exists;
if (!GetMapForArea(namespace_id, origin.spec(), &exists, &map_id)) if (GetMapForArea(namespace_id, origin.spec(), options, &exists, &map_id) &&
return; exists)
if (exists) ReadMap(map_id, options, result, false);
ReadMap(map_id, result, false); db_->ReleaseSnapshot(options.snapshot);
} }
bool SessionStorageDatabase::CommitAreaChanges(const std::string& namespace_id, bool SessionStorageDatabase::CommitAreaChanges(const std::string& namespace_id,
...@@ -78,7 +86,8 @@ bool SessionStorageDatabase::CommitAreaChanges(const std::string& namespace_id, ...@@ -78,7 +86,8 @@ bool SessionStorageDatabase::CommitAreaChanges(const std::string& namespace_id,
std::string map_id; std::string map_id;
bool exists; bool exists;
if (!GetMapForArea(namespace_id, origin.spec(), &exists, &map_id)) if (!GetMapForArea(namespace_id, origin.spec(), leveldb::ReadOptions(),
&exists, &map_id))
return false; return false;
if (exists) { if (exists) {
int64 ref_count; int64 ref_count;
...@@ -192,7 +201,9 @@ bool SessionStorageDatabase::ReadNamespaceIds( ...@@ -192,7 +201,9 @@ bool SessionStorageDatabase::ReadNamespaceIds(
std::string namespace_prefix = NamespacePrefix(); std::string namespace_prefix = NamespacePrefix();
scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions())); scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
it->Seek(namespace_prefix); it->Seek(namespace_prefix);
if (it->status().IsNotFound()) // If the key is not found, the status of the iterator won't be IsNotFound(),
// but the iterator will be invalid.
if (!it->Valid())
return true; return true;
if (!DatabaseErrorCheck(it->status().ok())) if (!DatabaseErrorCheck(it->status().ok()))
...@@ -223,6 +234,17 @@ bool SessionStorageDatabase::ReadNamespaceIds( ...@@ -223,6 +234,17 @@ bool SessionStorageDatabase::ReadNamespaceIds(
return true; return true;
} }
bool SessionStorageDatabase::ReadOriginsInNamespace(
const std::string& namespace_id, std::vector<GURL>* origins) {
std::map<std::string, std::string> areas;
if (!GetAreasInNamespace(namespace_id, &areas))
return false;
for (std::map<std::string, std::string>::const_iterator it = areas.begin();
it != areas.end(); ++it)
origins->push_back(GURL(it->first));
return true;
}
bool SessionStorageDatabase::LazyOpen(bool create_if_needed) { bool SessionStorageDatabase::LazyOpen(bool create_if_needed) {
base::AutoLock auto_lock(db_lock_); base::AutoLock auto_lock(db_lock_);
if (db_error_ || is_inconsistent_) { if (db_error_ || is_inconsistent_) {
...@@ -337,7 +359,9 @@ bool SessionStorageDatabase::GetAreasInNamespace( ...@@ -337,7 +359,9 @@ bool SessionStorageDatabase::GetAreasInNamespace(
std::string namespace_start_key = NamespaceStartKey(namespace_id); std::string namespace_start_key = NamespaceStartKey(namespace_id);
scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions())); scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
it->Seek(namespace_start_key); it->Seek(namespace_start_key);
if (it->status().IsNotFound()) { // If the key is not found, the status of the iterator won't be IsNotFound(),
// but the iterator will be invalid.
if (!it->Valid()) {
// The namespace_start_key is not found when the namespace doesn't contain // The namespace_start_key is not found when the namespace doesn't contain
// any areas. We don't need to do anything. // any areas. We don't need to do anything.
return true; return true;
...@@ -373,7 +397,8 @@ bool SessionStorageDatabase::DeleteAreaHelper( ...@@ -373,7 +397,8 @@ bool SessionStorageDatabase::DeleteAreaHelper(
leveldb::WriteBatch* batch) { leveldb::WriteBatch* batch) {
std::string map_id; std::string map_id;
bool exists; bool exists;
if (!GetMapForArea(namespace_id, origin, &exists, &map_id)) if (!GetMapForArea(namespace_id, origin, leveldb::ReadOptions(), &exists,
&map_id))
return false; return false;
if (!exists) if (!exists)
return true; // Nothing to delete. return true; // Nothing to delete.
...@@ -386,9 +411,10 @@ bool SessionStorageDatabase::DeleteAreaHelper( ...@@ -386,9 +411,10 @@ bool SessionStorageDatabase::DeleteAreaHelper(
bool SessionStorageDatabase::GetMapForArea(const std::string& namespace_id, bool SessionStorageDatabase::GetMapForArea(const std::string& namespace_id,
const std::string& origin, const std::string& origin,
const leveldb::ReadOptions& options,
bool* exists, std::string* map_id) { bool* exists, std::string* map_id) {
std::string namespace_key = NamespaceKey(namespace_id, origin); std::string namespace_key = NamespaceKey(namespace_id, origin);
leveldb::Status s = db_->Get(leveldb::ReadOptions(), namespace_key, map_id); leveldb::Status s = db_->Get(options, namespace_key, map_id);
if (s.IsNotFound()) { if (s.IsNotFound()) {
*exists = false; *exists = false;
return true; return true;
...@@ -421,13 +447,16 @@ bool SessionStorageDatabase::CreateMapForArea(const std::string& namespace_id, ...@@ -421,13 +447,16 @@ bool SessionStorageDatabase::CreateMapForArea(const std::string& namespace_id,
} }
bool SessionStorageDatabase::ReadMap(const std::string& map_id, bool SessionStorageDatabase::ReadMap(const std::string& map_id,
const leveldb::ReadOptions& options,
ValuesMap* result, ValuesMap* result,
bool only_keys) { bool only_keys) {
scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions())); scoped_ptr<leveldb::Iterator> it(db_->NewIterator(options));
std::string map_start_key = MapRefCountKey(map_id); std::string map_start_key = MapRefCountKey(map_id);
it->Seek(map_start_key); it->Seek(map_start_key);
// The map needs to exist, otherwise we have a stale map_id in the database. // If the key is not found, the status of the iterator won't be IsNotFound(),
if (!ConsistencyCheck(!it->status().IsNotFound())) // but the iterator will be invalid. The map needs to exist, otherwise we have
// a stale map_id in the database.
if (!ConsistencyCheck(it->Valid()))
return false; return false;
if (!DatabaseErrorCheck(it->status().ok())) if (!DatabaseErrorCheck(it->status().ok()))
return false; return false;
...@@ -518,7 +547,7 @@ bool SessionStorageDatabase::DecreaseMapRefCount(const std::string& map_id, ...@@ -518,7 +547,7 @@ bool SessionStorageDatabase::DecreaseMapRefCount(const std::string& map_id,
bool SessionStorageDatabase::ClearMap(const std::string& map_id, bool SessionStorageDatabase::ClearMap(const std::string& map_id,
leveldb::WriteBatch* batch) { leveldb::WriteBatch* batch) {
ValuesMap values; ValuesMap values;
if (!ReadMap(map_id, &values, true)) if (!ReadMap(map_id, leveldb::ReadOptions(), &values, true))
return false; return false;
for (ValuesMap::const_iterator it = values.begin(); it != values.end(); ++it) for (ValuesMap::const_iterator it = values.begin(); it != values.end(); ++it)
batch->Delete(MapKey(map_id, UTF16ToUTF8(it->first))); batch->Delete(MapKey(map_id, UTF16ToUTF8(it->first)));
...@@ -549,7 +578,7 @@ bool SessionStorageDatabase::DeepCopyArea( ...@@ -549,7 +578,7 @@ bool SessionStorageDatabase::DeepCopyArea(
// Read the values from the old map here. If we don't need to copy the data, // Read the values from the old map here. If we don't need to copy the data,
// this can stay empty. // this can stay empty.
ValuesMap values; ValuesMap values;
if (copy_data && !ReadMap(*map_id, &values, false)) if (copy_data && !ReadMap(*map_id, leveldb::ReadOptions(), &values, false))
return false; return false;
if (!DecreaseMapRefCount(*map_id, 1, batch)) if (!DecreaseMapRefCount(*map_id, 1, batch))
return false; return false;
......
...@@ -19,6 +19,7 @@ class GURL; ...@@ -19,6 +19,7 @@ class GURL;
namespace leveldb { namespace leveldb {
class DB; class DB;
struct ReadOptions;
class WriteBatch; class WriteBatch;
} // namespace leveldb } // namespace leveldb
...@@ -27,6 +28,9 @@ namespace dom_storage { ...@@ -27,6 +28,9 @@ namespace dom_storage {
// SessionStorageDatabase holds the data from multiple namespaces and multiple // SessionStorageDatabase holds the data from multiple namespaces and multiple
// origins. All DomStorageAreas for session storage share the same // origins. All DomStorageAreas for session storage share the same
// SessionStorageDatabase. // SessionStorageDatabase.
// Only one thread is allowed to call the public functions other than
// ReadAreaValues. Other threads area allowed to call ReadAreaValues.
class SessionStorageDatabase : class SessionStorageDatabase :
public base::RefCountedThreadSafe<SessionStorageDatabase> { public base::RefCountedThreadSafe<SessionStorageDatabase> {
public: public:
...@@ -65,6 +69,10 @@ class SessionStorageDatabase : ...@@ -65,6 +69,10 @@ class SessionStorageDatabase :
// Reads all namespace IDs from the database. // Reads all namespace IDs from the database.
bool ReadNamespaceIds(std::vector<std::string>* namespace_ids); bool ReadNamespaceIds(std::vector<std::string>* namespace_ids);
// Reads all origins which have data stored in |namespace_id|.
bool ReadOriginsInNamespace(const std::string& namespace_id,
std::vector<GURL>* origins);
private: private:
friend class base::RefCountedThreadSafe<SessionStorageDatabase>; friend class base::RefCountedThreadSafe<SessionStorageDatabase>;
friend class SessionStorageDatabaseTest; friend class SessionStorageDatabaseTest;
...@@ -125,6 +133,7 @@ class SessionStorageDatabase : ...@@ -125,6 +133,7 @@ class SessionStorageDatabase :
// the map doesn't exist. // the map doesn't exist.
bool GetMapForArea(const std::string& namespace_id, bool GetMapForArea(const std::string& namespace_id,
const std::string& origin, const std::string& origin,
const leveldb::ReadOptions& options,
bool* exists, bool* exists,
std::string* map_id); std::string* map_id);
...@@ -140,6 +149,7 @@ class SessionStorageDatabase : ...@@ -140,6 +149,7 @@ class SessionStorageDatabase :
// true, only keys are aread from the database and the values in |result| will // true, only keys are aread from the database and the values in |result| will
// be empty. // be empty.
bool ReadMap(const std::string& map_id, bool ReadMap(const std::string& map_id,
const leveldb::ReadOptions& options,
ValuesMap* result, ValuesMap* result,
bool only_keys); bool only_keys);
// Writes |values| into the map |map_id|. // Writes |values| into the map |map_id|.
......
...@@ -52,6 +52,9 @@ class SessionStorageDatabaseTest : public testing::Test { ...@@ -52,6 +52,9 @@ class SessionStorageDatabaseTest : public testing::Test {
void CompareValuesMaps(const ValuesMap& map1, const ValuesMap& map2) const; void CompareValuesMaps(const ValuesMap& map1, const ValuesMap& map2) const;
void CheckNamespaceIds( void CheckNamespaceIds(
const std::set<std::string>& expected_namespace_ids) const; const std::set<std::string>& expected_namespace_ids) const;
void CheckOrigins(
const std::string& namespace_id,
const std::set<GURL>& expected_origins) const;
std::string GetMapForArea(const std::string& namespace_id, std::string GetMapForArea(const std::string& namespace_id,
const GURL& origin) const; const GURL& origin) const;
int64 GetMapRefCount(const std::string& map_id) const; int64 GetMapRefCount(const std::string& map_id) const;
...@@ -335,18 +338,29 @@ void SessionStorageDatabaseTest::CheckNamespaceIds( ...@@ -335,18 +338,29 @@ void SessionStorageDatabaseTest::CheckNamespaceIds(
EXPECT_EQ(expected_namespace_ids.size(), namespace_ids.size()); EXPECT_EQ(expected_namespace_ids.size(), namespace_ids.size());
for (std::vector<std::string>::const_iterator it = namespace_ids.begin(); for (std::vector<std::string>::const_iterator it = namespace_ids.begin();
it != namespace_ids.end(); ++it) { it != namespace_ids.end(); ++it) {
LOG(WARNING) << *it;
EXPECT_TRUE(expected_namespace_ids.find(*it) != EXPECT_TRUE(expected_namespace_ids.find(*it) !=
expected_namespace_ids.end()); expected_namespace_ids.end());
} }
} }
void SessionStorageDatabaseTest::CheckOrigins(
const std::string& namespace_id,
const std::set<GURL>& expected_origins) const {
std::vector<GURL> origins;
EXPECT_TRUE(db_->ReadOriginsInNamespace(namespace_id, &origins));
EXPECT_EQ(expected_origins.size(), origins.size());
for (std::vector<GURL>::const_iterator it = origins.begin();
it != origins.end(); ++it) {
EXPECT_TRUE(expected_origins.find(*it) != expected_origins.end());
}
}
std::string SessionStorageDatabaseTest::GetMapForArea( std::string SessionStorageDatabaseTest::GetMapForArea(
const std::string& namespace_id, const GURL& origin) const { const std::string& namespace_id, const GURL& origin) const {
bool exists; bool exists;
std::string map_id; std::string map_id;
EXPECT_TRUE(db_->GetMapForArea(namespace_id, origin.spec(), EXPECT_TRUE(db_->GetMapForArea(namespace_id, origin.spec(),
&exists, &map_id)); leveldb::ReadOptions(), &exists, &map_id));
EXPECT_TRUE(exists); EXPECT_TRUE(exists);
return map_id; return map_id;
} }
...@@ -724,4 +738,37 @@ TEST_F(SessionStorageDatabaseTest, ReadNamespaceIds) { ...@@ -724,4 +738,37 @@ TEST_F(SessionStorageDatabaseTest, ReadNamespaceIds) {
CheckDatabaseConsistency(); CheckDatabaseConsistency();
} }
TEST_F(SessionStorageDatabaseTest, ReadNamespaceIdsInEmptyDatabase) {
std::set<std::string> expected_namespace_ids;
CheckNamespaceIds(expected_namespace_ids);
}
TEST_F(SessionStorageDatabaseTest, ReadOriginsInNamespace) {
ValuesMap data1;
data1[kKey1] = kValue1;
data1[kKey2] = kValue2;
data1[kKey3] = kValue3;
std::set<GURL> expected_origins1;
ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data1));
expected_origins1.insert(kOrigin1);
expected_origins1.insert(kOrigin2);
CheckOrigins(kNamespace1, expected_origins1);
std::set<GURL> expected_origins2;
ASSERT_TRUE(db_->CommitAreaChanges(kNamespace2, kOrigin2, false, data1));
expected_origins2.insert(kOrigin2);
CheckOrigins(kNamespace2, expected_origins2);
ASSERT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
CheckOrigins(kNamespaceClone, expected_origins1);
ASSERT_TRUE(db_->DeleteArea(kNamespace1, kOrigin2));
expected_origins1.erase(kOrigin2);
CheckOrigins(kNamespace1, expected_origins1);
CheckDatabaseConsistency();
}
} // namespace dom_storage } // namespace dom_storage
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment