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