[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / views / extensions / extension_message_bubble_view.cc
blob43532dbea569f12a943801f7830138ff319d2734
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.
5 #include "chrome/browser/ui/views/extensions/extension_message_bubble_view.h"
7 #include "base/strings/string_number_conversions.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/extensions/dev_mode_bubble_controller.h"
11 #include "chrome/browser/extensions/extension_action_manager.h"
12 #include "chrome/browser/extensions/extension_message_bubble_controller.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/settings_api_bubble_controller.h"
15 #include "chrome/browser/extensions/settings_api_helpers.h"
16 #include "chrome/browser/extensions/suspicious_extension_bubble_controller.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/views/frame/browser_view.h"
19 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
20 #include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
21 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
22 #include "extensions/browser/extension_prefs.h"
23 #include "extensions/browser/extension_system.h"
24 #include "grit/locale_settings.h"
25 #include "ui/accessibility/ax_view_state.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/views/controls/button/label_button.h"
28 #include "ui/views/controls/label.h"
29 #include "ui/views/controls/link.h"
30 #include "ui/views/layout/grid_layout.h"
31 #include "ui/views/view.h"
32 #include "ui/views/widget/widget.h"
34 namespace {
36 base::LazyInstance<std::set<Profile*> > g_profiles_evaluated =
37 LAZY_INSTANCE_INITIALIZER;
39 // Layout constants.
40 const int kExtensionListPadding = 10;
41 const int kInsetBottomRight = 13;
42 const int kInsetLeft = 14;
43 const int kInsetTop = 9;
44 const int kHeadlineMessagePadding = 4;
45 const int kHeadlineRowPadding = 10;
46 const int kMessageBubblePadding = 11;
48 // How many extensions to show in the bubble (max).
49 const size_t kMaxExtensionsToShow = 7;
51 // How long to wait until showing the bubble (in seconds).
52 const int kBubbleAppearanceWaitTime = 5;
54 } // namespace
56 namespace extensions {
58 ExtensionMessageBubbleView::ExtensionMessageBubbleView(
59 views::View* anchor_view,
60 views::BubbleBorder::Arrow arrow_location,
61 scoped_ptr<extensions::ExtensionMessageBubbleController> controller)
62 : BubbleDelegateView(anchor_view, arrow_location),
63 weak_factory_(this),
64 controller_(controller.Pass()),
65 headline_(NULL),
66 learn_more_(NULL),
67 dismiss_button_(NULL),
68 link_clicked_(false),
69 action_taken_(false) {
70 DCHECK(anchor_view->GetWidget());
71 set_close_on_deactivate(controller_->CloseOnDeactivate());
72 set_close_on_esc(true);
74 // Compensate for built-in vertical padding in the anchor view's image.
75 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
78 void ExtensionMessageBubbleView::OnActionButtonClicked(
79 const base::Closure& callback) {
80 action_callback_ = callback;
83 void ExtensionMessageBubbleView::OnDismissButtonClicked(
84 const base::Closure& callback) {
85 dismiss_callback_ = callback;
88 void ExtensionMessageBubbleView::OnLinkClicked(
89 const base::Closure& callback) {
90 link_callback_ = callback;
93 void ExtensionMessageBubbleView::Show() {
94 // Not showing the bubble right away (during startup) has a few benefits:
95 // We don't have to worry about focus being lost due to the Omnibox (or to
96 // other things that want focus at startup). This allows Esc to work to close
97 // the bubble and also solves the keyboard accessibility problem that comes
98 // with focus being lost (we don't have a good generic mechanism of injecting
99 // bubbles into the focus cycle). Another benefit of delaying the show is
100 // that fade-in works (the fade-in isn't apparent if the the bubble appears at
101 // startup).
102 base::MessageLoop::current()->PostDelayedTask(
103 FROM_HERE,
104 base::Bind(&ExtensionMessageBubbleView::ShowBubble,
105 weak_factory_.GetWeakPtr()),
106 base::TimeDelta::FromSeconds(kBubbleAppearanceWaitTime));
109 void ExtensionMessageBubbleView::OnWidgetDestroying(views::Widget* widget) {
110 // To catch Esc, we monitor destroy message. Unless the link has been clicked,
111 // we assume Dismiss was the action taken.
112 if (!link_clicked_ && !action_taken_)
113 dismiss_callback_.Run();
116 ////////////////////////////////////////////////////////////////////////////////
117 // ExtensionMessageBubbleView - private.
119 ExtensionMessageBubbleView::~ExtensionMessageBubbleView() {}
121 void ExtensionMessageBubbleView::ShowBubble() {
122 GetWidget()->Show();
125 void ExtensionMessageBubbleView::Init() {
126 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
128 views::GridLayout* layout = views::GridLayout::CreatePanel(this);
129 layout->SetInsets(kInsetTop, kInsetLeft,
130 kInsetBottomRight, kInsetBottomRight);
131 SetLayoutManager(layout);
133 ExtensionMessageBubbleController::Delegate* delegate =
134 controller_->delegate();
136 const int headline_column_set_id = 0;
137 views::ColumnSet* top_columns = layout->AddColumnSet(headline_column_set_id);
138 top_columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
139 0, views::GridLayout::USE_PREF, 0, 0);
140 top_columns->AddPaddingColumn(1, 0);
141 layout->StartRow(0, headline_column_set_id);
143 headline_ = new views::Label(delegate->GetTitle(),
144 rb.GetFontList(ui::ResourceBundle::MediumFont));
145 layout->AddView(headline_);
147 layout->AddPaddingRow(0, kHeadlineRowPadding);
149 const int text_column_set_id = 1;
150 views::ColumnSet* upper_columns = layout->AddColumnSet(text_column_set_id);
151 upper_columns->AddColumn(
152 views::GridLayout::LEADING, views::GridLayout::LEADING,
153 0, views::GridLayout::USE_PREF, 0, 0);
154 layout->StartRow(0, text_column_set_id);
156 views::Label* message = new views::Label();
157 message->SetMultiLine(true);
158 message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
159 message->SetText(delegate->GetMessageBody());
160 message->SizeToFit(views::Widget::GetLocalizedContentsWidth(
161 IDS_EXTENSION_WIPEOUT_BUBBLE_WIDTH_CHARS));
162 layout->AddView(message);
164 if (delegate->ShouldShowExtensionList()) {
165 const int extension_list_column_set_id = 2;
166 views::ColumnSet* middle_columns =
167 layout->AddColumnSet(extension_list_column_set_id);
168 middle_columns->AddPaddingColumn(0, kExtensionListPadding);
169 middle_columns->AddColumn(
170 views::GridLayout::LEADING, views::GridLayout::CENTER,
171 0, views::GridLayout::USE_PREF, 0, 0);
173 layout->StartRowWithPadding(0, extension_list_column_set_id,
174 0, kHeadlineMessagePadding);
175 views::Label* extensions = new views::Label();
176 extensions->SetMultiLine(true);
177 extensions->SetHorizontalAlignment(gfx::ALIGN_LEFT);
179 std::vector<base::string16> extension_list;
180 base::char16 bullet_point = 0x2022;
182 std::vector<base::string16> suspicious = controller_->GetExtensionList();
183 size_t i = 0;
184 for (; i < suspicious.size() && i < kMaxExtensionsToShow; ++i) {
185 // Add each extension with bullet point.
186 extension_list.push_back(
187 bullet_point + base::ASCIIToUTF16(" ") + suspicious[i]);
190 if (i > kMaxExtensionsToShow) {
191 base::string16 difference = base::IntToString16(i - kMaxExtensionsToShow);
192 extension_list.push_back(bullet_point + base::ASCIIToUTF16(" ") +
193 delegate->GetOverflowText(difference));
196 extensions->SetText(JoinString(extension_list, base::ASCIIToUTF16("\n")));
197 extensions->SizeToFit(views::Widget::GetLocalizedContentsWidth(
198 IDS_EXTENSION_WIPEOUT_BUBBLE_WIDTH_CHARS));
199 layout->AddView(extensions);
202 base::string16 action_button = delegate->GetActionButtonLabel();
204 const int action_row_column_set_id = 3;
205 views::ColumnSet* bottom_columns =
206 layout->AddColumnSet(action_row_column_set_id);
207 bottom_columns->AddColumn(views::GridLayout::LEADING,
208 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
209 bottom_columns->AddPaddingColumn(1, 0);
210 bottom_columns->AddColumn(views::GridLayout::TRAILING,
211 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
212 if (!action_button.empty()) {
213 bottom_columns->AddColumn(views::GridLayout::TRAILING,
214 views::GridLayout::CENTER, 0, views::GridLayout::USE_PREF, 0, 0);
216 layout->StartRowWithPadding(0, action_row_column_set_id,
217 0, kMessageBubblePadding);
219 learn_more_ = new views::Link(delegate->GetLearnMoreLabel());
220 learn_more_->set_listener(this);
221 layout->AddView(learn_more_);
223 if (!action_button.empty()) {
224 action_button_ = new views::LabelButton(this, action_button.c_str());
225 action_button_->SetStyle(views::Button::STYLE_BUTTON);
226 layout->AddView(action_button_);
229 dismiss_button_ = new views::LabelButton(this,
230 delegate->GetDismissButtonLabel());
231 dismiss_button_->SetStyle(views::Button::STYLE_BUTTON);
232 layout->AddView(dismiss_button_);
235 void ExtensionMessageBubbleView::ButtonPressed(views::Button* sender,
236 const ui::Event& event) {
237 if (sender == action_button_) {
238 action_taken_ = true;
239 action_callback_.Run();
240 } else {
241 DCHECK_EQ(dismiss_button_, sender);
243 GetWidget()->Close();
246 void ExtensionMessageBubbleView::LinkClicked(views::Link* source,
247 int event_flags) {
248 DCHECK_EQ(learn_more_, source);
249 link_clicked_ = true;
250 link_callback_.Run();
251 GetWidget()->Close();
254 void ExtensionMessageBubbleView::GetAccessibleState(
255 ui::AXViewState* state) {
256 state->role = ui::AX_ROLE_ALERT;
259 void ExtensionMessageBubbleView::ViewHierarchyChanged(
260 const ViewHierarchyChangedDetails& details) {
261 if (details.is_add && details.child == this)
262 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
265 ////////////////////////////////////////////////////////////////////////////////
266 // ExtensionMessageBubbleFactory
268 ExtensionMessageBubbleFactory::ExtensionMessageBubbleFactory(
269 Profile* profile,
270 ToolbarView* toolbar_view)
271 : profile_(profile),
272 toolbar_view_(toolbar_view),
273 shown_suspicious_extensions_bubble_(false),
274 shown_startup_override_extensions_bubble_(false),
275 shown_dev_mode_extensions_bubble_(false),
276 is_observing_(false),
277 stage_(STAGE_START),
278 container_(NULL),
279 anchor_view_(NULL) {}
281 ExtensionMessageBubbleFactory::~ExtensionMessageBubbleFactory() {
282 MaybeStopObserving();
285 void ExtensionMessageBubbleFactory::MaybeShow(views::View* anchor_view) {
286 #if defined(OS_WIN)
287 bool is_initial_check = IsInitialProfileCheck(profile_->GetOriginalProfile());
288 RecordProfileCheck(profile_->GetOriginalProfile());
290 // The list of suspicious extensions takes priority over the dev mode bubble
291 // and the settings API bubble, since that needs to be shown as soon as we
292 // disable something. The settings API bubble is shown on first startup after
293 // an extension has changed the startup pages and it is acceptable if that
294 // waits until the next startup because of the suspicious extension bubble.
295 // The dev mode bubble is not time sensitive like the other two so we'll catch
296 // the dev mode extensions on the next startup/next window that opens. That
297 // way, we're not too spammy with the bubbles.
298 if (!shown_suspicious_extensions_bubble_ &&
299 MaybeShowSuspiciousExtensionsBubble(anchor_view))
300 return;
302 if (!shown_startup_override_extensions_bubble_ &&
303 is_initial_check &&
304 MaybeShowStartupOverrideExtensionsBubble(anchor_view))
305 return;
307 if (!shown_dev_mode_extensions_bubble_)
308 MaybeShowDevModeExtensionsBubble(anchor_view);
309 #endif // OS_WIN
312 bool ExtensionMessageBubbleFactory::MaybeShowSuspiciousExtensionsBubble(
313 views::View* anchor_view) {
314 DCHECK(!shown_suspicious_extensions_bubble_);
316 scoped_ptr<SuspiciousExtensionBubbleController> suspicious_extensions(
317 new SuspiciousExtensionBubbleController(profile_));
318 if (!suspicious_extensions->ShouldShow())
319 return false;
321 shown_suspicious_extensions_bubble_ = true;
322 SuspiciousExtensionBubbleController* weak_controller =
323 suspicious_extensions.get();
324 ExtensionMessageBubbleView* bubble_delegate = new ExtensionMessageBubbleView(
325 anchor_view,
326 views::BubbleBorder::TOP_RIGHT,
327 suspicious_extensions.PassAs<ExtensionMessageBubbleController>());
329 views::BubbleDelegateView::CreateBubble(bubble_delegate);
330 weak_controller->Show(bubble_delegate);
332 return true;
335 bool ExtensionMessageBubbleFactory::MaybeShowStartupOverrideExtensionsBubble(
336 views::View* anchor_view) {
337 #if !defined(OS_WIN)
338 return false;
339 #endif
341 DCHECK(!shown_startup_override_extensions_bubble_);
343 const Extension* extension = OverridesStartupPages(profile_, NULL);
344 if (!extension)
345 return false;
347 scoped_ptr<SettingsApiBubbleController> settings_api_bubble(
348 new SettingsApiBubbleController(profile_,
349 BUBBLE_TYPE_STARTUP_PAGES));
350 if (!settings_api_bubble->ShouldShow(extension->id()))
351 return false;
353 shown_startup_override_extensions_bubble_ = true;
354 SettingsApiBubbleController* weak_controller = settings_api_bubble.get();
355 ExtensionMessageBubbleView* bubble_delegate = new ExtensionMessageBubbleView(
356 anchor_view,
357 views::BubbleBorder::TOP_RIGHT,
358 settings_api_bubble.PassAs<ExtensionMessageBubbleController>());
359 views::BubbleDelegateView::CreateBubble(bubble_delegate);
360 weak_controller->Show(bubble_delegate);
362 return true;
365 bool ExtensionMessageBubbleFactory::MaybeShowDevModeExtensionsBubble(
366 views::View* anchor_view) {
367 DCHECK(!shown_dev_mode_extensions_bubble_);
369 // Check the Developer Mode extensions.
370 scoped_ptr<DevModeBubbleController> dev_mode_extensions(
371 new DevModeBubbleController(profile_));
373 // Return early if we have none to show.
374 if (!dev_mode_extensions->ShouldShow())
375 return false;
377 shown_dev_mode_extensions_bubble_ = true;
379 // We should be in the start stage (i.e., should not have a pending attempt to
380 // show a bubble).
381 DCHECK_EQ(stage_, STAGE_START);
383 // Prepare to display and highlight the developer mode extensions before
384 // showing the bubble. Since this is an asynchronous process, set member
385 // variables for later use.
386 controller_ = dev_mode_extensions.Pass();
387 anchor_view_ = anchor_view;
388 container_ = toolbar_view_->browser_actions();
390 if (container_->animating())
391 MaybeObserve();
392 else
393 HighlightDevModeExtensions();
395 return true;
398 void ExtensionMessageBubbleFactory::MaybeObserve() {
399 if (!is_observing_) {
400 is_observing_ = true;
401 container_->AddObserver(this);
405 void ExtensionMessageBubbleFactory::MaybeStopObserving() {
406 if (is_observing_) {
407 is_observing_ = false;
408 container_->RemoveObserver(this);
412 void ExtensionMessageBubbleFactory::RecordProfileCheck(Profile* profile) {
413 g_profiles_evaluated.Get().insert(profile);
416 bool ExtensionMessageBubbleFactory::IsInitialProfileCheck(Profile* profile) {
417 return g_profiles_evaluated.Get().count(profile) == 0;
420 void ExtensionMessageBubbleFactory::OnBrowserActionsContainerAnimationEnded() {
421 MaybeStopObserving();
422 if (stage_ == STAGE_START) {
423 HighlightDevModeExtensions();
424 } else if (stage_ == STAGE_HIGHLIGHTED) {
425 ShowDevModeBubble();
426 } else { // We shouldn't be observing if we've completed the process.
427 NOTREACHED();
428 Finish();
432 void ExtensionMessageBubbleFactory::OnBrowserActionsContainerDestroyed() {
433 // If the container associated with the bubble is destroyed, abandon the
434 // process.
435 Finish();
438 void ExtensionMessageBubbleFactory::HighlightDevModeExtensions() {
439 DCHECK_EQ(STAGE_START, stage_);
440 stage_ = STAGE_HIGHLIGHTED;
442 const ExtensionIdList extension_list = controller_->GetExtensionIdList();
443 DCHECK(!extension_list.empty());
444 ExtensionToolbarModel::Get(profile_)->HighlightExtensions(extension_list);
445 if (container_->animating())
446 MaybeObserve();
447 else
448 ShowDevModeBubble();
451 void ExtensionMessageBubbleFactory::ShowDevModeBubble() {
452 DCHECK_EQ(stage_, STAGE_HIGHLIGHTED);
453 stage_ = STAGE_COMPLETE;
455 views::View* reference_view = NULL;
456 if (container_->num_browser_actions() > 0)
457 reference_view = container_->GetBrowserActionViewAt(0);
458 if (reference_view && reference_view->visible())
459 anchor_view_ = reference_view;
461 DevModeBubbleController* weak_controller = controller_.get();
462 ExtensionMessageBubbleView* bubble_delegate = new ExtensionMessageBubbleView(
463 anchor_view_,
464 views::BubbleBorder::TOP_RIGHT,
465 scoped_ptr<ExtensionMessageBubbleController>(controller_.release()));
466 views::BubbleDelegateView::CreateBubble(bubble_delegate);
467 weak_controller->Show(bubble_delegate);
469 Finish();
472 void ExtensionMessageBubbleFactory::Finish() {
473 MaybeStopObserving();
474 controller_.reset();
475 anchor_view_ = NULL;
476 container_ = NULL;
479 } // namespace extensions