Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/sdch/sdch_owner.h" | 5 #include "net/sdch/sdch_owner.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/logging.h" | |
| 8 #include "base/metrics/histogram_macros.h" | 9 #include "base/metrics/histogram_macros.h" |
| 10 #include "base/prefs/persistent_pref_store.h" | |
| 11 #include "base/prefs/value_map_pref_store.h" | |
| 9 #include "base/time/default_clock.h" | 12 #include "base/time/default_clock.h" |
| 13 #include "base/values.h" | |
| 10 #include "net/base/sdch_manager.h" | 14 #include "net/base/sdch_manager.h" |
| 11 #include "net/base/sdch_net_log_params.h" | 15 #include "net/base/sdch_net_log_params.h" |
| 12 | 16 |
| 17 namespace net { | |
| 18 | |
| 13 namespace { | 19 namespace { |
| 14 | 20 |
| 15 enum DictionaryFate { | 21 enum DictionaryFate { |
| 16 // A Get-Dictionary header wasn't acted on. | 22 // A Get-Dictionary header wasn't acted on. |
| 17 DICTIONARY_FATE_GET_IGNORED = 1, | 23 DICTIONARY_FATE_GET_IGNORED = 1, |
| 18 | 24 |
| 19 // A fetch was attempted, but failed. | 25 // A fetch was attempted, but failed. |
| 20 // TODO(rdsmith): Actually record this case. | 26 // TODO(rdsmith): Actually record this case. |
| 21 DICTIONARY_FATE_FETCH_FAILED = 2, | 27 DICTIONARY_FATE_FETCH_FAILED = 2, |
| 22 | 28 |
| 23 // A successful fetch was dropped on the floor, no space. | 29 // A successful fetch was dropped on the floor, no space. |
| 24 DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE = 3, | 30 DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE = 3, |
| 25 | 31 |
| 26 // A successful fetch was refused by the SdchManager. | 32 // A successful fetch was refused by the SdchManager. |
| 27 DICTIONARY_FATE_FETCH_MANAGER_REFUSED = 4, | 33 DICTIONARY_FATE_FETCH_MANAGER_REFUSED = 4, |
| 28 | 34 |
| 29 // A dictionary was successfully added. | 35 // A dictionary was successfully added based on |
| 30 DICTIONARY_FATE_ADDED = 5, | 36 // a Get-Dictionary header in a response. |
| 37 DICTIONARY_FATE_ADD_RESPONSE_TRIGGERED = 5, | |
| 31 | 38 |
| 32 // A dictionary was evicted by an incoming dict. | 39 // A dictionary was evicted by an incoming dict. |
| 33 DICTIONARY_FATE_EVICT_FOR_DICT = 6, | 40 DICTIONARY_FATE_EVICT_FOR_DICT = 6, |
| 34 | 41 |
| 35 // A dictionary was evicted by memory pressure. | 42 // A dictionary was evicted by memory pressure. |
| 36 DICTIONARY_FATE_EVICT_FOR_MEMORY = 7, | 43 DICTIONARY_FATE_EVICT_FOR_MEMORY = 7, |
| 37 | 44 |
| 38 // A dictionary was evicted on destruction. | 45 // A dictionary was evicted on destruction. |
| 39 DICTIONARY_FATE_EVICT_FOR_DESTRUCTION = 8, | 46 DICTIONARY_FATE_EVICT_FOR_DESTRUCTION = 8, |
| 40 | 47 |
| 41 DICTIONARY_FATE_MAX = 9 | 48 // A dictionary was successfully added based on |
| 49 // persistence from a previous browser revision. | |
| 50 DICTIONARY_FATE_ADD_PERSISTENCE_TRIGGERED = 9, | |
| 51 | |
| 52 // A dictionary was unloaded on destruction, but is still present on disk. | |
| 53 DICTIONARY_FATE_UNLOAD_FOR_DESTRUCTION = 10, | |
| 54 | |
| 55 DICTIONARY_FATE_MAX = 11 | |
| 42 }; | 56 }; |
| 43 | 57 |
| 58 enum PersistenceFailureReason { | |
| 59 // File didn't exist; is being created. | |
| 60 PERSISTENCE_FAILURE_REASON_NO_FILE = 1, | |
| 61 | |
| 62 // Error reading in information, but should be able to write. | |
| 63 PERSISTENCE_FAILURE_REASON_READ_FAILED = 2, | |
| 64 | |
| 65 // Error leading to abort on attempted persistence. | |
| 66 PERSISTENCE_FAILURE_REASON_WRITE_FAILED = 3, | |
| 67 | |
| 68 PERSISTENCE_FAILURE_REASON_MAX = 4 | |
| 69 }; | |
| 70 | |
| 71 // Dictionaries that haven't been touched in 24 hours may be evicted | |
| 72 // to make room for new dictionaries. | |
| 73 const int kFreshnessLifetimeHours = 24; | |
| 74 | |
| 75 // Dictionaries that have never been used only stay fresh for one hour. | |
| 76 const int kNeverUsedFreshnessLifetimeHours = 1; | |
| 77 | |
| 44 void RecordDictionaryFate(enum DictionaryFate fate) { | 78 void RecordDictionaryFate(enum DictionaryFate fate) { |
| 45 UMA_HISTOGRAM_ENUMERATION("Sdch3.DictionaryFate", fate, DICTIONARY_FATE_MAX); | 79 UMA_HISTOGRAM_ENUMERATION("Sdch3.DictionaryFate", fate, DICTIONARY_FATE_MAX); |
| 46 } | 80 } |
| 47 | 81 |
| 82 void RecordPersistenceFailure(PersistenceFailureReason failure_reason) { | |
| 83 UMA_HISTOGRAM_ENUMERATION("Sdch3.PersistenceFailureReason", failure_reason, | |
| 84 PERSISTENCE_FAILURE_REASON_MAX); | |
| 85 } | |
| 86 | |
| 48 void RecordDictionaryEviction(int use_count, DictionaryFate fate) { | 87 void RecordDictionaryEviction(int use_count, DictionaryFate fate) { |
| 49 DCHECK(fate == DICTIONARY_FATE_EVICT_FOR_DICT || | 88 DCHECK(fate == DICTIONARY_FATE_EVICT_FOR_DICT || |
| 50 fate == DICTIONARY_FATE_EVICT_FOR_MEMORY || | 89 fate == DICTIONARY_FATE_EVICT_FOR_MEMORY || |
| 51 fate == DICTIONARY_FATE_EVICT_FOR_DESTRUCTION); | 90 fate == DICTIONARY_FATE_EVICT_FOR_DESTRUCTION); |
| 52 | 91 |
| 53 UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", use_count); | 92 UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", use_count); |
| 54 RecordDictionaryFate(fate); | 93 RecordDictionaryFate(fate); |
| 55 } | 94 } |
| 56 | 95 |
| 96 void RecordDictionaryUnload(int use_count) { | |
|
Randy Smith (Not in Mondays)
2015/02/16 01:10:05
nit: Why the duplication with the above? I'd thin
Elly Fong-Jones
2015/02/17 20:35:29
Because I thought of Unloads as distinct from Evic
Randy Smith (Not in Mondays)
2015/02/17 20:49:36
Ah, good catch. That makes sense. I'm still not
| |
| 97 UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", 0); | |
| 98 RecordDictionaryFate(DICTIONARY_FATE_UNLOAD_FOR_DESTRUCTION); | |
| 99 } | |
| 100 | |
| 101 // Schema specifications and access routines. | |
| 102 | |
| 103 // The persistent prefs store is conceptually shared with any other network | |
| 104 // stack systems that want to persist data over browser restarts, and so | |
| 105 // use of it must be namespace restricted. | |
| 106 // Schema: | |
| 107 // pref_store_->GetValue(kPreferenceName) -> Dictionary { | |
| 108 // 'version' -> 1 [int] | |
| 109 // 'dictionaries' -> Dictionary { | |
| 110 // server_hash -> { | |
| 111 // 'url' -> URL [string] | |
| 112 // 'last_used' -> seconds since unix epoch [double] | |
| 113 // 'use_count' -> use count [int] | |
| 114 // 'size' -> size [int] | |
| 115 // } | |
| 116 // } | |
| 117 const char kPreferenceName[] = "SDCH"; | |
| 118 const char kVersionKey[] = "version"; | |
| 119 const char kDictionariesKey[] = "dictionaries"; | |
| 120 const char kDictionaryUrlKey[] = "url"; | |
| 121 const char kDictionaryLastUsedKey[] = "last_used"; | |
| 122 const char kDictionaryUseCountKey[] = "use_count"; | |
| 123 const char kDictionarySizeKey[] = "size"; | |
| 124 | |
| 125 const int kVersion = 1; | |
| 126 | |
| 127 // This function returns store[kPreferenceName/kDictionariesKey]. The caller | |
| 128 // is responsible for making sure any needed calls to | |
| 129 // |store->ReportValueChanged()| occur. | |
| 130 base::DictionaryValue* GetPersistentStoreDictionaryMap( | |
| 131 WriteablePrefStore* store) { | |
| 132 base::Value* result = nullptr; | |
| 133 bool success = store->GetMutableValue(kPreferenceName, &result); | |
| 134 DCHECK(success); | |
| 135 | |
| 136 base::DictionaryValue* preference_dictionary = nullptr; | |
| 137 success = result->GetAsDictionary(&preference_dictionary); | |
| 138 DCHECK(success); | |
| 139 DCHECK(preference_dictionary); | |
| 140 | |
| 141 base::DictionaryValue* dictionary_list_dictionary = nullptr; | |
| 142 success = preference_dictionary->GetDictionary(kDictionariesKey, | |
| 143 &dictionary_list_dictionary); | |
| 144 DCHECK(success); | |
| 145 DCHECK(dictionary_list_dictionary); | |
| 146 | |
| 147 return dictionary_list_dictionary; | |
| 148 } | |
| 149 | |
| 150 // This function initializes a pref store with an empty version of the | |
| 151 // above schema, removing anything previously in the store under | |
| 152 // kPreferenceName. | |
| 153 void InitializePrefStore(WriteablePrefStore* store) { | |
| 154 base::DictionaryValue* empty_store(new base::DictionaryValue); | |
| 155 empty_store->SetInteger(kVersionKey, kVersion); | |
| 156 empty_store->Set(kDictionariesKey, | |
| 157 make_scoped_ptr(new base::DictionaryValue)); | |
| 158 store->SetValue(kPreferenceName, empty_store); | |
| 159 } | |
| 160 | |
| 161 // A class to allow iteration over all dictionaries in the pref store, and | |
| 162 // easy lookup of the information associated with those dictionaries. | |
| 163 // Note that this is an "Iterator" in the same sense (and for the same | |
| 164 // reasons) that base::Dictionary::Iterator is an iterator--it allows | |
| 165 // iterating over all the dictionaries in the preference store, but it | |
| 166 // does not allow use as an STL iterator because the container it | |
| 167 // is iterating over does not export begin()/end() methods. | |
| 168 class DictionaryPreferenceIterator { | |
| 169 public: | |
| 170 explicit DictionaryPreferenceIterator(WriteablePrefStore* pref_store); | |
| 171 | |
| 172 bool IsAtEnd() const; | |
| 173 void Advance(); | |
| 174 | |
| 175 const std::string& server_hash() const { return server_hash_; } | |
| 176 const GURL url() const { return url_; } | |
| 177 base::Time last_used() const { return last_used_; } | |
| 178 int use_count() const { return use_count_; } | |
| 179 int size() const { return size_; } | |
| 180 | |
| 181 private: | |
| 182 void LoadDictionaryOrDie(); | |
| 183 | |
| 184 std::string server_hash_; | |
| 185 GURL url_; | |
| 186 base::Time last_used_; | |
| 187 int use_count_; | |
| 188 int size_; | |
| 189 | |
| 190 base::DictionaryValue::Iterator dictionary_iterator_; | |
| 191 }; | |
| 192 | |
| 193 DictionaryPreferenceIterator::DictionaryPreferenceIterator( | |
| 194 WriteablePrefStore* pref_store) | |
| 195 : dictionary_iterator_(*GetPersistentStoreDictionaryMap(pref_store)) { | |
| 196 if (!IsAtEnd()) | |
| 197 LoadDictionaryOrDie(); | |
| 198 } | |
| 199 | |
| 200 bool DictionaryPreferenceIterator::IsAtEnd() const { | |
| 201 return dictionary_iterator_.IsAtEnd(); | |
| 202 } | |
| 203 | |
| 204 void DictionaryPreferenceIterator::Advance() { | |
| 205 dictionary_iterator_.Advance(); | |
| 206 if (!IsAtEnd()) | |
| 207 LoadDictionaryOrDie(); | |
| 208 } | |
| 209 | |
| 210 void DictionaryPreferenceIterator::LoadDictionaryOrDie() { | |
| 211 double last_used_seconds_from_epoch; | |
| 212 const base::DictionaryValue* dict = nullptr; | |
| 213 bool success = | |
| 214 dictionary_iterator_.value().GetAsDictionary(&dict); | |
| 215 DCHECK(success); | |
| 216 | |
| 217 server_hash_ = dictionary_iterator_.key(); | |
| 218 | |
| 219 std::string url_spec; | |
| 220 success = dict->GetString(kDictionaryUrlKey, &url_spec); | |
| 221 DCHECK(success); | |
| 222 url_ = GURL(url_spec); | |
| 223 | |
| 224 success = dict->GetDouble(kDictionaryLastUsedKey, | |
| 225 &last_used_seconds_from_epoch); | |
| 226 DCHECK(success); | |
| 227 last_used_ = base::Time::FromDoubleT(last_used_seconds_from_epoch); | |
| 228 | |
| 229 success = dict->GetInteger(kDictionaryUseCountKey, &use_count_); | |
| 230 DCHECK(success); | |
| 231 | |
| 232 success = dict->GetInteger(kDictionarySizeKey, &size_); | |
| 233 DCHECK(success); | |
| 234 } | |
| 235 | |
| 236 // Triggers a ReportValueChanged() on the specified WriteablePrefStore | |
| 237 // when the object goes out of scope. | |
| 238 class ScopedPrefNotifier { | |
| 239 public: | |
| 240 // Caller must guarantee lifetime of |*pref_store| exceeds the | |
| 241 // lifetime of this object. | |
| 242 ScopedPrefNotifier(WriteablePrefStore* pref_store) | |
| 243 : pref_store_(pref_store) {} | |
| 244 ~ScopedPrefNotifier() { pref_store_->ReportValueChanged(kPreferenceName); } | |
| 245 | |
| 246 private: | |
| 247 WriteablePrefStore* pref_store_; | |
| 248 | |
| 249 DISALLOW_COPY_AND_ASSIGN(ScopedPrefNotifier); | |
| 250 }; | |
| 251 | |
| 57 } // namespace | 252 } // namespace |
| 58 | 253 |
| 59 namespace net { | |
| 60 | |
| 61 // Adjust SDCH limits downwards for mobile. | 254 // Adjust SDCH limits downwards for mobile. |
| 62 #if defined(OS_ANDROID) || defined(OS_IOS) | 255 #if defined(OS_ANDROID) || defined(OS_IOS) |
| 63 // static | 256 // static |
| 64 const size_t SdchOwner::kMaxTotalDictionarySize = 1000 * 1000; | 257 const size_t SdchOwner::kMaxTotalDictionarySize = 1000 * 1000; |
| 65 #else | 258 #else |
| 66 // static | 259 // static |
| 67 const size_t SdchOwner::kMaxTotalDictionarySize = 20 * 1000 * 1000; | 260 const size_t SdchOwner::kMaxTotalDictionarySize = 20 * 1000 * 1000; |
| 68 #endif | 261 #endif |
| 69 | 262 |
| 70 // Somewhat arbitrary, but we assume a dictionary smaller than | 263 // Somewhat arbitrary, but we assume a dictionary smaller than |
| 71 // 50K isn't going to do anyone any good. Note that this still doesn't | 264 // 50K isn't going to do anyone any good. Note that this still doesn't |
| 72 // prevent download and addition unless there is less than this | 265 // prevent download and addition unless there is less than this |
| 73 // amount of space available in storage. | 266 // amount of space available in storage. |
| 74 const size_t SdchOwner::kMinSpaceForDictionaryFetch = 50 * 1000; | 267 const size_t SdchOwner::kMinSpaceForDictionaryFetch = 50 * 1000; |
| 75 | 268 |
| 76 SdchOwner::SdchOwner(net::SdchManager* sdch_manager, | 269 SdchOwner::SdchOwner(SdchManager* sdch_manager, URLRequestContext* context) |
| 77 net::URLRequestContext* context) | |
| 78 : manager_(sdch_manager), | 270 : manager_(sdch_manager), |
| 79 fetcher_(context, | 271 fetcher_(new SdchDictionaryFetcher(context)), |
| 80 base::Bind(&SdchOwner::OnDictionaryFetched, | |
| 81 // Because |fetcher_| is owned by SdchOwner, the | |
| 82 // SdchOwner object will be available for the lifetime | |
| 83 // of |fetcher_|. | |
| 84 base::Unretained(this))), | |
| 85 total_dictionary_bytes_(0), | 272 total_dictionary_bytes_(0), |
| 86 clock_(new base::DefaultClock), | 273 clock_(new base::DefaultClock), |
| 87 max_total_dictionary_size_(kMaxTotalDictionarySize), | 274 max_total_dictionary_size_(kMaxTotalDictionarySize), |
| 88 min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch), | 275 min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch), |
| 276 in_memory_pref_store_(new ValueMapPrefStore()), | |
| 277 external_pref_store_(nullptr), | |
| 278 pref_store_(in_memory_pref_store_.get()), | |
| 89 memory_pressure_listener_( | 279 memory_pressure_listener_( |
| 90 base::Bind(&SdchOwner::OnMemoryPressure, | 280 base::Bind(&SdchOwner::OnMemoryPressure, |
| 91 // Because |memory_pressure_listener_| is owned by | 281 // Because |memory_pressure_listener_| is owned by |
| 92 // SdchOwner, the SdchOwner object will be available | 282 // SdchOwner, the SdchOwner object will be available |
| 93 // for the lifetime of |memory_pressure_listener_|. | 283 // for the lifetime of |memory_pressure_listener_|. |
| 94 base::Unretained(this))) { | 284 base::Unretained(this))) { |
| 95 manager_->AddObserver(this); | 285 manager_->AddObserver(this); |
| 286 InitializePrefStore(pref_store_); | |
| 96 } | 287 } |
| 97 | 288 |
| 98 SdchOwner::~SdchOwner() { | 289 SdchOwner::~SdchOwner() { |
| 99 for (auto it = local_dictionary_info_.begin(); | 290 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); |
| 100 it != local_dictionary_info_.end(); ++it) { | 291 it.Advance()) { |
| 101 RecordDictionaryEviction(it->second.use_count, | 292 if (IsPersistingDictionaries()) { |
| 102 DICTIONARY_FATE_EVICT_FOR_DESTRUCTION); | 293 RecordDictionaryUnload(it.use_count()); |
| 294 } else { | |
| 295 RecordDictionaryEviction(it.use_count(), | |
| 296 DICTIONARY_FATE_EVICT_FOR_DESTRUCTION); | |
| 297 } | |
| 103 } | 298 } |
| 104 manager_->RemoveObserver(this); | 299 manager_->RemoveObserver(this); |
| 300 | |
| 301 // This object only observes the external store during loading, | |
| 302 // i.e. before it's made the default preferences store. | |
| 303 if (external_pref_store_) | |
| 304 external_pref_store_->RemoveObserver(this); | |
| 305 } | |
| 306 | |
| 307 void SdchOwner::EnablePersistentStorage(PersistentPrefStore* pref_store) { | |
| 308 DCHECK(!external_pref_store_); | |
| 309 external_pref_store_ = pref_store; | |
| 310 external_pref_store_->AddObserver(this); | |
| 311 | |
| 312 if (external_pref_store_->IsInitializationComplete()) | |
| 313 OnInitializationCompleted(true); | |
| 105 } | 314 } |
| 106 | 315 |
| 107 void SdchOwner::SetMaxTotalDictionarySize(size_t max_total_dictionary_size) { | 316 void SdchOwner::SetMaxTotalDictionarySize(size_t max_total_dictionary_size) { |
| 108 max_total_dictionary_size_ = max_total_dictionary_size; | 317 max_total_dictionary_size_ = max_total_dictionary_size; |
| 109 } | 318 } |
| 110 | 319 |
| 111 void SdchOwner::SetMinSpaceForDictionaryFetch( | 320 void SdchOwner::SetMinSpaceForDictionaryFetch( |
| 112 size_t min_space_for_dictionary_fetch) { | 321 size_t min_space_for_dictionary_fetch) { |
| 113 min_space_for_dictionary_fetch_ = min_space_for_dictionary_fetch; | 322 min_space_for_dictionary_fetch_ = min_space_for_dictionary_fetch; |
| 114 } | 323 } |
| 115 | 324 |
| 116 void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text, | 325 void SdchOwner::OnDictionaryFetched(base::Time last_used, |
| 326 int use_count, | |
| 327 const std::string& dictionary_text, | |
| 117 const GURL& dictionary_url, | 328 const GURL& dictionary_url, |
| 118 const net::BoundNetLog& net_log) { | 329 const net::BoundNetLog& net_log) { |
| 119 struct DictionaryItem { | 330 struct DictionaryItem { |
| 120 base::Time last_used; | 331 base::Time last_used; |
| 121 std::string server_hash; | 332 std::string server_hash; |
| 122 int use_count; | 333 int use_count; |
| 123 size_t dictionary_size; | 334 size_t dictionary_size; |
| 124 | 335 |
| 125 DictionaryItem() : use_count(0), dictionary_size(0) {} | 336 DictionaryItem() : use_count(0), dictionary_size(0) {} |
| 126 DictionaryItem(const base::Time& last_used, | 337 DictionaryItem(const base::Time& last_used, |
| 127 const std::string& server_hash, | 338 const std::string& server_hash, |
| 128 int use_count, | 339 int use_count, |
| 129 size_t dictionary_size) | 340 size_t dictionary_size) |
| 130 : last_used(last_used), | 341 : last_used(last_used), |
| 131 server_hash(server_hash), | 342 server_hash(server_hash), |
| 132 use_count(use_count), | 343 use_count(use_count), |
| 133 dictionary_size(dictionary_size) {} | 344 dictionary_size(dictionary_size) {} |
| 134 DictionaryItem(const DictionaryItem& rhs) = default; | 345 DictionaryItem(const DictionaryItem& rhs) = default; |
| 135 DictionaryItem& operator=(const DictionaryItem& rhs) = default; | 346 DictionaryItem& operator=(const DictionaryItem& rhs) = default; |
| 136 bool operator<(const DictionaryItem& rhs) const { | 347 bool operator<(const DictionaryItem& rhs) const { |
| 137 return last_used < rhs.last_used; | 348 return last_used < rhs.last_used; |
| 138 } | 349 } |
| 139 }; | 350 }; |
| 140 | 351 |
| 352 // Figure out if there is space for the incoming dictionary; evict | |
| 353 // stale dictionaries if needed to make space. | |
| 354 | |
| 141 std::vector<DictionaryItem> stale_dictionary_list; | 355 std::vector<DictionaryItem> stale_dictionary_list; |
| 142 size_t recoverable_bytes = 0; | 356 size_t recoverable_bytes = 0; |
| 143 base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1)); | 357 base::Time now(clock_->Now()); |
| 144 for (auto used_it = local_dictionary_info_.begin(); | 358 // Dictionaries whose last used time is before |stale_boundary| are candidates |
| 145 used_it != local_dictionary_info_.end(); ++used_it) { | 359 // for eviction if necessary. |
| 146 if (used_it->second.last_used < stale_boundary) { | 360 base::Time stale_boundary( |
| 147 stale_dictionary_list.push_back( | 361 now - base::TimeDelta::FromHours(kFreshnessLifetimeHours)); |
| 148 DictionaryItem(used_it->second.last_used, used_it->first, | 362 // Dictionaries that have never been used and are from before |
| 149 used_it->second.use_count, used_it->second.size)); | 363 // |never_used_stale_boundary| are candidates for eviction if necessary. |
| 150 recoverable_bytes += used_it->second.size; | 364 base::Time never_used_stale_boundary( |
| 365 now - base::TimeDelta::FromHours(kNeverUsedFreshnessLifetimeHours)); | |
| 366 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); | |
| 367 it.Advance()) { | |
| 368 if (it.last_used() < stale_boundary || | |
| 369 (it.use_count() == 0 && it.last_used() < never_used_stale_boundary)) { | |
| 370 stale_dictionary_list.push_back(DictionaryItem( | |
| 371 it.last_used(), it.server_hash(), it.use_count(), it.size())); | |
| 372 recoverable_bytes += it.size(); | |
| 151 } | 373 } |
| 152 } | 374 } |
| 153 | 375 |
| 154 if (total_dictionary_bytes_ + dictionary_text.size() - recoverable_bytes > | 376 if (total_dictionary_bytes_ + dictionary_text.size() - recoverable_bytes > |
| 155 max_total_dictionary_size_) { | 377 max_total_dictionary_size_) { |
| 156 RecordDictionaryFate(DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE); | 378 RecordDictionaryFate(DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE); |
| 157 net::SdchManager::SdchErrorRecovery(SDCH_DICTIONARY_NO_ROOM); | 379 net::SdchManager::SdchErrorRecovery(SDCH_DICTIONARY_NO_ROOM); |
| 158 net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR, | 380 net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR, |
| 159 base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback, | 381 base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback, |
| 160 SDCH_DICTIONARY_NO_ROOM, dictionary_url, true)); | 382 SDCH_DICTIONARY_NO_ROOM, dictionary_url, true)); |
| 161 return; | 383 return; |
| 162 } | 384 } |
| 163 | 385 |
| 164 // Evict from oldest to youngest until we have space. | 386 // Add the new dictionary. This is done before removing the stale |
| 165 std::sort(stale_dictionary_list.begin(), stale_dictionary_list.end()); | 387 // dictionaries so that no state change will occur if dictionary addition |
| 166 size_t avail_bytes = max_total_dictionary_size_ - total_dictionary_bytes_; | 388 // fails. |
| 167 auto stale_it = stale_dictionary_list.begin(); | |
| 168 while (avail_bytes < dictionary_text.size() && | |
| 169 stale_it != stale_dictionary_list.end()) { | |
| 170 manager_->RemoveSdchDictionary(stale_it->server_hash); | |
| 171 RecordDictionaryEviction(stale_it->use_count, | |
| 172 DICTIONARY_FATE_EVICT_FOR_DICT); | |
| 173 local_dictionary_info_.erase(stale_it->server_hash); | |
| 174 avail_bytes += stale_it->dictionary_size; | |
| 175 ++stale_it; | |
| 176 } | |
| 177 DCHECK(avail_bytes >= dictionary_text.size()); | |
| 178 | |
| 179 std::string server_hash; | 389 std::string server_hash; |
| 180 net::SdchProblemCode rv = manager_->AddSdchDictionary( | 390 net::SdchProblemCode rv = manager_->AddSdchDictionary( |
| 181 dictionary_text, dictionary_url, &server_hash); | 391 dictionary_text, dictionary_url, &server_hash); |
| 182 if (rv != net::SDCH_OK) { | 392 if (rv != net::SDCH_OK) { |
| 183 RecordDictionaryFate(DICTIONARY_FATE_FETCH_MANAGER_REFUSED); | 393 RecordDictionaryFate(DICTIONARY_FATE_FETCH_MANAGER_REFUSED); |
| 184 net::SdchManager::SdchErrorRecovery(rv); | 394 net::SdchManager::SdchErrorRecovery(rv); |
| 185 net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR, | 395 net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR, |
| 186 base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback, | 396 base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback, |
| 187 rv, dictionary_url, true)); | 397 rv, dictionary_url, true)); |
| 188 return; | 398 return; |
| 189 } | 399 } |
| 190 | 400 |
| 191 RecordDictionaryFate(DICTIONARY_FATE_ADDED); | 401 base::DictionaryValue* pref_dictionary_map = |
| 402 GetPersistentStoreDictionaryMap(pref_store_); | |
| 403 ScopedPrefNotifier scoped_pref_notifier(pref_store_); | |
| 192 | 404 |
| 193 DCHECK(local_dictionary_info_.end() == | 405 // Remove the old dictionaries. |
| 194 local_dictionary_info_.find(server_hash)); | 406 std::sort(stale_dictionary_list.begin(), stale_dictionary_list.end()); |
| 407 size_t avail_bytes = max_total_dictionary_size_ - total_dictionary_bytes_; | |
| 408 auto stale_it = stale_dictionary_list.begin(); | |
| 409 while (avail_bytes < dictionary_text.size() && | |
| 410 stale_it != stale_dictionary_list.end()) { | |
| 411 manager_->RemoveSdchDictionary(stale_it->server_hash); | |
| 412 | |
| 413 DCHECK(pref_dictionary_map->HasKey(stale_it->server_hash)); | |
| 414 bool success = pref_dictionary_map->RemoveWithoutPathExpansion( | |
| 415 stale_it->server_hash, nullptr); | |
| 416 DCHECK(success); | |
| 417 | |
| 418 avail_bytes += stale_it->dictionary_size; | |
| 419 | |
| 420 RecordDictionaryEviction(stale_it->use_count, | |
| 421 DICTIONARY_FATE_EVICT_FOR_DICT); | |
| 422 | |
| 423 ++stale_it; | |
| 424 } | |
| 425 DCHECK_GE(avail_bytes, dictionary_text.size()); | |
| 426 | |
| 427 RecordDictionaryFate( | |
| 428 // Distinguish between loads triggered by network responses and | |
| 429 // loads triggered by persistence. | |
| 430 last_used.is_null() ? DICTIONARY_FATE_ADD_RESPONSE_TRIGGERED | |
| 431 : DICTIONARY_FATE_ADD_PERSISTENCE_TRIGGERED); | |
| 432 | |
| 433 // If a dictionary has never been used, its dictionary addition time | |
| 434 // is recorded as its last used time. Never used dictionaries are treated | |
| 435 // specially in the freshness logic. | |
| 436 if (last_used.is_null()) | |
| 437 last_used = clock_->Now(); | |
| 438 | |
| 195 total_dictionary_bytes_ += dictionary_text.size(); | 439 total_dictionary_bytes_ += dictionary_text.size(); |
| 196 local_dictionary_info_[server_hash] = DictionaryInfo( | 440 |
| 197 // Set the time last used to something to avoid thrashing, but not recent, | 441 // Record the addition in the pref store. |
| 198 // to avoid taking too much time/space with useless dictionaries/one-off | 442 scoped_ptr<base::DictionaryValue> dictionary_description( |
| 199 // visits to web sites. | 443 new base::DictionaryValue()); |
| 200 clock_->Now() - base::TimeDelta::FromHours(23), dictionary_text.size()); | 444 dictionary_description->SetString(kDictionaryUrlKey, dictionary_url.spec()); |
| 445 dictionary_description->SetDouble(kDictionaryLastUsedKey, | |
| 446 last_used.ToDoubleT()); | |
| 447 dictionary_description->SetInteger(kDictionaryUseCountKey, use_count); | |
| 448 dictionary_description->SetInteger(kDictionarySizeKey, | |
| 449 dictionary_text.size()); | |
| 450 pref_dictionary_map->Set(server_hash, dictionary_description.Pass()); | |
| 201 } | 451 } |
| 202 | 452 |
| 203 void SdchOwner::OnDictionaryUsed(SdchManager* manager, | 453 void SdchOwner::OnDictionaryUsed(SdchManager* manager, |
| 204 const std::string& server_hash) { | 454 const std::string& server_hash) { |
| 205 auto it = local_dictionary_info_.find(server_hash); | 455 base::Time now(clock_->Now()); |
| 206 DCHECK(local_dictionary_info_.end() != it); | 456 base::DictionaryValue* pref_dictionary_map = |
| 457 GetPersistentStoreDictionaryMap(pref_store_); | |
| 458 ScopedPrefNotifier scoped_pref_notifier(pref_store_); | |
| 207 | 459 |
| 208 it->second.last_used = clock_->Now(); | 460 base::Value* value = nullptr; |
| 209 it->second.use_count++; | 461 bool success = pref_dictionary_map->Get(server_hash, &value); |
| 462 DCHECK(success); | |
| 463 base::DictionaryValue* specific_dictionary_map = nullptr; | |
| 464 success = value->GetAsDictionary(&specific_dictionary_map); | |
| 465 DCHECK(success); | |
| 466 | |
| 467 double last_used_seconds_since_epoch = 0.0; | |
| 468 success = specific_dictionary_map->GetDouble(kDictionaryLastUsedKey, | |
| 469 &last_used_seconds_since_epoch); | |
| 470 DCHECK(success); | |
| 471 int use_count = 0; | |
| 472 success = | |
| 473 specific_dictionary_map->GetInteger(kDictionaryUseCountKey, &use_count); | |
| 474 DCHECK(success); | |
| 475 | |
| 476 base::TimeDelta time_since_last_used(now - | |
| 477 base::Time::FromDoubleT(last_used_seconds_since_epoch)); | |
| 478 | |
| 479 // TODO(rdsmith): Distinguish between "Never used" and "Actually not | |
| 480 // touched for 48 hours". | |
| 481 UMA_HISTOGRAM_CUSTOM_TIMES( | |
| 482 "Sdch3.UsageInterval", | |
| 483 use_count ? time_since_last_used : base::TimeDelta::FromHours(48), | |
| 484 base::TimeDelta(), base::TimeDelta::FromHours(48), 50); | |
| 485 | |
| 486 specific_dictionary_map->SetDouble(kDictionaryLastUsedKey, now.ToDoubleT()); | |
| 487 specific_dictionary_map->SetInteger(kDictionaryUseCountKey, use_count + 1); | |
| 210 } | 488 } |
| 211 | 489 |
| 212 void SdchOwner::OnGetDictionary(net::SdchManager* manager, | 490 void SdchOwner::OnGetDictionary(net::SdchManager* manager, |
| 213 const GURL& request_url, | 491 const GURL& request_url, |
| 214 const GURL& dictionary_url) { | 492 const GURL& dictionary_url) { |
| 215 base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1)); | 493 base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1)); |
| 216 size_t avail_bytes = 0; | 494 size_t avail_bytes = 0; |
| 217 for (auto it = local_dictionary_info_.begin(); | 495 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); |
| 218 it != local_dictionary_info_.end(); ++it) { | 496 it.Advance()) { |
| 219 if (it->second.last_used < stale_boundary) | 497 if (it.last_used() < stale_boundary) |
| 220 avail_bytes += it->second.size; | 498 avail_bytes += it.size(); |
| 221 } | 499 } |
| 222 | 500 |
| 223 // Don't initiate the fetch if we wouldn't be able to store any | 501 // Don't initiate the fetch if we wouldn't be able to store any |
| 224 // reasonable dictionary. | 502 // reasonable dictionary. |
| 225 // TODO(rdsmith): Maybe do a HEAD request to figure out how much | 503 // TODO(rdsmith): Maybe do a HEAD request to figure out how much |
| 226 // storage we'd actually need? | 504 // storage we'd actually need? |
| 227 if (max_total_dictionary_size_ < (total_dictionary_bytes_ - avail_bytes + | 505 if (max_total_dictionary_size_ < (total_dictionary_bytes_ - avail_bytes + |
| 228 min_space_for_dictionary_fetch_)) { | 506 min_space_for_dictionary_fetch_)) { |
| 229 RecordDictionaryFate(DICTIONARY_FATE_GET_IGNORED); | 507 RecordDictionaryFate(DICTIONARY_FATE_GET_IGNORED); |
| 230 // TODO(rdsmith): Log a net-internals error. This requires | 508 // TODO(rdsmith): Log a net-internals error. This requires |
| 231 // SdchManager to forward the URLRequest that detected the | 509 // SdchManager to forward the URLRequest that detected the |
| 232 // Get-Dictionary header to its observers, which is tricky | 510 // Get-Dictionary header to its observers, which is tricky |
| 233 // because SdchManager is layered underneath URLRequest. | 511 // because SdchManager is layered underneath URLRequest. |
| 234 return; | 512 return; |
| 235 } | 513 } |
| 236 | 514 |
| 237 fetcher_.Schedule(dictionary_url); | 515 fetcher_->Schedule(dictionary_url, |
| 516 base::Bind(&SdchOwner::OnDictionaryFetched, | |
| 517 // SdchOwner will outlive its member variables. | |
| 518 base::Unretained(this), base::Time(), 0)); | |
| 238 } | 519 } |
| 239 | 520 |
| 240 void SdchOwner::OnClearDictionaries(net::SdchManager* manager) { | 521 void SdchOwner::OnClearDictionaries(net::SdchManager* manager) { |
| 241 total_dictionary_bytes_ = 0; | 522 total_dictionary_bytes_ = 0; |
| 242 local_dictionary_info_.clear(); | 523 fetcher_->Cancel(); |
| 243 fetcher_.Cancel(); | 524 |
| 525 InitializePrefStore(pref_store_); | |
| 526 } | |
| 527 | |
| 528 void SdchOwner::OnPrefValueChanged(const std::string& key) { | |
| 529 } | |
| 530 | |
| 531 void SdchOwner::OnInitializationCompleted(bool succeeded) { | |
| 532 PersistentPrefStore::PrefReadError error = | |
| 533 external_pref_store_->GetReadError(); | |
| 534 // Errors on load are self-correcting; if dictionaries were not | |
| 535 // persisted from the last instance of the browser, they will be | |
| 536 // faulted in by user action over time. However, if a load error | |
| 537 // means that the dictionary information won't be able to be persisted, | |
| 538 // the in memory pref store is left in place. | |
| 539 if (!succeeded) { | |
| 540 // Failure means a write failed, since read failures are recoverable. | |
| 541 DCHECK_NE( | |
| 542 error, | |
| 543 PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE); | |
| 544 DCHECK_NE(error, | |
| 545 PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM); | |
| 546 | |
| 547 LOG(ERROR) << "Pref store write failed: " << error; | |
| 548 external_pref_store_->RemoveObserver(this); | |
| 549 external_pref_store_ = nullptr; | |
| 550 RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_WRITE_FAILED); | |
| 551 return; | |
| 552 } | |
| 553 switch (external_pref_store_->GetReadError()) { | |
| 554 case PersistentPrefStore::PREF_READ_ERROR_NONE: | |
| 555 break; | |
| 556 | |
| 557 case PersistentPrefStore::PREF_READ_ERROR_NO_FILE: | |
| 558 // First time reading; the file will be created. | |
| 559 RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_NO_FILE); | |
| 560 break; | |
| 561 | |
| 562 case PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE: | |
| 563 case PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE: | |
| 564 case PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER: | |
| 565 case PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED: | |
| 566 case PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT: | |
| 567 case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_IO: | |
| 568 case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY: | |
| 569 case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION: | |
| 570 RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_READ_FAILED); | |
| 571 break; | |
| 572 | |
| 573 case PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED: | |
| 574 case PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED: | |
| 575 case PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE: | |
| 576 case PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM: | |
| 577 // Shouldn't ever happen. ACCESS_DENIED and FILE_NOT_SPECIFIED should | |
| 578 // imply !succeeded, and TASK_INCOMPLETE should never be delivered. | |
| 579 NOTREACHED(); | |
| 580 break; | |
| 581 } | |
| 582 | |
| 583 | |
| 584 // TODO(rdsmith): Implement versioning. | |
|
Randy Smith (Not in Mondays)
2015/02/16 01:10:05
This CL actually implements probably the best vers
Elly Fong-Jones
2015/02/17 20:35:29
Done.
| |
| 585 | |
| 586 // Load in what was stored before chrome exited previously. | |
| 587 const base::Value* sdch_persistence_value = nullptr; | |
| 588 const base::DictionaryValue* sdch_persistence_dictionary = nullptr; | |
| 589 | |
| 590 // The GetPersistentStore() routine above assumes data formatted | |
| 591 // according to the schema described at the top of this file. Since | |
| 592 // this data comes from disk, to avoid disk corruption resulting in | |
| 593 // persistent chrome errors this code avoids those assupmtions. | |
| 594 if (external_pref_store_->GetValue(kPreferenceName, | |
| 595 &sdch_persistence_value) && | |
| 596 sdch_persistence_value->GetAsDictionary(&sdch_persistence_dictionary)) { | |
| 597 SchedulePersistedDictionaryLoads(*sdch_persistence_dictionary); | |
| 598 } | |
| 599 | |
| 600 // Reset the persistent store and update it with the accumulated | |
| 601 // information from the local store. | |
| 602 InitializePrefStore(external_pref_store_); | |
| 603 | |
| 604 ScopedPrefNotifier scoped_pref_notifier(external_pref_store_); | |
| 605 GetPersistentStoreDictionaryMap(external_pref_store_) | |
| 606 ->Swap(GetPersistentStoreDictionaryMap(in_memory_pref_store_.get())); | |
| 607 | |
| 608 // This object can stop waiting on (i.e. observing) the external preference | |
| 609 // store and switch over to using it as the primary preference store. | |
| 610 pref_store_ = external_pref_store_; | |
| 611 external_pref_store_->RemoveObserver(this); | |
| 612 external_pref_store_ = nullptr; | |
| 613 in_memory_pref_store_ = nullptr; | |
| 244 } | 614 } |
| 245 | 615 |
| 246 void SdchOwner::SetClockForTesting(scoped_ptr<base::Clock> clock) { | 616 void SdchOwner::SetClockForTesting(scoped_ptr<base::Clock> clock) { |
| 247 clock_ = clock.Pass(); | 617 clock_ = clock.Pass(); |
| 248 } | 618 } |
| 249 | 619 |
| 620 int SdchOwner::GetDictionaryCount() const { | |
| 621 int count = 0; | |
| 622 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); | |
| 623 it.Advance()) { | |
| 624 count++; | |
| 625 } | |
| 626 return count; | |
| 627 } | |
| 628 | |
| 629 bool SdchOwner::HasDictionaryFrom(const GURL& url) const { | |
| 630 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); | |
| 631 it.Advance()) { | |
| 632 if (it.url() == url) | |
| 633 return true; | |
| 634 } | |
| 635 return false; | |
| 636 } | |
| 637 | |
| 638 void SdchOwner::SetFetcherForTesting( | |
| 639 scoped_ptr<SdchDictionaryFetcher> fetcher) { | |
| 640 fetcher_.reset(fetcher.release()); | |
| 641 } | |
| 642 | |
| 250 void SdchOwner::OnMemoryPressure( | 643 void SdchOwner::OnMemoryPressure( |
| 251 base::MemoryPressureListener::MemoryPressureLevel level) { | 644 base::MemoryPressureListener::MemoryPressureLevel level) { |
| 252 DCHECK_NE(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, level); | 645 DCHECK_NE(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, level); |
| 253 | 646 |
| 254 for (auto it = local_dictionary_info_.begin(); | 647 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd(); |
| 255 it != local_dictionary_info_.end(); ++it) { | 648 it.Advance()) { |
| 256 RecordDictionaryEviction(it->second.use_count, | 649 RecordDictionaryEviction(it.use_count(), DICTIONARY_FATE_EVICT_FOR_MEMORY); |
| 257 DICTIONARY_FATE_EVICT_FOR_MEMORY); | |
| 258 } | 650 } |
| 259 | 651 |
| 260 // TODO(rdsmith): Make a distinction between moderate and critical | 652 // TODO(rdsmith): Make a distinction between moderate and critical |
| 261 // memory pressure. | 653 // memory pressure. |
| 262 manager_->ClearData(); | 654 manager_->ClearData(); |
| 263 } | 655 } |
| 264 | 656 |
| 657 bool SdchOwner::SchedulePersistedDictionaryLoads( | |
| 658 const base::DictionaryValue& persisted_info) { | |
| 659 // Any schema error will result in dropping the persisted info. | |
| 660 int version = 0; | |
| 661 if (!persisted_info.GetInteger(kVersionKey, &version)) | |
| 662 return false; | |
| 663 | |
| 664 // Any version mismatch will result in dropping the persisted info; | |
| 665 // it will be faulted in at small performance cost as URLs using | |
| 666 // dictionaries for encoding are visited. | |
| 667 if (version != kVersion) | |
| 668 return false; | |
| 669 | |
| 670 const base::DictionaryValue* dictionary_set = nullptr; | |
| 671 if (!persisted_info.GetDictionary(kDictionariesKey, &dictionary_set)) | |
| 672 return false; | |
| 673 | |
| 674 // Any formatting error will result in skipping that particular | |
| 675 // dictionary. | |
| 676 for (base::DictionaryValue::Iterator dict_it(*dictionary_set); | |
| 677 !dict_it.IsAtEnd(); dict_it.Advance()) { | |
| 678 const base::DictionaryValue* dict_info = nullptr; | |
| 679 if (!dict_it.value().GetAsDictionary(&dict_info)) | |
| 680 continue; | |
| 681 | |
| 682 std::string url_string; | |
| 683 if (!dict_info->GetString(kDictionaryUrlKey, &url_string)) | |
| 684 continue; | |
| 685 GURL dict_url(url_string); | |
| 686 | |
| 687 double last_used; | |
| 688 if (!dict_info->GetDouble(kDictionaryLastUsedKey, &last_used)) | |
| 689 continue; | |
| 690 | |
| 691 int use_count; | |
| 692 if (!dict_info->GetInteger(kDictionaryUseCountKey, &use_count)) | |
| 693 continue; | |
| 694 | |
| 695 fetcher_->ScheduleReload( | |
| 696 dict_url, base::Bind(&SdchOwner::OnDictionaryFetched, | |
| 697 // SdchOwner will outlive its member variables. | |
| 698 base::Unretained(this), | |
| 699 base::Time::FromDoubleT(last_used), use_count)); | |
| 700 } | |
| 701 | |
| 702 return true; | |
| 703 } | |
| 704 | |
| 705 bool SdchOwner::IsPersistingDictionaries() const { | |
| 706 return in_memory_pref_store_.get() != nullptr; | |
| 707 } | |
| 708 | |
| 265 } // namespace net | 709 } // namespace net |
| OLD | NEW |