1// Copyright (c) 2014 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/extensions/settings_api_bubble_controller.h"
6
7#include "base/metrics/histogram.h"
8#include "chrome/browser/extensions/extension_service.h"
9#include "chrome/browser/extensions/extension_toolbar_model.h"
10#include "chrome/browser/extensions/settings_api_helpers.h"
11#include "chrome/browser/profiles/profile.h"
12#include "chrome/browser/ui/startup/startup_browser_creator.h"
13#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
14#include "chrome/common/url_constants.h"
15#include "chrome/grit/chromium_strings.h"
16#include "chrome/grit/generated_resources.h"
17#include "extensions/browser/extension_prefs.h"
18#include "extensions/browser/extension_registry.h"
19#include "extensions/browser/extension_system.h"
20#include "grit/components_strings.h"
21#include "ui/base/l10n/l10n_util.h"
22
23namespace extensions {
24
25namespace {
26
27////////////////////////////////////////////////////////////////////////////////
28// SettingsApiBubbleDelegate
29
30class SettingsApiBubbleDelegate
31    : public ExtensionMessageBubbleController::Delegate {
32 public:
33  explicit SettingsApiBubbleDelegate(ExtensionService* service,
34                                     Profile* profile,
35                                     SettingsApiOverrideType type);
36  virtual ~SettingsApiBubbleDelegate();
37
38  // ExtensionMessageBubbleController::Delegate methods.
39  virtual bool ShouldIncludeExtension(const std::string& extension_id) OVERRIDE;
40  virtual void AcknowledgeExtension(
41      const std::string& extension_id,
42      ExtensionMessageBubbleController::BubbleAction user_action) OVERRIDE;
43  virtual void PerformAction(const ExtensionIdList& list) OVERRIDE;
44  virtual void OnClose() OVERRIDE;
45  virtual base::string16 GetTitle() const OVERRIDE;
46  virtual base::string16 GetMessageBody(
47      bool anchored_to_browser_action) const OVERRIDE;
48  virtual base::string16 GetOverflowText(
49      const base::string16& overflow_count) const OVERRIDE;
50  virtual base::string16 GetLearnMoreLabel() const OVERRIDE;
51  virtual GURL GetLearnMoreUrl() const OVERRIDE;
52  virtual base::string16 GetActionButtonLabel() const OVERRIDE;
53  virtual base::string16 GetDismissButtonLabel() const OVERRIDE;
54  virtual bool ShouldShowExtensionList() const OVERRIDE;
55  virtual void LogExtensionCount(size_t count) OVERRIDE;
56  virtual void LogAction(
57      ExtensionMessageBubbleController::BubbleAction action) OVERRIDE;
58
59 private:
60  // Our extension service. Weak, not owned by us.
61  ExtensionService* service_;
62
63  // A weak pointer to the profile we are associated with. Not owned by us.
64  Profile* profile_;
65
66  // The type of settings override this bubble will report on. This can be, for
67  // example, a bubble to notify the user that the search engine has been
68  // changed by an extension (or homepage/startup pages/etc).
69  SettingsApiOverrideType type_;
70
71  // The ID of the extension we are showing the bubble for.
72  std::string extension_id_;
73
74  DISALLOW_COPY_AND_ASSIGN(SettingsApiBubbleDelegate);
75};
76
77SettingsApiBubbleDelegate::SettingsApiBubbleDelegate(
78    ExtensionService* service,
79    Profile* profile,
80    SettingsApiOverrideType type)
81    : service_(service), profile_(profile), type_(type) {}
82
83SettingsApiBubbleDelegate::~SettingsApiBubbleDelegate() {}
84
85bool SettingsApiBubbleDelegate::ShouldIncludeExtension(
86    const std::string& extension_id) {
87  ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
88  const Extension* extension =
89      registry->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
90  if (!extension)
91    return false;  // The extension provided is no longer enabled.
92
93  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
94  if (prefs->HasSettingsApiBubbleBeenAcknowledged(extension_id))
95    return false;
96
97  const Extension* override = NULL;
98  switch (type_) {
99    case extensions::BUBBLE_TYPE_HOME_PAGE:
100      override = extensions::GetExtensionOverridingHomepage(profile_);
101      break;
102    case extensions::BUBBLE_TYPE_STARTUP_PAGES:
103      override = extensions::GetExtensionOverridingStartupPages(profile_);
104      break;
105    case extensions::BUBBLE_TYPE_SEARCH_ENGINE:
106      override = extensions::GetExtensionOverridingSearchEngine(profile_);
107      break;
108  }
109
110  if (!override || override->id() != extension->id())
111    return false;
112
113  extension_id_ = extension_id;
114  return true;
115}
116
117void SettingsApiBubbleDelegate::AcknowledgeExtension(
118    const std::string& extension_id,
119    ExtensionMessageBubbleController::BubbleAction user_action) {
120  if (user_action != ExtensionMessageBubbleController::ACTION_EXECUTE) {
121    ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
122    prefs->SetSettingsApiBubbleBeenAcknowledged(extension_id, true);
123  }
124}
125
126void SettingsApiBubbleDelegate::PerformAction(const ExtensionIdList& list) {
127  for (size_t i = 0; i < list.size(); ++i) {
128    service_->DisableExtension(list[i], Extension::DISABLE_USER_ACTION);
129  }
130}
131
132void SettingsApiBubbleDelegate::OnClose() {
133  ExtensionToolbarModel* toolbar_model = ExtensionToolbarModel::Get(profile_);
134  if (toolbar_model)
135    toolbar_model->StopHighlighting();
136}
137
138base::string16 SettingsApiBubbleDelegate::GetTitle() const {
139  switch (type_) {
140    case BUBBLE_TYPE_HOME_PAGE:
141      return l10n_util::GetStringUTF16(
142          IDS_EXTENSIONS_SETTINGS_API_TITLE_HOME_PAGE_BUBBLE);
143    case BUBBLE_TYPE_STARTUP_PAGES:
144      return l10n_util::GetStringUTF16(
145          IDS_EXTENSIONS_SETTINGS_API_TITLE_STARTUP_PAGES_BUBBLE);
146    case BUBBLE_TYPE_SEARCH_ENGINE:
147      return l10n_util::GetStringUTF16(
148          IDS_EXTENSIONS_SETTINGS_API_TITLE_SEARCH_ENGINE_BUBBLE);
149  }
150  NOTREACHED();
151  return base::string16();
152}
153
154base::string16 SettingsApiBubbleDelegate::GetMessageBody(
155    bool anchored_to_browser_action) const {
156  ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
157  const Extension* extension =
158      registry->GetExtensionById(extension_id_, ExtensionRegistry::ENABLED);
159  const SettingsOverrides* settings =
160      extension ? SettingsOverrides::Get(extension) : NULL;
161  if (!extension || !settings) {
162    NOTREACHED();
163    return base::string16();
164  }
165
166  bool home_change = settings->homepage != NULL;
167  bool startup_change = !settings->startup_pages.empty();
168  bool search_change = settings->search_engine != NULL;
169
170  base::string16 body;
171  switch (type_) {
172    case BUBBLE_TYPE_HOME_PAGE:
173      body = l10n_util::GetStringUTF16(
174          IDS_EXTENSIONS_SETTINGS_API_FIRST_LINE_HOME_PAGE);
175      if (startup_change && search_change) {
176        body += l10n_util::GetStringUTF16(
177            IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_AND_SEARCH);
178      } else if (startup_change) {
179        body += l10n_util::GetStringUTF16(
180            IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_PAGES);
181      } else if (search_change) {
182        body += l10n_util::GetStringUTF16(
183            IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_SEARCH_ENGINE);
184      }
185      break;
186    case BUBBLE_TYPE_STARTUP_PAGES:
187      body = l10n_util::GetStringUTF16(
188          IDS_EXTENSIONS_SETTINGS_API_FIRST_LINE_START_PAGES);
189      if (home_change && search_change) {
190        body += l10n_util::GetStringUTF16(
191            IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_HOME_AND_SEARCH);
192      } else if (home_change) {
193        body += l10n_util::GetStringUTF16(
194            IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_HOME_PAGE);
195      } else if (search_change) {
196        body += l10n_util::GetStringUTF16(
197            IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_SEARCH_ENGINE);
198      }
199      break;
200    case BUBBLE_TYPE_SEARCH_ENGINE:
201      body = l10n_util::GetStringUTF16(
202          IDS_EXTENSIONS_SETTINGS_API_FIRST_LINE_SEARCH_ENGINE);
203      if (startup_change && home_change) {
204        body += l10n_util::GetStringUTF16(
205            IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_AND_HOME);
206      } else if (startup_change) {
207        body += l10n_util::GetStringUTF16(
208            IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_START_PAGES);
209      } else if (home_change) {
210        body += l10n_util::GetStringUTF16(
211            IDS_EXTENSIONS_SETTINGS_API_SECOND_LINE_HOME_PAGE);
212      }
213      break;
214  }
215  if (!body.empty())
216    body += l10n_util::GetStringUTF16(
217            IDS_EXTENSIONS_SETTINGS_API_THIRD_LINE_CONFIRMATION);
218  return body;
219}
220
221base::string16 SettingsApiBubbleDelegate::GetOverflowText(
222    const base::string16& overflow_count) const {
223  // Does not have more than one extension in the list at a time.
224  NOTREACHED();
225  return base::string16();
226}
227
228base::string16 SettingsApiBubbleDelegate::GetLearnMoreLabel() const {
229  return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
230}
231
232GURL SettingsApiBubbleDelegate::GetLearnMoreUrl() const {
233  return GURL(chrome::kExtensionControlledSettingLearnMoreURL);
234}
235
236base::string16 SettingsApiBubbleDelegate::GetActionButtonLabel() const {
237  return l10n_util::GetStringUTF16(IDS_EXTENSION_CONTROLLED_RESTORE_SETTINGS);
238}
239
240base::string16 SettingsApiBubbleDelegate::GetDismissButtonLabel() const {
241  return l10n_util::GetStringUTF16(IDS_EXTENSION_CONTROLLED_KEEP_CHANGES);
242}
243
244bool SettingsApiBubbleDelegate::ShouldShowExtensionList() const {
245  return false;
246}
247
248void SettingsApiBubbleDelegate::LogExtensionCount(size_t count) {
249}
250
251void SettingsApiBubbleDelegate::LogAction(
252    ExtensionMessageBubbleController::BubbleAction action) {
253  switch (type_) {
254    case BUBBLE_TYPE_HOME_PAGE:
255      UMA_HISTOGRAM_ENUMERATION(
256          "ExtensionOverrideBubble.SettingsApiUserSelectionHomePage",
257          action,
258          ExtensionMessageBubbleController::ACTION_BOUNDARY);
259      break;
260    case BUBBLE_TYPE_STARTUP_PAGES:
261      UMA_HISTOGRAM_ENUMERATION(
262          "ExtensionOverrideBubble.SettingsApiUserSelectionStartupPage",
263          action,
264          ExtensionMessageBubbleController::ACTION_BOUNDARY);
265      break;
266    case BUBBLE_TYPE_SEARCH_ENGINE:
267      UMA_HISTOGRAM_ENUMERATION(
268          "ExtensionOverrideBubble.SettingsApiUserSelectionSearchEngine",
269          action,
270          ExtensionMessageBubbleController::ACTION_BOUNDARY);
271      break;
272  }
273}
274
275}  // namespace
276
277////////////////////////////////////////////////////////////////////////////////
278// SettingsApiBubbleController
279
280SettingsApiBubbleController::SettingsApiBubbleController(
281    Profile* profile,
282    SettingsApiOverrideType type)
283    : ExtensionMessageBubbleController(
284          new SettingsApiBubbleDelegate(
285              ExtensionSystem::Get(profile)->extension_service(),
286              profile,
287              type),
288          profile),
289      profile_(profile),
290      type_(type) {}
291
292SettingsApiBubbleController::~SettingsApiBubbleController() {}
293
294bool SettingsApiBubbleController::ShouldShow(const std::string& extension_id) {
295  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
296  if (prefs->HasSettingsApiBubbleBeenAcknowledged(extension_id))
297    return false;
298
299  if (!delegate()->ShouldIncludeExtension(extension_id))
300    return false;
301
302  // If the browser is showing the 'Chrome crashed' infobar, it won't be showing
303  // the startup pages, so there's no point in showing the bubble now.
304  if (type_ == BUBBLE_TYPE_STARTUP_PAGES)
305    return profile_->GetLastSessionExitType() != Profile::EXIT_CRASHED;
306
307  return true;
308}
309
310bool SettingsApiBubbleController::CloseOnDeactivate() {
311  // Startup bubbles tend to get lost in the focus storm that happens on
312  // startup. Other types should dismiss on focus loss.
313  return type_ != BUBBLE_TYPE_STARTUP_PAGES;
314}
315
316}  // namespace extensions
317