| Index: net/sdch/sdch_owner.cc
|
| diff --git a/net/sdch/sdch_owner.cc b/net/sdch/sdch_owner.cc
|
| index b0e440569646d0aa8ebd30d2a9ea05297a766b9c..f61710b24c2de1d13ecc621ed221c6ccd11cdd5a 100644
|
| --- a/net/sdch/sdch_owner.cc
|
| +++ b/net/sdch/sdch_owner.cc
|
| @@ -5,11 +5,17 @@
|
| #include "net/sdch/sdch_owner.h"
|
|
|
| #include "base/bind.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/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 DictionaryFate {
|
| @@ -26,8 +32,9 @@ enum DictionaryFate {
|
| // A successful fetch was refused by the SdchManager.
|
| DICTIONARY_FATE_FETCH_MANAGER_REFUSED = 4,
|
|
|
| - // A dictionary was successfully added.
|
| - DICTIONARY_FATE_ADDED = 5,
|
| + // A dictionary was successfully added based on
|
| + // a Get-Dictionary header in a response.
|
| + DICTIONARY_FATE_ADD_RESPONSE_TRIGGERRED = 5,
|
|
|
| // A dictionary was evicted by an incoming dict.
|
| DICTIONARY_FATE_EVICT_FOR_DICT = 6,
|
| @@ -38,13 +45,42 @@ enum DictionaryFate {
|
| // A dictionary was evicted on destruction.
|
| DICTIONARY_FATE_EVICT_FOR_DESTRUCTION = 8,
|
|
|
| - DICTIONARY_FATE_MAX = 9
|
| + // A dictionary was successfully added based on
|
| + // persistence from a previous browser revision.
|
| + DICTIONARY_FATE_ADD_PERSISTENCE_TRIGGERED = 9,
|
| +
|
| + DICTIONARY_FATE_MAX = 10
|
| };
|
|
|
| +enum PersistenceDrop {
|
| + // File didn't exist; is being created.
|
| + PERSISTENCE_DROP_CREATION = 1,
|
| +
|
| + // Error reading in information, but should be able to write.
|
| + PERSISTENCE_DROP_READ = 2,
|
| +
|
| + // Error leading to abort on attempted persistence.
|
| + PERSISTENCE_DROP_WRITE = 3,
|
| +
|
| + PERSISTENCE_DROP_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 haven't ever been used only stay fresh for one hour.
|
| +const int kNeverUsedFreshnessLifetimeHours = 1;
|
| +
|
| void RecordDictionaryFate(enum DictionaryFate fate) {
|
| UMA_HISTOGRAM_ENUMERATION("Sdch3.DictionaryFate", fate, DICTIONARY_FATE_MAX);
|
| }
|
|
|
| +void RecordPersistenceInfoDrop(enum PersistenceDrop drop_type) {
|
| + UMA_HISTOGRAM_ENUMERATION("Sdch3.PersistenceDrop", drop_type,
|
| + PERSISTENCE_DROP_MAX);
|
| +}
|
| +
|
| void RecordDictionaryEviction(int use_count, DictionaryFate fate) {
|
| DCHECK(fate == DICTIONARY_FATE_EVICT_FOR_DICT ||
|
| fate == DICTIONARY_FATE_EVICT_FOR_MEMORY ||
|
| @@ -54,9 +90,159 @@ void RecordDictionaryEviction(int use_count, DictionaryFate fate) {
|
| RecordDictionaryFate(fate);
|
| }
|
|
|
| -} // namespace
|
| +// 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 kVersionName[] = "version";
|
| +const char kDictionariesName[] = "dictionaries";
|
| +const char kDictionaryUrlName[] = "url";
|
| +const char kDictionaryLastUsedName[] = "last_used";
|
| +const char kDictionaryUseCountName[] = "use_count";
|
| +const char kDictionarySizeName[] = "size";
|
| +
|
| +const int kVersion = 1;
|
| +
|
| +// This function returns store[kPreferenceName/kDictionaries]. 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(kDictionariesName,
|
| + &dictionary_list_dictionary);
|
| + DCHECK(success);
|
| + DCHECK(dictionary_list_dictionary);
|
| +
|
| + return dictionary_list_dictionary;
|
| +}
|
|
|
| -namespace net {
|
| +// This function initializes a pref store with an empty version of the
|
| +// above schema, removing anything previously in the store under
|
| +// kPreferenceName.
|
| +void InitializePersistentStore(WriteablePrefStore* store) {
|
| + base::DictionaryValue* empty_store(new base::DictionaryValue);
|
| + empty_store->SetInteger(kVersionName, kVersion);
|
| + empty_store->Set(kDictionariesName,
|
| + make_scoped_ptr(new base::DictionaryValue));
|
| + store->SetValue(kPreferenceName, empty_store);
|
| +}
|
| +
|
| +// 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.
|
| +class DictionaryPreferenceIterator {
|
| + public:
|
| + explicit DictionaryPreferenceIterator(WriteablePrefStore* pref_store);
|
| +
|
| + bool IsAtEnd() const { return dictionary_iterator_.IsAtEnd(); }
|
| + void Advance() { dictionary_iterator_.Advance(); }
|
| +
|
| + const std::string& ServerHash() const;
|
| + GURL Url() const;
|
| + base::Time LastUsed() const;
|
| + int UseCount() const;
|
| + int Size() const;
|
| +
|
| + private:
|
| + const base::DictionaryValue* ValueAsDict() const;
|
| +
|
| + base::DictionaryValue::Iterator dictionary_iterator_;
|
| +};
|
| +
|
| +DictionaryPreferenceIterator::DictionaryPreferenceIterator(
|
| + WriteablePrefStore* pref_store)
|
| + : dictionary_iterator_(*GetPersistentStoreDictionaryMap(pref_store)) {
|
| +}
|
| +
|
| +const std::string& DictionaryPreferenceIterator::ServerHash() const {
|
| + return dictionary_iterator_.key();
|
| +}
|
| +
|
| +base::Time DictionaryPreferenceIterator::LastUsed() const {
|
| + double last_used_seconds_from_epoch;
|
| + bool success = ValueAsDict()->GetDouble(kDictionaryLastUsedName,
|
| + &last_used_seconds_from_epoch);
|
| + DCHECK(success);
|
| + return base::Time::FromDoubleT(last_used_seconds_from_epoch);
|
| +}
|
| +
|
| +int DictionaryPreferenceIterator::UseCount() const {
|
| + int use_count;
|
| + bool success = ValueAsDict()->GetInteger(kDictionaryUseCountName, &use_count);
|
| + DCHECK(success);
|
| +
|
| + return use_count;
|
| +}
|
| +
|
| +GURL DictionaryPreferenceIterator::Url() const {
|
| + std::string url_spec;
|
| + bool success = ValueAsDict()->GetString(kDictionaryUrlName, &url_spec);
|
| + DCHECK(success);
|
| +
|
| + return GURL(url_spec);
|
| +}
|
| +
|
| +int DictionaryPreferenceIterator::Size() const {
|
| + int size;
|
| + bool success = ValueAsDict()->GetInteger(kDictionarySizeName, &size);
|
| + DCHECK(success);
|
| +
|
| + return size;
|
| +}
|
| +
|
| +const base::DictionaryValue* DictionaryPreferenceIterator::ValueAsDict() const {
|
| + const base::DictionaryValue* individual_dictionary = nullptr;
|
| + bool success =
|
| + dictionary_iterator_.value().GetAsDictionary(&individual_dictionary);
|
| + DCHECK(success);
|
| +
|
| + return individual_dictionary;
|
| +}
|
| +
|
| +// Triggers a ReportValueChanged() on the specified WriteablePrefStore
|
| +// when the object goes out of scope. The
|
| +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); }
|
| +
|
| + private:
|
| + WriteablePrefStore* pref_store_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ScopedPrefNotifier);
|
| +};
|
| +
|
| +} // namespace
|
|
|
| // Adjust SDCH limits downwards for mobile.
|
| #if defined(OS_ANDROID) || defined(OS_IOS)
|
| @@ -76,16 +262,14 @@ const size_t SdchOwner::kMinSpaceForDictionaryFetch = 50 * 1000;
|
| SdchOwner::SdchOwner(net::SdchManager* sdch_manager,
|
| net::URLRequestContext* context)
|
| : manager_(sdch_manager),
|
| - fetcher_(context,
|
| - base::Bind(&SdchOwner::OnDictionaryFetched,
|
| - // Because |fetcher_| is owned by SdchOwner, the
|
| - // SdchOwner object will be available for the lifetime
|
| - // of |fetcher_|.
|
| - base::Unretained(this))),
|
| + fetcher_(context),
|
| total_dictionary_bytes_(0),
|
| clock_(new base::DefaultClock),
|
| max_total_dictionary_size_(kMaxTotalDictionarySize),
|
| min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch),
|
| + in_memory_pref_store_(new ValueMapPrefStore()),
|
| + external_pref_store_(nullptr),
|
| + pref_store_(in_memory_pref_store_.get()),
|
| memory_pressure_listener_(
|
| base::Bind(&SdchOwner::OnMemoryPressure,
|
| // Because |memory_pressure_listener_| is owned by
|
| @@ -93,15 +277,30 @@ SdchOwner::SdchOwner(net::SdchManager* sdch_manager,
|
| // for the lifetime of |memory_pressure_listener_|.
|
| base::Unretained(this))) {
|
| manager_->AddObserver(this);
|
| + InitializePersistentStore(pref_store_);
|
| }
|
|
|
| SdchOwner::~SdchOwner() {
|
| - for (auto it = local_dictionary_info_.begin();
|
| - it != local_dictionary_info_.end(); ++it) {
|
| - RecordDictionaryEviction(it->second.use_count,
|
| + for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
|
| + it.Advance()) {
|
| + RecordDictionaryEviction(it.UseCount(),
|
| DICTIONARY_FATE_EVICT_FOR_DESTRUCTION);
|
| }
|
| 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);
|
| +}
|
| +
|
| +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) {
|
| @@ -113,7 +312,14 @@ void SdchOwner::SetMinSpaceForDictionaryFetch(
|
| min_space_for_dictionary_fetch_ = min_space_for_dictionary_fetch;
|
| }
|
|
|
| -void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text,
|
| +// static
|
| +std::string SdchOwner::PreferenceName() {
|
| + return kPreferenceName;
|
| +}
|
| +
|
| +void SdchOwner::OnDictionaryFetched(base::Time last_used,
|
| + int use_count,
|
| + const std::string& dictionary_text,
|
| const GURL& dictionary_url,
|
| const net::BoundNetLog& net_log) {
|
| struct DictionaryItem {
|
| @@ -138,16 +344,23 @@ void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text,
|
| }
|
| };
|
|
|
| + // 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 stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1));
|
| - for (auto used_it = local_dictionary_info_.begin();
|
| - used_it != local_dictionary_info_.end(); ++used_it) {
|
| - if (used_it->second.last_used < stale_boundary) {
|
| - stale_dictionary_list.push_back(
|
| - DictionaryItem(used_it->second.last_used, used_it->first,
|
| - used_it->second.use_count, used_it->second.size));
|
| - recoverable_bytes += used_it->second.size;
|
| + base::Time now(clock_->Now());
|
| + base::Time stale_boundary(
|
| + now - base::TimeDelta::FromHours(kFreshnessLifetimeHours));
|
| + base::Time never_used_stale_boundary(
|
| + now - base::TimeDelta::FromHours(kNeverUsedFreshnessLifetimeHours));
|
| + for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
|
| + it.Advance()) {
|
| + if (it.LastUsed() < stale_boundary ||
|
| + (it.UseCount() == 0 && it.LastUsed() < never_used_stale_boundary)) {
|
| + stale_dictionary_list.push_back(DictionaryItem(
|
| + it.LastUsed(), it.ServerHash(), it.UseCount(), it.Size()));
|
| + recoverable_bytes += it.Size();
|
| }
|
| }
|
|
|
| @@ -161,52 +374,110 @@ void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text,
|
| return;
|
| }
|
|
|
| - // Evict from oldest to youngest until we have space.
|
| + // 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;
|
| + net::SdchProblemCode rv = manager_->AddSdchDictionary(
|
| + dictionary_text, dictionary_url, &server_hash);
|
| + if (rv != net::SDCH_OK) {
|
| + RecordDictionaryFate(DICTIONARY_FATE_FETCH_MANAGER_REFUSED);
|
| + net::SdchManager::SdchErrorRecovery(rv);
|
| + net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR,
|
| + base::Bind(&net::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;
|
| +
|
| RecordDictionaryEviction(stale_it->use_count,
|
| DICTIONARY_FATE_EVICT_FOR_DICT);
|
| - local_dictionary_info_.erase(stale_it->server_hash);
|
| - avail_bytes += stale_it->dictionary_size;
|
| +
|
| ++stale_it;
|
| }
|
| DCHECK(avail_bytes >= dictionary_text.size());
|
|
|
| - std::string server_hash;
|
| - net::SdchProblemCode rv = manager_->AddSdchDictionary(
|
| - dictionary_text, dictionary_url, &server_hash);
|
| - if (rv != net::SDCH_OK) {
|
| - RecordDictionaryFate(DICTIONARY_FATE_FETCH_MANAGER_REFUSED);
|
| - net::SdchManager::SdchErrorRecovery(rv);
|
| - net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR,
|
| - base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback,
|
| - rv, dictionary_url, true));
|
| - return;
|
| - }
|
| + RecordDictionaryFate(
|
| + // Distinguish between loads triggerred by network responses and
|
| + // loads triggerred by persistence.
|
| + last_used.is_null() ? DICTIONARY_FATE_ADD_RESPONSE_TRIGGERRED
|
| + : DICTIONARY_FATE_ADD_PERSISTENCE_TRIGGERED);
|
|
|
| - RecordDictionaryFate(DICTIONARY_FATE_ADDED);
|
| + // 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();
|
|
|
| - DCHECK(local_dictionary_info_.end() ==
|
| - local_dictionary_info_.find(server_hash));
|
| total_dictionary_bytes_ += dictionary_text.size();
|
| - local_dictionary_info_[server_hash] = DictionaryInfo(
|
| - // Set the time last used to something to avoid thrashing, but not recent,
|
| - // to avoid taking too much time/space with useless dictionaries/one-off
|
| - // visits to web sites.
|
| - clock_->Now() - base::TimeDelta::FromHours(23), dictionary_text.size());
|
| +
|
| + // Record the addition in the pref store.
|
| + scoped_ptr<base::DictionaryValue> dictionary_description(
|
| + new base::DictionaryValue());
|
| + dictionary_description->SetString(kDictionaryUrlName, dictionary_url.spec());
|
| + dictionary_description->SetDouble(kDictionaryLastUsedName,
|
| + last_used.ToDoubleT());
|
| + dictionary_description->SetInteger(kDictionaryUseCountName, use_count);
|
| + dictionary_description->SetInteger(kDictionarySizeName,
|
| + dictionary_text.size());
|
| + pref_dictionary_map->Set(server_hash, dictionary_description.Pass());
|
| }
|
|
|
| void SdchOwner::OnDictionaryUsed(SdchManager* manager,
|
| const std::string& server_hash) {
|
| - auto it = local_dictionary_info_.find(server_hash);
|
| - DCHECK(local_dictionary_info_.end() != it);
|
| -
|
| - it->second.last_used = clock_->Now();
|
| - it->second.use_count++;
|
| + 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);
|
| + // TODO(rdsmith): Is the behavior of the SdchManager certain enough for
|
| + // the following DCHECKs?
|
| + DCHECK(success);
|
| + base::DictionaryValue* specific_dictionary_map = nullptr;
|
| + success = value->GetAsDictionary(&specific_dictionary_map);
|
| + DCHECK(success);
|
| +
|
| + double last_used_time = 0.0;
|
| + success = specific_dictionary_map->GetDouble(kDictionaryLastUsedName,
|
| + &last_used_time);
|
| + DCHECK(success);
|
| + int use_count = 0;
|
| + success =
|
| + specific_dictionary_map->GetInteger(kDictionaryUseCountName, &use_count);
|
| + DCHECK(success);
|
| +
|
| + base::TimeDelta time_since_last_used(now -
|
| + base::Time::FromDoubleT(last_used_time));
|
| +
|
| + // 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(kDictionaryLastUsedName, now.ToDoubleT());
|
| + specific_dictionary_map->SetInteger(kDictionaryUseCountName, use_count + 1);
|
| }
|
|
|
| void SdchOwner::OnGetDictionary(net::SdchManager* manager,
|
| @@ -214,10 +485,10 @@ void SdchOwner::OnGetDictionary(net::SdchManager* manager,
|
| const GURL& dictionary_url) {
|
| base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1));
|
| size_t avail_bytes = 0;
|
| - for (auto it = local_dictionary_info_.begin();
|
| - it != local_dictionary_info_.end(); ++it) {
|
| - if (it->second.last_used < stale_boundary)
|
| - avail_bytes += it->second.size;
|
| + for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
|
| + it.Advance()) {
|
| + if (it.LastUsed() < stale_boundary)
|
| + avail_bytes += it.Size();
|
| }
|
|
|
| // Don't initiate the fetch if we wouldn't be able to store any
|
| @@ -234,13 +505,100 @@ void SdchOwner::OnGetDictionary(net::SdchManager* manager,
|
| return;
|
| }
|
|
|
| - fetcher_.Schedule(dictionary_url);
|
| + fetcher_.Schedule(dictionary_url,
|
| + base::Bind(&SdchOwner::OnDictionaryFetched,
|
| + // SdchOwner will outlive its member variables.
|
| + base::Unretained(this), base::Time(), 0));
|
| }
|
|
|
| void SdchOwner::OnClearDictionaries(net::SdchManager* manager) {
|
| total_dictionary_bytes_ = 0;
|
| - local_dictionary_info_.clear();
|
| fetcher_.Cancel();
|
| +
|
| + InitializePersistentStore(pref_store_);
|
| +}
|
| +
|
| +void SdchOwner::OnPrefValueChanged(const std::string& key) {
|
| +}
|
| +
|
| +void SdchOwner::OnInitializationCompleted(bool succeeded) {
|
| + // 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.
|
| + switch (external_pref_store_->GetReadError()) {
|
| + case PersistentPrefStore::PREF_READ_ERROR_NONE:
|
| + if (succeeded)
|
| + break;
|
| +
|
| + case PersistentPrefStore::PREF_READ_ERROR_NO_FILE:
|
| + // First time reading; the file will be created.
|
| + if (succeeded) {
|
| + RecordPersistenceInfoDrop(PERSISTENCE_DROP_CREATION);
|
| + 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:
|
| + // If the load was marked as succeeding (meaning the directory
|
| + // was present), even if the file wasn't read, it can be written.
|
| + if (succeeded) {
|
| + RecordPersistenceInfoDrop(PERSISTENCE_DROP_READ);
|
| + break;
|
| + }
|
| +
|
| + case PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED:
|
| + case PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED:
|
| + // Unrecoverable failure; we default to the internal pref store.
|
| + external_pref_store_->RemoveObserver(this);
|
| + external_pref_store_ = nullptr;
|
| + RecordPersistenceInfoDrop(PERSISTENCE_DROP_WRITE);
|
| + return;
|
| +
|
| + case PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE:
|
| + case PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM:
|
| + // Shouldn't ever happen.
|
| + NOTREACHED();
|
| + break;
|
| + }
|
| +
|
| + // TODO(rdsmith): Implement versioning.
|
| +
|
| + // 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.
|
| + InitializePersistentStore(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 (== 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) {
|
| @@ -251,10 +609,9 @@ void SdchOwner::OnMemoryPressure(
|
| base::MemoryPressureListener::MemoryPressureLevel level) {
|
| DCHECK_NE(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, level);
|
|
|
| - for (auto it = local_dictionary_info_.begin();
|
| - it != local_dictionary_info_.end(); ++it) {
|
| - RecordDictionaryEviction(it->second.use_count,
|
| - DICTIONARY_FATE_EVICT_FOR_MEMORY);
|
| + for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
|
| + it.Advance()) {
|
| + RecordDictionaryEviction(it.UseCount(), DICTIONARY_FATE_EVICT_FOR_MEMORY);
|
| }
|
|
|
| // TODO(rdsmith): Make a distinction between moderate and critical
|
| @@ -262,4 +619,52 @@ void SdchOwner::OnMemoryPressure(
|
| 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(kVersionName, &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(kDictionariesName, &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(kDictionaryUrlName, &url_string))
|
| + continue;
|
| + GURL dict_url(url_string);
|
| +
|
| + double last_used;
|
| + if (!dict_info->GetDouble(kDictionaryLastUsedName, &last_used))
|
| + continue;
|
| +
|
| + int use_count;
|
| + if (!dict_info->GetInteger(kDictionaryUseCountName, &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;
|
| +}
|
| +
|
| } // namespace net
|
|
|