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

Side by Side Diff: chrome/browser/ui/webui/options/password_manager_handler.cc

Issue 1193143003: Enable import/export of passwords into/from Password Manager (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 4 years, 8 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
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 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/ui/webui/options/password_manager_handler.h" 5 #include "chrome/browser/ui/webui/options/password_manager_handler.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/debug/leak_annotations.h"
8 #include "base/feature_list.h" 10 #include "base/feature_list.h"
11 #include "base/files/file_path.h"
9 #include "base/macros.h" 12 #include "base/macros.h"
13 #include "base/metrics/field_trial.h"
14 #include "base/metrics/histogram.h"
10 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_split.h" 16 #include "base/strings/string_split.h"
12 #include "base/strings/utf_string_conversions.h" 17 #include "base/strings/utf_string_conversions.h"
13 #include "base/values.h" 18 #include "base/values.h"
14 #include "build/build_config.h" 19 #include "build/build_config.h"
15 #include "chrome/browser/chrome_notification_types.h" 20 #include "chrome/browser/chrome_notification_types.h"
21 #include "chrome/browser/password_manager/password_store_factory.h"
16 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/sync/profile_sync_service_factory.h" 23 #include "chrome/browser/sync/profile_sync_service_factory.h"
24 #include "chrome/browser/ui/chrome_select_file_policy.h"
18 #include "chrome/common/pref_names.h" 25 #include "chrome/common/pref_names.h"
19 #include "chrome/common/url_constants.h" 26 #include "chrome/common/url_constants.h"
20 #include "chrome/grit/generated_resources.h" 27 #include "chrome/grit/generated_resources.h"
21 #include "components/autofill/core/common/password_form.h" 28 #include "components/autofill/core/common/password_form.h"
22 #include "components/browser_sync/browser/profile_sync_service.h" 29 #include "components/browser_sync/browser/profile_sync_service.h"
30 #include "components/password_manager/core/browser/export/password_exporter.h"
23 #include "components/password_manager/core/browser/password_bubble_experiment.h" 31 #include "components/password_manager/core/browser/password_bubble_experiment.h"
24 #include "components/password_manager/core/browser/password_manager_constants.h" 32 #include "components/password_manager/core/browser/password_manager_constants.h"
33 #include "components/password_manager/core/browser/password_store.h"
25 #include "components/password_manager/core/browser/password_ui_utils.h" 34 #include "components/password_manager/core/browser/password_ui_utils.h"
26 #include "components/password_manager/core/common/experiments.h" 35 #include "components/password_manager/core/common/experiments.h"
36 #include "components/password_manager/core/common/password_manager_features.h"
27 #include "components/prefs/pref_service.h" 37 #include "components/prefs/pref_service.h"
28 #include "components/strings/grit/components_strings.h" 38 #include "components/strings/grit/components_strings.h"
29 #include "components/url_formatter/url_formatter.h" 39 #include "components/url_formatter/url_formatter.h"
40 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/notification_details.h" 41 #include "content/public/browser/notification_details.h"
31 #include "content/public/browser/notification_source.h" 42 #include "content/public/browser/notification_source.h"
32 #include "content/public/browser/user_metrics.h" 43 #include "content/public/browser/user_metrics.h"
33 #include "content/public/browser/web_contents.h" 44 #include "content/public/browser/web_contents.h"
34 #include "content/public/browser/web_ui.h" 45 #include "content/public/browser/web_ui.h"
35 #include "content/public/common/content_features.h" 46 #include "content/public/common/content_features.h"
36 #include "content/public/common/origin_util.h" 47 #include "content/public/common/origin_util.h"
37 #include "ui/base/l10n/l10n_util.h" 48 #include "ui/base/l10n/l10n_util.h"
38 49
39 #if defined(OS_WIN) && defined(USE_ASH) 50 #if defined(OS_WIN) && defined(USE_ASH)
(...skipping 28 matching lines...) Expand all
68 DCHECK(link_url.is_valid()); 79 DCHECK(link_url.is_valid());
69 entry->SetString( 80 entry->SetString(
70 kUrlField, url_formatter::FormatUrl( 81 kUrlField, url_formatter::FormatUrl(
71 link_url, url_formatter::kFormatUrlOmitNothing, 82 link_url, url_formatter::kFormatUrlOmitNothing,
72 net::UnescapeRule::SPACES, nullptr, nullptr, nullptr)); 83 net::UnescapeRule::SPACES, nullptr, nullptr, nullptr));
73 entry->SetBoolean(kIsAndroidUriField, is_android_uri); 84 entry->SetBoolean(kIsAndroidUriField, is_android_uri);
74 entry->SetBoolean(kIsClickable, origin_is_clickable); 85 entry->SetBoolean(kIsClickable, origin_is_clickable);
75 entry->SetBoolean(kIsSecureField, content::IsOriginSecure(link_url)); 86 entry->SetBoolean(kIsSecureField, content::IsOriginSecure(link_url));
76 } 87 }
77 88
89 // Enumeration of different callers of SelectFile. Starting count at 1 so
90 // accidental call of SelectFile with params=NULL will error out.
Evan Stade 2016/04/06 21:25:22 in what way does it error out? PasswordManagerHand
xunlu 2016/04/08 15:38:21 I'm not sure how such error will occur. I added "d
Evan Stade 2016/04/08 17:28:28 better to not have default because then if you add
xunlu 2016/04/08 17:56:15 CertificateManagerHandler was doing this, so I ass
Evan Stade 2016/04/08 18:05:09 I don't think it's safe to copy-paste code unless
xunlu 2016/04/08 19:10:20 OK. I will remove "= 1" and the default logic in
91 enum FileSelectorCaller {
92 IMPORT_FILE_SELECTED = 1,
93 EXPORT_FILE_SELECTED,
94 };
95
78 } // namespace 96 } // namespace
79 97
80 PasswordManagerHandler::PasswordManagerHandler() 98 PasswordManagerHandler::PasswordManagerHandler() {
81 : password_manager_presenter_(this) {} 99 password_manager_presenter_.reset(new PasswordManagerPresenter(this));
100 }
101
102 PasswordManagerHandler::PasswordManagerHandler(
103 scoped_ptr<PasswordManagerPresenter> presenter)
104 : password_manager_presenter_(std::move(presenter)) {}
Evan Stade 2016/04/06 21:25:22 is this std::move necessary?
xunlu 2016/04/08 15:38:21 Just for or my education: why not? I thought ther
Evan Stade 2016/04/08 17:28:28 I posed it as a question because I didn't know the
xunlu 2016/04/08 19:10:20 Acknowledged.
82 105
83 PasswordManagerHandler::~PasswordManagerHandler() {} 106 PasswordManagerHandler::~PasswordManagerHandler() {}
84 107
85 Profile* PasswordManagerHandler::GetProfile() { 108 Profile* PasswordManagerHandler::GetProfile() {
86 return Profile::FromWebUI(web_ui()); 109 return Profile::FromWebUI(web_ui());
87 } 110 }
88 111
89 #if !defined(OS_ANDROID) 112 #if !defined(OS_ANDROID)
90 gfx::NativeWindow PasswordManagerHandler::GetNativeWindow() const { 113 gfx::NativeWindow PasswordManagerHandler::GetNativeWindow() const {
91 return web_ui()->GetWebContents()->GetTopLevelNativeWindow(); 114 return web_ui()->GetWebContents()->GetTopLevelNativeWindow();
(...skipping 10 matching lines...) Expand all
102 {"autoSigninDescription", IDS_PASSWORDS_AUTO_SIGNIN_DESCRIPTION}, 125 {"autoSigninDescription", IDS_PASSWORDS_AUTO_SIGNIN_DESCRIPTION},
103 {"savedPasswordsTitle", IDS_PASSWORD_MANAGER_SHOW_PASSWORDS_TAB_TITLE}, 126 {"savedPasswordsTitle", IDS_PASSWORD_MANAGER_SHOW_PASSWORDS_TAB_TITLE},
104 {"passwordExceptionsTitle", IDS_PASSWORD_MANAGER_EXCEPTIONS_TAB_TITLE}, 127 {"passwordExceptionsTitle", IDS_PASSWORD_MANAGER_EXCEPTIONS_TAB_TITLE},
105 {"passwordSearchPlaceholder", IDS_PASSWORDS_PAGE_SEARCH_PASSWORDS}, 128 {"passwordSearchPlaceholder", IDS_PASSWORDS_PAGE_SEARCH_PASSWORDS},
106 {"passwordShowButton", IDS_PASSWORDS_PAGE_VIEW_SHOW_BUTTON}, 129 {"passwordShowButton", IDS_PASSWORDS_PAGE_VIEW_SHOW_BUTTON},
107 {"passwordHideButton", IDS_PASSWORDS_PAGE_VIEW_HIDE_BUTTON}, 130 {"passwordHideButton", IDS_PASSWORDS_PAGE_VIEW_HIDE_BUTTON},
108 {"passwordsNoPasswordsDescription", 131 {"passwordsNoPasswordsDescription",
109 IDS_PASSWORDS_PAGE_VIEW_NO_PASSWORDS_DESCRIPTION}, 132 IDS_PASSWORDS_PAGE_VIEW_NO_PASSWORDS_DESCRIPTION},
110 {"passwordsNoExceptionsDescription", 133 {"passwordsNoExceptionsDescription",
111 IDS_PASSWORDS_PAGE_VIEW_NO_EXCEPTIONS_DESCRIPTION}, 134 IDS_PASSWORDS_PAGE_VIEW_NO_EXCEPTIONS_DESCRIPTION},
135 {"passwordManagerImportPasswordButtonText",
136 IDS_PASSWORD_MANAGER_IMPORT_BUTTON},
137 {"passwordManagerExportPasswordButtonText",
138 IDS_PASSWORD_MANAGER_EXPORT_BUTTON},
112 }; 139 };
113 140
114 RegisterStrings(localized_strings, resources, arraysize(resources)); 141 RegisterStrings(localized_strings, resources, arraysize(resources));
115 142
116 const ProfileSyncService* sync_service = 143 const ProfileSyncService* sync_service =
117 ProfileSyncServiceFactory::GetForProfile(GetProfile()); 144 ProfileSyncServiceFactory::GetForProfile(GetProfile());
118 int title_id = 145 int title_id =
119 password_bubble_experiment::IsSmartLockBrandingEnabled(sync_service) 146 password_bubble_experiment::IsSmartLockBrandingEnabled(sync_service)
120 ? IDS_PASSWORD_MANAGER_SMART_LOCK_FOR_PASSWORDS 147 ? IDS_PASSWORD_MANAGER_SMART_LOCK_FOR_PASSWORDS
121 : IDS_PASSWORDS_EXCEPTIONS_WINDOW_TITLE; 148 : IDS_PASSWORDS_EXCEPTIONS_WINDOW_TITLE;
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
168 base::Bind(&PasswordManagerHandler::HandleRemoveSavedPassword, 195 base::Bind(&PasswordManagerHandler::HandleRemoveSavedPassword,
169 base::Unretained(this))); 196 base::Unretained(this)));
170 web_ui()->RegisterMessageCallback( 197 web_ui()->RegisterMessageCallback(
171 "removePasswordException", 198 "removePasswordException",
172 base::Bind(&PasswordManagerHandler::HandleRemovePasswordException, 199 base::Bind(&PasswordManagerHandler::HandleRemovePasswordException,
173 base::Unretained(this))); 200 base::Unretained(this)));
174 web_ui()->RegisterMessageCallback( 201 web_ui()->RegisterMessageCallback(
175 "requestShowPassword", 202 "requestShowPassword",
176 base::Bind(&PasswordManagerHandler::HandleRequestShowPassword, 203 base::Bind(&PasswordManagerHandler::HandleRequestShowPassword,
177 base::Unretained(this))); 204 base::Unretained(this)));
205 web_ui()->RegisterMessageCallback(
206 "importPassword",
207 base::Bind(&PasswordManagerHandler::HandlePasswordImport,
208 base::Unretained(this)));
209 web_ui()->RegisterMessageCallback(
210 "exportPassword",
211 base::Bind(&PasswordManagerHandler::HandlePasswordExport,
212 base::Unretained(this)));
178 } 213 }
179 214
180 void PasswordManagerHandler::InitializeHandler() { 215 void PasswordManagerHandler::InitializeHandler() {
181 password_manager_presenter_.Initialize(); 216 password_manager_presenter_->Initialize();
217 }
218
219 void PasswordManagerHandler::InitializePage() {
220 if (base::FeatureList::IsEnabled(
221 password_manager::features::kPasswordImportExport))
Evan Stade 2016/04/06 21:25:22 nit: curlies
xunlu 2016/04/08 15:38:21 Done.
222 web_ui()->CallJavascriptFunction("PasswordManager.showImportExportButton");
182 } 223 }
183 224
184 void PasswordManagerHandler::HandleRemoveSavedPassword( 225 void PasswordManagerHandler::HandleRemoveSavedPassword(
185 const base::ListValue* args) { 226 const base::ListValue* args) {
186 std::string string_value = base::UTF16ToUTF8(ExtractStringValue(args)); 227 std::string string_value = base::UTF16ToUTF8(ExtractStringValue(args));
187 int index; 228 int index;
188 if (base::StringToInt(string_value, &index) && index >= 0) { 229 if (base::StringToInt(string_value, &index) && index >= 0) {
189 password_manager_presenter_.RemoveSavedPassword(static_cast<size_t>(index)); 230 password_manager_presenter_->RemoveSavedPassword(
231 static_cast<size_t>(index));
190 } 232 }
191 } 233 }
192 234
193 void PasswordManagerHandler::HandleRemovePasswordException( 235 void PasswordManagerHandler::HandleRemovePasswordException(
194 const base::ListValue* args) { 236 const base::ListValue* args) {
195 std::string string_value = base::UTF16ToUTF8(ExtractStringValue(args)); 237 std::string string_value = base::UTF16ToUTF8(ExtractStringValue(args));
196 int index; 238 int index;
197 if (base::StringToInt(string_value, &index) && index >= 0) { 239 if (base::StringToInt(string_value, &index) && index >= 0) {
198 password_manager_presenter_.RemovePasswordException( 240 password_manager_presenter_->RemovePasswordException(
199 static_cast<size_t>(index)); 241 static_cast<size_t>(index));
200 } 242 }
201 } 243 }
202 244
203 void PasswordManagerHandler::HandleRequestShowPassword( 245 void PasswordManagerHandler::HandleRequestShowPassword(
204 const base::ListValue* args) { 246 const base::ListValue* args) {
205 int index; 247 int index;
206 if (!ExtractIntegerValue(args, &index)) 248 if (!ExtractIntegerValue(args, &index))
207 NOTREACHED(); 249 NOTREACHED();
208 250
209 password_manager_presenter_.RequestShowPassword(static_cast<size_t>(index)); 251 password_manager_presenter_->RequestShowPassword(static_cast<size_t>(index));
210 } 252 }
211 253
212 void PasswordManagerHandler::ShowPassword( 254 void PasswordManagerHandler::ShowPassword(
213 size_t index, 255 size_t index,
214 const std::string& origin_url, 256 const std::string& origin_url,
215 const std::string& username, 257 const std::string& username,
216 const base::string16& password_value) { 258 const base::string16& password_value) {
217 // Call back the front end to reveal the password. 259 // Call back the front end to reveal the password.
218 web_ui()->CallJavascriptFunction( 260 web_ui()->CallJavascriptFunction(
219 "PasswordManager.showPassword", 261 "PasswordManager.showPassword",
220 base::FundamentalValue(static_cast<int>(index)), 262 base::FundamentalValue(static_cast<int>(index)),
221 base::StringValue(password_value)); 263 base::StringValue(password_value));
222 } 264 }
223 265
224 void PasswordManagerHandler::HandleUpdatePasswordLists( 266 void PasswordManagerHandler::HandleUpdatePasswordLists(
225 const base::ListValue* args) { 267 const base::ListValue* args) {
226 password_manager_presenter_.UpdatePasswordLists(); 268 password_manager_presenter_->UpdatePasswordLists();
227 } 269 }
228 270
229 void PasswordManagerHandler::SetPasswordList( 271 void PasswordManagerHandler::SetPasswordList(
230 const std::vector<scoped_ptr<autofill::PasswordForm>>& password_list) { 272 const std::vector<scoped_ptr<autofill::PasswordForm>>& password_list) {
231 base::ListValue entries; 273 base::ListValue entries;
232 base::string16 placeholder(base::ASCIIToUTF16(" ")); 274 base::string16 placeholder(base::ASCIIToUTF16(" "));
233 for (const auto& saved_password : password_list) { 275 for (const auto& saved_password : password_list) {
234 scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue); 276 scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue);
235 CopyOriginInfoOfPasswordForm(*saved_password, entry.get()); 277 CopyOriginInfoOfPasswordForm(*saved_password, entry.get());
236 278
(...skipping 24 matching lines...) Expand all
261 for (const auto& exception : password_exception_list) { 303 for (const auto& exception : password_exception_list) {
262 scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue); 304 scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue);
263 CopyOriginInfoOfPasswordForm(*exception, entry.get()); 305 CopyOriginInfoOfPasswordForm(*exception, entry.get());
264 entries.Append(entry.release()); 306 entries.Append(entry.release());
265 } 307 }
266 308
267 web_ui()->CallJavascriptFunction("PasswordManager.setPasswordExceptionsList", 309 web_ui()->CallJavascriptFunction("PasswordManager.setPasswordExceptionsList",
268 entries); 310 entries);
269 } 311 }
270 312
313 void PasswordManagerHandler::FileSelected(const base::FilePath& path,
314 int index,
315 void* params) {
316 switch (static_cast<FileSelectorCaller>(reinterpret_cast<intptr_t>(params))) {
Evan Stade 2016/04/06 21:25:22 why are there two casts here
xunlu 2016/04/08 15:38:21 Done.
Evan Stade 2016/04/08 17:28:28 what I meant was for you to cast to FileSelectorCa
xunlu 2016/04/08 17:56:15 In that case, I need to use two casts otherwise th
Evan Stade 2016/04/08 18:05:09 ok, then stick with two casts. Again, I asked the
xunlu 2016/04/08 19:10:20 Acknowledged.
317 case IMPORT_FILE_SELECTED:
318 ImportPasswordFileSelected(path);
319 break;
320 case EXPORT_FILE_SELECTED:
321 ExportPasswordFileSelected(path);
322 break;
323 }
324 }
325
326 void PasswordManagerHandler::HandlePasswordImport(const base::ListValue* args) {
327 #if !defined(OS_ANDROID) // This is never called on Android.
328 ui::SelectFileDialog::FileTypeInfo file_type_info;
329
330 file_type_info.extensions =
331 password_manager::PasswordImporter::GetSupportedFileExtensions();
332 DCHECK(!file_type_info.extensions.empty() &&
333 !file_type_info.extensions[0].empty());
334 file_type_info.include_all_files = true;
335 ChromeSelectFilePolicy* select_file_policy =
336 new ChromeSelectFilePolicy(web_ui()->GetWebContents());
337 ANNOTATE_LEAKING_OBJECT_PTR(select_file_policy);
338 selected_file_dialog_ =
339 ui::SelectFileDialog::Create(this, select_file_policy);
340 selected_file_dialog_->SelectFile(
341 ui::SelectFileDialog::SELECT_OPEN_FILE,
342 l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_IMPORT_DIALOG_TITLE),
343 base::FilePath(), &file_type_info, 1, file_type_info.extensions[0][0],
344 web_ui()->GetWebContents()->GetTopLevelNativeWindow(),
345 reinterpret_cast<void*>(IMPORT_FILE_SELECTED));
346 #endif
347 }
348
349 void PasswordManagerHandler::ImportPasswordFileSelected(
350 const base::FilePath& path) {
351 scoped_refptr<ImportPasswordResultConsumer> form_consumer(
352 new ImportPasswordResultConsumer(GetProfile()));
353
354 password_manager::PasswordImporter::Import(
355 path, content::BrowserThread::GetMessageLoopProxyForThread(
356 content::BrowserThread::FILE)
357 .get(),
358 base::Bind(&ImportPasswordResultConsumer::ConsumePassword,
359 form_consumer));
360 }
361
362 PasswordManagerHandler::ImportPasswordResultConsumer::
363 ImportPasswordResultConsumer(Profile* profile)
364 : profile_(profile) {}
365
366 void PasswordManagerHandler::ImportPasswordResultConsumer::ConsumePassword(
367 password_manager::PasswordImporter::Result result,
368 const std::vector<autofill::PasswordForm>& forms) {
369 UMA_HISTOGRAM_ENUMERATION(
370 "PasswordManager.ImportPasswordFromCSVResult", result,
371 password_manager::PasswordImporter::NUM_IMPORT_RESULTS);
372 if (result != password_manager::PasswordImporter::SUCCESS)
373 return;
374
375 UMA_HISTOGRAM_COUNTS("PasswordManager.ImportedPasswordsPerUserInCSV",
376 forms.size());
377
378 scoped_refptr<password_manager::PasswordStore> store(
379 PasswordStoreFactory::GetForProfile(profile_,
380 ServiceAccessType::EXPLICIT_ACCESS));
381 if (store) {
382 for (const autofill::PasswordForm& form : forms) {
383 store->AddLogin(form);
384 }
385 }
386 UMA_HISTOGRAM_BOOLEAN("PasswordManager.StorePasswordImportedFromCSVResult",
387 store);
388 }
389
390 void PasswordManagerHandler::HandlePasswordExport(const base::ListValue* args) {
391 #if !defined(OS_ANDROID) // This is never called on Android.
392 if (!password_manager_presenter_->IsUserAuthenticated()) {
393 return;
394 }
395 ui::SelectFileDialog::FileTypeInfo file_type_info;
396 file_type_info.extensions =
397 password_manager::PasswordExporter::GetSupportedFileExtensions();
398 DCHECK(!file_type_info.extensions.empty() &&
399 !file_type_info.extensions[0].empty());
400 file_type_info.include_all_files = true;
401 ChromeSelectFilePolicy* select_file_policy =
402 new ChromeSelectFilePolicy(web_ui()->GetWebContents());
403 ANNOTATE_LEAKING_OBJECT_PTR(select_file_policy);
404 selected_file_dialog_ =
405 ui::SelectFileDialog::Create(this, select_file_policy);
406 selected_file_dialog_->SelectFile(
407 ui::SelectFileDialog::SELECT_SAVEAS_FILE,
408 l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_EXPORT_DIALOG_TITLE),
409 base::FilePath(), &file_type_info, 1, file_type_info.extensions[0][0],
410 GetNativeWindow(), reinterpret_cast<void*>(EXPORT_FILE_SELECTED));
411 #endif
412 }
413
414 void PasswordManagerHandler::ExportPasswordFileSelected(
415 const base::FilePath& path) {
416 std::vector<scoped_ptr<autofill::PasswordForm>> password_list =
417 password_manager_presenter_->GetAllPasswords();
418 UMA_HISTOGRAM_COUNTS("PasswordManager.ExportedPasswordsPerUserInCSV",
419 password_list.size());
420 password_manager::PasswordExporter::Export(
421 path, std::move(password_list),
422 content::BrowserThread::GetMessageLoopProxyForThread(
423 content::BrowserThread::FILE)
424 .get());
425 }
426
271 } // namespace options 427 } // namespace options
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698