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