| // Copyright 2016 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 "content/renderer/dom_storage/local_storage_cached_area.h" |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "components/services/leveldb/public/cpp/util.h" |
| #include "content/common/dom_storage/dom_storage_map.h" |
| #include "content/renderer/dom_storage/local_storage_area.h" |
| #include "content/renderer/dom_storage/local_storage_cached_areas.h" |
| #include "content/renderer/dom_storage/session_web_storage_namespace_impl.h" |
| #include "content/renderer/render_thread_impl.h" |
| #include "mojo/public/cpp/bindings/strong_associated_binding.h" |
| #include "third_party/blink/public/mojom/dom_storage/session_storage_namespace.mojom.h" |
| #include "third_party/blink/public/mojom/dom_storage/storage_partition_service.mojom.h" |
| #include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h" |
| #include "third_party/blink/public/platform/web_url.h" |
| #include "third_party/blink/public/web/web_storage_event_dispatcher.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Don't change or reorder any of the values in this enum, as these values |
| // are serialized on disk. |
| enum class StorageFormat : uint8_t { UTF16 = 0, Latin1 = 1 }; |
| |
| class GetAllCallback : public blink::mojom::StorageAreaGetAllCallback { |
| public: |
| static blink::mojom::StorageAreaGetAllCallbackAssociatedPtrInfo CreateAndBind( |
| base::OnceCallback<void(bool)> callback) { |
| blink::mojom::StorageAreaGetAllCallbackAssociatedPtrInfo ptr_info; |
| auto request = mojo::MakeRequest(&ptr_info); |
| mojo::MakeStrongAssociatedBinding( |
| base::WrapUnique(new GetAllCallback(std::move(callback))), |
| std::move(request)); |
| return ptr_info; |
| } |
| |
| private: |
| explicit GetAllCallback(base::OnceCallback<void(bool)> callback) |
| : m_callback(std::move(callback)) {} |
| void Complete(bool success) override { std::move(m_callback).Run(success); } |
| |
| base::OnceCallback<void(bool)> m_callback; |
| }; |
| |
| } // namespace |
| |
| // 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 std::string& source, |
| GURL* page_url, |
| std::string* storage_area_id) { |
| std::vector<std::string> result = base::SplitString( |
| source, "\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 std::string& namespace_id, |
| const url::Origin& origin, |
| blink::mojom::SessionStorageNamespace* session_namespace, |
| LocalStorageCachedAreas* cached_areas, |
| blink::scheduler::WebThreadScheduler* main_thread_scheduler) |
| : namespace_id_(namespace_id), |
| origin_(origin), |
| binding_(this), |
| cached_areas_(cached_areas), |
| main_thread_scheduler_(main_thread_scheduler), |
| weak_factory_(this) { |
| DCHECK(!namespace_id_.empty()); |
| blink::mojom::StorageAreaAssociatedPtrInfo wrapper_ptr_info; |
| session_namespace->OpenArea(origin_, mojo::MakeRequest(&wrapper_ptr_info)); |
| leveldb_.Bind(std::move(wrapper_ptr_info), |
| main_thread_scheduler->IPCTaskRunner()); |
| blink::mojom::StorageAreaObserverAssociatedPtrInfo ptr_info; |
| binding_.Bind(mojo::MakeRequest(&ptr_info), |
| main_thread_scheduler->IPCTaskRunner()); |
| leveldb_->AddObserver(std::move(ptr_info)); |
| } |
| |
| LocalStorageCachedArea::LocalStorageCachedArea( |
| const url::Origin& origin, |
| blink::mojom::StoragePartitionService* storage_partition_service, |
| LocalStorageCachedAreas* cached_areas, |
| blink::scheduler::WebThreadScheduler* main_thread_scheduler) |
| : origin_(origin), |
| binding_(this), |
| cached_areas_(cached_areas), |
| main_thread_scheduler_(main_thread_scheduler), |
| weak_factory_(this) { |
| DCHECK(namespace_id_.empty()); |
| blink::mojom::StorageAreaPtrInfo wrapper_ptr_info; |
| storage_partition_service->OpenLocalStorage( |
| origin_, mojo::MakeRequest(&wrapper_ptr_info)); |
| leveldb_.Bind(std::move(wrapper_ptr_info), |
| main_thread_scheduler->IPCTaskRunner()); |
| blink::mojom::StorageAreaObserverAssociatedPtrInfo ptr_info; |
| binding_.Bind(mojo::MakeRequest(&ptr_info), |
| main_thread_scheduler->IPCTaskRunner()); |
| leveldb_->AddObserver(std::move(ptr_info)); |
| } |
| |
| LocalStorageCachedArea::~LocalStorageCachedArea() {} |
| |
| unsigned LocalStorageCachedArea::GetLength() { |
| EnsureLoaded(); |
| return map_->Length(); |
| } |
| |
| base::NullableString16 LocalStorageCachedArea::GetKey( |
| unsigned index, |
| bool* did_decrease_iterator) { |
| EnsureLoaded(); |
| return map_->Key(index, did_decrease_iterator); |
| } |
| |
| base::NullableString16 LocalStorageCachedArea::GetItem( |
| const base::string16& key) { |
| EnsureLoaded(); |
| return map_->GetItem(key); |
| } |
| |
| bool LocalStorageCachedArea::SetItem(const base::string16& key, |
| const base::string16& value, |
| 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()) * sizeof(base::char16) > |
| kPerStorageAreaQuota) |
| return false; |
| |
| EnsureLoaded(); |
| bool result = false; |
| base::NullableString16 old_nullable_value; |
| if (should_send_old_value_on_mutations_) |
| result = map_->SetItem(key, value, &old_nullable_value); |
| else |
| result = map_->SetItem(key, value, nullptr); |
| if (!result) |
| return false; |
| |
| // Determine data formats. |
| bool is_session_storage = IsSessionStorage(); |
| FormatOption key_format = is_session_storage |
| ? FormatOption::kSessionStorageForceUTF8 |
| : FormatOption::kLocalStorageDetectFormat; |
| FormatOption value_format = is_session_storage |
| ? FormatOption::kSessionStorageForceUTF16 |
| : FormatOption::kLocalStorageDetectFormat; |
| |
| // Ignore mutations to |key| until OnSetItemComplete. |
| ignore_key_mutations_[key]++; |
| base::Optional<std::vector<uint8_t>> optional_old_value; |
| if (!old_nullable_value.is_null()) |
| optional_old_value = |
| String16ToUint8Vector(old_nullable_value.string(), value_format); |
| |
| blink::WebScopedVirtualTimePauser virtual_time_pauser = |
| main_thread_scheduler_->CreateWebScopedVirtualTimePauser( |
| "LocalStorageCachedArea"); |
| virtual_time_pauser.PauseVirtualTime(); |
| leveldb_->Put(String16ToUint8Vector(key, key_format), |
| String16ToUint8Vector(value, value_format), optional_old_value, |
| PackSource(page_url, storage_area_id), |
| base::BindOnce(&LocalStorageCachedArea::OnSetItemComplete, |
| weak_factory_.GetWeakPtr(), key, |
| std::move(virtual_time_pauser))); |
| if (IsSessionStorage() && |
| (old_nullable_value.is_null() || old_nullable_value.string() != value)) { |
| blink::WebStorageArea* originating_area = areas_[storage_area_id]; |
| DCHECK_NE(nullptr, originating_area); |
| SessionWebStorageNamespaceImpl session_namespace_for_event_dispatch( |
| namespace_id_, nullptr); |
| blink::WebStorageEventDispatcher::DispatchSessionStorageEvent( |
| blink::WebString::FromUTF16(key), |
| blink::WebString::FromUTF16(old_nullable_value), |
| blink::WebString::FromUTF16(value), origin_.GetURL(), page_url, |
| session_namespace_for_event_dispatch, originating_area); |
| } |
| return true; |
| } |
| |
| void LocalStorageCachedArea::RemoveItem(const base::string16& key, |
| const GURL& page_url, |
| const std::string& storage_area_id) { |
| EnsureLoaded(); |
| bool result = false; |
| base::string16 old_value; |
| if (should_send_old_value_on_mutations_) |
| result = map_->RemoveItem(key, &old_value); |
| else |
| result = map_->RemoveItem(key, nullptr); |
| if (!result) |
| return; |
| |
| // Determine data formats. |
| bool is_session_storage = IsSessionStorage(); |
| FormatOption key_format = is_session_storage |
| ? FormatOption::kSessionStorageForceUTF8 |
| : FormatOption::kLocalStorageDetectFormat; |
| FormatOption value_format = is_session_storage |
| ? FormatOption::kSessionStorageForceUTF16 |
| : FormatOption::kLocalStorageDetectFormat; |
| |
| // Ignore mutations to |key| until OnRemoveItemComplete. |
| ignore_key_mutations_[key]++; |
| base::Optional<std::vector<uint8_t>> optional_old_value; |
| if (should_send_old_value_on_mutations_) |
| optional_old_value = String16ToUint8Vector(old_value, value_format); |
| |
| blink::WebScopedVirtualTimePauser virtual_time_pauser = |
| main_thread_scheduler_->CreateWebScopedVirtualTimePauser( |
| "LocalStorageCachedArea"); |
| virtual_time_pauser.PauseVirtualTime(); |
| leveldb_->Delete(String16ToUint8Vector(key, key_format), optional_old_value, |
| PackSource(page_url, storage_area_id), |
| base::BindOnce(&LocalStorageCachedArea::OnRemoveItemComplete, |
| weak_factory_.GetWeakPtr(), key, |
| std::move(virtual_time_pauser))); |
| if (IsSessionStorage() && old_value != base::string16()) { |
| blink::WebStorageArea* originating_area = areas_[storage_area_id]; |
| DCHECK_NE(nullptr, originating_area); |
| SessionWebStorageNamespaceImpl session_namespace_for_event_dispatch( |
| namespace_id_, nullptr); |
| blink::WebStorageEventDispatcher::DispatchSessionStorageEvent( |
| blink::WebString::FromUTF16(key), |
| blink::WebString::FromUTF16(old_value), blink::WebString(), |
| origin_.GetURL(), page_url, session_namespace_for_event_dispatch, |
| originating_area); |
| } |
| } |
| |
| void LocalStorageCachedArea::Clear(const GURL& page_url, |
| const std::string& storage_area_id) { |
| bool already_empty = false; |
| if (IsSessionStorage()) { |
| EnsureLoaded(); |
| already_empty = map_->Length() == 0u; |
| } |
| // No need to prime the cache in this case. |
| Reset(); |
| map_ = new DOMStorageMap(kPerStorageAreaQuota); |
| ignore_all_mutations_ = true; |
| |
| blink::WebScopedVirtualTimePauser virtual_time_pauser = |
| main_thread_scheduler_->CreateWebScopedVirtualTimePauser( |
| "LocalStorageCachedArea"); |
| virtual_time_pauser.PauseVirtualTime(); |
| leveldb_->DeleteAll(PackSource(page_url, storage_area_id), |
| base::BindOnce(&LocalStorageCachedArea::OnClearComplete, |
| weak_factory_.GetWeakPtr(), |
| std::move(virtual_time_pauser))); |
| if (IsSessionStorage() && !already_empty) { |
| blink::WebStorageArea* originating_area = areas_[storage_area_id]; |
| DCHECK_NE(nullptr, originating_area); |
| SessionWebStorageNamespaceImpl session_namespace_for_event_dispatch( |
| namespace_id_, nullptr); |
| blink::WebStorageEventDispatcher::DispatchSessionStorageEvent( |
| blink::WebString(), blink::WebString(), blink::WebString(), |
| origin_.GetURL(), page_url, session_namespace_for_event_dispatch, |
| originating_area); |
| } |
| } |
| |
| void LocalStorageCachedArea::AreaCreated(LocalStorageArea* area) { |
| areas_[area->id()] = area; |
| } |
| |
| void LocalStorageCachedArea::AreaDestroyed(LocalStorageArea* area) { |
| areas_.erase(area->id()); |
| } |
| |
| // static |
| base::string16 LocalStorageCachedArea::Uint8VectorToString16( |
| const std::vector<uint8_t>& input, |
| FormatOption format_option) { |
| if (input.empty()) |
| return base::string16(); |
| size_t input_size = input.size(); |
| base::string16 result; |
| switch (format_option) { |
| case FormatOption::kSessionStorageForceUTF16: |
| if (input_size % sizeof(base::char16) != 0) { |
| // TODO(mek): Better error recovery when corrupt (or otherwise invalid) |
| // data is detected. |
| LOCAL_HISTOGRAM_BOOLEAN("LocalStorageCachedArea.CorruptData", true); |
| LOG(ERROR) << "Corrupt data in domstorage"; |
| return base::string16(); |
| } |
| result.resize(input_size / sizeof(base::char16)); |
| std::memcpy(&result[0], input.data(), input_size); |
| return result; |
| case FormatOption::kSessionStorageForceUTF8: |
| // Encoding / codepoint errors are ignored on purpose. |
| return base::UTF8ToUTF16(leveldb::Uint8VectorToStringPiece(input)); |
| case FormatOption::kLocalStorageDetectFormat: |
| break; |
| } |
| StorageFormat format = static_cast<StorageFormat>(input[0]); |
| const size_t payload_size = input_size - 1; |
| bool corrupt = false; |
| switch (format) { |
| case StorageFormat::UTF16: |
| if (payload_size % sizeof(base::char16) != 0) { |
| corrupt = true; |
| break; |
| } |
| result.resize(payload_size / sizeof(base::char16)); |
| std::memcpy(&result[0], input.data() + 1, payload_size); |
| break; |
| case StorageFormat::Latin1: |
| result.resize(payload_size); |
| std::copy(input.begin() + 1, input.end(), result.begin()); |
| break; |
| default: |
| corrupt = true; |
| } |
| if (corrupt) { |
| // TODO(mek): Better error recovery when corrupt (or otherwise invalid) data |
| // is detected. |
| LOCAL_HISTOGRAM_BOOLEAN("LocalStorageCachedArea.CorruptData", true); |
| LOG(ERROR) << "Corrupt data in localstorage"; |
| return base::string16(); |
| } |
| return result; |
| } |
| |
| // static |
| std::vector<uint8_t> LocalStorageCachedArea::String16ToUint8Vector( |
| const base::string16& input, |
| FormatOption format_option) { |
| switch (format_option) { |
| case FormatOption::kSessionStorageForceUTF16: { |
| std::vector<uint8_t> result; |
| result.reserve(input.size() * sizeof(base::char16)); |
| const uint8_t* data = reinterpret_cast<const uint8_t*>(input.data()); |
| result.insert(result.begin(), data, |
| data + input.size() * sizeof(base::char16)); |
| return result; |
| } |
| case FormatOption::kSessionStorageForceUTF8: { |
| // Encoding / codepoint errors are ignored on purpose. |
| std::string utf8 = base::UTF16ToUTF8(base::StringPiece16(input)); |
| return leveldb::StdStringToUint8Vector(utf8); |
| } |
| case FormatOption::kLocalStorageDetectFormat: |
| break; |
| } |
| bool is_8bit = true; |
| for (const auto& c : input) { |
| if (c & 0xff00) { |
| is_8bit = false; |
| break; |
| } |
| } |
| if (is_8bit) { |
| std::vector<uint8_t> result(input.size() + 1); |
| result[0] = static_cast<uint8_t>(StorageFormat::Latin1); |
| std::copy(input.begin(), input.end(), result.begin() + 1); |
| return result; |
| } |
| const uint8_t* data = reinterpret_cast<const uint8_t*>(input.data()); |
| std::vector<uint8_t> result; |
| result.reserve(input.size() * sizeof(base::char16) + 1); |
| result.push_back(static_cast<uint8_t>(StorageFormat::UTF16)); |
| result.insert(result.end(), data, data + input.size() * sizeof(base::char16)); |
| return result; |
| } |
| |
| void LocalStorageCachedArea::KeyAdded(const std::vector<uint8_t>& key, |
| const std::vector<uint8_t>& value, |
| const std::string& source) { |
| DCHECK(!IsSessionStorage()); |
| base::NullableString16 null_value; |
| KeyAddedOrChanged(key, value, null_value, source); |
| } |
| |
| void LocalStorageCachedArea::KeyChanged(const std::vector<uint8_t>& key, |
| const std::vector<uint8_t>& new_value, |
| const std::vector<uint8_t>& old_value, |
| const std::string& source) { |
| DCHECK(!IsSessionStorage()); |
| base::NullableString16 old_value_str( |
| Uint8VectorToString16(old_value, FormatOption::kLocalStorageDetectFormat), |
| false); |
| KeyAddedOrChanged(key, new_value, old_value_str, source); |
| } |
| |
| void LocalStorageCachedArea::KeyDeleted(const std::vector<uint8_t>& key, |
| const std::vector<uint8_t>& old_value, |
| const std::string& source) { |
| DCHECK(!IsSessionStorage()); |
| GURL page_url; |
| std::string storage_area_id; |
| UnpackSource(source, &page_url, &storage_area_id); |
| |
| base::string16 key_string = |
| Uint8VectorToString16(key, FormatOption::kLocalStorageDetectFormat); |
| |
| 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 if (map_ && !ignore_all_mutations_) { |
| // 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()) |
| map_->RemoveItem(key_string, nullptr); |
| } |
| |
| blink::WebStorageEventDispatcher::DispatchLocalStorageEvent( |
| blink::WebString::FromUTF16(key_string), |
| blink::WebString::FromUTF16(Uint8VectorToString16( |
| old_value, FormatOption::kLocalStorageDetectFormat)), |
| blink::WebString(), origin_.GetURL(), page_url, originating_area); |
| } |
| |
| void LocalStorageCachedArea::AllDeleted(const std::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 if (map_ && !ignore_all_mutations_) { |
| 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()) |
| map_->SetItem(iter->first, value.string(), nullptr); |
| ++iter; |
| } |
| } |
| |
| if (IsSessionStorage()) { |
| SessionWebStorageNamespaceImpl session_namespace_for_event_dispatch( |
| namespace_id_, nullptr); |
| blink::WebStorageEventDispatcher::DispatchSessionStorageEvent( |
| blink::WebString(), blink::WebString(), blink::WebString(), |
| origin_.GetURL(), page_url, session_namespace_for_event_dispatch, |
| originating_area); |
| } else { |
| blink::WebStorageEventDispatcher::DispatchLocalStorageEvent( |
| blink::WebString(), blink::WebString(), blink::WebString(), |
| origin_.GetURL(), page_url, originating_area); |
| } |
| } |
| |
| void LocalStorageCachedArea::ShouldSendOldValueOnMutations(bool value) { |
| DCHECK(!IsSessionStorage()); |
| should_send_old_value_on_mutations_ = value; |
| } |
| |
| void LocalStorageCachedArea::KeyAddedOrChanged( |
| const std::vector<uint8_t>& key, |
| const std::vector<uint8_t>& new_value, |
| const base::NullableString16& old_value, |
| const std::string& source) { |
| DCHECK(!IsSessionStorage()); |
| GURL page_url; |
| std::string storage_area_id; |
| UnpackSource(source, &page_url, &storage_area_id); |
| |
| base::string16 key_string = |
| Uint8VectorToString16(key, FormatOption::kLocalStorageDetectFormat); |
| base::string16 new_value_string = |
| Uint8VectorToString16(new_value, FormatOption::kLocalStorageDetectFormat); |
| |
| 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 if (map_ && !ignore_all_mutations_) { |
| // 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. |
| map_->set_quota(std::numeric_limits<int32_t>::max()); |
| map_->SetItem(key_string, new_value_string, nullptr); |
| map_->set_quota(kPerStorageAreaQuota); |
| } |
| } |
| |
| blink::WebStorageEventDispatcher::DispatchLocalStorageEvent( |
| blink::WebString::FromUTF16(key_string), |
| blink::WebString::FromUTF16(old_value), |
| blink::WebString::FromUTF16(new_value_string), origin_.GetURL(), page_url, |
| originating_area); |
| } |
| |
| void LocalStorageCachedArea::EnsureLoaded() { |
| if (map_) |
| return; |
| |
| base::TimeTicks before = base::TimeTicks::Now(); |
| ignore_all_mutations_ = true; |
| bool success = false; |
| std::vector<blink::mojom::KeyValuePtr> data; |
| leveldb_->GetAll(GetAllCallback::CreateAndBind( |
| base::BindOnce(&LocalStorageCachedArea::OnGetAllComplete, |
| weak_factory_.GetWeakPtr())), |
| &success, &data); |
| |
| DOMStorageValuesMap values; |
| bool is_session_storage = IsSessionStorage(); |
| FormatOption key_format = is_session_storage |
| ? FormatOption::kSessionStorageForceUTF8 |
| : FormatOption::kLocalStorageDetectFormat; |
| FormatOption value_format = is_session_storage |
| ? FormatOption::kSessionStorageForceUTF16 |
| : FormatOption::kLocalStorageDetectFormat; |
| for (size_t i = 0; i < data.size(); ++i) { |
| values[Uint8VectorToString16(data[i]->key, key_format)] = |
| base::NullableString16( |
| Uint8VectorToString16(data[i]->value, value_format), 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_->storage_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, |
| 1, 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, |
| blink::WebScopedVirtualTimePauser, |
| bool success) { |
| if (!success) { |
| 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, |
| blink::WebScopedVirtualTimePauser, |
| bool success) { |
| DCHECK(success); |
| 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(blink::WebScopedVirtualTimePauser, |
| bool success) { |
| DCHECK(success); |
| DCHECK(ignore_all_mutations_); |
| ignore_all_mutations_ = false; |
| } |
| |
| void LocalStorageCachedArea::OnGetAllComplete(bool success) { |
| // Since the GetAll method is synchronous, we need this asynchronously |
| // delivered notification to avoid applying changes to the returned array |
| // that we already have. |
| DCHECK(success); |
| DCHECK(ignore_all_mutations_); |
| ignore_all_mutations_ = false; |
| } |
| |
| void LocalStorageCachedArea::Reset() { |
| map_ = nullptr; |
| ignore_key_mutations_.clear(); |
| ignore_all_mutations_ = false; |
| weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| } // namespace content |