OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/ui/views/extensions/extension_message_bubble_view.h" | 5 #include "chrome/browser/ui/views/extensions/extension_message_bubble_view.h" |
6 | 6 |
7 #include "base/strings/string_number_conversions.h" | 7 #include "base/strings/string_number_conversions.h" |
8 #include "base/strings/string_util.h" | 8 #include "base/strings/string_util.h" |
9 #include "base/strings/utf_string_conversions.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" | 10 #include "chrome/browser/extensions/extension_message_bubble_controller.h" |
13 #include "chrome/browser/extensions/extension_service.h" | |
14 #include "chrome/browser/extensions/extension_toolbar_model.h" | |
15 #include "chrome/browser/extensions/proxy_overridden_bubble_controller.h" | |
16 #include "chrome/browser/extensions/settings_api_bubble_controller.h" | |
17 #include "chrome/browser/extensions/settings_api_helpers.h" | |
18 #include "chrome/browser/extensions/suspicious_extension_bubble_controller.h" | |
19 #include "chrome/browser/profiles/profile.h" | |
20 #include "chrome/browser/ui/view_ids.h" | 11 #include "chrome/browser/ui/view_ids.h" |
21 #include "chrome/browser/ui/views/frame/browser_view.h" | |
22 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h" | |
23 #include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h" | |
24 #include "chrome/browser/ui/views/toolbar/toolbar_view.h" | |
25 #include "chrome/grit/locale_settings.h" | 12 #include "chrome/grit/locale_settings.h" |
26 #include "extensions/browser/extension_prefs.h" | |
27 #include "extensions/browser/extension_system.h" | |
28 #include "ui/accessibility/ax_view_state.h" | 13 #include "ui/accessibility/ax_view_state.h" |
29 #include "ui/base/resource/resource_bundle.h" | 14 #include "ui/base/resource/resource_bundle.h" |
30 #include "ui/views/controls/button/label_button.h" | 15 #include "ui/views/controls/button/label_button.h" |
31 #include "ui/views/controls/label.h" | 16 #include "ui/views/controls/label.h" |
32 #include "ui/views/controls/link.h" | 17 #include "ui/views/controls/link.h" |
33 #include "ui/views/layout/grid_layout.h" | 18 #include "ui/views/layout/grid_layout.h" |
34 #include "ui/views/view.h" | 19 #include "ui/views/view.h" |
35 #include "ui/views/widget/widget.h" | 20 #include "ui/views/widget/widget.h" |
36 | 21 |
37 namespace { | 22 namespace { |
38 | 23 |
39 base::LazyInstance<std::set<Profile*> > g_profiles_evaluated = | |
40 LAZY_INSTANCE_INITIALIZER; | |
41 | |
42 // Layout constants. | 24 // Layout constants. |
43 const int kExtensionListPadding = 10; | 25 const int kExtensionListPadding = 10; |
44 const int kInsetBottomRight = 13; | 26 const int kInsetBottomRight = 13; |
45 const int kInsetLeft = 14; | 27 const int kInsetLeft = 14; |
46 const int kInsetTop = 9; | 28 const int kInsetTop = 9; |
47 const int kHeadlineMessagePadding = 4; | 29 const int kHeadlineMessagePadding = 4; |
48 const int kHeadlineRowPadding = 10; | 30 const int kHeadlineRowPadding = 10; |
49 const int kMessageBubblePadding = 11; | 31 const int kMessageBubblePadding = 11; |
50 | 32 |
51 // How many extensions to show in the bubble (max). | 33 // How many extensions to show in the bubble (max). |
(...skipping 20 matching lines...) Expand all Loading... |
72 action_taken_(false), | 54 action_taken_(false), |
73 weak_factory_(this) { | 55 weak_factory_(this) { |
74 DCHECK(anchor_view->GetWidget()); | 56 DCHECK(anchor_view->GetWidget()); |
75 set_close_on_deactivate(controller_->CloseOnDeactivate()); | 57 set_close_on_deactivate(controller_->CloseOnDeactivate()); |
76 set_close_on_esc(true); | 58 set_close_on_esc(true); |
77 | 59 |
78 // Compensate for built-in vertical padding in the anchor view's image. | 60 // Compensate for built-in vertical padding in the anchor view's image. |
79 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); | 61 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); |
80 } | 62 } |
81 | 63 |
82 void ExtensionMessageBubbleView::OnActionButtonClicked( | |
83 const base::Closure& callback) { | |
84 action_callback_ = callback; | |
85 } | |
86 | |
87 void ExtensionMessageBubbleView::OnDismissButtonClicked( | |
88 const base::Closure& callback) { | |
89 dismiss_callback_ = callback; | |
90 } | |
91 | |
92 void ExtensionMessageBubbleView::OnLinkClicked( | |
93 const base::Closure& callback) { | |
94 link_callback_ = callback; | |
95 } | |
96 | |
97 void ExtensionMessageBubbleView::Show() { | 64 void ExtensionMessageBubbleView::Show() { |
98 // Not showing the bubble right away (during startup) has a few benefits: | 65 // Not showing the bubble right away (during startup) has a few benefits: |
99 // We don't have to worry about focus being lost due to the Omnibox (or to | 66 // We don't have to worry about focus being lost due to the Omnibox (or to |
100 // other things that want focus at startup). This allows Esc to work to close | 67 // other things that want focus at startup). This allows Esc to work to close |
101 // the bubble and also solves the keyboard accessibility problem that comes | 68 // the bubble and also solves the keyboard accessibility problem that comes |
102 // with focus being lost (we don't have a good generic mechanism of injecting | 69 // with focus being lost (we don't have a good generic mechanism of injecting |
103 // bubbles into the focus cycle). Another benefit of delaying the show is | 70 // bubbles into the focus cycle). Another benefit of delaying the show is |
104 // that fade-in works (the fade-in isn't apparent if the the bubble appears at | 71 // that fade-in works (the fade-in isn't apparent if the the bubble appears at |
105 // startup). | 72 // startup). |
106 base::MessageLoop::current()->PostDelayedTask( | 73 base::MessageLoop::current()->PostDelayedTask( |
107 FROM_HERE, | 74 FROM_HERE, |
108 base::Bind(&ExtensionMessageBubbleView::ShowBubble, | 75 base::Bind(&ExtensionMessageBubbleView::ShowBubble, |
109 weak_factory_.GetWeakPtr()), | 76 weak_factory_.GetWeakPtr()), |
110 base::TimeDelta::FromSeconds(kBubbleAppearanceWaitTime)); | 77 base::TimeDelta::FromSeconds(kBubbleAppearanceWaitTime)); |
111 } | 78 } |
112 | 79 |
113 void ExtensionMessageBubbleView::OnWidgetDestroying(views::Widget* widget) { | 80 void ExtensionMessageBubbleView::OnWidgetDestroying(views::Widget* widget) { |
114 // To catch Esc, we monitor destroy message. Unless the link has been clicked, | 81 // To catch Esc, we monitor destroy message. Unless the link has been clicked, |
115 // we assume Dismiss was the action taken. | 82 // we assume Dismiss was the action taken. |
116 if (!link_clicked_ && !action_taken_) | 83 if (!link_clicked_ && !action_taken_) |
117 dismiss_callback_.Run(); | 84 controller_->OnBubbleDismiss(); |
118 } | 85 } |
119 | 86 |
120 //////////////////////////////////////////////////////////////////////////////// | 87 //////////////////////////////////////////////////////////////////////////////// |
121 // ExtensionMessageBubbleView - private. | 88 // ExtensionMessageBubbleView - private. |
122 | 89 |
123 ExtensionMessageBubbleView::~ExtensionMessageBubbleView() {} | 90 ExtensionMessageBubbleView::~ExtensionMessageBubbleView() {} |
124 | 91 |
125 void ExtensionMessageBubbleView::ShowBubble() { | 92 void ExtensionMessageBubbleView::ShowBubble() { |
126 GetWidget()->Show(); | 93 GetWidget()->Show(); |
127 } | 94 } |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
234 dismiss_button_ = new views::LabelButton(this, | 201 dismiss_button_ = new views::LabelButton(this, |
235 delegate->GetDismissButtonLabel()); | 202 delegate->GetDismissButtonLabel()); |
236 dismiss_button_->SetStyle(views::Button::STYLE_BUTTON); | 203 dismiss_button_->SetStyle(views::Button::STYLE_BUTTON); |
237 layout->AddView(dismiss_button_); | 204 layout->AddView(dismiss_button_); |
238 } | 205 } |
239 | 206 |
240 void ExtensionMessageBubbleView::ButtonPressed(views::Button* sender, | 207 void ExtensionMessageBubbleView::ButtonPressed(views::Button* sender, |
241 const ui::Event& event) { | 208 const ui::Event& event) { |
242 if (sender == action_button_) { | 209 if (sender == action_button_) { |
243 action_taken_ = true; | 210 action_taken_ = true; |
244 action_callback_.Run(); | 211 controller_->OnBubbleAction(); |
245 } else { | 212 } else { |
246 DCHECK_EQ(dismiss_button_, sender); | 213 DCHECK_EQ(dismiss_button_, sender); |
247 } | 214 } |
248 GetWidget()->Close(); | 215 GetWidget()->Close(); |
249 } | 216 } |
250 | 217 |
251 void ExtensionMessageBubbleView::LinkClicked(views::Link* source, | 218 void ExtensionMessageBubbleView::LinkClicked(views::Link* source, |
252 int event_flags) { | 219 int event_flags) { |
253 DCHECK_EQ(learn_more_, source); | 220 DCHECK_EQ(learn_more_, source); |
254 link_clicked_ = true; | 221 link_clicked_ = true; |
255 link_callback_.Run(); | 222 controller_->OnLinkClicked(); |
256 GetWidget()->Close(); | 223 GetWidget()->Close(); |
257 } | 224 } |
258 | 225 |
259 void ExtensionMessageBubbleView::GetAccessibleState( | 226 void ExtensionMessageBubbleView::GetAccessibleState( |
260 ui::AXViewState* state) { | 227 ui::AXViewState* state) { |
261 state->role = ui::AX_ROLE_ALERT; | 228 state->role = ui::AX_ROLE_ALERT; |
262 } | 229 } |
263 | 230 |
264 void ExtensionMessageBubbleView::ViewHierarchyChanged( | 231 void ExtensionMessageBubbleView::ViewHierarchyChanged( |
265 const ViewHierarchyChangedDetails& details) { | 232 const ViewHierarchyChangedDetails& details) { |
266 if (details.is_add && details.child == this) | 233 if (details.is_add && details.child == this) |
267 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); | 234 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); |
268 } | 235 } |
269 | 236 |
270 //////////////////////////////////////////////////////////////////////////////// | |
271 // ExtensionMessageBubbleFactory | |
272 | |
273 ExtensionMessageBubbleFactory::ExtensionMessageBubbleFactory( | |
274 Profile* profile, | |
275 ToolbarView* toolbar_view) | |
276 : profile_(profile), | |
277 toolbar_view_(toolbar_view), | |
278 shown_suspicious_extensions_bubble_(false), | |
279 shown_startup_override_extensions_bubble_(false), | |
280 shown_proxy_override_extensions_bubble_(false), | |
281 shown_dev_mode_extensions_bubble_(false), | |
282 is_observing_(false), | |
283 stage_(STAGE_START), | |
284 container_(NULL), | |
285 anchor_view_(NULL) {} | |
286 | |
287 ExtensionMessageBubbleFactory::~ExtensionMessageBubbleFactory() { | |
288 MaybeStopObserving(); | |
289 } | |
290 | |
291 void ExtensionMessageBubbleFactory::MaybeShow(views::View* anchor_view) { | |
292 #if defined(OS_WIN) | |
293 bool is_initial_check = IsInitialProfileCheck(profile_->GetOriginalProfile()); | |
294 RecordProfileCheck(profile_->GetOriginalProfile()); | |
295 | |
296 // The list of suspicious extensions takes priority over the dev mode bubble | |
297 // and the settings API bubble, since that needs to be shown as soon as we | |
298 // disable something. The settings API bubble is shown on first startup after | |
299 // an extension has changed the startup pages and it is acceptable if that | |
300 // waits until the next startup because of the suspicious extension bubble. | |
301 // The dev mode bubble is not time sensitive like the other two so we'll catch | |
302 // the dev mode extensions on the next startup/next window that opens. That | |
303 // way, we're not too spammy with the bubbles. | |
304 if (!shown_suspicious_extensions_bubble_ && | |
305 MaybeShowSuspiciousExtensionsBubble(anchor_view)) | |
306 return; | |
307 | |
308 if (!shown_startup_override_extensions_bubble_ && | |
309 is_initial_check && | |
310 MaybeShowStartupOverrideExtensionsBubble(anchor_view)) | |
311 return; | |
312 | |
313 if (!shown_proxy_override_extensions_bubble_ && | |
314 MaybeShowProxyOverrideExtensionsBubble(anchor_view)) | |
315 return; | |
316 | |
317 if (!shown_dev_mode_extensions_bubble_) | |
318 MaybeShowDevModeExtensionsBubble(anchor_view); | |
319 #endif // OS_WIN | |
320 } | |
321 | |
322 bool ExtensionMessageBubbleFactory::MaybeShowSuspiciousExtensionsBubble( | |
323 views::View* anchor_view) { | |
324 DCHECK(!shown_suspicious_extensions_bubble_); | |
325 | |
326 scoped_ptr<SuspiciousExtensionBubbleController> suspicious_extensions( | |
327 new SuspiciousExtensionBubbleController(profile_)); | |
328 if (!suspicious_extensions->ShouldShow()) | |
329 return false; | |
330 | |
331 shown_suspicious_extensions_bubble_ = true; | |
332 SuspiciousExtensionBubbleController* weak_controller = | |
333 suspicious_extensions.get(); | |
334 ExtensionMessageBubbleView* bubble_delegate = | |
335 new ExtensionMessageBubbleView(anchor_view, | |
336 views::BubbleBorder::TOP_RIGHT, | |
337 suspicious_extensions.Pass()); | |
338 | |
339 views::BubbleDelegateView::CreateBubble(bubble_delegate); | |
340 weak_controller->Show(bubble_delegate); | |
341 | |
342 return true; | |
343 } | |
344 | |
345 bool ExtensionMessageBubbleFactory::MaybeShowStartupOverrideExtensionsBubble( | |
346 views::View* anchor_view) { | |
347 #if !defined(OS_WIN) | |
348 return false; | |
349 #else | |
350 DCHECK(!shown_startup_override_extensions_bubble_); | |
351 | |
352 const Extension* extension = GetExtensionOverridingStartupPages(profile_); | |
353 if (!extension) | |
354 return false; | |
355 | |
356 scoped_ptr<SettingsApiBubbleController> settings_api_bubble( | |
357 new SettingsApiBubbleController(profile_, | |
358 BUBBLE_TYPE_STARTUP_PAGES)); | |
359 if (!settings_api_bubble->ShouldShow(extension->id())) | |
360 return false; | |
361 | |
362 shown_startup_override_extensions_bubble_ = true; | |
363 PrepareToHighlightExtensions(settings_api_bubble.Pass(), anchor_view); | |
364 return true; | |
365 #endif | |
366 } | |
367 | |
368 bool ExtensionMessageBubbleFactory::MaybeShowProxyOverrideExtensionsBubble( | |
369 views::View* anchor_view) { | |
370 #if !defined(OS_WIN) | |
371 return false; | |
372 #else | |
373 DCHECK(!shown_proxy_override_extensions_bubble_); | |
374 | |
375 const Extension* extension = GetExtensionOverridingProxy(profile_); | |
376 if (!extension) | |
377 return false; | |
378 | |
379 scoped_ptr<ProxyOverriddenBubbleController> proxy_bubble( | |
380 new ProxyOverriddenBubbleController(profile_)); | |
381 if (!proxy_bubble->ShouldShow(extension->id())) | |
382 return false; | |
383 | |
384 shown_proxy_override_extensions_bubble_ = true; | |
385 PrepareToHighlightExtensions(proxy_bubble.Pass(), anchor_view); | |
386 return true; | |
387 #endif | |
388 } | |
389 | |
390 bool ExtensionMessageBubbleFactory::MaybeShowDevModeExtensionsBubble( | |
391 views::View* anchor_view) { | |
392 DCHECK(!shown_dev_mode_extensions_bubble_); | |
393 | |
394 // Check the Developer Mode extensions. | |
395 scoped_ptr<DevModeBubbleController> dev_mode_extensions( | |
396 new DevModeBubbleController(profile_)); | |
397 | |
398 // Return early if we have none to show. | |
399 if (!dev_mode_extensions->ShouldShow()) | |
400 return false; | |
401 | |
402 shown_dev_mode_extensions_bubble_ = true; | |
403 PrepareToHighlightExtensions(dev_mode_extensions.Pass(), anchor_view); | |
404 return true; | |
405 } | |
406 | |
407 void ExtensionMessageBubbleFactory::MaybeObserve() { | |
408 if (!is_observing_) { | |
409 is_observing_ = true; | |
410 container_->AddObserver(this); | |
411 } | |
412 } | |
413 | |
414 void ExtensionMessageBubbleFactory::MaybeStopObserving() { | |
415 if (is_observing_) { | |
416 is_observing_ = false; | |
417 container_->RemoveObserver(this); | |
418 } | |
419 } | |
420 | |
421 void ExtensionMessageBubbleFactory::RecordProfileCheck(Profile* profile) { | |
422 g_profiles_evaluated.Get().insert(profile); | |
423 } | |
424 | |
425 bool ExtensionMessageBubbleFactory::IsInitialProfileCheck(Profile* profile) { | |
426 return g_profiles_evaluated.Get().count(profile) == 0; | |
427 } | |
428 | |
429 void ExtensionMessageBubbleFactory::OnBrowserActionsContainerAnimationEnded() { | |
430 MaybeStopObserving(); | |
431 if (stage_ == STAGE_START) { | |
432 HighlightExtensions(); | |
433 } else if (stage_ == STAGE_HIGHLIGHTED) { | |
434 ShowHighlightingBubble(); | |
435 } else { // We shouldn't be observing if we've completed the process. | |
436 NOTREACHED(); | |
437 Finish(); | |
438 } | |
439 } | |
440 | |
441 void ExtensionMessageBubbleFactory::OnBrowserActionsContainerDestroyed() { | |
442 // If the container associated with the bubble is destroyed, abandon the | |
443 // process. | |
444 Finish(); | |
445 } | |
446 | |
447 void ExtensionMessageBubbleFactory::PrepareToHighlightExtensions( | |
448 scoped_ptr<ExtensionMessageBubbleController> controller, | |
449 views::View* anchor_view) { | |
450 // We should be in the start stage (i.e., should not have a pending attempt to | |
451 // show a bubble). | |
452 DCHECK_EQ(stage_, STAGE_START); | |
453 | |
454 // Prepare to display and highlight the extensions before showing the bubble. | |
455 // Since this is an asynchronous process, set member variables for later use. | |
456 controller_ = controller.Pass(); | |
457 anchor_view_ = anchor_view; | |
458 container_ = toolbar_view_->browser_actions(); | |
459 | |
460 if (container_->animating()) | |
461 MaybeObserve(); | |
462 else | |
463 HighlightExtensions(); | |
464 } | |
465 | |
466 void ExtensionMessageBubbleFactory::HighlightExtensions() { | |
467 DCHECK_EQ(STAGE_START, stage_); | |
468 stage_ = STAGE_HIGHLIGHTED; | |
469 | |
470 const ExtensionIdList extension_list = controller_->GetExtensionIdList(); | |
471 DCHECK(!extension_list.empty()); | |
472 ExtensionToolbarModel::Get(profile_)->HighlightExtensions(extension_list); | |
473 if (container_->animating()) | |
474 MaybeObserve(); | |
475 else | |
476 ShowHighlightingBubble(); | |
477 } | |
478 | |
479 void ExtensionMessageBubbleFactory::ShowHighlightingBubble() { | |
480 DCHECK_EQ(stage_, STAGE_HIGHLIGHTED); | |
481 stage_ = STAGE_COMPLETE; | |
482 | |
483 views::View* reference_view = NULL; | |
484 if (container_->num_toolbar_actions() > 0u) | |
485 reference_view = container_->GetToolbarActionViewAt(0); | |
486 if (reference_view && reference_view->visible()) | |
487 anchor_view_ = reference_view; | |
488 | |
489 ExtensionMessageBubbleController* weak_controller = controller_.get(); | |
490 ExtensionMessageBubbleView* bubble_delegate = | |
491 new ExtensionMessageBubbleView( | |
492 anchor_view_, | |
493 views::BubbleBorder::TOP_RIGHT, | |
494 scoped_ptr<ExtensionMessageBubbleController>( | |
495 controller_.release())); | |
496 views::BubbleDelegateView::CreateBubble(bubble_delegate); | |
497 weak_controller->Show(bubble_delegate); | |
498 | |
499 Finish(); | |
500 } | |
501 | |
502 void ExtensionMessageBubbleFactory::Finish() { | |
503 MaybeStopObserving(); | |
504 controller_.reset(); | |
505 anchor_view_ = NULL; | |
506 container_ = NULL; | |
507 } | |
508 | |
509 } // namespace extensions | 237 } // namespace extensions |
OLD | NEW |