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" |
10 #include "chrome/browser/extensions/extension_message_bubble_controller.h" | 12 #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" |
11 #include "chrome/browser/ui/view_ids.h" | 20 #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" |
12 #include "chrome/grit/locale_settings.h" | 25 #include "chrome/grit/locale_settings.h" |
| 26 #include "extensions/browser/extension_prefs.h" |
| 27 #include "extensions/browser/extension_system.h" |
13 #include "ui/accessibility/ax_view_state.h" | 28 #include "ui/accessibility/ax_view_state.h" |
14 #include "ui/base/resource/resource_bundle.h" | 29 #include "ui/base/resource/resource_bundle.h" |
15 #include "ui/views/controls/button/label_button.h" | 30 #include "ui/views/controls/button/label_button.h" |
16 #include "ui/views/controls/label.h" | 31 #include "ui/views/controls/label.h" |
17 #include "ui/views/controls/link.h" | 32 #include "ui/views/controls/link.h" |
18 #include "ui/views/layout/grid_layout.h" | 33 #include "ui/views/layout/grid_layout.h" |
19 #include "ui/views/view.h" | 34 #include "ui/views/view.h" |
20 #include "ui/views/widget/widget.h" | 35 #include "ui/views/widget/widget.h" |
21 | 36 |
22 namespace { | 37 namespace { |
23 | 38 |
| 39 base::LazyInstance<std::set<Profile*> > g_profiles_evaluated = |
| 40 LAZY_INSTANCE_INITIALIZER; |
| 41 |
24 // Layout constants. | 42 // Layout constants. |
25 const int kExtensionListPadding = 10; | 43 const int kExtensionListPadding = 10; |
26 const int kInsetBottomRight = 13; | 44 const int kInsetBottomRight = 13; |
27 const int kInsetLeft = 14; | 45 const int kInsetLeft = 14; |
28 const int kInsetTop = 9; | 46 const int kInsetTop = 9; |
29 const int kHeadlineMessagePadding = 4; | 47 const int kHeadlineMessagePadding = 4; |
30 const int kHeadlineRowPadding = 10; | 48 const int kHeadlineRowPadding = 10; |
31 const int kMessageBubblePadding = 11; | 49 const int kMessageBubblePadding = 11; |
32 | 50 |
33 // How many extensions to show in the bubble (max). | 51 // How many extensions to show in the bubble (max). |
(...skipping 20 matching lines...) Expand all Loading... |
54 action_taken_(false), | 72 action_taken_(false), |
55 weak_factory_(this) { | 73 weak_factory_(this) { |
56 DCHECK(anchor_view->GetWidget()); | 74 DCHECK(anchor_view->GetWidget()); |
57 set_close_on_deactivate(controller_->CloseOnDeactivate()); | 75 set_close_on_deactivate(controller_->CloseOnDeactivate()); |
58 set_close_on_esc(true); | 76 set_close_on_esc(true); |
59 | 77 |
60 // Compensate for built-in vertical padding in the anchor view's image. | 78 // Compensate for built-in vertical padding in the anchor view's image. |
61 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); | 79 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); |
62 } | 80 } |
63 | 81 |
| 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 |
64 void ExtensionMessageBubbleView::Show() { | 97 void ExtensionMessageBubbleView::Show() { |
65 // Not showing the bubble right away (during startup) has a few benefits: | 98 // Not showing the bubble right away (during startup) has a few benefits: |
66 // We don't have to worry about focus being lost due to the Omnibox (or to | 99 // We don't have to worry about focus being lost due to the Omnibox (or to |
67 // other things that want focus at startup). This allows Esc to work to close | 100 // other things that want focus at startup). This allows Esc to work to close |
68 // the bubble and also solves the keyboard accessibility problem that comes | 101 // the bubble and also solves the keyboard accessibility problem that comes |
69 // with focus being lost (we don't have a good generic mechanism of injecting | 102 // with focus being lost (we don't have a good generic mechanism of injecting |
70 // bubbles into the focus cycle). Another benefit of delaying the show is | 103 // bubbles into the focus cycle). Another benefit of delaying the show is |
71 // that fade-in works (the fade-in isn't apparent if the the bubble appears at | 104 // that fade-in works (the fade-in isn't apparent if the the bubble appears at |
72 // startup). | 105 // startup). |
73 base::MessageLoop::current()->PostDelayedTask( | 106 base::MessageLoop::current()->PostDelayedTask( |
74 FROM_HERE, | 107 FROM_HERE, |
75 base::Bind(&ExtensionMessageBubbleView::ShowBubble, | 108 base::Bind(&ExtensionMessageBubbleView::ShowBubble, |
76 weak_factory_.GetWeakPtr()), | 109 weak_factory_.GetWeakPtr()), |
77 base::TimeDelta::FromSeconds(kBubbleAppearanceWaitTime)); | 110 base::TimeDelta::FromSeconds(kBubbleAppearanceWaitTime)); |
78 } | 111 } |
79 | 112 |
80 void ExtensionMessageBubbleView::OnWidgetDestroying(views::Widget* widget) { | 113 void ExtensionMessageBubbleView::OnWidgetDestroying(views::Widget* widget) { |
81 // To catch Esc, we monitor destroy message. Unless the link has been clicked, | 114 // To catch Esc, we monitor destroy message. Unless the link has been clicked, |
82 // we assume Dismiss was the action taken. | 115 // we assume Dismiss was the action taken. |
83 if (!link_clicked_ && !action_taken_) | 116 if (!link_clicked_ && !action_taken_) |
84 controller_->OnBubbleDismiss(); | 117 dismiss_callback_.Run(); |
85 } | 118 } |
86 | 119 |
87 //////////////////////////////////////////////////////////////////////////////// | 120 //////////////////////////////////////////////////////////////////////////////// |
88 // ExtensionMessageBubbleView - private. | 121 // ExtensionMessageBubbleView - private. |
89 | 122 |
90 ExtensionMessageBubbleView::~ExtensionMessageBubbleView() {} | 123 ExtensionMessageBubbleView::~ExtensionMessageBubbleView() {} |
91 | 124 |
92 void ExtensionMessageBubbleView::ShowBubble() { | 125 void ExtensionMessageBubbleView::ShowBubble() { |
93 GetWidget()->Show(); | 126 GetWidget()->Show(); |
94 } | 127 } |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
201 dismiss_button_ = new views::LabelButton(this, | 234 dismiss_button_ = new views::LabelButton(this, |
202 delegate->GetDismissButtonLabel()); | 235 delegate->GetDismissButtonLabel()); |
203 dismiss_button_->SetStyle(views::Button::STYLE_BUTTON); | 236 dismiss_button_->SetStyle(views::Button::STYLE_BUTTON); |
204 layout->AddView(dismiss_button_); | 237 layout->AddView(dismiss_button_); |
205 } | 238 } |
206 | 239 |
207 void ExtensionMessageBubbleView::ButtonPressed(views::Button* sender, | 240 void ExtensionMessageBubbleView::ButtonPressed(views::Button* sender, |
208 const ui::Event& event) { | 241 const ui::Event& event) { |
209 if (sender == action_button_) { | 242 if (sender == action_button_) { |
210 action_taken_ = true; | 243 action_taken_ = true; |
211 controller_->OnBubbleAction(); | 244 action_callback_.Run(); |
212 } else { | 245 } else { |
213 DCHECK_EQ(dismiss_button_, sender); | 246 DCHECK_EQ(dismiss_button_, sender); |
214 } | 247 } |
215 GetWidget()->Close(); | 248 GetWidget()->Close(); |
216 } | 249 } |
217 | 250 |
218 void ExtensionMessageBubbleView::LinkClicked(views::Link* source, | 251 void ExtensionMessageBubbleView::LinkClicked(views::Link* source, |
219 int event_flags) { | 252 int event_flags) { |
220 DCHECK_EQ(learn_more_, source); | 253 DCHECK_EQ(learn_more_, source); |
221 link_clicked_ = true; | 254 link_clicked_ = true; |
222 controller_->OnLinkClicked(); | 255 link_callback_.Run(); |
223 GetWidget()->Close(); | 256 GetWidget()->Close(); |
224 } | 257 } |
225 | 258 |
226 void ExtensionMessageBubbleView::GetAccessibleState( | 259 void ExtensionMessageBubbleView::GetAccessibleState( |
227 ui::AXViewState* state) { | 260 ui::AXViewState* state) { |
228 state->role = ui::AX_ROLE_ALERT; | 261 state->role = ui::AX_ROLE_ALERT; |
229 } | 262 } |
230 | 263 |
231 void ExtensionMessageBubbleView::ViewHierarchyChanged( | 264 void ExtensionMessageBubbleView::ViewHierarchyChanged( |
232 const ViewHierarchyChangedDetails& details) { | 265 const ViewHierarchyChangedDetails& details) { |
233 if (details.is_add && details.child == this) | 266 if (details.is_add && details.child == this) |
234 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); | 267 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true); |
235 } | 268 } |
236 | 269 |
| 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 |
237 } // namespace extensions | 509 } // namespace extensions |
OLD | NEW |