|  | // 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 <utility> | 
|  |  | 
|  | #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<base::DictionaryValue> empty_store(new base::DictionaryValue); | 
|  | empty_store->SetInteger(kVersionKey, kVersion); | 
|  | empty_store->Set(kDictionariesKey, | 
|  | make_scoped_ptr(new base::DictionaryValue)); | 
|  | store->SetValue(kPreferenceName, std::move(empty_store), | 
|  | 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->GetWeakPtr()), | 
|  | fetcher_(new SdchDictionaryFetcher(context)), | 
|  | total_dictionary_bytes_(0), | 
|  | clock_(new base::DefaultClock), | 
|  | max_total_dictionary_size_(kMaxTotalDictionarySize), | 
|  | min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch), | 
|  | #if defined(OS_CHROMEOS) | 
|  | // For debugging http://crbug.com/454198; remove when resolved. | 
|  | destroyed_(0), | 
|  | #endif | 
|  | 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()) { | 
|  | #if defined(OS_CHROMEOS) | 
|  | // For debugging http://crbug.com/454198; remove when resolved. | 
|  | CHECK(clock_.get()); | 
|  | #endif | 
|  | manager_->AddObserver(this); | 
|  | InitializePrefStore(pref_store_); | 
|  | } | 
|  |  | 
|  | SdchOwner::~SdchOwner() { | 
|  | #if defined(OS_CHROMEOS) | 
|  | // For debugging http://crbug.com/454198; remove when resolved. | 
|  | CHECK_EQ(0u, destroyed_); | 
|  | CHECK(clock_.get()); | 
|  | CHECK(manager_.get()); | 
|  | #endif | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | #if defined(OS_CHROMEOS) | 
|  | destroyed_ = 0xdeadbeef; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | 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 defined(OS_CHROMEOS) | 
|  | // For debugging http://crbug.com/454198; remove when resolved. | 
|  | CHECK_EQ(0u, destroyed_); | 
|  | CHECK(clock_.get()); | 
|  | #endif | 
|  |  | 
|  | 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<DictionaryItem> 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 defined(OS_CHROMEOS) | 
|  | // For debugging http://crbug.com/454198; remove when resolved. | 
|  | CHECK_EQ(0u, destroyed_); | 
|  | CHECK(clock_.get()); | 
|  | #endif | 
|  |  | 
|  | 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(); | 
|  |  | 
|  | #if defined(OS_CHROMEOS) | 
|  | // For debugging http://crbug.com/454198; remove when resolved. | 
|  | CHECK_EQ(0u, destroyed_); | 
|  | CHECK(clock_.get()); | 
|  | #endif | 
|  |  | 
|  | // Record the addition in the pref store. | 
|  | scoped_ptr<base::DictionaryValue> 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); | 
|  | // TODO(rdsmith); Switch back to DCHECK() after http://crbug.com/454198 is | 
|  | // resolved. | 
|  | CHECK(success); | 
|  |  | 
|  | double last_used_seconds_since_epoch = 0.0; | 
|  | success = specific_dictionary_map->GetDouble(kDictionaryLastUsedKey, | 
|  | &last_used_seconds_since_epoch); | 
|  | // TODO(rdsmith); Switch back to DCHECK() after http://crbug.com/454198 is | 
|  | // resolved. | 
|  | CHECK(success); | 
|  | int use_count = 0; | 
|  | success = | 
|  | specific_dictionary_map->GetInteger(kDictionaryUseCountKey, &use_count); | 
|  | // TODO(rdsmith); Switch back to DCHECK() after http://crbug.com/454198 is | 
|  | // resolved. | 
|  | CHECK(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) { | 
|  | #if defined(OS_CHROMEOS) | 
|  | // For debugging http://crbug.com/454198; remove when resolved. | 
|  | char url_buf[128]; | 
|  | if (0u != destroyed_ || !clock_.get()) { | 
|  | base::strlcpy(url_buf, request_url.spec().c_str(), arraysize(url_buf)); | 
|  | } | 
|  | base::debug::Alias(url_buf); | 
|  |  | 
|  | CHECK_EQ(0u, destroyed_); | 
|  | CHECK(clock_.get()); | 
|  | #endif | 
|  |  | 
|  | 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: | 
|  | case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_IO: | 
|  | case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY: | 
|  | case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION: | 
|  | 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<base::Clock> clock) { | 
|  | clock_ = clock.Pass(); | 
|  |  | 
|  | #if defined(OS_CHROMEOS) | 
|  | // For debugging http://crbug.com/454198; remove when resolved. | 
|  | CHECK_EQ(0u, destroyed_); | 
|  | CHECK(clock_.get()); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | 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<SdchDictionaryFetcher> 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 |