| 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 |