1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/profile_resetter/resettable_settings_snapshot.h"
6
7#include "base/json/json_writer.h"
8#include "base/prefs/pref_service.h"
9#include "base/strings/string_util.h"
10#include "base/strings/utf_string_conversions.h"
11#include "base/synchronization/cancellation_flag.h"
12#include "base/values.h"
13#include "chrome/browser/browser_process.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/search_engines/template_url_service_factory.h"
16#include "chrome/common/chrome_content_client.h"
17#include "chrome/common/chrome_version_info.h"
18#include "chrome/common/pref_names.h"
19#include "chrome/grit/generated_resources.h"
20#include "chrome/grit/google_chrome_strings.h"
21#include "components/feedback/feedback_data.h"
22#include "components/feedback/feedback_util.h"
23#include "components/search_engines/template_url_service.h"
24#include "content/public/browser/browser_thread.h"
25#include "extensions/browser/extension_registry.h"
26#include "ui/base/l10n/l10n_util.h"
27
28using feedback::FeedbackData;
29
30namespace {
31
32// Feedback bucket labels.
33const char kProfileResetPromptBucket[] = "SamplingOfSettingsResetPrompt";
34const char kProfileResetWebUIBucket[] = "ProfileResetReport";
35
36// Dictionary keys for feedback report.
37const char kDefaultSearchEnginePath[] = "default_search_engine";
38const char kEnabledExtensions[] = "enabled_extensions";
39const char kHomepageIsNewTabPage[] = "homepage_is_ntp";
40const char kHomepagePath[] = "homepage";
41const char kShortcuts[] = "shortcuts";
42const char kShowHomeButton[] = "show_home_button";
43const char kStartupTypePath[] = "startup_type";
44const char kStartupURLPath[] = "startup_urls";
45
46template <class StringType>
47void AddPair(base::ListValue* list,
48             const base::string16& key,
49             const StringType& value) {
50  base::DictionaryValue* results = new base::DictionaryValue();
51  results->SetString("key", key);
52  results->SetString("value", value);
53  list->Append(results);
54}
55
56}  // namespace
57
58ResettableSettingsSnapshot::ResettableSettingsSnapshot(
59    Profile* profile)
60    : startup_(SessionStartupPref::GetStartupPref(profile)),
61      shortcuts_determined_(false),
62      weak_ptr_factory_(this) {
63  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
64  // URLs are always stored sorted.
65  std::sort(startup_.urls.begin(), startup_.urls.end());
66
67  PrefService* prefs = profile->GetPrefs();
68  DCHECK(prefs);
69  homepage_ = prefs->GetString(prefs::kHomePage);
70  homepage_is_ntp_ = prefs->GetBoolean(prefs::kHomePageIsNewTabPage);
71  show_home_button_ = prefs->GetBoolean(prefs::kShowHomeButton);
72
73  TemplateURLService* service =
74      TemplateURLServiceFactory::GetForProfile(profile);
75  DCHECK(service);
76  TemplateURL* dse = service->GetDefaultSearchProvider();
77  if (dse)
78    dse_url_ = dse->url();
79
80  const extensions::ExtensionSet& enabled_ext =
81      extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
82  enabled_extensions_.reserve(enabled_ext.size());
83
84  for (extensions::ExtensionSet::const_iterator it = enabled_ext.begin();
85       it != enabled_ext.end(); ++it)
86    enabled_extensions_.push_back(std::make_pair((*it)->id(), (*it)->name()));
87
88  // ExtensionSet is sorted but it seems to be an implementation detail.
89  std::sort(enabled_extensions_.begin(), enabled_extensions_.end());
90}
91
92ResettableSettingsSnapshot::~ResettableSettingsSnapshot() {
93  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
94  if (cancellation_flag_.get())
95    cancellation_flag_->data.Set();
96}
97
98void ResettableSettingsSnapshot::Subtract(
99    const ResettableSettingsSnapshot& snapshot) {
100  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
101  ExtensionList extensions = base::STLSetDifference<ExtensionList>(
102      enabled_extensions_, snapshot.enabled_extensions_);
103  enabled_extensions_.swap(extensions);
104}
105
106int ResettableSettingsSnapshot::FindDifferentFields(
107    const ResettableSettingsSnapshot& snapshot) const {
108  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
109  int bit_mask = 0;
110
111  if (startup_.type != snapshot.startup_.type ||
112      startup_.urls != snapshot.startup_.urls)
113    bit_mask |= STARTUP_MODE;
114
115  if (homepage_is_ntp_ != snapshot.homepage_is_ntp_ ||
116      homepage_ != snapshot.homepage_ ||
117      show_home_button_ != snapshot.show_home_button_)
118    bit_mask |= HOMEPAGE;
119
120  if (dse_url_ != snapshot.dse_url_)
121    bit_mask |= DSE_URL;
122
123  if (enabled_extensions_ != snapshot.enabled_extensions_)
124    bit_mask |= EXTENSIONS;
125
126  if (shortcuts_ != snapshot.shortcuts_)
127    bit_mask |= SHORTCUTS;
128
129  COMPILE_ASSERT(ResettableSettingsSnapshot::ALL_FIELDS == 31,
130                 add_new_field_here);
131
132  return bit_mask;
133}
134
135void ResettableSettingsSnapshot::RequestShortcuts(
136    const base::Closure& callback) {
137  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
138  DCHECK(!cancellation_flag_.get() && !shortcuts_determined());
139
140  cancellation_flag_ = new SharedCancellationFlag;
141  content::BrowserThread::PostTaskAndReplyWithResult(
142      content::BrowserThread::FILE,
143      FROM_HERE,
144      base::Bind(&GetChromeLaunchShortcuts, cancellation_flag_),
145      base::Bind(&ResettableSettingsSnapshot::SetShortcutsAndReport,
146                 weak_ptr_factory_.GetWeakPtr(),
147                 callback));
148}
149
150void ResettableSettingsSnapshot::SetShortcutsAndReport(
151    const base::Closure& callback,
152    const std::vector<ShortcutCommand>& shortcuts) {
153  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
154  shortcuts_ = shortcuts;
155  shortcuts_determined_ = true;
156  cancellation_flag_ = NULL;
157
158  if (!callback.is_null())
159    callback.Run();
160}
161
162std::string SerializeSettingsReport(const ResettableSettingsSnapshot& snapshot,
163                                    int field_mask) {
164  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
165  base::DictionaryValue dict;
166
167  if (field_mask & ResettableSettingsSnapshot::STARTUP_MODE) {
168    base::ListValue* list = new base::ListValue;
169    const std::vector<GURL>& urls = snapshot.startup_urls();
170    for (std::vector<GURL>::const_iterator i = urls.begin();
171         i != urls.end(); ++i)
172      list->AppendString(i->spec());
173    dict.Set(kStartupURLPath, list);
174    dict.SetInteger(kStartupTypePath, snapshot.startup_type());
175  }
176
177  if (field_mask & ResettableSettingsSnapshot::HOMEPAGE) {
178    dict.SetString(kHomepagePath, snapshot.homepage());
179    dict.SetBoolean(kHomepageIsNewTabPage, snapshot.homepage_is_ntp());
180    dict.SetBoolean(kShowHomeButton, snapshot.show_home_button());
181  }
182
183  if (field_mask & ResettableSettingsSnapshot::DSE_URL)
184    dict.SetString(kDefaultSearchEnginePath, snapshot.dse_url());
185
186  if (field_mask & ResettableSettingsSnapshot::EXTENSIONS) {
187    base::ListValue* list = new base::ListValue;
188    const ResettableSettingsSnapshot::ExtensionList& extensions =
189        snapshot.enabled_extensions();
190    for (ResettableSettingsSnapshot::ExtensionList::const_iterator i =
191         extensions.begin(); i != extensions.end(); ++i) {
192      // Replace "\"" to simplify server-side analysis.
193      std::string ext_name;
194      base::ReplaceChars(i->second, "\"", "\'", &ext_name);
195      list->AppendString(i->first + ";" + ext_name);
196    }
197    dict.Set(kEnabledExtensions, list);
198  }
199
200  if (field_mask & ResettableSettingsSnapshot::SHORTCUTS) {
201    base::ListValue* list = new base::ListValue;
202    const std::vector<ShortcutCommand>& shortcuts = snapshot.shortcuts();
203    for (std::vector<ShortcutCommand>::const_iterator i = shortcuts.begin();
204         i != shortcuts.end(); ++i) {
205      base::string16 arguments;
206      // Replace "\"" to simplify server-side analysis.
207      base::ReplaceChars(i->second, base::ASCIIToUTF16("\""),
208                         base::ASCIIToUTF16("\'"), &arguments);
209      list->AppendString(arguments);
210    }
211    dict.Set(kShortcuts, list);
212  }
213
214  COMPILE_ASSERT(ResettableSettingsSnapshot::ALL_FIELDS == 31,
215                 serialize_new_field_here);
216
217  std::string json;
218  base::JSONWriter::Write(&dict, &json);
219  return json;
220}
221
222void SendSettingsFeedback(const std::string& report,
223                          Profile* profile,
224                          SnapshotCaller caller) {
225  scoped_refptr<FeedbackData> feedback_data = new FeedbackData();
226  std::string bucket;
227  switch (caller) {
228    case PROFILE_RESET_WEBUI:
229      bucket = kProfileResetWebUIBucket;
230      break;
231    case PROFILE_RESET_PROMPT:
232      bucket = kProfileResetPromptBucket;
233      break;
234  }
235  feedback_data->set_category_tag(bucket);
236  feedback_data->set_description(report);
237
238  feedback_data->set_image(make_scoped_ptr(new std::string));
239  feedback_data->set_context(profile);
240
241  feedback_data->set_page_url("");
242  feedback_data->set_user_email("");
243
244  feedback_util::SendReport(feedback_data);
245}
246
247scoped_ptr<base::ListValue> GetReadableFeedbackForSnapshot(
248    Profile* profile,
249    const ResettableSettingsSnapshot& snapshot) {
250  DCHECK(profile);
251  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
252  scoped_ptr<base::ListValue> list(new base::ListValue);
253  AddPair(list.get(),
254          l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_LOCALE),
255          g_browser_process->GetApplicationLocale());
256  AddPair(list.get(),
257          l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_USER_AGENT),
258          GetUserAgent());
259  chrome::VersionInfo version_info;
260  std::string version = version_info.Version();
261  version += chrome::VersionInfo::GetVersionStringModifier();
262  AddPair(list.get(),
263          l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
264          version);
265
266  // Add snapshot data.
267  const std::vector<GURL>& urls = snapshot.startup_urls();
268  std::string startup_urls;
269  for (std::vector<GURL>::const_iterator i = urls.begin();
270       i != urls.end(); ++i) {
271    if (!startup_urls.empty())
272      startup_urls += ' ';
273    startup_urls += i->host();
274  }
275  if (!startup_urls.empty()) {
276    AddPair(list.get(),
277            l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_STARTUP_URLS),
278            startup_urls);
279  }
280
281  base::string16 startup_type;
282  switch (snapshot.startup_type()) {
283    case SessionStartupPref::DEFAULT:
284      startup_type = l10n_util::GetStringUTF16(IDS_OPTIONS_STARTUP_SHOW_NEWTAB);
285      break;
286    case SessionStartupPref::LAST:
287      startup_type = l10n_util::GetStringUTF16(
288          IDS_OPTIONS_STARTUP_RESTORE_LAST_SESSION);
289      break;
290    case SessionStartupPref::URLS:
291      startup_type = l10n_util::GetStringUTF16(IDS_OPTIONS_STARTUP_SHOW_PAGES);
292      break;
293    default:
294      break;
295  }
296  AddPair(list.get(),
297          l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_STARTUP_TYPE),
298          startup_type);
299
300  if (!snapshot.homepage().empty()) {
301    AddPair(list.get(),
302            l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_HOMEPAGE),
303            snapshot.homepage());
304  }
305
306  int is_ntp_message_id = snapshot.homepage_is_ntp() ?
307      IDS_RESET_PROFILE_SETTINGS_HOMEPAGE_IS_NTP_TRUE :
308      IDS_RESET_PROFILE_SETTINGS_HOMEPAGE_IS_NTP_FALSE;
309  AddPair(list.get(),
310          l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_HOMEPAGE_IS_NTP),
311          l10n_util::GetStringUTF16(is_ntp_message_id));
312
313  int show_home_button_id = snapshot.show_home_button() ?
314      IDS_RESET_PROFILE_SETTINGS_SHOW_HOME_BUTTON_TRUE :
315      IDS_RESET_PROFILE_SETTINGS_SHOW_HOME_BUTTON_FALSE;
316  AddPair(
317      list.get(),
318      l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_SHOW_HOME_BUTTON),
319      l10n_util::GetStringUTF16(show_home_button_id));
320
321  TemplateURLService* service =
322      TemplateURLServiceFactory::GetForProfile(profile);
323  DCHECK(service);
324  TemplateURL* dse = service->GetDefaultSearchProvider();
325  if (dse) {
326    AddPair(list.get(),
327            l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_DSE),
328            dse->GenerateSearchURL(service->search_terms_data()).host());
329  }
330
331  if (snapshot.shortcuts_determined()) {
332    base::string16 shortcut_targets;
333    const std::vector<ShortcutCommand>& shortcuts = snapshot.shortcuts();
334    for (std::vector<ShortcutCommand>::const_iterator i =
335         shortcuts.begin(); i != shortcuts.end(); ++i) {
336      if (!shortcut_targets.empty())
337        shortcut_targets += base::ASCIIToUTF16("\n");
338      shortcut_targets += base::ASCIIToUTF16("chrome.exe ");
339      shortcut_targets += i->second;
340    }
341    if (!shortcut_targets.empty()) {
342      AddPair(list.get(),
343              l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_SHORTCUTS),
344              shortcut_targets);
345    }
346  } else {
347    AddPair(list.get(),
348            l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_SHORTCUTS),
349            l10n_util::GetStringUTF16(
350                IDS_RESET_PROFILE_SETTINGS_PROCESSING_SHORTCUTS));
351  }
352
353  const ResettableSettingsSnapshot::ExtensionList& extensions =
354      snapshot.enabled_extensions();
355  std::string extension_names;
356  for (ResettableSettingsSnapshot::ExtensionList::const_iterator i =
357       extensions.begin(); i != extensions.end(); ++i) {
358    if (!extension_names.empty())
359      extension_names += '\n';
360    extension_names += i->second;
361  }
362  if (!extension_names.empty()) {
363    AddPair(list.get(),
364            l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_EXTENSIONS),
365            extension_names);
366  }
367  return list.Pass();
368}
369