blob: ad96a81eb0de669bf9b8233669f3904d999a015b [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include <list>
#include <memory>
#include <string>
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/strings/nullable_string16.h"
#include "base/strings/string16.h"
#include "content/common/content_export.h"
#include "content/common/dom_storage/dom_storage_map.h"
#include "content/common/dom_storage/dom_storage_types.h"
#include "url/origin.h"
namespace base {
namespace trace_event {
class ProcessMemoryDump;
namespace content {
class DOMStorageDatabaseAdapter;
class DOMStorageTaskRunner;
class SessionStorageDatabase;
// Container for a per-origin Map of key/value pairs potentially
// backed by storage on disk and lazily commits changes to disk.
// See class comments for DOMStorageContextImpl for a larger overview.
: public base::RefCountedThreadSafe<DOMStorageArea> {
static const base::FilePath::CharType kDatabaseFileExtension[];
static base::FilePath DatabaseFileNameFromOrigin(const url::Origin& origin);
static url::Origin OriginFromDatabaseFileName(
const base::FilePath& file_name);
// Commence aggressive flushing. This should be called early in the startup -
// before any localStorage writing. Currently scheduled writes will not be
// rescheduled and will be flushed at the scheduled time after which
// aggressive flushing will commence.
static void EnableAggressiveCommitDelay();
// Session storage. Backed on disk if |session_storage_backing| is not NULL.
DOMStorageArea(const std::string& namespace_id,
std::vector<std::string> original_namespace_ids,
const url::Origin& origin,
SessionStorageDatabase* session_storage_backing,
DOMStorageTaskRunner* task_runner);
const url::Origin& origin() const { return origin_; }
const std::string& namespace_id() const { return namespace_id_; }
size_t map_memory_used() const { return map_ ? map_->memory_used() : 0; }
// Writes a copy of the current set of values in the area to the |map|.
void ExtractValues(DOMStorageValuesMap* map);
unsigned Length();
base::NullableString16 Key(unsigned index);
base::NullableString16 GetItem(const base::string16& key);
bool SetItem(const base::string16& key,
const base::string16& value,
const base::NullableString16& client_old_value,
base::NullableString16* old_value);
bool RemoveItem(const base::string16& key,
const base::NullableString16& client_old_value,
base::string16* old_value);
bool Clear();
void FastClear();
DOMStorageArea* ShallowCopy(const std::string& destination_namespace_id);
bool HasUncommittedChanges() const;
void ScheduleImmediateCommit();
void ClearShallowCopiedCommitBatches();
// Stores only the keys in the in-memory cache when set to true. Changing this
// behavior will reload the cache only when needed and not immediately.
// Note: Do not use ExtractValues() frequently since will have a disk access
// when only keys are stored.
void SetCacheOnlyKeys(bool value);
// Frees up memory when possible. Typically, this method returns
// the object to its just constructed state, however if uncommitted
// changes are pending, it does nothing.
void PurgeMemory();
// Schedules the commit of any unsaved changes and enters a
// shutdown state such that the value getters and setters will
// no longer do anything.
void Shutdown();
// Returns true if data needs to be loaded in memory.
bool IsMapReloadNeeded();
// Adds memory statistics to |pmd| for chrome://tracing.
void OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd);
friend class DOMStorageAreaTest;
friend class DOMStorageAreaMock;
FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaParamTest, DOMStorageAreaBasics);
FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, BackingDatabaseOpened);
FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaParamTest, ShallowCopyWithBacking);
FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, SetCacheOnlyKeysWithoutBacking);
FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, SetCacheOnlyKeysWithBacking);
FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, TestDatabaseFilePath);
FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaParamTest, CommitTasks);
FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaParamTest, CommitChangesAtShutdown);
FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaParamTest, PurgeMemory);
FRIEND_TEST_ALL_PREFIXES(DOMStorageAreaTest, RateLimiter);
FRIEND_TEST_ALL_PREFIXES(DOMStorageContextImplTest, PersistentIds);
FRIEND_TEST_ALL_PREFIXES(DOMStorageContextImplTest, PurgeMemory);
friend class base::RefCountedThreadSafe<DOMStorageArea>;
enum LoadState {
// Used to rate limit commits.
class CONTENT_EXPORT RateLimiter {
RateLimiter(size_t desired_rate, base::TimeDelta time_quantum);
void add_samples(size_t samples) {
samples_ += samples;
// Computes the total time needed to process the total samples seen
// at the desired rate.
base::TimeDelta ComputeTimeNeeded() const;
// Given the elapsed time since the start of the rate limiting session,
// computes the delay needed to mimic having processed the total samples
// seen at the desired rate.
base::TimeDelta ComputeDelayNeeded(
const base::TimeDelta elapsed_time) const;
float rate_;
float samples_;
base::TimeDelta time_quantum_;
struct CONTENT_EXPORT CommitBatch
: public base::RefCountedThreadSafe<CommitBatch> {
bool clear_all_first;
DOMStorageValuesMap changed_values;
size_t GetDataSize() const;
friend class base::RefCountedThreadSafe<CommitBatch>;
struct CONTENT_EXPORT CommitBatchHolder {
Type type;
scoped_refptr<CommitBatch> batch;
CommitBatchHolder(Type, scoped_refptr<CommitBatch>);
CommitBatchHolder(const CommitBatchHolder& other);
virtual ~DOMStorageArea();
// Loads the map for this origin into the |map_| if desired load state is not
// met and into the |map| if it's not null.
void LoadMapAndApplyUncommittedChangesIfNeeded(DOMStorageValuesMap* map);
// If the cache state is inconsistent with the map storage, purges the map and
// updates with new data if possible without reading from database. Noop if
// the area has uncommitted changes.
void UnloadMapIfDesired();
// Post tasks to defer writing a batch of changed values to
// disk on the commit sequence, and to call back on the primary
// task sequence when complete.
CommitBatch* CreateCommitBatchIfNeeded();
const CommitBatchHolder* GetCurrentCommitBatch() const;
bool HasCommitBatchInFlight() const;
void PopulateCommitBatchValues();
void StartCommitTimer();
virtual void OnCommitTimer();
void PostCommitTask();
void CommitChanges(const CommitBatch* commit_batch);
void OnCommitComplete();
base::TimeDelta ComputeCommitDelay() const;
void ShutdownInCommitSequence();
static bool s_aggressive_flushing_enabled_;
std::string namespace_id_;
std::vector<std::string> original_namespace_ids_;
url::Origin origin_;
scoped_refptr<DOMStorageTaskRunner> task_runner_;
LoadState desired_load_state_;
LoadState load_state_;
scoped_refptr<DOMStorageMap> map_;
std::unique_ptr<DOMStorageDatabaseAdapter> backing_;
scoped_refptr<SessionStorageDatabase> session_storage_backing_;
bool is_shutdown_;
std::list<CommitBatchHolder> commit_batches_;
base::TimeTicks start_time_;
RateLimiter data_rate_limiter_;
RateLimiter commit_rate_limiter_;
} // namespace content