Index: content/renderer/dom_storage/local_storage_cached_area.cc |
diff --git a/content/renderer/dom_storage/local_storage_cached_area.cc b/content/renderer/dom_storage/local_storage_cached_area.cc |
index 45336e2afcc8efc058f122edb3cf918840edfe72..27d463328a12033d6e6109079d807c69b8baf3b0 100644 |
--- a/content/renderer/dom_storage/local_storage_cached_area.cc |
+++ b/content/renderer/dom_storage/local_storage_cached_area.cc |
@@ -4,11 +4,39 @@ |
#include "content/renderer/dom_storage/local_storage_cached_area.h" |
+#include "base/bind.h" |
+#include "base/metrics/histogram_macros.h" |
+#include "base/strings/string_split.h" |
+#include "base/time/time.h" |
+#include "content/common/dom_storage/dom_storage_map.h" |
#include "content/common/storage_partition_service.mojom.h" |
+#include "content/renderer/dom_storage/local_storage_area.h" |
#include "content/renderer/dom_storage/local_storage_cached_areas.h" |
+#include "mojo/common/common_type_converters.h" |
+#include "third_party/WebKit/public/platform/WebURL.h" |
+#include "third_party/WebKit/public/web/WebStorageEventDispatcher.h" |
+#include "url/gurl.h" |
namespace content { |
+// These methods are used to pack and unpack the page_url/storage_area_id into |
+// source strings to/from the browser. |
+std::string PackSource(const GURL& page_url, |
+ const std::string& storage_area_id) { |
+ return page_url.spec() + "\n" + storage_area_id; |
+} |
+ |
+void UnpackSource(const mojo::String& source, |
+ GURL* page_url, |
+ std::string* storage_area_id) { |
+ std::vector<std::string> result = base::SplitString( |
+ source.To<std::string>(), "\n", base::KEEP_WHITESPACE, |
+ base::SPLIT_WANT_ALL); |
+ DCHECK_EQ(result.size(), 2u); |
+ *page_url = GURL(result[0]); |
+ *storage_area_id = result[1]; |
+} |
+ |
LocalStorageCachedArea::LocalStorageCachedArea( |
const url::Origin& origin, |
StoragePartitionService* storage_partition_service, |
@@ -20,56 +48,181 @@ LocalStorageCachedArea::LocalStorageCachedArea( |
} |
LocalStorageCachedArea::~LocalStorageCachedArea() { |
- cached_areas_->LocalStorageCacheAreaClosed(this); |
+ cached_areas_->CacheAreaClosed(this); |
} |
unsigned LocalStorageCachedArea::GetLength() { |
EnsureLoaded(); |
- return 0u; |
+ return map_->Length(); |
} |
base::NullableString16 LocalStorageCachedArea::GetKey(unsigned index) { |
EnsureLoaded(); |
- return base::NullableString16(); |
+ return map_->Key(index); |
} |
base::NullableString16 LocalStorageCachedArea::GetItem( |
const base::string16& key) { |
EnsureLoaded(); |
- return base::NullableString16(); |
+ return map_->GetItem(key); |
} |
bool LocalStorageCachedArea::SetItem(const base::string16& key, |
const base::string16& value, |
- const GURL& page_url) { |
+ const GURL& page_url, |
+ const std::string& storage_area_id) { |
+ // A quick check to reject obviously overbudget items to avoid priming the |
+ // cache. |
+ if (key.length() + value.length() > kPerStorageAreaQuota) |
+ return false; |
+ |
EnsureLoaded(); |
- return false; |
+ base::NullableString16 unused; |
+ if (!map_->SetItem(key, value, &unused)) |
+ return false; |
+ |
+ // Ignore mutations to |key| until OnSetItemComplete. |
+ ignore_key_mutations_[key]++; |
+ leveldb_->Put(mojo::Array<uint8_t>::From(key), |
+ mojo::Array<uint8_t>::From(value), |
+ PackSource(page_url, storage_area_id), |
+ base::Bind(&LocalStorageCachedArea::OnSetItemComplete, |
+ base::Unretained(this), key)); |
+ return true; |
} |
void LocalStorageCachedArea::RemoveItem(const base::string16& key, |
- const GURL& page_url) { |
+ const GURL& page_url, |
+ const std::string& storage_area_id) { |
EnsureLoaded(); |
+ base::string16 unused; |
+ if (!map_->RemoveItem(key, &unused)) |
+ return; |
+ |
+ // Ignore mutations to |key| until OnRemoveItemComplete. |
+ ignore_key_mutations_[key]++; |
+ leveldb_->Delete(mojo::Array<uint8_t>::From(key), |
+ PackSource(page_url, storage_area_id), |
+ base::Bind(&LocalStorageCachedArea::OnRemoveItemComplete, |
+ base::Unretained(this), key)); |
} |
-void LocalStorageCachedArea::Clear(const GURL& page_url) { |
+void LocalStorageCachedArea::Clear(const GURL& page_url, |
+ const std::string& storage_area_id) { |
// No need to prime the cache in this case. |
+ Reset(); |
+ map_ = new DOMStorageMap(kPerStorageAreaQuota); |
binding_.Close(); |
- // TODO: |
- // binding_.CreateInterfacePtrAndBind() |
+ |
+ leveldb_->DeleteAll(binding_.CreateInterfacePtrAndBind(), |
+ PackSource(page_url, storage_area_id), |
+ base::Bind(&LocalStorageCachedArea::OnClearComplete, |
+ base::Unretained(this))); |
+} |
+ |
+void LocalStorageCachedArea::AreaCreated(LocalStorageArea* area) { |
+ areas_[area->id()] = area; |
+} |
+ |
+void LocalStorageCachedArea::AreaDestroyed(LocalStorageArea* area) { |
+ areas_.erase(area->id()); |
} |
void LocalStorageCachedArea::KeyChanged(mojo::Array<uint8_t> key, |
mojo::Array<uint8_t> new_value, |
mojo::Array<uint8_t> old_value, |
const mojo::String& source) { |
+ GURL page_url; |
+ std::string storage_area_id; |
+ UnpackSource(source, &page_url, &storage_area_id); |
+ |
+ base::string16 key_string = key.To<base::string16>(); |
+ base::string16 new_value_string = new_value.To<base::string16>(); |
+ |
+ blink::WebStorageArea* originating_area = nullptr; |
+ if (areas_.find(storage_area_id) != areas_.end()) { |
+ // The source storage area is in this process. |
+ originating_area = areas_[storage_area_id]; |
+ } else { |
+ // This was from another process or the storage area is gone. If the former, |
+ // apply it to our cache if we haven't already changed it and are waiting |
+ // for the confirmation callback. In the latter case, we won't do anything |
+ // because ignore_key_mutations_ won't be updated until the callback runs. |
+ if (ignore_key_mutations_.find(key_string) != ignore_key_mutations_.end()) { |
+ // We turn off quota checking here to accomodate the over budget allowance |
+ // that's provided in the browser process. |
+ base::NullableString16 unused; |
+ map_->set_quota(std::numeric_limits<int32_t>::max()); |
+ map_->SetItem(key_string, new_value_string, &unused); |
+ map_->set_quota(kPerStorageAreaQuota); |
+ } |
+ } |
+ |
+ blink::WebStorageEventDispatcher::dispatchLocalStorageEvent( |
+ key_string, old_value.To<base::string16>(), new_value_string, |
+ GURL(origin_.Serialize()), page_url, originating_area); |
} |
void LocalStorageCachedArea::KeyDeleted(mojo::Array<uint8_t> key, |
+ mojo::Array<uint8_t> old_value, |
const mojo::String& source) { |
+ GURL page_url; |
+ std::string storage_area_id; |
+ UnpackSource(source, &page_url, &storage_area_id); |
+ |
+ base::string16 key_string = key.To<base::string16>(); |
+ |
+ blink::WebStorageArea* originating_area = nullptr; |
+ if (areas_.find(storage_area_id) != areas_.end()) { |
+ // The source storage area is in this process. |
+ originating_area = areas_[storage_area_id]; |
+ } else { |
+ // This was from another process or the storage area is gone. If the former, |
+ // remove it from our cache if we haven't already changed it and are waiting |
+ // for the confirmation callback. In the latter case, we won't do anything |
+ // because ignore_key_mutations_ won't be updated until the callback runs. |
+ if (ignore_key_mutations_.find(key_string) != ignore_key_mutations_.end()) { |
+ base::string16 unused; |
+ map_->RemoveItem(key_string, &unused); |
+ } |
+ } |
+ |
+ blink::WebStorageEventDispatcher::dispatchLocalStorageEvent( |
+ key_string, old_value.To<base::string16>(), base::NullableString16(), |
+ GURL(origin_.Serialize()), page_url, originating_area); |
} |
void LocalStorageCachedArea::AllDeleted(const mojo::String& source) { |
+ GURL page_url; |
+ std::string storage_area_id; |
+ UnpackSource(source, &page_url, &storage_area_id); |
+ |
+ blink::WebStorageArea* originating_area = nullptr; |
+ if (areas_.find(storage_area_id) != areas_.end()) { |
+ // The source storage area is in this process. |
+ originating_area = areas_[storage_area_id]; |
+ } else { |
+ scoped_refptr<DOMStorageMap> old = map_; |
+ map_ = new DOMStorageMap(kPerStorageAreaQuota); |
+ |
+ // We have to retain local additions which happened after this clear |
+ // operation from another process. |
+ auto iter = ignore_key_mutations_.begin(); |
+ while (iter != ignore_key_mutations_.end()) { |
+ base::NullableString16 value = old->GetItem(iter->first); |
+ if (!value.is_null()) { |
+ base::NullableString16 unused; |
+ map_->SetItem(iter->first, value.string(), &unused); |
+ } |
+ ++iter; |
+ } |
+ } |
+ |
+ blink::WebStorageEventDispatcher::dispatchLocalStorageEvent( |
+ base::NullableString16(), base::NullableString16(), |
+ base::NullableString16(), GURL(origin_.Serialize()), page_url, |
+ originating_area); |
} |
void LocalStorageCachedArea::EnsureLoaded() { |
@@ -77,9 +230,71 @@ void LocalStorageCachedArea::EnsureLoaded() { |
return; |
loaded_ = true; |
+ base::TimeTicks before = base::TimeTicks::Now(); |
leveldb::DatabaseError status = leveldb::DatabaseError::OK; |
mojo::Array<content::KeyValuePtr> data; |
leveldb_->GetAll(binding_.CreateInterfacePtrAndBind(), &status, &data); |
+ |
+ DOMStorageValuesMap values; |
+ for (size_t i = 0; i < data.size(); ++i) { |
+ values[data[i]->key.To<base::string16>()] = |
+ base::NullableString16(data[i]->value.To<base::string16>(), false); |
+ } |
+ |
+ map_ = new DOMStorageMap(kPerStorageAreaQuota); |
+ map_->SwapValues(&values); |
+ |
+ base::TimeDelta time_to_prime = base::TimeTicks::Now() - before; |
+ UMA_HISTOGRAM_TIMES("LocalStorage.MojoTimeToPrime", time_to_prime); |
+ |
+ size_t local_storage_size_kb = map_->bytes_used() / 1024; |
+ // Track localStorage size, from 0-6MB. Note that the maximum size should be |
+ // 5MB, but we add some slop since we want to make sure the max size is always |
+ // above what we see in practice, since histograms can't change. |
+ UMA_HISTOGRAM_CUSTOM_COUNTS("LocalStorage.MojoSizeInKB", |
+ local_storage_size_kb, |
+ 0, 6 * 1024, 50); |
+ if (local_storage_size_kb < 100) { |
+ UMA_HISTOGRAM_TIMES("LocalStorage.MojoTimeToPrimeForUnder100KB", |
+ time_to_prime); |
+ } else if (local_storage_size_kb < 1000) { |
+ UMA_HISTOGRAM_TIMES("LocalStorage.MojoTimeToPrimeFor100KBTo1MB", |
+ time_to_prime); |
+ } else { |
+ UMA_HISTOGRAM_TIMES("LocalStorage.MojoTimeToPrimeFor1MBTo5MB", |
+ time_to_prime); |
+ } |
+} |
+ |
+void LocalStorageCachedArea::OnSetItemComplete(const base::string16& key, |
+ leveldb::DatabaseError result) { |
+ if (result != leveldb::DatabaseError::OK) { |
+ Reset(); |
+ return; |
+ } |
+ |
+ auto found = ignore_key_mutations_.find(key); |
+ DCHECK(found != ignore_key_mutations_.end()); |
+ if (--found->second == 0) |
+ ignore_key_mutations_.erase(found); |
+} |
+ |
+void LocalStorageCachedArea::OnRemoveItemComplete( |
+ const base::string16& key, leveldb::DatabaseError result) { |
+ DCHECK_EQ(result, leveldb::DatabaseError::OK); |
+ auto found = ignore_key_mutations_.find(key); |
+ DCHECK(found != ignore_key_mutations_.end()); |
+ if (--found->second == 0) |
+ ignore_key_mutations_.erase(found); |
+} |
+ |
+void LocalStorageCachedArea::OnClearComplete(leveldb::DatabaseError result) { |
+ DCHECK_EQ(result, leveldb::DatabaseError::OK); |
+} |
+ |
+void LocalStorageCachedArea::Reset() { |
+ map_ = NULL; |
+ ignore_key_mutations_.clear(); |
} |
} // namespace content |