Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(139)

Side by Side Diff: chrome/browser/supervised_user/supervised_user_settings_service.cc

Issue 1563833002: [Part-2]Add Statistics for SupervisedUserSettingService during merging and syncing data. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | chrome/browser/supervised_user/supervised_user_settings_service_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 "chrome/browser/supervised_user/supervised_user_settings_service.h" 5 #include "chrome/browser/supervised_user/supervised_user_settings_service.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 #include <utility> 8 #include <utility>
9 9
10 #include "base/callback.h" 10 #include "base/callback.h"
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
71 PersistentPrefStore* store = new JsonPrefStore( 71 PersistentPrefStore* store = new JsonPrefStore(
72 path, sequenced_task_runner, scoped_ptr<PrefFilter>()); 72 path, sequenced_task_runner, scoped_ptr<PrefFilter>());
73 Init(store); 73 Init(store);
74 if (load_synchronously) { 74 if (load_synchronously) {
75 store_->ReadPrefs(); 75 store_->ReadPrefs();
76 // TODO(bauerb): Temporary CHECK while investigating 76 // TODO(bauerb): Temporary CHECK while investigating
77 // https://crbug.com/425785. Remove (or change to DCHECK) once the bug 77 // https://crbug.com/425785. Remove (or change to DCHECK) once the bug
78 // is fixed. 78 // is fixed.
79 CHECK(store_->IsInitializationComplete()); 79 CHECK(store_->IsInitializationComplete());
80 } else { 80 } else {
81 store_->ReadPrefsAsync(NULL); 81 store_->ReadPrefsAsync(nullptr);
82 } 82 }
83 } 83 }
84 84
85 void SupervisedUserSettingsService::Init( 85 void SupervisedUserSettingsService::Init(
86 scoped_refptr<PersistentPrefStore> store) { 86 scoped_refptr<PersistentPrefStore> store) {
87 DCHECK(!store_.get()); 87 DCHECK(!store_.get());
88 store_ = store; 88 store_ = store;
89 store_->AddObserver(this); 89 store_->AddObserver(this);
90 } 90 }
91 91
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
127 const std::string& prefix, 127 const std::string& prefix,
128 const std::string& key) { 128 const std::string& key) {
129 return prefix + kSplitSettingKeySeparator + key; 129 return prefix + kSplitSettingKeySeparator + key;
130 } 130 }
131 131
132 void SupervisedUserSettingsService::UploadItem(const std::string& key, 132 void SupervisedUserSettingsService::UploadItem(const std::string& key,
133 scoped_ptr<base::Value> value) { 133 scoped_ptr<base::Value> value) {
134 DCHECK(!SettingShouldApplyToPrefs(key)); 134 DCHECK(!SettingShouldApplyToPrefs(key));
135 135
136 std::string key_suffix = key; 136 std::string key_suffix = key;
137 base::DictionaryValue* dict = NULL; 137 base::DictionaryValue* dict = nullptr;
138 if (sync_processor_) { 138 if (sync_processor_) {
139 content::RecordAction(UserMetricsAction("ManagedUsers_UploadItem_Syncing")); 139 content::RecordAction(UserMetricsAction("ManagedUsers_UploadItem_Syncing"));
140 dict = GetDictionaryAndSplitKey(&key_suffix); 140 dict = GetDictionaryAndSplitKey(&key_suffix);
141 DCHECK(GetQueuedItems()->empty()); 141 DCHECK(GetQueuedItems()->empty());
142 SyncChangeList change_list; 142 SyncChangeList change_list;
143 SyncData data = CreateSyncDataForSetting(key, *value); 143 SyncData data = CreateSyncDataForSetting(key, *value);
144 SyncChange::SyncChangeType change_type = 144 SyncChange::SyncChangeType change_type =
145 dict->HasKey(key_suffix) ? SyncChange::ACTION_UPDATE 145 dict->HasKey(key_suffix) ? SyncChange::ACTION_UPDATE
146 : SyncChange::ACTION_ADD; 146 : SyncChange::ACTION_ADD;
147 change_list.push_back(SyncChange(FROM_HERE, change_type, data)); 147 change_list.push_back(SyncChange(FROM_HERE, change_type, data));
148 SyncError error = 148 SyncError error =
149 sync_processor_->ProcessSyncChanges(FROM_HERE, change_list); 149 sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
150 DCHECK(!error.IsSet()) << error.ToString(); 150 DCHECK(!error.IsSet()) << error.ToString();
151 } else { 151 } else {
152 // Queue the item up to be uploaded when we start syncing 152 // Queue the item up to be uploaded when we start syncing
153 // (in MergeDataAndStartSyncing()). 153 // (in MergeDataAndStartSyncing()).
154 content::RecordAction(UserMetricsAction("ManagedUsers_UploadItem_Queued")); 154 content::RecordAction(UserMetricsAction("ManagedUsers_UploadItem_Queued"));
155 dict = GetQueuedItems(); 155 dict = GetQueuedItems();
156 } 156 }
157 dict->SetWithoutPathExpansion(key_suffix, value.release()); 157 dict->SetWithoutPathExpansion(key_suffix, value.release());
158 } 158 }
159 159
160 void SupervisedUserSettingsService::SetLocalSetting( 160 void SupervisedUserSettingsService::SetLocalSetting(
161 const std::string& key, 161 const std::string& key,
162 scoped_ptr<base::Value> value) { 162 scoped_ptr<base::Value> value) {
163 if (value) 163 if (value)
164 local_settings_->SetWithoutPathExpansion(key, value.release()); 164 local_settings_->SetWithoutPathExpansion(key, value.release());
165 else 165 else
166 local_settings_->RemoveWithoutPathExpansion(key, NULL); 166 local_settings_->RemoveWithoutPathExpansion(key, nullptr);
167 167
168 InformSubscribers(); 168 InformSubscribers();
169 } 169 }
170 170
171 // static 171 // static
172 SyncData SupervisedUserSettingsService::CreateSyncDataForSetting( 172 SyncData SupervisedUserSettingsService::CreateSyncDataForSetting(
173 const std::string& name, 173 const std::string& name,
174 const base::Value& value) { 174 const base::Value& value) {
175 std::string json_value; 175 std::string json_value;
176 base::JSONWriter::Write(value, &json_value); 176 base::JSONWriter::Write(value, &json_value);
177 ::sync_pb::EntitySpecifics specifics; 177 ::sync_pb::EntitySpecifics specifics;
178 specifics.mutable_managed_user_setting()->set_name(name); 178 specifics.mutable_managed_user_setting()->set_name(name);
179 specifics.mutable_managed_user_setting()->set_value(json_value); 179 specifics.mutable_managed_user_setting()->set_value(json_value);
180 return SyncData::CreateLocalData(name, name, specifics); 180 return SyncData::CreateLocalData(name, name, specifics);
181 } 181 }
182 182
183 void SupervisedUserSettingsService::Shutdown() { 183 void SupervisedUserSettingsService::Shutdown() {
184 store_->RemoveObserver(this); 184 store_->RemoveObserver(this);
185 } 185 }
186 186
187 SyncMergeResult SupervisedUserSettingsService::MergeDataAndStartSyncing( 187 SyncMergeResult SupervisedUserSettingsService::MergeDataAndStartSyncing(
188 ModelType type, 188 ModelType type,
189 const SyncDataList& initial_sync_data, 189 const SyncDataList& initial_sync_data,
190 scoped_ptr<SyncChangeProcessor> sync_processor, 190 scoped_ptr<SyncChangeProcessor> sync_processor,
191 scoped_ptr<SyncErrorFactory> error_handler) { 191 scoped_ptr<SyncErrorFactory> error_handler) {
192 DCHECK_EQ(SUPERVISED_USER_SETTINGS, type); 192 DCHECK_EQ(SUPERVISED_USER_SETTINGS, type);
193 sync_processor_ = std::move(sync_processor); 193 sync_processor_ = std::move(sync_processor);
194 error_handler_ = std::move(error_handler); 194 error_handler_ = std::move(error_handler);
195 195
196 std::map<std::string, const base::Value*> seen_keys;
197 int num_before_association = GetAtomicSettings()->size();
198 for (base::DictionaryValue::Iterator it(*GetAtomicSettings()); !it.IsAtEnd();
199 it.Advance()) {
200 seen_keys[it.key()] = &it.value();
201 }
202 for (base::DictionaryValue::Iterator it(*GetSplitSettings()); !it.IsAtEnd();
203 it.Advance()) {
204 const base::DictionaryValue* dict = nullptr;
205 it.value().GetAsDictionary(&dict);
206 num_before_association += dict->size();
207 for (base::DictionaryValue::Iterator jt(*dict); !jt.IsAtEnd();
208 jt.Advance()) {
209 seen_keys[MakeSplitSettingKey(it.key(), jt.key())] = &jt.value();
210 }
211 }
212
196 // Clear all atomic and split settings, then recreate them from Sync data. 213 // Clear all atomic and split settings, then recreate them from Sync data.
197 Clear(); 214 Clear();
215 int num_added = 0;
216 int num_modified = 0;
217 std::map<std::string, const base::Value*> added_sync_keys;
198 for (const SyncData& sync_data : initial_sync_data) { 218 for (const SyncData& sync_data : initial_sync_data) {
199 DCHECK_EQ(SUPERVISED_USER_SETTINGS, sync_data.GetDataType()); 219 DCHECK_EQ(SUPERVISED_USER_SETTINGS, sync_data.GetDataType());
200 const ::sync_pb::ManagedUserSettingSpecifics& supervised_user_setting = 220 const ::sync_pb::ManagedUserSettingSpecifics& supervised_user_setting =
201 sync_data.GetSpecifics().managed_user_setting(); 221 sync_data.GetSpecifics().managed_user_setting();
202 scoped_ptr<base::Value> value = 222 scoped_ptr<base::Value> value =
203 JSONReader::Read(supervised_user_setting.value()); 223 JSONReader::Read(supervised_user_setting.value());
204 std::string name_suffix = supervised_user_setting.name(); 224 std::string name_suffix = supervised_user_setting.name();
225 std::string name_key = name_suffix;
205 base::DictionaryValue* dict = GetDictionaryAndSplitKey(&name_suffix); 226 base::DictionaryValue* dict = GetDictionaryAndSplitKey(&name_suffix);
206 dict->SetWithoutPathExpansion(name_suffix, value.release()); 227 dict->SetWithoutPathExpansion(name_suffix, value.release());
228 const auto& it = seen_keys.find(name_key);
Bernhard Bauer 2016/01/20 17:03:04 Just auto (the return value of find is an iterator
Deepak 2016/01/21 06:15:59 Done.
229 if (it == seen_keys.end()) {
230 added_sync_keys[name_key] = value.get();
231 num_added++;
232 } else if (!Value::Equals(it->second, value.get())) {
Bernhard Bauer 2016/01/20 17:03:04 You can call the .Equals() method on either of the
Deepak 2016/01/21 06:15:59 After thinking a little more, I feel no need to ch
233 num_modified++;
234 }
207 } 235 }
236
208 store_->ReportValueChanged(kAtomicSettings, 237 store_->ReportValueChanged(kAtomicSettings,
209 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); 238 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
210 store_->ReportValueChanged(kSplitSettings, 239 store_->ReportValueChanged(kSplitSettings,
211 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); 240 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
212 InformSubscribers(); 241 InformSubscribers();
213 242
214 // Upload all the queued up items (either with an ADD or an UPDATE action, 243 // Upload all the queued up items (either with an ADD or an UPDATE action,
215 // depending on whether they already exist) and move them to split settings. 244 // depending on whether they already exist) and move them to split settings.
216 SyncChangeList change_list; 245 SyncChangeList change_list;
217 base::DictionaryValue* queued_items = GetQueuedItems(); 246 base::DictionaryValue* queued_items = GetQueuedItems();
218 for (base::DictionaryValue::Iterator it(*queued_items); !it.IsAtEnd(); 247 for (base::DictionaryValue::Iterator it(*queued_items); !it.IsAtEnd();
219 it.Advance()) { 248 it.Advance()) {
220 std::string key_suffix = it.key(); 249 std::string key_suffix = it.key();
250 std::string name_key = key_suffix;
221 base::DictionaryValue* dict = GetDictionaryAndSplitKey(&key_suffix); 251 base::DictionaryValue* dict = GetDictionaryAndSplitKey(&key_suffix);
222 SyncData data = CreateSyncDataForSetting(it.key(), it.value()); 252 SyncData data = CreateSyncDataForSetting(it.key(), it.value());
223 SyncChange::SyncChangeType change_type = 253 SyncChange::SyncChangeType change_type =
224 dict->HasKey(key_suffix) ? SyncChange::ACTION_UPDATE 254 dict->HasKey(key_suffix) ? SyncChange::ACTION_UPDATE
225 : SyncChange::ACTION_ADD; 255 : SyncChange::ACTION_ADD;
226 change_list.push_back(SyncChange(FROM_HERE, change_type, data)); 256 change_list.push_back(SyncChange(FROM_HERE, change_type, data));
227 dict->SetWithoutPathExpansion(key_suffix, it.value().DeepCopy()); 257 dict->SetWithoutPathExpansion(key_suffix, it.value().DeepCopy());
258 if (added_sync_keys.find(name_key) != added_sync_keys.end()) {
259 num_added--;
260 num_modified++;
Bernhard Bauer 2016/01/20 17:03:04 I don't even think this one is modified locally, b
Deepak 2016/01/21 06:15:59 Is this means we should consider only addition and
261 }
228 } 262 }
229 queued_items->Clear(); 263 queued_items->Clear();
230 264
231 SyncMergeResult result(SUPERVISED_USER_SETTINGS); 265 SyncMergeResult result(SUPERVISED_USER_SETTINGS);
232 // Process all the accumulated changes from the queued items. 266 // Process all the accumulated changes from the queued items.
233 if (change_list.size() > 0) { 267 if (change_list.size() > 0) {
234 store_->ReportValueChanged(kQueuedItems, 268 store_->ReportValueChanged(kQueuedItems,
235 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); 269 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
236 result.set_error( 270 result.set_error(
237 sync_processor_->ProcessSyncChanges(FROM_HERE, change_list)); 271 sync_processor_->ProcessSyncChanges(FROM_HERE, change_list));
238 } 272 }
239 273
240 // TODO(bauerb): Statistics? 274 // Calculating number of items after association.
275 int num_after_association = GetAtomicSettings()->size();
276 for (base::DictionaryValue::Iterator it(*GetSplitSettings()); !it.IsAtEnd();
277 it.Advance()) {
278 const base::DictionaryValue* dict = nullptr;
279 it.value().GetAsDictionary(&dict);
280 num_after_association += dict->size();
281 }
282
283 result.set_num_items_added(num_added);
284 result.set_num_items_modified(num_modified);
285 result.set_num_items_deleted(num_after_association -
286 (num_added + num_modified));
287 result.set_num_items_before_association(num_before_association);
288 result.set_num_items_after_association(num_after_association);
241 return result; 289 return result;
242 } 290 }
243 291
244 void SupervisedUserSettingsService::StopSyncing(ModelType type) { 292 void SupervisedUserSettingsService::StopSyncing(ModelType type) {
245 DCHECK_EQ(syncer::SUPERVISED_USER_SETTINGS, type); 293 DCHECK_EQ(syncer::SUPERVISED_USER_SETTINGS, type);
246 sync_processor_.reset(); 294 sync_processor_.reset();
247 error_handler_.reset(); 295 error_handler_.reset();
248 } 296 }
249 297
250 SyncDataList SupervisedUserSettingsService::GetAllSyncData( 298 SyncDataList SupervisedUserSettingsService::GetAllSyncData(
251 ModelType type) const { 299 ModelType type) const {
252 DCHECK_EQ(syncer::SUPERVISED_USER_SETTINGS, type); 300 DCHECK_EQ(syncer::SUPERVISED_USER_SETTINGS, type);
253 SyncDataList data; 301 SyncDataList data;
254 for (base::DictionaryValue::Iterator it(*GetAtomicSettings()); !it.IsAtEnd(); 302 for (base::DictionaryValue::Iterator it(*GetAtomicSettings()); !it.IsAtEnd();
255 it.Advance()) { 303 it.Advance()) {
256 data.push_back(CreateSyncDataForSetting(it.key(), it.value())); 304 data.push_back(CreateSyncDataForSetting(it.key(), it.value()));
257 } 305 }
258 for (base::DictionaryValue::Iterator it(*GetSplitSettings()); !it.IsAtEnd(); 306 for (base::DictionaryValue::Iterator it(*GetSplitSettings()); !it.IsAtEnd();
259 it.Advance()) { 307 it.Advance()) {
260 const base::DictionaryValue* dict = NULL; 308 const base::DictionaryValue* dict = nullptr;
261 it.value().GetAsDictionary(&dict); 309 it.value().GetAsDictionary(&dict);
262 for (base::DictionaryValue::Iterator jt(*dict); 310 for (base::DictionaryValue::Iterator jt(*dict);
263 !jt.IsAtEnd(); jt.Advance()) { 311 !jt.IsAtEnd(); jt.Advance()) {
264 data.push_back(CreateSyncDataForSetting( 312 data.push_back(CreateSyncDataForSetting(
265 MakeSplitSettingKey(it.key(), jt.key()), jt.value())); 313 MakeSplitSettingKey(it.key(), jt.key()), jt.value()));
266 } 314 }
267 } 315 }
268 DCHECK_EQ(0u, GetQueuedItems()->size()); 316 DCHECK_EQ(0u, GetQueuedItems()->size());
269 return data; 317 return data;
270 } 318 }
(...skipping 20 matching lines...) Expand all
291 } else { 339 } else {
292 DLOG_IF(WARNING, change_type == SyncChange::ACTION_UPDATE) 340 DLOG_IF(WARNING, change_type == SyncChange::ACTION_UPDATE)
293 << "Value for key " << key << " doesn't exist yet"; 341 << "Value for key " << key << " doesn't exist yet";
294 } 342 }
295 dict->SetWithoutPathExpansion(key, value.release()); 343 dict->SetWithoutPathExpansion(key, value.release());
296 break; 344 break;
297 } 345 }
298 case SyncChange::ACTION_DELETE: { 346 case SyncChange::ACTION_DELETE: {
299 DLOG_IF(WARNING, !dict->HasKey(key)) << "Trying to delete nonexistent " 347 DLOG_IF(WARNING, !dict->HasKey(key)) << "Trying to delete nonexistent "
300 << "key " << key; 348 << "key " << key;
301 dict->RemoveWithoutPathExpansion(key, NULL); 349 dict->RemoveWithoutPathExpansion(key, nullptr);
302 break; 350 break;
303 } 351 }
304 case SyncChange::ACTION_INVALID: { 352 case SyncChange::ACTION_INVALID: {
305 NOTREACHED(); 353 NOTREACHED();
306 break; 354 break;
307 } 355 }
308 } 356 }
309 } 357 }
310 store_->ReportValueChanged(kAtomicSettings, 358 store_->ReportValueChanged(kAtomicSettings,
311 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); 359 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
(...skipping 18 matching lines...) Expand all
330 } 378 }
331 379
332 // TODO(bauerb): Temporary CHECK while investigating https://crbug.com/425785. 380 // TODO(bauerb): Temporary CHECK while investigating https://crbug.com/425785.
333 // Remove (or change back to DCHECK) once the bug is fixed. 381 // Remove (or change back to DCHECK) once the bug is fixed.
334 CHECK(IsReady()); 382 CHECK(IsReady());
335 InformSubscribers(); 383 InformSubscribers();
336 } 384 }
337 385
338 base::DictionaryValue* SupervisedUserSettingsService::GetOrCreateDictionary( 386 base::DictionaryValue* SupervisedUserSettingsService::GetOrCreateDictionary(
339 const std::string& key) const { 387 const std::string& key) const {
340 base::Value* value = NULL; 388 base::Value* value = nullptr;
341 base::DictionaryValue* dict = NULL; 389 base::DictionaryValue* dict = nullptr;
342 if (store_->GetMutableValue(key, &value)) { 390 if (store_->GetMutableValue(key, &value)) {
343 bool success = value->GetAsDictionary(&dict); 391 bool success = value->GetAsDictionary(&dict);
344 DCHECK(success); 392 DCHECK(success);
345 } else { 393 } else {
346 dict = new base::DictionaryValue; 394 dict = new base::DictionaryValue;
347 store_->SetValue(key, make_scoped_ptr(dict), 395 store_->SetValue(key, make_scoped_ptr(dict),
348 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); 396 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
349 } 397 }
350 398
351 return dict; 399 return dict;
(...skipping 13 matching lines...) Expand all
365 } 413 }
366 414
367 base::DictionaryValue* SupervisedUserSettingsService::GetDictionaryAndSplitKey( 415 base::DictionaryValue* SupervisedUserSettingsService::GetDictionaryAndSplitKey(
368 std::string* key) const { 416 std::string* key) const {
369 size_t pos = key->find_first_of(kSplitSettingKeySeparator); 417 size_t pos = key->find_first_of(kSplitSettingKeySeparator);
370 if (pos == std::string::npos) 418 if (pos == std::string::npos)
371 return GetAtomicSettings(); 419 return GetAtomicSettings();
372 420
373 base::DictionaryValue* split_settings = GetSplitSettings(); 421 base::DictionaryValue* split_settings = GetSplitSettings();
374 std::string prefix = key->substr(0, pos); 422 std::string prefix = key->substr(0, pos);
375 base::DictionaryValue* dict = NULL; 423 base::DictionaryValue* dict = nullptr;
376 if (!split_settings->GetDictionary(prefix, &dict)) { 424 if (!split_settings->GetDictionary(prefix, &dict)) {
377 dict = new base::DictionaryValue; 425 dict = new base::DictionaryValue;
378 DCHECK(!split_settings->HasKey(prefix)); 426 DCHECK(!split_settings->HasKey(prefix));
379 split_settings->Set(prefix, dict); 427 split_settings->Set(prefix, dict);
380 } 428 }
381 key->erase(0, pos + 1); 429 key->erase(0, pos + 1);
382 return dict; 430 return dict;
383 } 431 }
384 432
385 scoped_ptr<base::DictionaryValue> SupervisedUserSettingsService::GetSettings() { 433 scoped_ptr<base::DictionaryValue> SupervisedUserSettingsService::GetSettings() {
(...skipping 24 matching lines...) Expand all
410 return settings; 458 return settings;
411 } 459 }
412 460
413 void SupervisedUserSettingsService::InformSubscribers() { 461 void SupervisedUserSettingsService::InformSubscribers() {
414 if (!IsReady()) 462 if (!IsReady())
415 return; 463 return;
416 464
417 scoped_ptr<base::DictionaryValue> settings = GetSettings(); 465 scoped_ptr<base::DictionaryValue> settings = GetSettings();
418 callback_list_.Notify(settings.get()); 466 callback_list_.Notify(settings.get());
419 } 467 }
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/supervised_user/supervised_user_settings_service_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698