Skip to content

Commit

Permalink
Some fleshing out the mojo based localstorage implementation.
Browse files Browse the repository at this point in the history
Data is not yet stored on disk, but its functional from a pages point of view. The mojo based plumbing between the caching layers in the renderer and and browser process is working.

The browser side now enforces a size constraint.

The observer on the renderer side now longer lived so storage events are not dropped. Events have to be raised even if the local cache is not loaded.

BUG=586194

Review URL: https://codereview.chromium.org/1837883003

Cr-Commit-Position: refs/heads/master@{#385533}
  • Loading branch information
michaeln authored and Commit bot committed Apr 6, 2016
1 parent b0737c7 commit 7337bd9
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 97 deletions.
24 changes: 17 additions & 7 deletions content/browser/dom_storage/dom_storage_context_wrapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ class DOMStorageContextWrapper::MojoState {
weak_ptr_factory_(this) {}

void OpenLocalStorage(const url::Origin& origin,
mojom::LevelDBObserverPtr observer,
mojom::LevelDBWrapperRequest request);

private:
void LevelDBWrapperImplHasNoBindings(const url::Origin& origin) {
void OnLevelDDWrapperHasNoBindings(const url::Origin& origin) {
DCHECK(level_db_wrappers_.find(origin) != level_db_wrappers_.end());
level_db_wrappers_.erase(origin);
}
Expand All @@ -99,6 +100,7 @@ class DOMStorageContextWrapper::MojoState {
// The (possibly delayed) implementation of OpenLocalStorage(). Can be called
// directly from that function, or through |on_database_open_callbacks_|.
void BindLocalStorage(const url::Origin& origin,
mojom::LevelDBObserverPtr observer,
mojom::LevelDBWrapperRequest request);

// Maps between an origin and its prefixed LevelDB view.
Expand Down Expand Up @@ -127,6 +129,7 @@ class DOMStorageContextWrapper::MojoState {

void DOMStorageContextWrapper::MojoState::OpenLocalStorage(
const url::Origin& origin,
mojom::LevelDBObserverPtr observer,
mojom::LevelDBWrapperRequest request) {
// If we don't have a filesystem_connection_, we'll need to establish one.
if (connection_state_ == NO_CONNECTION) {
Expand Down Expand Up @@ -158,11 +161,11 @@ void DOMStorageContextWrapper::MojoState::OpenLocalStorage(
// Queue this OpenLocalStorage call for when we have a level db pointer.
on_database_opened_callbacks_.push_back(
base::Bind(&MojoState::BindLocalStorage, weak_ptr_factory_.GetWeakPtr(),
origin, base::Passed(&request)));
origin, base::Passed(&observer), base::Passed(&request)));
return;
}

BindLocalStorage(origin, std::move(request));
BindLocalStorage(origin, std::move(observer), std::move(request));
}

void DOMStorageContextWrapper::MojoState::OnDirectoryOpened(
Expand Down Expand Up @@ -208,17 +211,22 @@ void DOMStorageContextWrapper::MojoState::OnDatabaseOpened(

void DOMStorageContextWrapper::MojoState::BindLocalStorage(
const url::Origin& origin,
mojom::LevelDBObserverPtr observer,
mojom::LevelDBWrapperRequest request) {
if (level_db_wrappers_.find(origin) == level_db_wrappers_.end()) {
auto found = level_db_wrappers_.find(origin);
if (found == level_db_wrappers_.end()) {
level_db_wrappers_[origin] = make_scoped_ptr(new LevelDBWrapperImpl(
database_.get(),
origin.Serialize(),
base::Bind(&MojoState::LevelDBWrapperImplHasNoBindings,
kPerStorageAreaQuota + kPerStorageAreaOverQuotaAllowance,
base::Bind(&MojoState::OnLevelDDWrapperHasNoBindings,
base::Unretained(this),
origin)));
found = level_db_wrappers_.find(origin);
}

level_db_wrappers_[origin]->Bind(std::move(request));
found->second->Bind(std::move(request));
found->second->AddObserver(std::move(observer));
}

DOMStorageContextWrapper::DOMStorageContextWrapper(
Expand Down Expand Up @@ -336,8 +344,10 @@ void DOMStorageContextWrapper::Flush() {

void DOMStorageContextWrapper::OpenLocalStorage(
const url::Origin& origin,
mojom::LevelDBObserverPtr observer,
mojom::LevelDBWrapperRequest request) {
mojo_state_->OpenLocalStorage(origin, std::move(request));
mojo_state_->OpenLocalStorage(
origin, std::move(observer), std::move(request));
}

} // namespace content
1 change: 1 addition & 0 deletions content/browser/dom_storage/dom_storage_context_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class CONTENT_EXPORT DOMStorageContextWrapper :

// See mojom::StoragePartitionService interface.
void OpenLocalStorage(const url::Origin& origin,
mojom::LevelDBObserverPtr observer,
mojom::LevelDBWrapperRequest request);

private:
Expand Down
105 changes: 88 additions & 17 deletions content/browser/leveldb_wrapper_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
#include "content/browser/leveldb_wrapper_impl.h"

#include "base/bind.h"
#include "mojo/common/common_type_converters.h"

namespace content {

LevelDBWrapperImpl::LevelDBWrapperImpl(
leveldb::LevelDBDatabase* database,
const std::string& prefix,
size_t max_size,
const base::Closure& no_bindings_callback)
: prefix_(prefix),
no_bindings_callback_(no_bindings_callback),
database_(database) {
database_(database),
bytes_used_(0),
max_size_(max_size) {
bindings_.set_connection_error_handler(base::Bind(
&LevelDBWrapperImpl::OnConnectionError, base::Unretained(this)));
}
Expand All @@ -23,43 +27,110 @@ void LevelDBWrapperImpl::Bind(mojom::LevelDBWrapperRequest request) {
bindings_.AddBinding(this, std::move(request));
}

void LevelDBWrapperImpl::AddObserver(mojom::LevelDBObserverPtr observer) {
observers_.AddInterfacePtr(std::move(observer));
}

LevelDBWrapperImpl::~LevelDBWrapperImpl() {}

void LevelDBWrapperImpl::Put(mojo::Array<uint8_t> key,
mojo::Array<uint8_t> value,
const mojo::String& source,
const PutCallback& callback) {
NOTIMPLEMENTED();
callback.Run(leveldb::DatabaseError::NOT_SUPPORTED);
bool has_old_item = false;
mojo::Array<uint8_t> old_value;
size_t old_item_size = 0;
auto found = map_.find(key);
if (found != map_.end()) {
old_value = std::move(found->second);
old_item_size = key.size() + old_value.size();
has_old_item = true;
}
size_t new_item_size = key.size() + value.size();
size_t new_bytes_used = bytes_used_ - old_item_size + new_item_size;

// Only check quota if the size is increasing, this allows
// shrinking changes to pre-existing maps that are over budget.
if (new_item_size > old_item_size && new_bytes_used > max_size_) {
callback.Run(false);
return;
}

map_[key.Clone()] = value.Clone();
bytes_used_ = new_bytes_used;
if (!has_old_item) {
// We added a new key/value pair.
observers_.ForAllPtrs(
[&key, &value, &source](mojom::LevelDBObserver* observer) {
observer->KeyAdded(key.Clone(), value.Clone(), source);
});
} else {
// We changed the value for an existing key.
observers_.ForAllPtrs(
[&key, &value, &source, &old_value](mojom::LevelDBObserver* observer) {
observer->KeyChanged(
key.Clone(), value.Clone(), old_value.Clone(), source);
});
}
callback.Run(true);
}

void LevelDBWrapperImpl::Delete(mojo::Array<uint8_t> key,
const mojo::String& source,
const DeleteCallback& callback) {
NOTIMPLEMENTED();
callback.Run(leveldb::DatabaseError::NOT_SUPPORTED);
auto found = map_.find(key);
if (found == map_.end()) {
callback.Run(true);
return;
}

mojo::Array<uint8_t> old_value = std::move(found->second);
map_.erase(found);
bytes_used_ -= key.size() + old_value.size();
observers_.ForAllPtrs(
[&key, &source, &old_value](mojom::LevelDBObserver* observer) {
observer->KeyDeleted(
key.Clone(), old_value.Clone(), source);
});
callback.Run(true);
}

void LevelDBWrapperImpl::DeleteAll(mojom::LevelDBObserverPtr observer,
const mojo::String& source,
void LevelDBWrapperImpl::DeleteAll(const mojo::String& source,
const DeleteAllCallback& callback) {
// TODO(jam): store observer and call it when changes occur.
NOTIMPLEMENTED();
callback.Run(leveldb::DatabaseError::NOT_SUPPORTED);
map_.clear();
bytes_used_ = 0;
observers_.ForAllPtrs(
[&source](mojom::LevelDBObserver* observer) {
observer->AllDeleted(source);
});
callback.Run(true);
}

void LevelDBWrapperImpl::Get(mojo::Array<uint8_t> key,
const GetCallback& callback) {
NOTIMPLEMENTED();
callback.Run(leveldb::DatabaseError::NOT_SUPPORTED, mojo::Array<uint8_t>());
auto found = map_.find(key);
if (found == map_.end()) {
callback.Run(false, mojo::Array<uint8_t>());
return;
}
callback.Run(true, found->second.Clone());
}

void LevelDBWrapperImpl::GetAll(mojom::LevelDBObserverPtr observer,
void LevelDBWrapperImpl::GetAll(const mojo::String& source,
const GetAllCallback& callback) {
// TODO(jam): store observer and call it when changes occur.
NOTIMPLEMENTED();
callback.Run(leveldb::DatabaseError::NOT_SUPPORTED,
mojo::Array<mojom::KeyValuePtr>());

mojo::Array<mojom::KeyValuePtr> all(map_.size());
for (const auto& it : map_) {
mojom::KeyValuePtr kv = mojom::KeyValue::New();
kv->key = it.first.Clone();
kv->value = it.second.Clone();
all.push_back(std::move(kv));
}
callback.Run(leveldb::DatabaseError::OK, std::move(all));
observers_.ForAllPtrs(
[source](mojom::LevelDBObserver* observer) {
observer->GetAllComplete(source);
});
}

void LevelDBWrapperImpl::OnConnectionError() {
Expand Down
18 changes: 12 additions & 6 deletions content/browser/leveldb_wrapper_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "base/macros.h"
#include "content/common/leveldb_wrapper.mojom.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/bindings/interface_ptr_set.h"

namespace content {

Expand All @@ -17,19 +18,21 @@ namespace content {
// 1) Adds the given prefix, if any, to all keys. This allows the sharing of one
// database across many, possibly untrusted, consumers and ensuring that they
// can't access each other's values.
// 2) Informs an observer when the given prefix' values are modified by another
// process.
// 3) Throttles requests to avoid overwhelming the disk.
// 2) Enforces a max_size constraint.
// 3) Informs an observer when the given prefix' values are modified.
// 4) Throttles requests to avoid overwhelming the disk.
class LevelDBWrapperImpl : public mojom::LevelDBWrapper {
public:
// |no_bindings_callback| will be called when this object has no more
// bindings.
LevelDBWrapperImpl(leveldb::LevelDBDatabase* database,
const std::string& prefix,
size_t max_size,
const base::Closure& no_bindings_callback);
~LevelDBWrapperImpl() override;

void Bind(mojom::LevelDBWrapperRequest request);
void AddObserver(mojom::LevelDBObserverPtr observer);

private:
// LevelDBWrapperImpl:
Expand All @@ -40,19 +43,22 @@ class LevelDBWrapperImpl : public mojom::LevelDBWrapper {
void Delete(mojo::Array<uint8_t> key,
const mojo::String& source,
const DeleteCallback& callback) override;
void DeleteAll(mojom::LevelDBObserverPtr observer,
const mojo::String& source,
void DeleteAll(const mojo::String& source,
const DeleteAllCallback& callback) override;
void Get(mojo::Array<uint8_t> key, const GetCallback& callback) override;
void GetAll(mojom::LevelDBObserverPtr observer,
void GetAll(const mojo::String& source,
const GetAllCallback& callback) override;

void OnConnectionError();

std::string prefix_;
mojo::BindingSet<mojom::LevelDBWrapper> bindings_;
mojo::InterfacePtrSet<mojom::LevelDBObserver> observers_;
base::Closure no_bindings_callback_;
leveldb::LevelDBDatabase* database_;
std::map<mojo::Array<uint8_t>, mojo::Array<uint8_t>> map_;
size_t bytes_used_;
size_t max_size_;

DISALLOW_COPY_AND_ASSIGN(LevelDBWrapperImpl);
};
Expand Down
4 changes: 3 additions & 1 deletion content/browser/storage_partition_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -587,8 +587,10 @@ BackgroundSyncContextImpl* StoragePartitionImpl::GetBackgroundSyncContext() {

void StoragePartitionImpl::OpenLocalStorage(
const url::Origin& origin,
mojom::LevelDBObserverPtr observer,
mojo::InterfaceRequest<mojom::LevelDBWrapper> request) {
dom_storage_context_->OpenLocalStorage(origin, std::move(request));
dom_storage_context_->OpenLocalStorage(
origin, std::move(observer), std::move(request));
}

void StoragePartitionImpl::ClearDataImpl(
Expand Down
1 change: 1 addition & 0 deletions content/browser/storage_partition_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class StoragePartitionImpl : public StoragePartition,
// mojom::StoragePartitionService interface.
void OpenLocalStorage(
const url::Origin& origin,
mojom::LevelDBObserverPtr observer,
mojo::InterfaceRequest<mojom::LevelDBWrapper> request) override;

void ClearDataForOrigin(uint32_t remove_mask,
Expand Down
24 changes: 11 additions & 13 deletions content/common/leveldb_wrapper.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ module content.mojom;
import "components/leveldb/public/interfaces/leveldb.mojom";

// Gives information about changes to a LevelDB database.
// The reason this is a parameter to DeleteAll and GetAll below, instead of
// being specified when opening a LevelDBWrapper, is to avoid the client getting
// callbacks for changes that have already been applied to its database that
// it's fetching via GetAll or it's clearing via DeleteAll.
// In the methods below, |source| is a user-defined string which was passed to
// the various LevelDBWrapper methods.
// Note that observer methods are called before the callbacks for the
// LevelDBWrapper methods are run.
interface LevelDBObserver {
KeyAdded(array<uint8> key, array<uint8> value, string source);
KeyChanged(array<uint8> key, array<uint8> new_value, array<uint8> old_value,
string source);
KeyDeleted(array<uint8> key, array<uint8> old_value, string source);
AllDeleted(string source);

// Since the GetAll call is synchronous, observers need this asynchronously
// delivered notification to avoid applying changes to the returned array
// that it already contains.
GetAllComplete(string source);
};

struct KeyValue {
Expand All @@ -31,23 +31,21 @@ struct KeyValue {
// change.
interface LevelDBWrapper {
// Sets the database entry for |key| to |value|. Returns OK on success.
Put(array<uint8> key, array<uint8> value, string source)
=> (leveldb.DatabaseError status);
Put(array<uint8> key, array<uint8> value, string source) => (bool success);

// Remove the database entry (if any) for |key|. Returns OK on success, and a
// non-OK status on error. It is not an error if |key| did not exist in the
// database.
Delete(array<uint8> key, string source) => (leveldb.DatabaseError status);
Delete(array<uint8> key, string source) => (bool success);

// Removes all the entries.
DeleteAll(LevelDBObserver observer, string source)
=> (leveldb.DatabaseError status);
DeleteAll(string source) => (bool success);

// Returns the value of the |key|.
Get(array<uint8> key) => (leveldb.DatabaseError status, array<uint8> value);
Get(array<uint8> key) => (bool success, array<uint8> value);

// Only used with small databases. Returns all key/value pairs.
[Sync]
GetAll(LevelDBObserver observer)
GetAll(string source)
=> (leveldb.DatabaseError status, array<KeyValue> data);
};
4 changes: 3 additions & 1 deletion content/common/storage_partition_service.mojom
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ import "url/mojo/origin.mojom";

// Returns services related to the current storage partition.
interface StoragePartitionService {
OpenLocalStorage(url.mojom.Origin origin, LevelDBWrapper& database);
OpenLocalStorage(url.mojom.Origin origin,
LevelDBObserver observer,
LevelDBWrapper& database);
};
Loading

0 comments on commit 7337bd9

Please sign in to comment.