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