summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorrdevlin.cronin <rdevlin.cronin@chromium.org>2015-04-15 10:49:06 -0700
committerCommit bot <commit-bot@chromium.org>2015-04-15 17:49:38 +0000
commit70fc60535fb99318170676856ecf1044ba083f68 (patch)
tree2cb6361dba604c4bea23d7eb078810c319fad310 /chrome/browser
parent97178058a1ab20f88d55a641d8d07e875aa504f0 (diff)
downloadchromium_src-70fc60535fb99318170676856ecf1044ba083f68.zip
chromium_src-70fc60535fb99318170676856ecf1044ba083f68.tar.gz
chromium_src-70fc60535fb99318170676856ecf1044ba083f68.tar.bz2
[Reland] [Extensions] Make extension message bubble factory platform-abstract
We want to show extension message bubbles on multiple platforms; the first step towards this is to make the bubble factory platform-abstract. Do so, and also add a handful of tests for the process of showing the bubble. BUG=474092 TBR=sky@chromium.org (no relevant changes since original land) Review URL: https://codereview.chromium.org/1087713002 Cr-Commit-Position: refs/heads/master@{#325269}
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/extensions/dev_mode_bubble_controller.cc5
-rw-r--r--chrome/browser/extensions/extension_action_test_util.cc8
-rw-r--r--chrome/browser/extensions/extension_action_test_util.h5
-rw-r--r--chrome/browser/extensions/extension_message_bubble.h10
-rw-r--r--chrome/browser/extensions/extension_message_bubble_controller.cc23
-rw-r--r--chrome/browser/extensions/extension_message_bubble_controller.h7
-rw-r--r--chrome/browser/extensions/extension_message_bubble_controller_unittest.cc94
-rw-r--r--chrome/browser/extensions/extension_toolbar_model.cc11
-rw-r--r--chrome/browser/extensions/ntp_overridden_bubble_controller.cc5
-rw-r--r--chrome/browser/extensions/proxy_overridden_bubble_controller.cc5
-rw-r--r--chrome/browser/extensions/settings_api_bubble_controller.cc27
-rw-r--r--chrome/browser/extensions/settings_api_bubble_controller.h7
-rw-r--r--chrome/browser/extensions/suspicious_extension_bubble_controller.cc8
-rw-r--r--chrome/browser/ui/cocoa/extensions/browser_actions_controller.mm8
-rw-r--r--chrome/browser/ui/extensions/OWNERS5
-rw-r--r--chrome/browser/ui/extensions/extension_message_bubble_factory.cc103
-rw-r--r--chrome/browser/ui/extensions/extension_message_bubble_factory.h37
-rw-r--r--chrome/browser/ui/toolbar/toolbar_actions_bar.cc27
-rw-r--r--chrome/browser/ui/toolbar/toolbar_actions_bar.h10
-rw-r--r--chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h9
-rw-r--r--chrome/browser/ui/views/extensions/extension_message_bubble_view.cc278
-rw-r--r--chrome/browser/ui/views/extensions/extension_message_bubble_view.h125
-rw-r--r--chrome/browser/ui/views/extensions/extension_message_bubble_view_browsertest.cc146
-rw-r--r--chrome/browser/ui/views/settings_api_bubble_helper_views.cc40
-rw-r--r--chrome/browser/ui/views/toolbar/browser_actions_container.cc56
-rw-r--r--chrome/browser/ui/views/toolbar/browser_actions_container.h22
-rw-r--r--chrome/browser/ui/views/toolbar/toolbar_view.cc14
-rw-r--r--chrome/browser/ui/views/toolbar/toolbar_view.h7
28 files changed, 573 insertions, 529 deletions
diff --git a/chrome/browser/extensions/dev_mode_bubble_controller.cc b/chrome/browser/extensions/dev_mode_bubble_controller.cc
index 202207c..d6914e5 100644
--- a/chrome/browser/extensions/dev_mode_bubble_controller.cc
+++ b/chrome/browser/extensions/dev_mode_bubble_controller.cc
@@ -52,6 +52,7 @@ class DevModeBubbleDelegate
base::string16 GetActionButtonLabel() const override;
base::string16 GetDismissButtonLabel() const override;
bool ShouldShowExtensionList() const override;
+ bool ShouldHighlightExtensions() const override;
void LogExtensionCount(size_t count) override;
void LogAction(
ExtensionMessageBubbleController::BubbleAction action) override;
@@ -127,6 +128,10 @@ bool DevModeBubbleDelegate::ShouldShowExtensionList() const {
return false;
}
+bool DevModeBubbleDelegate::ShouldHighlightExtensions() const {
+ return true;
+}
+
void DevModeBubbleDelegate::LogExtensionCount(size_t count) {
UMA_HISTOGRAM_COUNTS_100(
"ExtensionBubble.ExtensionsInDevModeCount", count);
diff --git a/chrome/browser/extensions/extension_action_test_util.cc b/chrome/browser/extensions/extension_action_test_util.cc
index cab838e..da6756e 100644
--- a/chrome/browser/extensions/extension_action_test_util.cc
+++ b/chrome/browser/extensions/extension_action_test_util.cc
@@ -116,6 +116,13 @@ size_t GetTotalPageActionCount(content::WebContents* web_contents) {
scoped_refptr<const Extension> CreateActionExtension(const std::string& name,
ActionType action_type) {
+ return CreateActionExtension(name, action_type, Manifest::INTERNAL);
+}
+
+scoped_refptr<const Extension> CreateActionExtension(
+ const std::string& name,
+ ActionType action_type,
+ Manifest::Location location) {
DictionaryBuilder manifest;
manifest.Set("name", name)
.Set("description", "An extension")
@@ -139,6 +146,7 @@ scoped_refptr<const Extension> CreateActionExtension(const std::string& name,
return ExtensionBuilder().SetManifest(manifest.Pass()).
SetID(crx_file::id_util::GenerateId(name)).
+ SetLocation(location).
Build();
}
diff --git a/chrome/browser/extensions/extension_action_test_util.h b/chrome/browser/extensions/extension_action_test_util.h
index ddad3a4..0d52726 100644
--- a/chrome/browser/extensions/extension_action_test_util.h
+++ b/chrome/browser/extensions/extension_action_test_util.h
@@ -9,6 +9,7 @@
#include "base/basictypes.h"
#include "base/memory/ref_counted.h"
+#include "extensions/common/manifest.h"
class Profile;
@@ -45,6 +46,10 @@ size_t GetTotalPageActionCount(content::WebContents* web_contents);
// Does not add the extension to the extension service or registry.
scoped_refptr<const Extension> CreateActionExtension(const std::string& name,
ActionType action_type);
+scoped_refptr<const Extension> CreateActionExtension(
+ const std::string& name,
+ ActionType action_type,
+ Manifest::Location location);
// Creates a new ExtensionToolbarModel for the given |profile|, and associates
// it with the profile as a keyed service.
diff --git a/chrome/browser/extensions/extension_message_bubble.h b/chrome/browser/extensions/extension_message_bubble.h
index 71d6e30..3819a82 100644
--- a/chrome/browser/extensions/extension_message_bubble.h
+++ b/chrome/browser/extensions/extension_message_bubble.h
@@ -15,16 +15,6 @@ namespace extensions {
// controller.
class ExtensionMessageBubble {
public:
- // Setup the callback for when the action button is clicked in the
- // bubble.
- virtual void OnActionButtonClicked(const base::Closure& callback) = 0;
-
- // Setup the callback for when the dismiss button is clicked.
- virtual void OnDismissButtonClicked(const base::Closure& callback) = 0;
-
- // Setup the callback for when the link is clicked in the bubble.
- virtual void OnLinkClicked(const base::Closure& callback) = 0;
-
// Instruct the bubble to appear.
virtual void Show() = 0;
};
diff --git a/chrome/browser/extensions/extension_message_bubble_controller.cc b/chrome/browser/extensions/extension_message_bubble_controller.cc
index 778c7ad..a30b1f0 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller.cc
+++ b/chrome/browser/extensions/extension_message_bubble_controller.cc
@@ -8,6 +8,7 @@
#include "base/metrics/histogram.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_message_bubble.h"
+#include "chrome/browser/extensions/extension_toolbar_model.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
@@ -122,21 +123,15 @@ const ExtensionIdList& ExtensionMessageBubbleController::GetExtensionIdList() {
bool ExtensionMessageBubbleController::CloseOnDeactivate() { return false; }
-void ExtensionMessageBubbleController::Show(ExtensionMessageBubble* bubble) {
- // Wire up all the callbacks, to get notified what actions the user took.
- base::Closure dismiss_button_callback =
- base::Bind(&ExtensionMessageBubbleController::OnBubbleDismiss,
- base::Unretained(this));
- base::Closure action_button_callback =
- base::Bind(&ExtensionMessageBubbleController::OnBubbleAction,
- base::Unretained(this));
- base::Closure link_callback =
- base::Bind(&ExtensionMessageBubbleController::OnLinkClicked,
- base::Unretained(this));
- bubble->OnActionButtonClicked(action_button_callback);
- bubble->OnDismissButtonClicked(dismiss_button_callback);
- bubble->OnLinkClicked(link_callback);
+void ExtensionMessageBubbleController::HighlightExtensionsIfNecessary() {
+ if (delegate_->ShouldHighlightExtensions()) {
+ const ExtensionIdList& extension_ids = GetExtensionIdList();
+ DCHECK(!extension_ids.empty());
+ ExtensionToolbarModel::Get(profile_)->HighlightExtensions(extension_ids);
+ }
+}
+void ExtensionMessageBubbleController::Show(ExtensionMessageBubble* bubble) {
bubble->Show();
}
diff --git a/chrome/browser/extensions/extension_message_bubble_controller.h b/chrome/browser/extensions/extension_message_bubble_controller.h
index d932e1d..e56ff0c 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller.h
+++ b/chrome/browser/extensions/extension_message_bubble_controller.h
@@ -59,6 +59,10 @@ class ExtensionMessageBubbleController {
// Whether to show a list of extensions in the bubble.
virtual bool ShouldShowExtensionList() const = 0;
+ // Returns true if the set of affected extensions should be highlighted in
+ // the toolbar.
+ virtual bool ShouldHighlightExtensions() const = 0;
+
// In some cases, we want the delegate only to handle a single extension
// and this sets which extension.
virtual void RestrictToSingleExtension(const std::string& extension_id);
@@ -101,6 +105,9 @@ class ExtensionMessageBubbleController {
// Whether to close the bubble when it loses focus.
virtual bool CloseOnDeactivate();
+ // Highlights the affected extensions if appropriate.
+ void HighlightExtensionsIfNecessary();
+
// Sets up the callbacks and shows the bubble.
virtual void Show(ExtensionMessageBubble* bubble);
diff --git a/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc b/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc
index 40f7c48..1a50092 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc
+++ b/chrome/browser/extensions/extension_message_bubble_controller_unittest.cc
@@ -33,6 +33,12 @@
#include "extensions/common/value_builder.h"
#include "testing/gtest/include/gtest/gtest.h"
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/chromeos/settings/device_settings_service.h"
+#endif
+
namespace {
const char kId1[] = "iccfkkhkfiphcjdakkmcjmkfboccmndk";
@@ -207,39 +213,29 @@ class FakeExtensionMessageBubble : public ExtensionMessageBubble {
BUBBLE_ACTION_CLICK_LINK,
};
- FakeExtensionMessageBubble() {}
+ FakeExtensionMessageBubble() : controller_(nullptr) {}
void set_action_on_show(ExtensionBubbleAction action) {
action_ = action;
}
+ void set_controller(ExtensionMessageBubbleController* controller) {
+ controller_ = controller;
+ }
void Show() override {
if (action_ == BUBBLE_ACTION_CLICK_ACTION_BUTTON)
- action_callback_.Run();
+ controller_->OnBubbleAction();
else if (action_ == BUBBLE_ACTION_CLICK_DISMISS_BUTTON)
- dismiss_callback_.Run();
+ controller_->OnBubbleDismiss();
else if (action_ == BUBBLE_ACTION_CLICK_LINK)
- link_callback_.Run();
- }
-
- void OnActionButtonClicked(const base::Closure& callback) override {
- action_callback_ = callback;
- }
-
- void OnDismissButtonClicked(const base::Closure& callback) override {
- dismiss_callback_ = callback;
- }
-
- void OnLinkClicked(const base::Closure& callback) override {
- link_callback_ = callback;
+ controller_->OnLinkClicked();
}
private:
ExtensionBubbleAction action_;
+ ExtensionMessageBubbleController* controller_;
- base::Closure action_callback_;
- base::Closure dismiss_callback_;
- base::Closure link_callback_;
+ DISALLOW_COPY_AND_ASSIGN(FakeExtensionMessageBubble);
};
class ExtensionMessageBubbleTest : public testing::Test {
@@ -390,7 +386,6 @@ class ExtensionMessageBubbleTest : public testing::Test {
void Init() {
// The two lines of magical incantation required to get the extension
// service to work inside a unit test and access the extension prefs.
- thread_bundle_.reset(new content::TestBrowserThreadBundle);
profile_.reset(new TestingProfile);
static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile()))
->CreateExtensionService(base::CommandLine::ForCurrentProcess(),
@@ -423,21 +418,22 @@ class ExtensionMessageBubbleTest : public testing::Test {
ExtensionService* service_;
private:
+ content::TestBrowserThreadBundle thread_bundle_;
scoped_ptr<base::CommandLine> command_line_;
- scoped_ptr<content::TestBrowserThreadBundle> thread_bundle_;
scoped_ptr<TestingProfile> profile_;
+#if defined OS_CHROMEOS
+ chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
+ chromeos::ScopedTestCrosSettings test_cros_settings_;
+ chromeos::ScopedTestUserManager test_user_manager_;
+#endif
+
DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleTest);
};
-// The feature this is meant to test is only implemented on Windows.
-#if defined(OS_WIN)
-#define MAYBE_WipeoutControllerTest WipeoutControllerTest
-#else
-#define MAYBE_WipeoutControllerTest DISABLED_WipeoutControllerTest
-#endif
-
-TEST_F(ExtensionMessageBubbleTest, MAYBE_WipeoutControllerTest) {
+// The feature this is meant to test is only enacted on Windows, but it should
+// pass on all platforms.
+TEST_F(ExtensionMessageBubbleTest, WipeoutControllerTest) {
Init();
// Add three extensions, and control two of them in this test (extension 1
// and 2).
@@ -474,6 +470,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_WipeoutControllerTest) {
suspicious_extensions = controller->GetExtensionList();
ASSERT_EQ(1U, suspicious_extensions.size());
EXPECT_TRUE(base::ASCIIToUTF16("Extension 1") == suspicious_extensions[0]);
+ bubble.set_controller(controller.get());
controller->Show(&bubble); // Simulate showing the bubble.
EXPECT_EQ(0U, controller->link_click_count());
EXPECT_EQ(1U, controller->dismiss_click_count());
@@ -497,20 +494,16 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_WipeoutControllerTest) {
ASSERT_EQ(2U, suspicious_extensions.size());
EXPECT_TRUE(base::ASCIIToUTF16("Extension 1") == suspicious_extensions[1]);
EXPECT_TRUE(base::ASCIIToUTF16("Extension 2") == suspicious_extensions[0]);
+ bubble.set_controller(controller.get());
controller->Show(&bubble); // Simulate showing the bubble.
EXPECT_EQ(1U, controller->link_click_count());
EXPECT_EQ(0U, controller->dismiss_click_count());
EXPECT_TRUE(controller->delegate()->HasBubbleInfoBeenAcknowledged(kId1));
}
-// The feature this is meant to test is only implemented on Windows.
-#if defined(OS_WIN)
-#define MAYBE_DevModeControllerTest DevModeControllerTest
-#else
-#define MAYBE_DevModeControllerTest DISABLED_DevModeControllerTest
-#endif
-
-TEST_F(ExtensionMessageBubbleTest, MAYBE_DevModeControllerTest) {
+// The feature this is meant to test is only enacted on Windows, but it should
+// pass on all platforms.
+TEST_F(ExtensionMessageBubbleTest, DevModeControllerTest) {
FeatureSwitch::ScopedOverride force_dev_mode_highlighting(
FeatureSwitch::force_dev_mode_highlighting(), true);
Init();
@@ -539,6 +532,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_DevModeControllerTest) {
FakeExtensionMessageBubble bubble;
bubble.set_action_on_show(
FakeExtensionMessageBubble::BUBBLE_ACTION_CLICK_DISMISS_BUTTON);
+ bubble.set_controller(controller.get());
controller->Show(&bubble);
EXPECT_EQ(0U, controller->link_click_count());
EXPECT_EQ(0U, controller->action_click_count());
@@ -556,6 +550,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_DevModeControllerTest) {
EXPECT_TRUE(controller->ShouldShow());
dev_mode_extensions = controller->GetExtensionList();
EXPECT_EQ(2U, dev_mode_extensions.size());
+ bubble.set_controller(controller.get());
controller->Show(&bubble); // Simulate showing the bubble.
EXPECT_EQ(0U, controller->link_click_count());
EXPECT_EQ(1U, controller->action_click_count());
@@ -576,6 +571,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_DevModeControllerTest) {
EXPECT_TRUE(controller->ShouldShow());
dev_mode_extensions = controller->GetExtensionList();
EXPECT_EQ(2U, dev_mode_extensions.size());
+ bubble.set_controller(controller.get());
controller->Show(&bubble); // Simulate showing the bubble.
EXPECT_EQ(1U, controller->link_click_count());
EXPECT_EQ(0U, controller->action_click_count());
@@ -643,7 +639,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_SettingsApiControllerTest) {
profile(), static_cast<SettingsApiOverrideType>(i)));
// The list will contain one enabled unpacked extension (ext 2).
- EXPECT_TRUE(controller->ShouldShow(kId2));
+ EXPECT_TRUE(controller->ShouldShow());
std::vector<base::string16> override_extensions =
controller->GetExtensionList();
ASSERT_EQ(1U, override_extensions.size());
@@ -657,6 +653,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_SettingsApiControllerTest) {
FakeExtensionMessageBubble bubble;
bubble.set_action_on_show(
FakeExtensionMessageBubble::BUBBLE_ACTION_CLICK_DISMISS_BUTTON);
+ bubble.set_controller(controller.get());
controller->Show(&bubble);
EXPECT_EQ(0U, controller->link_click_count());
EXPECT_EQ(0U, controller->action_click_count());
@@ -678,6 +675,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_SettingsApiControllerTest) {
FakeExtensionMessageBubble::BUBBLE_ACTION_CLICK_LINK);
controller.reset(new TestSettingsApiBubbleController(
profile(), static_cast<SettingsApiOverrideType>(i)));
+ bubble.set_controller(controller.get());
controller->Show(&bubble);
EXPECT_EQ(1U, controller->link_click_count());
EXPECT_EQ(0U, controller->action_click_count());
@@ -698,9 +696,10 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_SettingsApiControllerTest) {
FakeExtensionMessageBubble::BUBBLE_ACTION_CLICK_ACTION_BUTTON);
controller.reset(new TestSettingsApiBubbleController(
profile(), static_cast<SettingsApiOverrideType>(i)));
- EXPECT_TRUE(controller->ShouldShow(kId2));
+ EXPECT_TRUE(controller->ShouldShow());
override_extensions = controller->GetExtensionList();
EXPECT_EQ(1U, override_extensions.size());
+ bubble.set_controller(controller.get());
controller->Show(&bubble); // Simulate showing the bubble.
EXPECT_EQ(0U, controller->link_click_count());
EXPECT_EQ(1U, controller->action_click_count());
@@ -730,14 +729,9 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_SettingsApiControllerTest) {
}
}
-// The feature this is meant to test is only implemented on Windows.
-#if defined(OS_WIN)
-#define MAYBE_NtpOverriddenControllerTest NtpOverriddenControllerTest
-#else
-#define MAYBE_NtpOverriddenControllerTest DISABLED_NtpOverriddenControllerTest
-#endif
-
-TEST_F(ExtensionMessageBubbleTest, MAYBE_NtpOverriddenControllerTest) {
+// The feature this is meant to test is only enacted on Windows, but it should
+// pass on all platforms.
+TEST_F(ExtensionMessageBubbleTest, NtpOverriddenControllerTest) {
Init();
// Load two extensions overriding new tab page and one overriding something
// unrelated (to check for interference). Extension 2 should still win
@@ -765,6 +759,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_NtpOverriddenControllerTest) {
bubble.set_action_on_show(
FakeExtensionMessageBubble::BUBBLE_ACTION_CLICK_DISMISS_BUTTON);
EXPECT_TRUE(controller->ShouldShow(kId2));
+ bubble.set_controller(controller.get());
controller->Show(&bubble);
EXPECT_EQ(0U, controller->link_click_count());
EXPECT_EQ(0U, controller->action_click_count());
@@ -786,6 +781,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_NtpOverriddenControllerTest) {
FakeExtensionMessageBubble::BUBBLE_ACTION_CLICK_LINK);
controller.reset(new TestNtpOverriddenBubbleController(profile()));
EXPECT_TRUE(controller->ShouldShow(kId2));
+ bubble.set_controller(controller.get());
controller->Show(&bubble);
EXPECT_EQ(1U, controller->link_click_count());
EXPECT_EQ(0U, controller->action_click_count());
@@ -808,6 +804,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_NtpOverriddenControllerTest) {
EXPECT_TRUE(controller->ShouldShow(kId2));
override_extensions = controller->GetExtensionList();
EXPECT_EQ(1U, override_extensions.size());
+ bubble.set_controller(controller.get());
controller->Show(&bubble); // Simulate showing the bubble.
EXPECT_EQ(0U, controller->link_click_count());
EXPECT_EQ(1U, controller->action_click_count());
@@ -895,6 +892,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_ProxyOverriddenControllerTest) {
FakeExtensionMessageBubble bubble;
bubble.set_action_on_show(
FakeExtensionMessageBubble::BUBBLE_ACTION_CLICK_DISMISS_BUTTON);
+ bubble.set_controller(controller.get());
controller->Show(&bubble);
EXPECT_EQ(0U, controller->link_click_count());
EXPECT_EQ(0U, controller->action_click_count());
@@ -916,6 +914,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_ProxyOverriddenControllerTest) {
FakeExtensionMessageBubble::BUBBLE_ACTION_CLICK_LINK);
controller.reset(new TestProxyOverriddenBubbleController(profile()));
EXPECT_TRUE(controller->ShouldShow(kId2));
+ bubble.set_controller(controller.get());
controller->Show(&bubble);
EXPECT_EQ(1U, controller->link_click_count());
EXPECT_EQ(0U, controller->action_click_count());
@@ -938,6 +937,7 @@ TEST_F(ExtensionMessageBubbleTest, MAYBE_ProxyOverriddenControllerTest) {
EXPECT_TRUE(controller->ShouldShow(kId2));
override_extensions = controller->GetExtensionList();
EXPECT_EQ(1U, override_extensions.size());
+ bubble.set_controller(controller.get());
controller->Show(&bubble); // Simulate showing the bubble.
EXPECT_EQ(0U, controller->link_click_count());
EXPECT_EQ(1U, controller->action_click_count());
diff --git a/chrome/browser/extensions/extension_toolbar_model.cc b/chrome/browser/extensions/extension_toolbar_model.cc
index a0ee22a..5a61ea6 100644
--- a/chrome/browser/extensions/extension_toolbar_model.cc
+++ b/chrome/browser/extensions/extension_toolbar_model.cc
@@ -717,10 +717,13 @@ bool ExtensionToolbarModel::HighlightExtensions(
// mode.
if (highlighted_items_.size()) {
old_visible_icon_count_ = visible_icon_count_;
- is_highlighting_ = true;
if (visible_icon_count() < extension_ids.size())
SetVisibleIconCount(extension_ids.size());
+ // It's important that is_highlighting_ is changed right immediately before
+ // the observers are notified since it changes the result of
+ // toolbar_items().
+ is_highlighting_ = true;
FOR_EACH_OBSERVER(Observer, observers_,
OnToolbarHighlightModeChanged(true));
return true;
@@ -736,9 +739,13 @@ bool ExtensionToolbarModel::HighlightExtensions(
void ExtensionToolbarModel::StopHighlighting() {
if (is_highlighting_) {
highlighted_items_.clear();
- is_highlighting_ = false;
if (old_visible_icon_count_ != visible_icon_count_)
SetVisibleIconCount(old_visible_icon_count_);
+
+ // It's important that is_highlighting_ is changed right immediately before
+ // the observers are notified since it changes the result of
+ // toolbar_items().
+ is_highlighting_ = false;
FOR_EACH_OBSERVER(Observer, observers_,
OnToolbarHighlightModeChanged(false));
}
diff --git a/chrome/browser/extensions/ntp_overridden_bubble_controller.cc b/chrome/browser/extensions/ntp_overridden_bubble_controller.cc
index bec4ed3..317b7ba 100644
--- a/chrome/browser/extensions/ntp_overridden_bubble_controller.cc
+++ b/chrome/browser/extensions/ntp_overridden_bubble_controller.cc
@@ -47,6 +47,7 @@ class NtpOverriddenBubbleDelegate
base::string16 GetActionButtonLabel() const override;
base::string16 GetDismissButtonLabel() const override;
bool ShouldShowExtensionList() const override;
+ bool ShouldHighlightExtensions() const override;
void RestrictToSingleExtension(const std::string& extension_id) override;
void LogExtensionCount(size_t count) override;
void LogAction(extensions::ExtensionMessageBubbleController::BubbleAction
@@ -142,6 +143,10 @@ bool NtpOverriddenBubbleDelegate::ShouldShowExtensionList() const {
return false;
}
+bool NtpOverriddenBubbleDelegate::ShouldHighlightExtensions() const {
+ return false;
+}
+
void NtpOverriddenBubbleDelegate::RestrictToSingleExtension(
const std::string& extension_id) {
extension_id_ = extension_id;
diff --git a/chrome/browser/extensions/proxy_overridden_bubble_controller.cc b/chrome/browser/extensions/proxy_overridden_bubble_controller.cc
index a3cde4c..d25204d 100644
--- a/chrome/browser/extensions/proxy_overridden_bubble_controller.cc
+++ b/chrome/browser/extensions/proxy_overridden_bubble_controller.cc
@@ -51,6 +51,7 @@ class ProxyOverriddenBubbleDelegate
base::string16 GetActionButtonLabel() const override;
base::string16 GetDismissButtonLabel() const override;
bool ShouldShowExtensionList() const override;
+ bool ShouldHighlightExtensions() const override;
void RestrictToSingleExtension(const std::string& extension_id) override;
void LogExtensionCount(size_t count) override;
void LogAction(
@@ -161,6 +162,10 @@ bool ProxyOverriddenBubbleDelegate::ShouldShowExtensionList() const {
return false;
}
+bool ProxyOverriddenBubbleDelegate::ShouldHighlightExtensions() const {
+ return true;
+}
+
void ProxyOverriddenBubbleDelegate::RestrictToSingleExtension(
const std::string& extension_id) {
extension_id_ = extension_id;
diff --git a/chrome/browser/extensions/settings_api_bubble_controller.cc b/chrome/browser/extensions/settings_api_bubble_controller.cc
index e89f42f..e90cf93 100644
--- a/chrome/browser/extensions/settings_api_bubble_controller.cc
+++ b/chrome/browser/extensions/settings_api_bubble_controller.cc
@@ -54,6 +54,7 @@ class SettingsApiBubbleDelegate
base::string16 GetActionButtonLabel() const override;
base::string16 GetDismissButtonLabel() const override;
bool ShouldShowExtensionList() const override;
+ bool ShouldHighlightExtensions() const override;
void LogExtensionCount(size_t count) override;
void LogAction(
ExtensionMessageBubbleController::BubbleAction action) override;
@@ -244,6 +245,10 @@ bool SettingsApiBubbleDelegate::ShouldShowExtensionList() const {
return false;
}
+bool SettingsApiBubbleDelegate::ShouldHighlightExtensions() const {
+ return type_ == BUBBLE_TYPE_STARTUP_PAGES;
+}
+
void SettingsApiBubbleDelegate::LogExtensionCount(size_t count) {
}
@@ -290,11 +295,27 @@ SettingsApiBubbleController::SettingsApiBubbleController(
SettingsApiBubbleController::~SettingsApiBubbleController() {}
-bool SettingsApiBubbleController::ShouldShow(const std::string& extension_id) {
- if (delegate()->HasBubbleInfoBeenAcknowledged(extension_id))
+bool SettingsApiBubbleController::ShouldShow() {
+ const Extension* extension = nullptr;
+ switch (type_) {
+ case BUBBLE_TYPE_HOME_PAGE:
+ extension = GetExtensionOverridingHomepage(profile_);
+ break;
+ case BUBBLE_TYPE_SEARCH_ENGINE:
+ extension = GetExtensionOverridingSearchEngine(profile_);
+ break;
+ case BUBBLE_TYPE_STARTUP_PAGES:
+ extension = GetExtensionOverridingStartupPages(profile_);
+ break;
+ }
+
+ if (!extension)
+ return false;
+
+ if (delegate()->HasBubbleInfoBeenAcknowledged(extension->id()))
return false;
- if (!delegate()->ShouldIncludeExtension(extension_id))
+ if (!delegate()->ShouldIncludeExtension(extension->id()))
return false;
// If the browser is showing the 'Chrome crashed' infobar, it won't be showing
diff --git a/chrome/browser/extensions/settings_api_bubble_controller.h b/chrome/browser/extensions/settings_api_bubble_controller.h
index df64077..4209533 100644
--- a/chrome/browser/extensions/settings_api_bubble_controller.h
+++ b/chrome/browser/extensions/settings_api_bubble_controller.h
@@ -6,6 +6,7 @@
#define CHROME_BROWSER_EXTENSIONS_SETTINGS_API_BUBBLE_CONTROLLER_H_
#include <string>
+
#include "chrome/browser/extensions/extension_message_bubble_controller.h"
#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
@@ -18,9 +19,9 @@ class SettingsApiBubbleController : public ExtensionMessageBubbleController {
SettingsApiBubbleController(Profile* profile, SettingsApiOverrideType type);
~SettingsApiBubbleController() override;
- // Whether the controller knows that we should show the bubble for extension
- // with |extension_id|. Returns true if so.
- bool ShouldShow(const std::string& extension_id);
+ // Returns true if we should show the bubble for the extension actively
+ // overriding the setting of |type_|.
+ bool ShouldShow();
// ExtensionMessageBubbleController:
bool CloseOnDeactivate() override;
diff --git a/chrome/browser/extensions/suspicious_extension_bubble_controller.cc b/chrome/browser/extensions/suspicious_extension_bubble_controller.cc
index da364b2..94d189f 100644
--- a/chrome/browser/extensions/suspicious_extension_bubble_controller.cc
+++ b/chrome/browser/extensions/suspicious_extension_bubble_controller.cc
@@ -53,6 +53,7 @@ class SuspiciousExtensionBubbleDelegate
base::string16 GetActionButtonLabel() const override;
base::string16 GetDismissButtonLabel() const override;
bool ShouldShowExtensionList() const override;
+ bool ShouldHighlightExtensions() const override;
void LogExtensionCount(size_t count) override;
void LogAction(
ExtensionMessageBubbleController::BubbleAction action) override;
@@ -126,11 +127,14 @@ SuspiciousExtensionBubbleDelegate::GetDismissButtonLabel() const {
return l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNSUPPORTED_DISABLED_BUTTON);
}
-bool
-SuspiciousExtensionBubbleDelegate::ShouldShowExtensionList() const {
+bool SuspiciousExtensionBubbleDelegate::ShouldShowExtensionList() const {
return true;
}
+bool SuspiciousExtensionBubbleDelegate::ShouldHighlightExtensions() const {
+ return false;
+}
+
void SuspiciousExtensionBubbleDelegate::LogExtensionCount(
size_t count) {
UMA_HISTOGRAM_COUNTS_100(
diff --git a/chrome/browser/ui/cocoa/extensions/browser_actions_controller.mm b/chrome/browser/ui/cocoa/extensions/browser_actions_controller.mm
index dc640a1..19f9ac8 100644
--- a/chrome/browser/ui/cocoa/extensions/browser_actions_controller.mm
+++ b/chrome/browser/ui/cocoa/extensions/browser_actions_controller.mm
@@ -171,6 +171,9 @@ class ToolbarActionsBarBridge : public ToolbarActionsBarDelegate {
bool IsPopupRunning() const override;
void OnOverflowedActionWantsToRunChanged(bool overflowed_action_wants_to_run)
override;
+ void ShowExtensionMessageBubble(
+ scoped_ptr<extensions::ExtensionMessageBubbleController> controller)
+ override;
// The owning BrowserActionsController; weak.
BrowserActionsController* controller_;
@@ -248,6 +251,11 @@ void ToolbarActionsBarBridge::OnOverflowedActionWantsToRunChanged(
setOverflowedToolbarActionWantsToRun:overflowed_action_wants_to_run];
}
+void ToolbarActionsBarBridge::ShowExtensionMessageBubble(
+ scoped_ptr<extensions::ExtensionMessageBubbleController> controller) {
+ NOTREACHED(); // Not yet implemented on Mac.
+}
+
} // namespace
@implementation BrowserActionsController
diff --git a/chrome/browser/ui/extensions/OWNERS b/chrome/browser/ui/extensions/OWNERS
index 98867c4..e7cdf1a 100644
--- a/chrome/browser/ui/extensions/OWNERS
+++ b/chrome/browser/ui/extensions/OWNERS
@@ -1,6 +1,9 @@
benwells@chromium.org
miket@chromium.org
-# Extension/Toolbar Actions Files
per-file extension_action*=finnur@chromium.org
per-file extension_action*=rdevlin.cronin@chromium.org
+per-file extension_installed_bubble*=finnur@chromium.org
+per-file extension_installed_bubble*=rdevlin.cronin@chromium.org
+per-file extension_message_bubble*=finnur@chromium.org
+per-file extension_message_bubble*=rdevlin.cronin@chromium.org
diff --git a/chrome/browser/ui/extensions/extension_message_bubble_factory.cc b/chrome/browser/ui/extensions/extension_message_bubble_factory.cc
new file mode 100644
index 0000000..b4a67c1
--- /dev/null
+++ b/chrome/browser/ui/extensions/extension_message_bubble_factory.cc
@@ -0,0 +1,103 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/extensions/extension_message_bubble_factory.h"
+
+#include "base/lazy_instance.h"
+#include "chrome/browser/extensions/dev_mode_bubble_controller.h"
+#include "chrome/browser/extensions/extension_message_bubble_controller.h"
+#include "chrome/browser/extensions/proxy_overridden_bubble_controller.h"
+#include "chrome/browser/extensions/settings_api_bubble_controller.h"
+#include "chrome/browser/extensions/settings_api_helpers.h"
+#include "chrome/browser/extensions/suspicious_extension_bubble_controller.h"
+#include "chrome/browser/profiles/profile.h"
+
+namespace {
+
+// A map of all profiles evaluated, so we can tell if it's the initial check.
+// TODO(devlin): It would be nice to coalesce all the "profiles evaluated" maps
+// that are in the different bubble controllers.
+base::LazyInstance<std::set<Profile*> > g_profiles_evaluated =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Currently, we only show these bubbles on windows platforms. This can be
+// overridden for testing purposes.
+#if defined(OS_WIN)
+bool g_enabled = true;
+#else
+bool g_enabled = false;
+#endif
+
+} // namespace
+
+ExtensionMessageBubbleFactory::ExtensionMessageBubbleFactory(Profile* profile)
+ : profile_(profile) {
+}
+
+ExtensionMessageBubbleFactory::~ExtensionMessageBubbleFactory() {
+}
+
+scoped_ptr<extensions::ExtensionMessageBubbleController>
+ExtensionMessageBubbleFactory::GetController() {
+ if (!g_enabled)
+ return scoped_ptr<extensions::ExtensionMessageBubbleController>();
+
+ Profile* original_profile = profile_->GetOriginalProfile();
+ std::set<Profile*>& profiles_evaluated = g_profiles_evaluated.Get();
+ bool is_initial_check = profiles_evaluated.count(original_profile) == 0;
+ profiles_evaluated.insert(original_profile);
+
+ // The list of suspicious extensions takes priority over the dev mode bubble
+ // and the settings API bubble, since that needs to be shown as soon as we
+ // disable something. The settings API bubble is shown on first startup after
+ // an extension has changed the startup pages and it is acceptable if that
+ // waits until the next startup because of the suspicious extension bubble.
+ // The dev mode bubble is not time sensitive like the other two so we'll catch
+ // the dev mode extensions on the next startup/next window that opens. That
+ // way, we're not too spammy with the bubbles.
+ {
+ scoped_ptr<extensions::SuspiciousExtensionBubbleController> controller(
+ new extensions::SuspiciousExtensionBubbleController(profile_));
+ if (controller->ShouldShow())
+ return controller.Pass();
+ }
+
+ {
+ // No use showing this if it's not the startup of the profile.
+ if (is_initial_check) {
+ scoped_ptr<extensions::SettingsApiBubbleController> controller(
+ new extensions::SettingsApiBubbleController(
+ profile_, extensions::BUBBLE_TYPE_STARTUP_PAGES));
+ if (controller->ShouldShow())
+ return controller.Pass();
+ }
+ }
+
+ {
+ // TODO(devlin): Move the "GetExtensionOverridingProxy" part into the
+ // proxy bubble controller.
+ const extensions::Extension* extension =
+ extensions::GetExtensionOverridingProxy(profile_);
+ if (extension) {
+ scoped_ptr<extensions::ProxyOverriddenBubbleController> controller(
+ new extensions::ProxyOverriddenBubbleController(profile_));
+ if (controller->ShouldShow(extension->id()))
+ return controller.Pass();
+ }
+ }
+
+ {
+ scoped_ptr<extensions::DevModeBubbleController> controller(
+ new extensions::DevModeBubbleController(profile_));
+ if (controller->ShouldShow())
+ return controller.Pass();
+ }
+
+ return scoped_ptr<extensions::ExtensionMessageBubbleController>();
+}
+
+// static
+void ExtensionMessageBubbleFactory::set_enabled_for_tests(bool enabled) {
+ g_enabled = enabled;
+}
diff --git a/chrome/browser/ui/extensions/extension_message_bubble_factory.h b/chrome/browser/ui/extensions/extension_message_bubble_factory.h
new file mode 100644
index 0000000..e9b0fd6
--- /dev/null
+++ b/chrome/browser/ui/extensions/extension_message_bubble_factory.h
@@ -0,0 +1,37 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_EXTENSIONS_EXTENSION_MESSAGE_BUBBLE_FACTORY_H_
+#define CHROME_BROWSER_UI_EXTENSIONS_EXTENSION_MESSAGE_BUBBLE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+class Profile;
+
+namespace extensions {
+class ExtensionMessageBubbleController;
+} // namespace extensions
+
+// Create and show ExtensionMessageBubbles for either extensions that look
+// suspicious and have therefore been disabled, or for extensions that are
+// running in developer mode that we want to warn the user about.
+class ExtensionMessageBubbleFactory {
+ public:
+ explicit ExtensionMessageBubbleFactory(Profile* profile);
+ ~ExtensionMessageBubbleFactory();
+
+ // Returns the controller for the bubble that should be shown, if any.
+ scoped_ptr<extensions::ExtensionMessageBubbleController> GetController();
+
+ // Enables the bubbles across all platforms for testing.
+ static void set_enabled_for_tests(bool enabled);
+
+ private:
+ Profile* profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleFactory);
+};
+
+#endif // CHROME_BROWSER_UI_EXTENSIONS_EXTENSION_MESSAGE_BUBBLE_FACTORY_H_
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.cc b/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
index be3687c..5f423e1 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
@@ -7,12 +7,14 @@
#include "base/auto_reset.h"
#include "base/profiler/scoped_tracker.h"
#include "chrome/browser/extensions/extension_action_manager.h"
+#include "chrome/browser/extensions/extension_message_bubble_controller.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/extensions/extension_action_view_controller.h"
+#include "chrome/browser/ui/extensions/extension_message_bubble_factory.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/browser/ui/toolbar/component_toolbar_actions_factory.h"
@@ -418,7 +420,9 @@ ToolbarActionsBar::ToolbarActionsBar(ToolbarActionsBarDelegate* delegate,
model_observer_(this),
suppress_layout_(false),
suppress_animation_(true),
- overflowed_action_wants_to_run_(false) {
+ overflowed_action_wants_to_run_(false),
+ checked_extension_bubble_(false),
+ weak_ptr_factory_(this) {
if (model_) // |model_| can be null in unittests.
model_observer_.Add(model_);
@@ -631,6 +635,18 @@ void ToolbarActionsBar::CreateActions() {
// Once the actions are created, we should animate the changes.
suppress_animation_ = false;
+
+ // CreateActions() can be called multiple times, so we need to make sure we
+ // haven't already shown the bubble.
+ if (!checked_extension_bubble_) {
+ checked_extension_bubble_ = true;
+ // CreateActions() can be called as part of the browser window set up, which
+ // we need to let finish before showing the actions.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ToolbarActionsBar::MaybeShowExtensionBubble,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
}
void ToolbarActionsBar::DeleteActions() {
@@ -729,6 +745,15 @@ bool ToolbarActionsBar::ShouldShowInfoBubble() {
return true;
}
+void ToolbarActionsBar::MaybeShowExtensionBubble() {
+ scoped_ptr<extensions::ExtensionMessageBubbleController> controller =
+ ExtensionMessageBubbleFactory(browser_->profile()).GetController();
+ if (controller) {
+ controller->HighlightExtensionsIfNecessary();
+ delegate_->ShowExtensionMessageBubble(controller.Pass());
+ }
+}
+
void ToolbarActionsBar::OnToolbarExtensionAdded(
const extensions::Extension* extension,
int index) {
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.h b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
index 3a6798f..349736c 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
@@ -7,6 +7,7 @@
#include "base/macros.h"
#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h"
#include "chrome/browser/extensions/extension_toolbar_model.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_bar_bubble_delegate.h"
@@ -198,6 +199,9 @@ class ToolbarActionsBar : public extensions::ExtensionToolbarModel::Observer,
// Sets |overflowed_action_wants_to_run_| to the proper value.
void SetOverflowedActionWantsToRun();
+ // Shows an extension message bubble, if any should be shown.
+ void MaybeShowExtensionBubble();
+
bool in_overflow_mode() const { return main_bar_ != nullptr; }
// The delegate for this object (in a real build, this is the view).
@@ -247,6 +251,12 @@ class ToolbarActionsBar : public extensions::ExtensionToolbarModel::Observer,
// True if an action in the overflow menu wants to run.
bool overflowed_action_wants_to_run_;
+ // True if we have checked to see if there is an extension bubble that should
+ // be displayed, and, if there is, shown that bubble.
+ bool checked_extension_bubble_;
+
+ base::WeakPtrFactory<ToolbarActionsBar> weak_ptr_factory_;
+
DISALLOW_COPY_AND_ASSIGN(ToolbarActionsBar);
};
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h b/chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h
index 9a24e6d..af71316 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h
@@ -5,11 +5,16 @@
#ifndef CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_ACTIONS_BAR_DELEGATE_H_
#define CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_ACTIONS_BAR_DELEGATE_H_
+#include "base/memory/scoped_ptr.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/geometry/size.h"
class ToolbarActionViewController;
+namespace extensions {
+class ExtensionMessageBubbleController;
+}
+
// The delegate class (which, in production, represents the view) of the
// ToolbarActionsBar.
class ToolbarActionsBarDelegate {
@@ -59,6 +64,10 @@ class ToolbarActionsBarDelegate {
// action wants to run has changed.
virtual void OnOverflowedActionWantsToRunChanged(
bool overflowed_action_wants_to_run) = 0;
+
+ // Displays the bubble for the passed ExtensionMessageBubbleController.
+ virtual void ShowExtensionMessageBubble(
+ scoped_ptr<extensions::ExtensionMessageBubbleController> controller) = 0;
};
#endif // CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_ACTIONS_BAR_DELEGATE_H_
diff --git a/chrome/browser/ui/views/extensions/extension_message_bubble_view.cc b/chrome/browser/ui/views/extensions/extension_message_bubble_view.cc
index 578d693..a2b70d4 100644
--- a/chrome/browser/ui/views/extensions/extension_message_bubble_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_message_bubble_view.cc
@@ -7,24 +7,9 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/extensions/dev_mode_bubble_controller.h"
-#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_message_bubble_controller.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_toolbar_model.h"
-#include "chrome/browser/extensions/proxy_overridden_bubble_controller.h"
-#include "chrome/browser/extensions/settings_api_bubble_controller.h"
-#include "chrome/browser/extensions/settings_api_helpers.h"
-#include "chrome/browser/extensions/suspicious_extension_bubble_controller.h"
-#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/view_ids.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
-#include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
-#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/grit/locale_settings.h"
-#include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_system.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/views/controls/button/label_button.h"
@@ -36,9 +21,6 @@
namespace {
-base::LazyInstance<std::set<Profile*> > g_profiles_evaluated =
- LAZY_INSTANCE_INITIALIZER;
-
// Layout constants.
const int kExtensionListPadding = 10;
const int kInsetBottomRight = 13;
@@ -79,21 +61,6 @@ ExtensionMessageBubbleView::ExtensionMessageBubbleView(
set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
}
-void ExtensionMessageBubbleView::OnActionButtonClicked(
- const base::Closure& callback) {
- action_callback_ = callback;
-}
-
-void ExtensionMessageBubbleView::OnDismissButtonClicked(
- const base::Closure& callback) {
- dismiss_callback_ = callback;
-}
-
-void ExtensionMessageBubbleView::OnLinkClicked(
- const base::Closure& callback) {
- link_callback_ = callback;
-}
-
void ExtensionMessageBubbleView::Show() {
// Not showing the bubble right away (during startup) has a few benefits:
// We don't have to worry about focus being lost due to the Omnibox (or to
@@ -114,7 +81,7 @@ void ExtensionMessageBubbleView::OnWidgetDestroying(views::Widget* widget) {
// To catch Esc, we monitor destroy message. Unless the link has been clicked,
// we assume Dismiss was the action taken.
if (!link_clicked_ && !action_taken_)
- dismiss_callback_.Run();
+ controller_->OnBubbleDismiss();
}
////////////////////////////////////////////////////////////////////////////////
@@ -241,7 +208,7 @@ void ExtensionMessageBubbleView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
if (sender == action_button_) {
action_taken_ = true;
- action_callback_.Run();
+ controller_->OnBubbleAction();
} else {
DCHECK_EQ(dismiss_button_, sender);
}
@@ -252,7 +219,7 @@ void ExtensionMessageBubbleView::LinkClicked(views::Link* source,
int event_flags) {
DCHECK_EQ(learn_more_, source);
link_clicked_ = true;
- link_callback_.Run();
+ controller_->OnLinkClicked();
GetWidget()->Close();
}
@@ -267,243 +234,4 @@ void ExtensionMessageBubbleView::ViewHierarchyChanged(
NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
}
-////////////////////////////////////////////////////////////////////////////////
-// ExtensionMessageBubbleFactory
-
-ExtensionMessageBubbleFactory::ExtensionMessageBubbleFactory(
- Profile* profile,
- ToolbarView* toolbar_view)
- : profile_(profile),
- toolbar_view_(toolbar_view),
- shown_suspicious_extensions_bubble_(false),
- shown_startup_override_extensions_bubble_(false),
- shown_proxy_override_extensions_bubble_(false),
- shown_dev_mode_extensions_bubble_(false),
- is_observing_(false),
- stage_(STAGE_START),
- container_(NULL),
- anchor_view_(NULL) {}
-
-ExtensionMessageBubbleFactory::~ExtensionMessageBubbleFactory() {
- MaybeStopObserving();
-}
-
-void ExtensionMessageBubbleFactory::MaybeShow(views::View* anchor_view) {
-#if defined(OS_WIN)
- bool is_initial_check = IsInitialProfileCheck(profile_->GetOriginalProfile());
- RecordProfileCheck(profile_->GetOriginalProfile());
-
- // The list of suspicious extensions takes priority over the dev mode bubble
- // and the settings API bubble, since that needs to be shown as soon as we
- // disable something. The settings API bubble is shown on first startup after
- // an extension has changed the startup pages and it is acceptable if that
- // waits until the next startup because of the suspicious extension bubble.
- // The dev mode bubble is not time sensitive like the other two so we'll catch
- // the dev mode extensions on the next startup/next window that opens. That
- // way, we're not too spammy with the bubbles.
- if (!shown_suspicious_extensions_bubble_ &&
- MaybeShowSuspiciousExtensionsBubble(anchor_view))
- return;
-
- if (!shown_startup_override_extensions_bubble_ &&
- is_initial_check &&
- MaybeShowStartupOverrideExtensionsBubble(anchor_view))
- return;
-
- if (!shown_proxy_override_extensions_bubble_ &&
- MaybeShowProxyOverrideExtensionsBubble(anchor_view))
- return;
-
- if (!shown_dev_mode_extensions_bubble_)
- MaybeShowDevModeExtensionsBubble(anchor_view);
-#endif // OS_WIN
-}
-
-bool ExtensionMessageBubbleFactory::MaybeShowSuspiciousExtensionsBubble(
- views::View* anchor_view) {
- DCHECK(!shown_suspicious_extensions_bubble_);
-
- scoped_ptr<SuspiciousExtensionBubbleController> suspicious_extensions(
- new SuspiciousExtensionBubbleController(profile_));
- if (!suspicious_extensions->ShouldShow())
- return false;
-
- shown_suspicious_extensions_bubble_ = true;
- SuspiciousExtensionBubbleController* weak_controller =
- suspicious_extensions.get();
- ExtensionMessageBubbleView* bubble_delegate =
- new ExtensionMessageBubbleView(anchor_view,
- views::BubbleBorder::TOP_RIGHT,
- suspicious_extensions.Pass());
-
- views::BubbleDelegateView::CreateBubble(bubble_delegate);
- weak_controller->Show(bubble_delegate);
-
- return true;
-}
-
-bool ExtensionMessageBubbleFactory::MaybeShowStartupOverrideExtensionsBubble(
- views::View* anchor_view) {
-#if !defined(OS_WIN)
- return false;
-#else
- DCHECK(!shown_startup_override_extensions_bubble_);
-
- const Extension* extension = GetExtensionOverridingStartupPages(profile_);
- if (!extension)
- return false;
-
- scoped_ptr<SettingsApiBubbleController> settings_api_bubble(
- new SettingsApiBubbleController(profile_,
- BUBBLE_TYPE_STARTUP_PAGES));
- if (!settings_api_bubble->ShouldShow(extension->id()))
- return false;
-
- shown_startup_override_extensions_bubble_ = true;
- PrepareToHighlightExtensions(settings_api_bubble.Pass(), anchor_view);
- return true;
-#endif
-}
-
-bool ExtensionMessageBubbleFactory::MaybeShowProxyOverrideExtensionsBubble(
- views::View* anchor_view) {
-#if !defined(OS_WIN)
- return false;
-#else
- DCHECK(!shown_proxy_override_extensions_bubble_);
-
- const Extension* extension = GetExtensionOverridingProxy(profile_);
- if (!extension)
- return false;
-
- scoped_ptr<ProxyOverriddenBubbleController> proxy_bubble(
- new ProxyOverriddenBubbleController(profile_));
- if (!proxy_bubble->ShouldShow(extension->id()))
- return false;
-
- shown_proxy_override_extensions_bubble_ = true;
- PrepareToHighlightExtensions(proxy_bubble.Pass(), anchor_view);
- return true;
-#endif
-}
-
-bool ExtensionMessageBubbleFactory::MaybeShowDevModeExtensionsBubble(
- views::View* anchor_view) {
- DCHECK(!shown_dev_mode_extensions_bubble_);
-
- // Check the Developer Mode extensions.
- scoped_ptr<DevModeBubbleController> dev_mode_extensions(
- new DevModeBubbleController(profile_));
-
- // Return early if we have none to show.
- if (!dev_mode_extensions->ShouldShow())
- return false;
-
- shown_dev_mode_extensions_bubble_ = true;
- PrepareToHighlightExtensions(dev_mode_extensions.Pass(), anchor_view);
- return true;
-}
-
-void ExtensionMessageBubbleFactory::MaybeObserve() {
- if (!is_observing_) {
- is_observing_ = true;
- container_->AddObserver(this);
- }
-}
-
-void ExtensionMessageBubbleFactory::MaybeStopObserving() {
- if (is_observing_) {
- is_observing_ = false;
- container_->RemoveObserver(this);
- }
-}
-
-void ExtensionMessageBubbleFactory::RecordProfileCheck(Profile* profile) {
- g_profiles_evaluated.Get().insert(profile);
-}
-
-bool ExtensionMessageBubbleFactory::IsInitialProfileCheck(Profile* profile) {
- return g_profiles_evaluated.Get().count(profile) == 0;
-}
-
-void ExtensionMessageBubbleFactory::OnBrowserActionsContainerAnimationEnded() {
- MaybeStopObserving();
- if (stage_ == STAGE_START) {
- HighlightExtensions();
- } else if (stage_ == STAGE_HIGHLIGHTED) {
- ShowHighlightingBubble();
- } else { // We shouldn't be observing if we've completed the process.
- NOTREACHED();
- Finish();
- }
-}
-
-void ExtensionMessageBubbleFactory::OnBrowserActionsContainerDestroyed() {
- // If the container associated with the bubble is destroyed, abandon the
- // process.
- Finish();
-}
-
-void ExtensionMessageBubbleFactory::PrepareToHighlightExtensions(
- scoped_ptr<ExtensionMessageBubbleController> controller,
- views::View* anchor_view) {
- // We should be in the start stage (i.e., should not have a pending attempt to
- // show a bubble).
- DCHECK_EQ(stage_, STAGE_START);
-
- // Prepare to display and highlight the extensions before showing the bubble.
- // Since this is an asynchronous process, set member variables for later use.
- controller_ = controller.Pass();
- anchor_view_ = anchor_view;
- container_ = toolbar_view_->browser_actions();
-
- if (container_->animating())
- MaybeObserve();
- else
- HighlightExtensions();
-}
-
-void ExtensionMessageBubbleFactory::HighlightExtensions() {
- DCHECK_EQ(STAGE_START, stage_);
- stage_ = STAGE_HIGHLIGHTED;
-
- const ExtensionIdList extension_list = controller_->GetExtensionIdList();
- DCHECK(!extension_list.empty());
- ExtensionToolbarModel::Get(profile_)->HighlightExtensions(extension_list);
- if (container_->animating())
- MaybeObserve();
- else
- ShowHighlightingBubble();
-}
-
-void ExtensionMessageBubbleFactory::ShowHighlightingBubble() {
- DCHECK_EQ(stage_, STAGE_HIGHLIGHTED);
- stage_ = STAGE_COMPLETE;
-
- views::View* reference_view = NULL;
- if (container_->num_toolbar_actions() > 0u)
- reference_view = container_->GetToolbarActionViewAt(0);
- if (reference_view && reference_view->visible())
- anchor_view_ = reference_view;
-
- ExtensionMessageBubbleController* weak_controller = controller_.get();
- ExtensionMessageBubbleView* bubble_delegate =
- new ExtensionMessageBubbleView(
- anchor_view_,
- views::BubbleBorder::TOP_RIGHT,
- scoped_ptr<ExtensionMessageBubbleController>(
- controller_.release()));
- views::BubbleDelegateView::CreateBubble(bubble_delegate);
- weak_controller->Show(bubble_delegate);
-
- Finish();
-}
-
-void ExtensionMessageBubbleFactory::Finish() {
- MaybeStopObserving();
- controller_.reset();
- anchor_view_ = NULL;
- container_ = NULL;
-}
-
} // namespace extensions
diff --git a/chrome/browser/ui/views/extensions/extension_message_bubble_view.h b/chrome/browser/ui/views/extensions/extension_message_bubble_view.h
index eadaf71..177673f 100644
--- a/chrome/browser/ui/views/extensions/extension_message_bubble_view.h
+++ b/chrome/browser/ui/views/extensions/extension_message_bubble_view.h
@@ -5,18 +5,14 @@
#ifndef CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSION_MESSAGE_BUBBLE_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSION_MESSAGE_BUBBLE_VIEW_H_
-#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/browser/extensions/extension_message_bubble.h"
-#include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
#include "ui/views/bubble/bubble_delegate.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/link_listener.h"
class Profile;
-class BrowserActionsContainer;
-class ToolbarView;
namespace views {
class Label;
@@ -26,121 +22,8 @@ class View;
namespace extensions {
-class DevModeBubbleController;
class ExtensionMessageBubbleController;
-// Create and show ExtensionMessageBubbles for either extensions that look
-// suspicious and have therefore been disabled, or for extensions that are
-// running in developer mode that we want to warn the user about.
-// Calling MaybeShow() will show one of the bubbles, if there is cause to (we
-// don't show both in order to avoid spamminess). The suspicious extensions
-// bubble takes priority over the developer mode extensions bubble.
-class ExtensionMessageBubbleFactory : public BrowserActionsContainerObserver {
- public:
- ExtensionMessageBubbleFactory(Profile* profile, ToolbarView* toolbar_view);
- ~ExtensionMessageBubbleFactory() override;
-
- void MaybeShow(views::View* anchor_view);
-
- private:
- // The stage of showing the developer mode extensions bubble. STAGE_START
- // corresponds to the beginning of the process, when nothing has been done.
- // STAGE_HIGHLIGHTED indicates that the toolbar should be highlighting
- // dangerous extensions. STAGE_COMPLETE means that the process should be
- // ended.
- enum Stage { STAGE_START, STAGE_HIGHLIGHTED, STAGE_COMPLETE };
-
- // Shows the suspicious extensions bubble, if there are suspicious extensions
- // and we have not done so already.
- // Returns true if we have show the view.
- bool MaybeShowSuspiciousExtensionsBubble(views::View* anchor_view);
-
- // Shows the settings API extensions bubble, if there are extensions
- // overriding the startup pages and we have not done so already.
- // Returns true if we show the view (or start the process).
- bool MaybeShowStartupOverrideExtensionsBubble(views::View* anchor_view);
-
- // Shows the bubble for when there are extensions overriding the proxy (if we
- // have not done so already). Returns true if we show the view (or start the
- // process of doing so).
- bool MaybeShowProxyOverrideExtensionsBubble(views::View* anchor_view);
-
- // Shows the developer mode extensions bubble, if there are extensions running
- // in developer mode and we have not done so already.
- // Returns true if we show the view (or start the process).
- bool MaybeShowDevModeExtensionsBubble(views::View* anchor_view);
-
- // Starts or stops observing the BrowserActionsContainer, if necessary.
- void MaybeObserve();
- void MaybeStopObserving();
-
- // Adds |profile| to the list of profiles that have been evaluated for showing
- // a bubble. Handy for things that only want to check once per profile.
- void RecordProfileCheck(Profile* profile);
- // Returns false if this profile has been evaluated before.
- bool IsInitialProfileCheck(Profile* profile);
-
- // BrowserActionsContainer::Observer implementation.
- void OnBrowserActionsContainerAnimationEnded() override;
- void OnBrowserActionsContainerDestroyed() override;
-
- // Sets the stage for highlighting extensions and then showing the bubble
- // controlled by |controller|, anchored to |anchor_view|.
- void PrepareToHighlightExtensions(
- scoped_ptr<ExtensionMessageBubbleController> controller,
- views::View* anchor_view);
-
- // Inform the ExtensionToolbarModel to highlight the appropriate extensions.
- void HighlightExtensions();
-
- // Shows the waiting bubbble, after highlighting the extensions.
- void ShowHighlightingBubble();
-
- // Finishes the process of showing the developer mode bubble.
- void Finish();
-
- // The associated profile.
- Profile* profile_;
-
- // The toolbar view that the ExtensionMessageBubbleViews will attach to.
- ToolbarView* toolbar_view_;
-
- // Whether or not we have shown the suspicious extensions bubble.
- bool shown_suspicious_extensions_bubble_;
-
- // Whether or not we have shown the Settings API extensions bubble notifying
- // the user about the startup pages being overridden.
- bool shown_startup_override_extensions_bubble_;
-
- // Whether or not we have shown the bubble notifying the user about the proxy
- // being overridden.
- bool shown_proxy_override_extensions_bubble_;
-
- // Whether or not we have shown the developer mode extensions bubble.
- bool shown_dev_mode_extensions_bubble_;
-
- // Whether or not we are already observing the BrowserActionsContainer (so
- // we don't add ourselves twice).
- bool is_observing_;
-
- // The current stage of showing the bubble.
- Stage stage_;
-
- // The BrowserActionsContainer for the profile. This will be NULL if the
- // factory is not currently in the process of showing a bubble.
- BrowserActionsContainer* container_;
-
- // The default view to anchor the bubble to. This will be NULL if the factory
- // is not currently in the process of showing a bubble.
- views::View* anchor_view_;
-
- // The controller to show a bubble for. This will be NULL if the factory is
- // not currently in the process of showing a bubble.
- scoped_ptr<ExtensionMessageBubbleController> controller_;
-
- DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleFactory);
-};
-
// This is a class that implements the UI for the bubble showing which
// extensions look suspicious and have therefore been automatically disabled.
class ExtensionMessageBubbleView : public ExtensionMessageBubble,
@@ -154,9 +37,6 @@ class ExtensionMessageBubbleView : public ExtensionMessageBubble,
scoped_ptr<ExtensionMessageBubbleController> controller);
// ExtensionMessageBubble methods.
- void OnActionButtonClicked(const base::Closure& callback) override;
- void OnDismissButtonClicked(const base::Closure& callback) override;
- void OnLinkClicked(const base::Closure& callback) override;
void Show() override;
// WidgetObserver methods.
@@ -198,11 +78,6 @@ class ExtensionMessageBubbleView : public ExtensionMessageBubble,
bool link_clicked_;
bool action_taken_;
- // Callbacks into the controller.
- base::Closure action_callback_;
- base::Closure dismiss_callback_;
- base::Closure link_callback_;
-
base::WeakPtrFactory<ExtensionMessageBubbleView> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleView);
diff --git a/chrome/browser/ui/views/extensions/extension_message_bubble_view_browsertest.cc b/chrome/browser/ui/views/extensions/extension_message_bubble_view_browsertest.cc
new file mode 100644
index 0000000..771f41d
--- /dev/null
+++ b/chrome/browser/ui/views/extensions/extension_message_bubble_view_browsertest.cc
@@ -0,0 +1,146 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/run_loop.h"
+#include "chrome/browser/extensions/extension_action_test_util.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/ui/extensions/extension_message_bubble_factory.h"
+#include "chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h"
+#include "chrome/browser/ui/views/extensions/extension_message_bubble_view.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
+#include "chrome/browser/ui/views/toolbar/wrench_toolbar_button.h"
+#include "extensions/common/feature_switch.h"
+
+namespace {
+
+// Checks that the |bubble| is using the |expected_reference_view|, and is in
+// approximately the correct position.
+void CheckBubbleAndReferenceView(views::BubbleDelegateView* bubble,
+ views::View* expected_reference_view) {
+ ASSERT_TRUE(bubble);
+ ASSERT_TRUE(expected_reference_view);
+ EXPECT_EQ(expected_reference_view, bubble->GetAnchorView());
+
+ // Do a rough check that the bubble is in the right place.
+ gfx::Rect bubble_bounds = bubble->GetBoundsInScreen();
+ gfx::Rect reference_bounds = expected_reference_view->GetBoundsInScreen();
+ // It should be below the reference view, but not too far below.
+ EXPECT_GE(bubble_bounds.y(), reference_bounds.bottom());
+ // "Too far below" is kind of ambiguous. The exact logic of where a bubble
+ // is positioned with respect to its anchor view should be tested as part of
+ // the bubble logic, but we still want to make sure we didn't accidentally
+ // place it somewhere crazy (which can happen if we draw it, and then
+ // animate or reposition the reference view).
+ const int kFudgeFactor = 50;
+ EXPECT_LE(bubble_bounds.y(), reference_bounds.bottom() + kFudgeFactor);
+ // The bubble should intersect the reference view somewhere along the x-axis.
+ EXPECT_FALSE(bubble_bounds.x() > reference_bounds.right());
+ EXPECT_FALSE(reference_bounds.x() > bubble_bounds.right());
+
+ // And, of course, the bubble should be visible.
+ EXPECT_TRUE(bubble->visible());
+}
+
+} // namespace
+
+class ExtensionMessageBubbleViewBrowserTest
+ : public BrowserActionsBarBrowserTest {
+ protected:
+ ExtensionMessageBubbleViewBrowserTest() {}
+ ~ExtensionMessageBubbleViewBrowserTest() override {}
+
+ void SetUpCommandLine(base::CommandLine* command_line) override;
+
+ private:
+ scoped_ptr<extensions::FeatureSwitch::ScopedOverride>
+ dev_mode_bubble_override_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleViewBrowserTest);
+};
+
+void ExtensionMessageBubbleViewBrowserTest::SetUpCommandLine(
+ base::CommandLine* command_line) {
+ BrowserActionsBarBrowserTest::SetUpCommandLine(command_line);
+ // The dev mode warning bubble is an easy one to trigger, so we use that for
+ // our testing purposes.
+ dev_mode_bubble_override_.reset(
+ new extensions::FeatureSwitch::ScopedOverride(
+ extensions::FeatureSwitch::force_dev_mode_highlighting(),
+ true));
+ ExtensionMessageBubbleFactory::set_enabled_for_tests(true);
+}
+
+// Tests that an extension bubble will be anchored to the wrench menu when there
+// aren't any extensions with actions.
+// This also tests that the crashes in crbug.com/476426 are fixed.
+IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleViewBrowserTest,
+ ExtensionBubbleAnchoredToWrenchMenu) {
+ scoped_refptr<const extensions::Extension> action_extension =
+ extensions::extension_action_test_util::CreateActionExtension(
+ "action_extension",
+ extensions::extension_action_test_util::BROWSER_ACTION,
+ extensions::Manifest::UNPACKED);
+ extension_service()->AddExtension(action_extension.get());
+
+ Browser* second_browser = new Browser(
+ Browser::CreateParams(profile(), browser()->host_desktop_type()));
+ base::RunLoop().RunUntilIdle();
+
+ BrowserActionsContainer* second_container =
+ BrowserView::GetBrowserViewForBrowser(second_browser)->toolbar()->
+ browser_actions();
+ views::BubbleDelegateView* bubble = second_container->active_bubble();
+ CheckBubbleAndReferenceView(bubble,
+ second_container->GetToolbarActionViewAt(0));
+
+ bubble->GetWidget()->Close();
+ EXPECT_EQ(nullptr, second_container->active_bubble());
+}
+
+// Tests that an extension bubble will be anchored to an extension action when
+// there are extensions with actions.
+IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleViewBrowserTest,
+ ExtensionBubbleAnchoredToExtensionAction) {
+ scoped_refptr<const extensions::Extension> no_action_extension =
+ extensions::extension_action_test_util::CreateActionExtension(
+ "action_extension",
+ extensions::extension_action_test_util::NO_ACTION,
+ extensions::Manifest::UNPACKED);
+ extension_service()->AddExtension(no_action_extension.get());
+
+ Browser* second_browser = new Browser(
+ Browser::CreateParams(profile(), browser()->host_desktop_type()));
+ ASSERT_TRUE(second_browser);
+ base::RunLoop().RunUntilIdle();
+
+ ToolbarView* toolbar =
+ BrowserView::GetBrowserViewForBrowser(second_browser)->toolbar();
+ BrowserActionsContainer* second_container = toolbar->browser_actions();
+ views::BubbleDelegateView* bubble = second_container->active_bubble();
+ CheckBubbleAndReferenceView(bubble, toolbar->app_menu());
+
+ bubble->GetWidget()->Close();
+ EXPECT_EQ(nullptr, second_container->active_bubble());
+}
+
+// Tests that the extension bubble will show on startup.
+IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleViewBrowserTest,
+ PRE_ExtensionBubbleShowsOnStartup) {
+ LoadExtension(test_data_dir_.AppendASCII("good_unpacked"));
+}
+
+IN_PROC_BROWSER_TEST_F(ExtensionMessageBubbleViewBrowserTest,
+ ExtensionBubbleShowsOnStartup) {
+ base::RunLoop().RunUntilIdle();
+ BrowserActionsContainer* container =
+ BrowserView::GetBrowserViewForBrowser(browser())->toolbar()->
+ browser_actions();
+ views::BubbleDelegateView* bubble = container->active_bubble();
+ CheckBubbleAndReferenceView(bubble, container->GetToolbarActionViewAt(0));
+
+ bubble->GetWidget()->Close();
+ EXPECT_EQ(nullptr, container->active_bubble());
+}
diff --git a/chrome/browser/ui/views/settings_api_bubble_helper_views.cc b/chrome/browser/ui/views/settings_api_bubble_helper_views.cc
index 341dc61..06bb439 100644
--- a/chrome/browser/ui/views/settings_api_bubble_helper_views.cc
+++ b/chrome/browser/ui/views/settings_api_bubble_helper_views.cc
@@ -25,13 +25,12 @@ namespace extensions {
namespace {
void ShowSettingsApiBubble(SettingsApiOverrideType type,
- const std::string& extension_id,
Profile* profile,
views::View* anchor_view,
views::BubbleBorder::Arrow arrow) {
scoped_ptr<SettingsApiBubbleController> settings_api_bubble(
new SettingsApiBubbleController(profile, type));
- if (!settings_api_bubble->ShouldShow(extension_id))
+ if (!settings_api_bubble->ShouldShow())
return;
SettingsApiBubbleController* controller = settings_api_bubble.get();
@@ -48,18 +47,13 @@ void MaybeShowExtensionControlledHomeNotification(Browser* browser) {
return;
#endif
- const Extension* extension =
- GetExtensionOverridingHomepage(browser->profile());
- if (extension) {
- // The bubble will try to anchor itself against the home button
- views::View* anchor_view = BrowserView::GetBrowserViewForBrowser(browser)->
- toolbar()->home_button();
- ShowSettingsApiBubble(BUBBLE_TYPE_HOME_PAGE,
- extension->id(),
- browser->profile(),
- anchor_view,
- views::BubbleBorder::TOP_LEFT);
- }
+ // The bubble will try to anchor itself against the home button
+ views::View* anchor_view = BrowserView::GetBrowserViewForBrowser(browser)->
+ toolbar()->home_button();
+ ShowSettingsApiBubble(BUBBLE_TYPE_HOME_PAGE,
+ browser->profile(),
+ anchor_view,
+ views::BubbleBorder::TOP_LEFT);
}
void MaybeShowExtensionControlledSearchNotification(
@@ -72,17 +66,13 @@ void MaybeShowExtensionControlledSearchNotification(
if (AutocompleteMatch::IsSearchType(match.type) &&
match.type != AutocompleteMatchType::SEARCH_OTHER_ENGINE) {
- const Extension* extension = GetExtensionOverridingSearchEngine(profile);
- if (extension) {
- ToolbarView* toolbar =
- BrowserView::GetBrowserViewForBrowser(
- chrome::FindBrowserWithWebContents(web_contents))->toolbar();
- ShowSettingsApiBubble(BUBBLE_TYPE_SEARCH_ENGINE,
- extension->id(),
- profile,
- toolbar->app_menu(),
- views::BubbleBorder::TOP_RIGHT);
- }
+ ToolbarView* toolbar =
+ BrowserView::GetBrowserViewForBrowser(
+ chrome::FindBrowserWithWebContents(web_contents))->toolbar();
+ ShowSettingsApiBubble(BUBBLE_TYPE_SEARCH_ENGINE,
+ profile,
+ toolbar->app_menu(),
+ views::BubbleBorder::TOP_RIGHT);
}
}
diff --git a/chrome/browser/ui/views/toolbar/browser_actions_container.cc b/chrome/browser/ui/views/toolbar/browser_actions_container.cc
index b86b084..3130267 100644
--- a/chrome/browser/ui/views/toolbar/browser_actions_container.cc
+++ b/chrome/browser/ui/views/toolbar/browser_actions_container.cc
@@ -6,6 +6,7 @@
#include "base/compiler_specific.h"
#include "base/stl_util.h"
+#include "chrome/browser/extensions/extension_message_bubble_controller.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
@@ -14,6 +15,7 @@
#include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
+#include "chrome/browser/ui/views/extensions/extension_message_bubble_view.h"
#include "chrome/browser/ui/views/extensions/extension_toolbar_icon_surfacing_bubble_views.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h"
@@ -33,6 +35,7 @@
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/resources/grit/ui_resources.h"
+#include "ui/views/bubble/bubble_delegate.h"
#include "ui/views/controls/resize_area.h"
#include "ui/views/painter.h"
#include "ui/views/widget/widget.h"
@@ -83,7 +86,8 @@ BrowserActionsContainer::BrowserActionsContainer(
added_to_view_(false),
shown_bubble_(false),
resize_amount_(0),
- animation_target_size_(0) {
+ animation_target_size_(0),
+ active_bubble_(nullptr) {
set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR);
bool overflow_experiment =
@@ -112,6 +116,12 @@ BrowserActionsContainer::BrowserActionsContainer(
}
BrowserActionsContainer::~BrowserActionsContainer() {
+ if (active_bubble_)
+ active_bubble_->GetWidget()->Close();
+ // We should synchronously receive the OnWidgetClosing() event, so we should
+ // always have cleared the active bubble by now.
+ DCHECK(!active_bubble_);
+
FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
observers_,
OnBrowserActionsContainerDestroyed());
@@ -339,6 +349,40 @@ void BrowserActionsContainer::OnOverflowedActionWantsToRunChanged(
overflowed_action_wants_to_run);
}
+void BrowserActionsContainer::ShowExtensionMessageBubble(
+ scoped_ptr<extensions::ExtensionMessageBubbleController> controller) {
+ if (animating()) {
+ // Similarly, if the container is animating, we can't effectively anchor the
+ // bubble, so wait until animation stops.
+ pending_extension_bubble_controller_ = controller.Pass();
+ return;
+ }
+
+ views::View* reference_view = VisibleBrowserActions() > 0 ?
+ static_cast<views::View*>(toolbar_action_views_[0]) :
+ BrowserView::GetBrowserViewForBrowser(browser_)->toolbar()->app_menu();
+
+ extensions::ExtensionMessageBubbleController* weak_controller =
+ controller.get();
+ extensions::ExtensionMessageBubbleView* bubble =
+ new extensions::ExtensionMessageBubbleView(
+ reference_view,
+ views::BubbleBorder::TOP_RIGHT,
+ controller.Pass());
+ views::BubbleDelegateView::CreateBubble(bubble);
+ active_bubble_ = bubble;
+ active_bubble_->GetWidget()->AddObserver(this);
+ weak_controller->Show(bubble);
+}
+
+void BrowserActionsContainer::OnWidgetClosing(views::Widget* widget) {
+ ClearActiveBubble(widget);
+}
+
+void BrowserActionsContainer::OnWidgetDestroying(views::Widget* widget) {
+ ClearActiveBubble(widget);
+}
+
void BrowserActionsContainer::AddObserver(
BrowserActionsContainerObserver* observer) {
observers_.AddObserver(observer);
@@ -668,6 +712,9 @@ void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) {
FOR_EACH_OBSERVER(BrowserActionsContainerObserver,
observers_,
OnBrowserActionsContainerAnimationEnded());
+
+ if (pending_extension_bubble_controller_)
+ ShowExtensionMessageBubble(pending_extension_bubble_controller_.Pass());
}
content::WebContents* BrowserActionsContainer::GetCurrentWebContents() {
@@ -780,3 +827,10 @@ void BrowserActionsContainer::LoadImages() {
const int kImages[] = IMAGE_GRID(IDR_DEVELOPER_MODE_HIGHLIGHT);
highlight_painter_.reset(views::Painter::CreateImageGridPainter(kImages));
}
+
+void BrowserActionsContainer::ClearActiveBubble(views::Widget* widget) {
+ DCHECK(active_bubble_);
+ DCHECK_EQ(active_bubble_->GetWidget(), widget);
+ widget->RemoveObserver(this);
+ active_bubble_ = nullptr;
+}
diff --git a/chrome/browser/ui/views/toolbar/browser_actions_container.h b/chrome/browser/ui/views/toolbar/browser_actions_container.h
index ab40e10..94ebf43 100644
--- a/chrome/browser/ui/views/toolbar/browser_actions_container.h
+++ b/chrome/browser/ui/views/toolbar/browser_actions_container.h
@@ -18,6 +18,7 @@
#include "ui/views/controls/resize_area_delegate.h"
#include "ui/views/drag_controller.h"
#include "ui/views/view.h"
+#include "ui/views/widget/widget_observer.h"
class BrowserActionsContainerObserver;
class ExtensionPopup;
@@ -29,6 +30,7 @@ class Extension;
}
namespace views {
+class BubbleDelegateView;
class ResizeArea;
}
@@ -125,6 +127,7 @@ class BrowserActionsContainer
public views::ResizeAreaDelegate,
public gfx::AnimationDelegate,
public ToolbarActionView::Delegate,
+ public views::WidgetObserver,
public extensions::ExtensionKeybindingRegistry::Delegate {
public:
// Constructs a BrowserActionContainer for a particular |browser| object. For
@@ -252,6 +255,13 @@ class BrowserActionsContainer
bool IsPopupRunning() const override;
void OnOverflowedActionWantsToRunChanged(
bool overflowed_action_wants_to_run) override;
+ void ShowExtensionMessageBubble(
+ scoped_ptr<extensions::ExtensionMessageBubbleController> controller)
+ override;
+
+ // views::WidgetObserver:
+ void OnWidgetClosing(views::Widget* widget) override;
+ void OnWidgetDestroying(views::Widget* widget) override;
// Overridden from extension::ExtensionKeybindingRegistry::Delegate:
extensions::ActiveTabPermissionGranter* GetActiveTabPermissionGranter()
@@ -260,6 +270,8 @@ class BrowserActionsContainer
// Retrieve the current popup. This should only be used by unit tests.
gfx::NativeView TestGetPopup();
+ views::BubbleDelegateView* active_bubble() { return active_bubble_; }
+
protected:
// Overridden from views::View:
void ViewHierarchyChanged(
@@ -275,6 +287,9 @@ class BrowserActionsContainer
void LoadImages();
+ // Clears the |active_bubble_|, and unregisters the container as an observer.
+ void ClearActiveBubble(views::Widget* widget);
+
const ToolbarActionsBar::PlatformSettings& platform_settings() const {
return toolbar_actions_bar_->platform_settings();
}
@@ -342,6 +357,13 @@ class BrowserActionsContainer
// The class that registers for keyboard shortcuts for extension commands.
scoped_ptr<ExtensionKeybindingRegistryViews> extension_keybinding_registry_;
+ // The controller of the bubble to show once animation finishes, if any.
+ scoped_ptr<extensions::ExtensionMessageBubbleController>
+ pending_extension_bubble_controller_;
+
+ // The extension bubble that is actively showing, if any.
+ views::BubbleDelegateView* active_bubble_;
+
ObserverList<BrowserActionsContainerObserver> observers_;
DISALLOW_COPY_AND_ASSIGN(BrowserActionsContainer);
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index 7c4073b..df70ca4 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -31,7 +31,6 @@
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/toolbar/wrench_menu_model.h"
#include "chrome/browser/ui/view_ids.h"
-#include "chrome/browser/ui/views/extensions/extension_message_bubble_view.h"
#include "chrome/browser/ui/views/extensions/extension_popup.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/page_action_image_view.h"
@@ -130,10 +129,7 @@ ToolbarView::ToolbarView(Browser* browser)
browser_actions_(NULL),
app_menu_(NULL),
browser_(browser),
- badge_controller_(browser->profile(), this),
- extension_message_bubble_factory_(
- new extensions::ExtensionMessageBubbleFactory(browser->profile(),
- this)) {
+ badge_controller_(browser->profile(), this) {
set_id(VIEW_ID_TOOLBAR);
SetEventTargeter(
@@ -271,14 +267,6 @@ void ToolbarView::Init() {
}
}
-void ToolbarView::OnWidgetVisibilityChanged(views::Widget* widget,
- bool visible) {
- if (visible) {
- // Safe to call multiple times; the bubble will only appear once.
- extension_message_bubble_factory_->MaybeShow(app_menu_);
- }
-}
-
void ToolbarView::OnWidgetActivationChanged(views::Widget* widget,
bool active) {
extensions::ExtensionCommandsGlobalRegistry* registry =
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.h b/chrome/browser/ui/views/toolbar/toolbar_view.h
index 32fe3f6..0e60a16 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.h
@@ -31,7 +31,6 @@ class WrenchToolbarButton;
namespace extensions {
class Command;
class Extension;
-class ExtensionMessageBubbleFactory;
}
namespace views {
@@ -137,7 +136,6 @@ class ToolbarView : public views::AccessiblePaneView,
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// views::WidgetObserver:
- void OnWidgetVisibilityChanged(views::Widget* widget, bool visible) override;
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
// content::NotificationObserver:
@@ -245,11 +243,6 @@ class ToolbarView : public views::AccessiblePaneView,
scoped_ptr<WrenchMenuModel> wrench_menu_model_;
scoped_ptr<WrenchMenu> wrench_menu_;
- // The factory to create bubbles to warn about dangerous/suspicious
- // extensions.
- scoped_ptr<extensions::ExtensionMessageBubbleFactory>
- extension_message_bubble_factory_;
-
// A list of listeners to call when the menu opens.
ObserverList<views::MenuListener> menu_listeners_;