// Copyright 2014 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 "net/sdch/sdch_owner.h" #include "base/bind.h" #include "base/debug/alias.h" #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/prefs/persistent_pref_store.h" #include "base/prefs/value_map_pref_store.h" #include "base/strings/string_util.h" #include "base/time/default_clock.h" #include "base/values.h" #include "net/base/sdch_manager.h" #include "net/base/sdch_net_log_params.h" namespace net { namespace { enum PersistenceFailureReason { // File didn't exist; is being created. PERSISTENCE_FAILURE_REASON_NO_FILE = 1, // Error reading in information, but should be able to write. PERSISTENCE_FAILURE_REASON_READ_FAILED = 2, // Error leading to abort on attempted persistence. PERSISTENCE_FAILURE_REASON_WRITE_FAILED = 3, PERSISTENCE_FAILURE_REASON_MAX = 4 }; // Dictionaries that haven't been touched in 24 hours may be evicted // to make room for new dictionaries. const int kFreshnessLifetimeHours = 24; // Dictionaries that have never been used only stay fresh for one hour. const int kNeverUsedFreshnessLifetimeHours = 1; void RecordPersistenceFailure(PersistenceFailureReason failure_reason) { UMA_HISTOGRAM_ENUMERATION("Sdch3.PersistenceFailureReason", failure_reason, PERSISTENCE_FAILURE_REASON_MAX); } // Schema specifications and access routines. // The persistent prefs store is conceptually shared with any other network // stack systems that want to persist data over browser restarts, and so // use of it must be namespace restricted. // Schema: // pref_store_->GetValue(kPreferenceName) -> Dictionary { // 'version' -> 1 [int] // 'dictionaries' -> Dictionary { // server_hash -> { // 'url' -> URL [string] // 'last_used' -> seconds since unix epoch [double] // 'use_count' -> use count [int] // 'size' -> size [int] // } // } const char kPreferenceName[] = "SDCH"; const char kVersionKey[] = "version"; const char kDictionariesKey[] = "dictionaries"; const char kDictionaryUrlKey[] = "url"; const char kDictionaryLastUsedKey[] = "last_used"; const char kDictionaryUseCountKey[] = "use_count"; const char kDictionarySizeKey[] = "size"; const int kVersion = 1; // This function returns store[kPreferenceName/kDictionariesKey]. The caller // is responsible for making sure any needed calls to // |store->ReportValueChanged()| occur. base::DictionaryValue* GetPersistentStoreDictionaryMap( WriteablePrefStore* store) { base::Value* result = nullptr; bool success = store->GetMutableValue(kPreferenceName, &result); DCHECK(success); base::DictionaryValue* preference_dictionary = nullptr; success = result->GetAsDictionary(&preference_dictionary); DCHECK(success); DCHECK(preference_dictionary); base::DictionaryValue* dictionary_list_dictionary = nullptr; success = preference_dictionary->GetDictionary(kDictionariesKey, &dictionary_list_dictionary); DCHECK(success); DCHECK(dictionary_list_dictionary); return dictionary_list_dictionary; } // This function initializes a pref store with an empty version of the // above schema, removing anything previously in the store under // kPreferenceName. void InitializePrefStore(WriteablePrefStore* store) { scoped_ptr empty_store(new base::DictionaryValue); empty_store->SetInteger(kVersionKey, kVersion); empty_store->Set(kDictionariesKey, make_scoped_ptr(new base::DictionaryValue)); store->SetValue(kPreferenceName, empty_store.Pass(), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); } // A class to allow iteration over all dictionaries in the pref store, and // easy lookup of the information associated with those dictionaries. // Note that this is an "Iterator" in the same sense (and for the same // reasons) that base::Dictionary::Iterator is an iterator--it allows // iterating over all the dictionaries in the preference store, but it // does not allow use as an STL iterator because the container it // is iterating over does not export begin()/end() methods. This iterator can // only be safely used on sanitized pref stores that are known to conform to the // pref store schema. class DictionaryPreferenceIterator { public: explicit DictionaryPreferenceIterator(WriteablePrefStore* pref_store); bool IsAtEnd() const; void Advance(); const std::string& server_hash() const { return server_hash_; } const GURL& url() const { return url_; } base::Time last_used() const { return last_used_; } int use_count() const { return use_count_; } int size() const { return size_; } private: void LoadDictionaryOrDie(); std::string server_hash_; GURL url_; base::Time last_used_; int use_count_; int size_; base::DictionaryValue::Iterator dictionary_iterator_; }; DictionaryPreferenceIterator::DictionaryPreferenceIterator( WriteablePrefStore* pref_store) : dictionary_iterator_(*GetPersistentStoreDictionaryMap(pref_store)) { if (!IsAtEnd()) LoadDictionaryOrDie(); } bool DictionaryPreferenceIterator::IsAtEnd() const { return dictionary_iterator_.IsAtEnd(); } void DictionaryPreferenceIterator::Advance() { dictionary_iterator_.Advance(); if (!IsAtEnd()) LoadDictionaryOrDie(); } void DictionaryPreferenceIterator::LoadDictionaryOrDie() { double last_used_seconds_from_epoch; const base::DictionaryValue* dict = nullptr; bool success = dictionary_iterator_.value().GetAsDictionary(&dict); DCHECK(success); server_hash_ = dictionary_iterator_.key(); std::string url_spec; success = dict->GetString(kDictionaryUrlKey, &url_spec); DCHECK(success); url_ = GURL(url_spec); success = dict->GetDouble(kDictionaryLastUsedKey, &last_used_seconds_from_epoch); DCHECK(success); last_used_ = base::Time::FromDoubleT(last_used_seconds_from_epoch); success = dict->GetInteger(kDictionaryUseCountKey, &use_count_); DCHECK(success); success = dict->GetInteger(kDictionarySizeKey, &size_); DCHECK(success); } // Triggers a ReportValueChanged() on the specified WriteablePrefStore // when the object goes out of scope. class ScopedPrefNotifier { public: // Caller must guarantee lifetime of |*pref_store| exceeds the // lifetime of this object. ScopedPrefNotifier(WriteablePrefStore* pref_store) : pref_store_(pref_store) {} ~ScopedPrefNotifier() { pref_store_->ReportValueChanged( kPreferenceName, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); } private: WriteablePrefStore* pref_store_; DISALLOW_COPY_AND_ASSIGN(ScopedPrefNotifier); }; } // namespace // Adjust SDCH limits downwards for mobile. #if defined(OS_ANDROID) || defined(OS_IOS) // static const size_t SdchOwner::kMaxTotalDictionarySize = 2 * 500 * 1000; #else // static const size_t SdchOwner::kMaxTotalDictionarySize = 20 * 1000 * 1000; #endif // Somewhat arbitrary, but we assume a dictionary smaller than // 50K isn't going to do anyone any good. Note that this still doesn't // prevent download and addition unless there is less than this // amount of space available in storage. const size_t SdchOwner::kMinSpaceForDictionaryFetch = 50 * 1000; void SdchOwner::RecordDictionaryFate(enum DictionaryFate fate) { UMA_HISTOGRAM_ENUMERATION("Sdch3.DictionaryFate", fate, DICTIONARY_FATE_MAX); } void SdchOwner::RecordDictionaryEvictionOrUnload(const std::string& server_hash, size_t size, int use_count, DictionaryFate fate) { DCHECK(fate == DICTIONARY_FATE_EVICT_FOR_DICT || fate == DICTIONARY_FATE_EVICT_FOR_MEMORY || fate == DICTIONARY_FATE_EVICT_FOR_DESTRUCTION || fate == DICTIONARY_FATE_UNLOAD_FOR_DESTRUCTION); UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", use_count); RecordDictionaryFate(fate); DCHECK(load_times_.count(server_hash) == 1); base::Time now = clock_->Now(); base::TimeDelta dict_lifetime = now - load_times_[server_hash]; consumed_byte_seconds_.push_back(size * dict_lifetime.InMilliseconds()); load_times_.erase(server_hash); } SdchOwner::SdchOwner(SdchManager* sdch_manager, URLRequestContext* context) : manager_(sdch_manager), fetcher_(new SdchDictionaryFetcher(context)), total_dictionary_bytes_(0), clock_(new base::DefaultClock), max_total_dictionary_size_(kMaxTotalDictionarySize), min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch), memory_pressure_listener_( base::Bind(&SdchOwner::OnMemoryPressure, // Because |memory_pressure_listener_| is owned by // SdchOwner, the SdchOwner object will be available // for the lifetime of |memory_pressure_listener_|. base::Unretained(this))), in_memory_pref_store_(new ValueMapPrefStore()), external_pref_store_(nullptr), pref_store_(in_memory_pref_store_.get()), creation_time_(clock_->Now()) { manager_->AddObserver(this); InitializePrefStore(pref_store_); } SdchOwner::~SdchOwner() { for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); it.Advance()) { int new_uses = it.use_count() - use_counts_at_load_[it.server_hash()]; DictionaryFate fate = IsPersistingDictionaries() ? DICTIONARY_FATE_UNLOAD_FOR_DESTRUCTION : DICTIONARY_FATE_EVICT_FOR_DESTRUCTION; RecordDictionaryEvictionOrUnload(it.server_hash(), it.size(), new_uses, fate); } manager_->RemoveObserver(this); // This object only observes the external store during loading, // i.e. before it's made the default preferences store. if (external_pref_store_) external_pref_store_->RemoveObserver(this); int64 object_lifetime = (clock_->Now() - creation_time_).InMilliseconds(); for (const auto& val : consumed_byte_seconds_) { if (object_lifetime > 0) { // Objects that are created and immediately destroyed don't add any memory // pressure over time (and also cause a crash here). UMA_HISTOGRAM_MEMORY_KB("Sdch3.TimeWeightedMemoryUse", val / object_lifetime); } } } void SdchOwner::EnablePersistentStorage(PersistentPrefStore* pref_store) { DCHECK(!external_pref_store_); external_pref_store_ = pref_store; external_pref_store_->AddObserver(this); if (external_pref_store_->IsInitializationComplete()) OnInitializationCompleted(true); } void SdchOwner::SetMaxTotalDictionarySize(size_t max_total_dictionary_size) { max_total_dictionary_size_ = max_total_dictionary_size; } void SdchOwner::SetMinSpaceForDictionaryFetch( size_t min_space_for_dictionary_fetch) { min_space_for_dictionary_fetch_ = min_space_for_dictionary_fetch; } void SdchOwner::OnDictionaryFetched(base::Time last_used, int use_count, const std::string& dictionary_text, const GURL& dictionary_url, const BoundNetLog& net_log, bool was_from_cache) { struct DictionaryItem { base::Time last_used; std::string server_hash; int use_count; size_t dictionary_size; DictionaryItem() : use_count(0), dictionary_size(0) {} DictionaryItem(const base::Time& last_used, const std::string& server_hash, int use_count, size_t dictionary_size) : last_used(last_used), server_hash(server_hash), use_count(use_count), dictionary_size(dictionary_size) {} DictionaryItem(const DictionaryItem& rhs) = default; DictionaryItem& operator=(const DictionaryItem& rhs) = default; bool operator<(const DictionaryItem& rhs) const { return last_used < rhs.last_used; } }; if (!was_from_cache) UMA_HISTOGRAM_COUNTS("Sdch3.NetworkBytesSpent", dictionary_text.size()); // Figure out if there is space for the incoming dictionary; evict // stale dictionaries if needed to make space. std::vector stale_dictionary_list; size_t recoverable_bytes = 0; base::Time now(clock_->Now()); // Dictionaries whose last used time is before |stale_boundary| are candidates // for eviction if necessary. base::Time stale_boundary( now - base::TimeDelta::FromHours(kFreshnessLifetimeHours)); // Dictionaries that have never been used and are from before // |never_used_stale_boundary| are candidates for eviction if necessary. base::Time never_used_stale_boundary( now - base::TimeDelta::FromHours(kNeverUsedFreshnessLifetimeHours)); for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); it.Advance()) { if (it.last_used() < stale_boundary || (it.use_count() == 0 && it.last_used() < never_used_stale_boundary)) { stale_dictionary_list.push_back(DictionaryItem( it.last_used(), it.server_hash(), it.use_count(), it.size())); recoverable_bytes += it.size(); } } if (total_dictionary_bytes_ + dictionary_text.size() - recoverable_bytes > max_total_dictionary_size_) { RecordDictionaryFate(DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE); SdchManager::SdchErrorRecovery(SDCH_DICTIONARY_NO_ROOM); net_log.AddEvent(NetLog::TYPE_SDCH_DICTIONARY_ERROR, base::Bind(&NetLogSdchDictionaryFetchProblemCallback, SDCH_DICTIONARY_NO_ROOM, dictionary_url, true)); return; } // Add the new dictionary. This is done before removing the stale // dictionaries so that no state change will occur if dictionary addition // fails. std::string server_hash; SdchProblemCode rv = manager_->AddSdchDictionary( dictionary_text, dictionary_url, &server_hash); if (rv != SDCH_OK) { RecordDictionaryFate(DICTIONARY_FATE_FETCH_MANAGER_REFUSED); SdchManager::SdchErrorRecovery(rv); net_log.AddEvent(NetLog::TYPE_SDCH_DICTIONARY_ERROR, base::Bind(&NetLogSdchDictionaryFetchProblemCallback, rv, dictionary_url, true)); return; } base::DictionaryValue* pref_dictionary_map = GetPersistentStoreDictionaryMap(pref_store_); ScopedPrefNotifier scoped_pref_notifier(pref_store_); // Remove the old dictionaries. std::sort(stale_dictionary_list.begin(), stale_dictionary_list.end()); size_t avail_bytes = max_total_dictionary_size_ - total_dictionary_bytes_; auto stale_it = stale_dictionary_list.begin(); while (avail_bytes < dictionary_text.size() && stale_it != stale_dictionary_list.end()) { manager_->RemoveSdchDictionary(stale_it->server_hash); DCHECK(pref_dictionary_map->HasKey(stale_it->server_hash)); bool success = pref_dictionary_map->RemoveWithoutPathExpansion( stale_it->server_hash, nullptr); DCHECK(success); avail_bytes += stale_it->dictionary_size; int new_uses = stale_it->use_count - use_counts_at_load_[stale_it->server_hash]; RecordDictionaryEvictionOrUnload(stale_it->server_hash, stale_it->dictionary_size, new_uses, DICTIONARY_FATE_EVICT_FOR_DICT); ++stale_it; } DCHECK_GE(avail_bytes, dictionary_text.size()); RecordDictionaryFate( // Distinguish between loads triggered by network responses and // loads triggered by persistence. last_used.is_null() ? DICTIONARY_FATE_ADD_RESPONSE_TRIGGERED : DICTIONARY_FATE_ADD_PERSISTENCE_TRIGGERED); // If a dictionary has never been used, its dictionary addition time // is recorded as its last used time. Never used dictionaries are treated // specially in the freshness logic. if (last_used.is_null()) last_used = clock_->Now(); total_dictionary_bytes_ += dictionary_text.size(); // Record the addition in the pref store. scoped_ptr dictionary_description( new base::DictionaryValue()); dictionary_description->SetString(kDictionaryUrlKey, dictionary_url.spec()); dictionary_description->SetDouble(kDictionaryLastUsedKey, last_used.ToDoubleT()); dictionary_description->SetInteger(kDictionaryUseCountKey, use_count); dictionary_description->SetInteger(kDictionarySizeKey, dictionary_text.size()); pref_dictionary_map->Set(server_hash, dictionary_description.Pass()); load_times_[server_hash] = clock_->Now(); } void SdchOwner::OnDictionaryAdded(const GURL& dictionary_url, const std::string& server_hash) { } void SdchOwner::OnDictionaryRemoved(const std::string& server_hash) { } void SdchOwner::OnDictionaryUsed(const std::string& server_hash) { base::Time now(clock_->Now()); base::DictionaryValue* pref_dictionary_map = GetPersistentStoreDictionaryMap(pref_store_); ScopedPrefNotifier scoped_pref_notifier(pref_store_); base::Value* value = nullptr; bool success = pref_dictionary_map->Get(server_hash, &value); if (!success) { // SdchManager::GetDictionarySet() pins the referenced dictionaries in // memory past a possible deletion. For this reason, OnDictionaryUsed() // notifications may occur after SdchOwner thinks that dictionaries // have been deleted. SdchManager::SdchErrorRecovery(SDCH_DICTIONARY_USED_AFTER_DELETION); return; } base::DictionaryValue* specific_dictionary_map = nullptr; success = value->GetAsDictionary(&specific_dictionary_map); DCHECK(success); double last_used_seconds_since_epoch = 0.0; success = specific_dictionary_map->GetDouble(kDictionaryLastUsedKey, &last_used_seconds_since_epoch); DCHECK(success); int use_count = 0; success = specific_dictionary_map->GetInteger(kDictionaryUseCountKey, &use_count); DCHECK(success); if (use_counts_at_load_.count(server_hash) == 0) { use_counts_at_load_[server_hash] = use_count; } base::TimeDelta time_since_last_used(now - base::Time::FromDoubleT(last_used_seconds_since_epoch)); // TODO(rdsmith): Distinguish between "Never used" and "Actually not // touched for 48 hours". UMA_HISTOGRAM_CUSTOM_TIMES( "Sdch3.UsageInterval", use_count ? time_since_last_used : base::TimeDelta::FromHours(48), base::TimeDelta(), base::TimeDelta::FromHours(48), 50); specific_dictionary_map->SetDouble(kDictionaryLastUsedKey, now.ToDoubleT()); specific_dictionary_map->SetInteger(kDictionaryUseCountKey, use_count + 1); } void SdchOwner::OnGetDictionary(const GURL& request_url, const GURL& dictionary_url) { base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1)); size_t avail_bytes = 0; for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); it.Advance()) { if (it.last_used() < stale_boundary) avail_bytes += it.size(); } // Don't initiate the fetch if we wouldn't be able to store any // reasonable dictionary. // TODO(rdsmith): Maybe do a HEAD request to figure out how much // storage we'd actually need? if (max_total_dictionary_size_ < (total_dictionary_bytes_ - avail_bytes + min_space_for_dictionary_fetch_)) { RecordDictionaryFate(DICTIONARY_FATE_GET_IGNORED); // TODO(rdsmith): Log a net-internals error. This requires // SdchManager to forward the URLRequest that detected the // Get-Dictionary header to its observers, which is tricky // because SdchManager is layered underneath URLRequest. return; } fetcher_->Schedule(dictionary_url, base::Bind(&SdchOwner::OnDictionaryFetched, // SdchOwner will outlive its member variables. base::Unretained(this), base::Time(), 0)); } void SdchOwner::OnClearDictionaries() { total_dictionary_bytes_ = 0; fetcher_->Cancel(); InitializePrefStore(pref_store_); } void SdchOwner::OnPrefValueChanged(const std::string& key) { } void SdchOwner::OnInitializationCompleted(bool succeeded) { PersistentPrefStore::PrefReadError error = external_pref_store_->GetReadError(); // Errors on load are self-correcting; if dictionaries were not // persisted from the last instance of the browser, they will be // faulted in by user action over time. However, if a load error // means that the dictionary information won't be able to be persisted, // the in memory pref store is left in place. if (!succeeded) { // Failure means a write failed, since read failures are recoverable. DCHECK_NE( error, PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE); DCHECK_NE(error, PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM); LOG(ERROR) << "Pref store write failed: " << error; external_pref_store_->RemoveObserver(this); external_pref_store_ = nullptr; RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_WRITE_FAILED); return; } switch (external_pref_store_->GetReadError()) { case PersistentPrefStore::PREF_READ_ERROR_NONE: break; case PersistentPrefStore::PREF_READ_ERROR_NO_FILE: // First time reading; the file will be created. RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_NO_FILE); break; case PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE: case PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE: case PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER: case PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED: case PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT: RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_READ_FAILED); break; case PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED: case PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED: case PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE: case PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM: // Shouldn't ever happen. ACCESS_DENIED and FILE_NOT_SPECIFIED should // imply !succeeded, and TASK_INCOMPLETE should never be delivered. NOTREACHED(); break; } // Load in what was stored before chrome exited previously. const base::Value* sdch_persistence_value = nullptr; const base::DictionaryValue* sdch_persistence_dictionary = nullptr; // The GetPersistentStore() routine above assumes data formatted // according to the schema described at the top of this file. Since // this data comes from disk, to avoid disk corruption resulting in // persistent chrome errors this code avoids those assupmtions. if (external_pref_store_->GetValue(kPreferenceName, &sdch_persistence_value) && sdch_persistence_value->GetAsDictionary(&sdch_persistence_dictionary)) { SchedulePersistedDictionaryLoads(*sdch_persistence_dictionary); } // Reset the persistent store and update it with the accumulated // information from the local store. InitializePrefStore(external_pref_store_); ScopedPrefNotifier scoped_pref_notifier(external_pref_store_); GetPersistentStoreDictionaryMap(external_pref_store_) ->Swap(GetPersistentStoreDictionaryMap(in_memory_pref_store_.get())); // This object can stop waiting on (i.e. observing) the external preference // store and switch over to using it as the primary preference store. pref_store_ = external_pref_store_; external_pref_store_->RemoveObserver(this); external_pref_store_ = nullptr; in_memory_pref_store_ = nullptr; } void SdchOwner::SetClockForTesting(scoped_ptr clock) { clock_ = clock.Pass(); } int SdchOwner::GetDictionaryCountForTesting() const { int count = 0; for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); it.Advance()) { count++; } return count; } bool SdchOwner::HasDictionaryFromURLForTesting(const GURL& url) const { for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); it.Advance()) { if (it.url() == url) return true; } return false; } void SdchOwner::SetFetcherForTesting( scoped_ptr fetcher) { fetcher_.reset(fetcher.release()); } void SdchOwner::OnMemoryPressure( base::MemoryPressureListener::MemoryPressureLevel level) { DCHECK_NE(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, level); for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); it.Advance()) { int new_uses = it.use_count() - use_counts_at_load_[it.server_hash()]; RecordDictionaryEvictionOrUnload(it.server_hash(), it.size(), new_uses, DICTIONARY_FATE_EVICT_FOR_MEMORY); } // TODO(rdsmith): Make a distinction between moderate and critical // memory pressure. manager_->ClearData(); } bool SdchOwner::SchedulePersistedDictionaryLoads( const base::DictionaryValue& persisted_info) { // Any schema error will result in dropping the persisted info. int version = 0; if (!persisted_info.GetInteger(kVersionKey, &version)) return false; // Any version mismatch will result in dropping the persisted info; // it will be faulted in at small performance cost as URLs using // dictionaries for encoding are visited. if (version != kVersion) return false; const base::DictionaryValue* dictionary_set = nullptr; if (!persisted_info.GetDictionary(kDictionariesKey, &dictionary_set)) return false; // Any formatting error will result in skipping that particular // dictionary. for (base::DictionaryValue::Iterator dict_it(*dictionary_set); !dict_it.IsAtEnd(); dict_it.Advance()) { const base::DictionaryValue* dict_info = nullptr; if (!dict_it.value().GetAsDictionary(&dict_info)) continue; std::string url_string; if (!dict_info->GetString(kDictionaryUrlKey, &url_string)) continue; GURL dict_url(url_string); double last_used; if (!dict_info->GetDouble(kDictionaryLastUsedKey, &last_used)) continue; int use_count; if (!dict_info->GetInteger(kDictionaryUseCountKey, &use_count)) continue; fetcher_->ScheduleReload( dict_url, base::Bind(&SdchOwner::OnDictionaryFetched, // SdchOwner will outlive its member variables. base::Unretained(this), base::Time::FromDoubleT(last_used), use_count)); } return true; } bool SdchOwner::IsPersistingDictionaries() const { return in_memory_pref_store_.get() != nullptr; } } // namespace net