Update To 11.40.268.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       controller_(controller.Pass()),
66       anchor_view_(anchor_view),
67       headline_(NULL),
68       learn_more_(NULL),
69       dismiss_button_(NULL),
70       link_clicked_(false),
71       action_taken_(false),
72       weak_factory_(this) {
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 =
334       new ExtensionMessageBubbleView(anchor_view,
335                                      views::BubbleBorder::TOP_RIGHT,
336                                      suspicious_extensions.Pass());
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(settings_api_bubble.Pass(), anchor_view);
363   return true;
364 #endif
365 }
366
367 bool ExtensionMessageBubbleFactory::MaybeShowProxyOverrideExtensionsBubble(
368     views::View* anchor_view) {
369 #if !defined(OS_WIN)
370   return false;
371 #else
372   DCHECK(!shown_proxy_override_extensions_bubble_);
373
374   const Extension* extension = GetExtensionOverridingProxy(profile_);
375   if (!extension)
376     return false;
377
378   scoped_ptr<ProxyOverriddenBubbleController> proxy_bubble(
379       new ProxyOverriddenBubbleController(profile_));
380   if (!proxy_bubble->ShouldShow(extension->id()))
381     return false;
382
383   shown_proxy_override_extensions_bubble_ = true;
384   PrepareToHighlightExtensions(proxy_bubble.Pass(), anchor_view);
385   return true;
386 #endif
387 }
388
389 bool ExtensionMessageBubbleFactory::MaybeShowDevModeExtensionsBubble(
390     views::View* anchor_view) {
391   DCHECK(!shown_dev_mode_extensions_bubble_);
392
393   // Check the Developer Mode extensions.
394   scoped_ptr<DevModeBubbleController> dev_mode_extensions(
395       new DevModeBubbleController(profile_));
396
397   // Return early if we have none to show.
398   if (!dev_mode_extensions->ShouldShow())
399     return false;
400
401   shown_dev_mode_extensions_bubble_ = true;
402   PrepareToHighlightExtensions(dev_mode_extensions.Pass(), anchor_view);
403   return true;
404 }
405
406 void ExtensionMessageBubbleFactory::MaybeObserve() {
407   if (!is_observing_) {
408     is_observing_ = true;
409     container_->AddObserver(this);
410   }
411 }
412
413 void ExtensionMessageBubbleFactory::MaybeStopObserving() {
414   if (is_observing_) {
415     is_observing_ = false;
416     container_->RemoveObserver(this);
417   }
418 }
419
420 void ExtensionMessageBubbleFactory::RecordProfileCheck(Profile* profile) {
421   g_profiles_evaluated.Get().insert(profile);
422 }
423
424 bool ExtensionMessageBubbleFactory::IsInitialProfileCheck(Profile* profile) {
425   return g_profiles_evaluated.Get().count(profile) == 0;
426 }
427
428 void ExtensionMessageBubbleFactory::OnBrowserActionsContainerAnimationEnded() {
429   MaybeStopObserving();
430   if (stage_ == STAGE_START) {
431     HighlightExtensions();
432   } else if (stage_ == STAGE_HIGHLIGHTED) {
433     ShowHighlightingBubble();
434   } else {  // We shouldn't be observing if we've completed the process.
435     NOTREACHED();
436     Finish();
437   }
438 }
439
440 void ExtensionMessageBubbleFactory::OnBrowserActionsContainerDestroyed() {
441   // If the container associated with the bubble is destroyed, abandon the
442   // process.
443   Finish();
444 }
445
446 void ExtensionMessageBubbleFactory::PrepareToHighlightExtensions(
447     scoped_ptr<ExtensionMessageBubbleController> controller,
448     views::View* anchor_view) {
449   // We should be in the start stage (i.e., should not have a pending attempt to
450   // show a bubble).
451   DCHECK_EQ(stage_, STAGE_START);
452
453   // Prepare to display and highlight the extensions before showing the bubble.
454   // Since this is an asynchronous process, set member variables for later use.
455   controller_ = controller.Pass();
456   anchor_view_ = anchor_view;
457   container_ = toolbar_view_->browser_actions();
458
459   if (container_->animating())
460     MaybeObserve();
461   else
462     HighlightExtensions();
463 }
464
465 void ExtensionMessageBubbleFactory::HighlightExtensions() {
466   DCHECK_EQ(STAGE_START, stage_);
467   stage_ = STAGE_HIGHLIGHTED;
468
469   const ExtensionIdList extension_list = controller_->GetExtensionIdList();
470   DCHECK(!extension_list.empty());
471   ExtensionToolbarModel::Get(profile_)->HighlightExtensions(extension_list);
472   if (container_->animating())
473     MaybeObserve();
474   else
475     ShowHighlightingBubble();
476 }
477
478 void ExtensionMessageBubbleFactory::ShowHighlightingBubble() {
479   DCHECK_EQ(stage_, STAGE_HIGHLIGHTED);
480   stage_ = STAGE_COMPLETE;
481
482   views::View* reference_view = NULL;
483   if (container_->num_toolbar_actions() > 0u)
484     reference_view = container_->GetToolbarActionViewAt(0);
485   if (reference_view && reference_view->visible())
486     anchor_view_ = reference_view;
487
488   ExtensionMessageBubbleController* weak_controller = controller_.get();
489   ExtensionMessageBubbleView* bubble_delegate =
490       new ExtensionMessageBubbleView(
491           anchor_view_,
492           views::BubbleBorder::TOP_RIGHT,
493           scoped_ptr<ExtensionMessageBubbleController>(
494               controller_.release()));
495   views::BubbleDelegateView::CreateBubble(bubble_delegate);
496   weak_controller->Show(bubble_delegate);
497
498   Finish();
499 }
500
501 void ExtensionMessageBubbleFactory::Finish() {
502   MaybeStopObserving();
503   controller_.reset();
504   anchor_view_ = NULL;
505   container_ = NULL;
506 }
507
508 }  // namespace extensions