Do not let BUBBLE_ALERT replace any currently visible external error alert.
If an extension install error bubble alert or a dialog alert is visible,
we should not replace that if a new error is added to external install manager.
Without this CL, if the new error is of type BUBBLE_ALERT, it will
replace any alerts related to previous errors that is currently visible.
BUG=588181
Test=See comment #0 in http://crbug.com/588181
Review URL: https://codereview.chromium.org/1749023002
Cr-Commit-Position: refs/heads/master@{#379876}
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index a68d8f9..19db4d8 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -69,6 +69,9 @@
#include "chrome/browser/extensions/updater/extension_updater.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_factory.h"
+#include "chrome/browser/ui/global_error/global_error.h"
+#include "chrome/browser/ui/global_error/global_error_service.h"
+#include "chrome/browser/ui/global_error/global_error_service_factory.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/plugins/plugins_handler.h"
@@ -193,8 +196,26 @@
const char theme2_crx[] = "pjpgmfcmabopnnfonnhmdjglfpjjfkbf";
const char permissions_crx[] = "eagpmdpfmaekmmcejjbmjoecnejeiiin";
const char updates_from_webstore[] = "akjooamlhcgeopfifcmlggaebeocgokj";
+const char updates_from_webstore2[] = "oolblhbomdbcpmafphaodhjfcgbihcdg";
+const char updates_from_webstore3[] = "bmfoocgfinpmkmlbjhcbofejhkhlbchk";
const char permissions_blocklist[] = "noffkehfcaggllbcojjbopcmlhcnhcdn";
+struct BubbleErrorsTestData {
+ BubbleErrorsTestData(const std::string& id,
+ const std::string& version,
+ const base::FilePath& crx_path,
+ size_t expected_bubble_error_count)
+ : id(id),
+ version(version),
+ crx_path(crx_path),
+ expected_bubble_error_count(expected_bubble_error_count) {}
+ std::string id;
+ std::string version;
+ base::FilePath crx_path;
+ size_t expected_bubble_error_count;
+ bool expect_has_shown_bubble_view;
+};
+
static void AddPattern(URLPatternSet* extent, const std::string& pattern) {
int schemes = URLPattern::SCHEME_ALL;
extent->AddPattern(URLPattern(schemes, pattern));
@@ -225,6 +246,15 @@
return found != errors.end();
}
+size_t GetExternalInstallBubbleCount(ExtensionService* service) {
+ size_t bubble_count = 0u;
+ std::vector<ExternalInstallError*> errors =
+ service->external_install_manager()->GetErrorsForTesting();
+ for (const auto& error : errors)
+ bubble_count += error->alert_type() == ExternalInstallError::BUBBLE_ALERT;
+ return bubble_count;
+}
+
} // namespace
class MockExtensionProvider : public extensions::ExternalProviderInterface {
@@ -6073,6 +6103,221 @@
EXPECT_FALSE(HasExternalInstallErrors(service_));
}
+TEST_F(ExtensionServiceTest, MultipleExternalInstallBubbleErrors) {
+ FeatureSwitch::ScopedOverride prompt(
+ FeatureSwitch::prompt_for_external_extensions(), true);
+ // This sets up the ExtensionPrefs used by our ExtensionService to be
+ // post-first run.
+ ExtensionServiceInitParams params = CreateDefaultInitParams();
+ params.is_first_run = false;
+ InitializeExtensionService(params);
+
+ MockExtensionProvider* provider =
+ new MockExtensionProvider(service(), Manifest::EXTERNAL_PREF);
+ AddMockExternalProvider(provider);
+
+ std::vector<BubbleErrorsTestData> data;
+ data.push_back(
+ BubbleErrorsTestData(updates_from_webstore, "1",
+ temp_dir().path().AppendASCII("webstore.crx"), 1u));
+ data.push_back(
+ BubbleErrorsTestData(updates_from_webstore2, "1",
+ temp_dir().path().AppendASCII("webstore2.crx"), 2u));
+ data.push_back(BubbleErrorsTestData(good_crx, "1.0.0.0",
+ data_dir().AppendASCII("good.crx"), 2u));
+
+ PackCRX(data_dir().AppendASCII("update_from_webstore"),
+ data_dir().AppendASCII("update_from_webstore.pem"), data[0].crx_path);
+ PackCRX(data_dir().AppendASCII("update_from_webstore2"),
+ data_dir().AppendASCII("update_from_webstore2.pem"),
+ data[1].crx_path);
+
+ // Install extensions from |data| one by one and expect each of them to result
+ // in an error. The first two extensions are from webstore, so they will
+ // trigger BUBBLE_ALERT type errors. After each step, we verify that we got
+ // the expected number of errors in external_install_manager(). We also verify
+ // that only the first BUBBLE_ALERT error is shown.
+ for (size_t i = 0; i < data.size(); ++i) {
+ content::WindowedNotificationObserver global_error_observer(
+ chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED,
+ content::NotificationService::AllSources());
+ content::WindowedNotificationObserver observer(
+ extensions::NOTIFICATION_CRX_INSTALLER_DONE,
+ content::NotificationService::AllSources());
+ provider->UpdateOrAddExtension(data[i].id, data[i].version,
+ data[i].crx_path);
+ service()->CheckForExternalUpdates();
+ observer.Wait();
+ // Make sure ExternalInstallError::OnDialogReady() fires.
+ global_error_observer.Wait();
+
+ const size_t expected_error_count = i + 1u;
+ std::vector<ExternalInstallError*> errors =
+ service_->external_install_manager()->GetErrorsForTesting();
+ EXPECT_EQ(expected_error_count, errors.size());
+ EXPECT_EQ(data[i].expected_bubble_error_count,
+ GetExternalInstallBubbleCount(service()));
+ EXPECT_TRUE(service()
+ ->external_install_manager()
+ ->has_currently_visible_install_alert());
+ // Make sure that the first error is only being shown.
+ EXPECT_EQ(errors[0], service()
+ ->external_install_manager()
+ ->currently_visible_install_alert_for_testing());
+ EXPECT_FALSE(service()->IsExtensionEnabled(data[i].id));
+ }
+
+ // Cancel all the install prompts.
+ for (size_t i = 0; i < data.size(); ++i) {
+ const std::string& extension_id = data[i].id;
+ EXPECT_TRUE(GetError(extension_id));
+ GetError(extension_id)
+ ->OnInstallPromptDone(ExtensionInstallPrompt::Result::USER_CANCELED);
+ EXPECT_FALSE(GetError(extension_id));
+ }
+ EXPECT_FALSE(service()
+ ->external_install_manager()
+ ->has_currently_visible_install_alert());
+ EXPECT_EQ(0u, GetExternalInstallBubbleCount(service()));
+ EXPECT_FALSE(HasExternalInstallErrors(service()));
+
+ // Add a new webstore install. Verify that this shows an error bubble since
+ // there are no error bubbles pending at this point. Also verify that the
+ // error bubble is for this newly added extension.
+ {
+ base::FilePath webstore_crx_three =
+ temp_dir().path().AppendASCII("webstore3.crx");
+ PackCRX(data_dir().AppendASCII("update_from_webstore3"),
+ data_dir().AppendASCII("update_from_webstore3.pem"),
+ webstore_crx_three);
+
+ content::WindowedNotificationObserver global_error_observer(
+ chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED,
+ content::NotificationService::AllSources());
+ content::WindowedNotificationObserver observer(
+ extensions::NOTIFICATION_CRX_INSTALLER_DONE,
+ content::NotificationService::AllSources());
+ provider->UpdateOrAddExtension(
+ updates_from_webstore3, "1",
+ temp_dir().path().AppendASCII("webstore3.crx"));
+ service()->CheckForExternalUpdates();
+ observer.Wait();
+ // Make sure ExternalInstallError::OnDialogReady() fires.
+ global_error_observer.Wait();
+
+ std::vector<ExternalInstallError*> errors =
+ service_->external_install_manager()->GetErrorsForTesting();
+ EXPECT_EQ(1u, errors.size());
+ EXPECT_EQ(1u, GetExternalInstallBubbleCount(service()));
+ EXPECT_TRUE(service()
+ ->external_install_manager()
+ ->has_currently_visible_install_alert());
+ // Verify that the visible alert is for the current error.
+ EXPECT_EQ(errors[0], service()
+ ->external_install_manager()
+ ->currently_visible_install_alert_for_testing());
+ EXPECT_FALSE(service()->IsExtensionEnabled(updates_from_webstore3));
+ }
+}
+
+// Verifies that an error alert of type BUBBLE_ALERT does not replace an
+// existing visible alert that was previously opened by clicking menu item.
+TEST_F(ExtensionServiceTest, BubbleAlertDoesNotHideAnotherAlertFromMenu) {
+ FeatureSwitch::ScopedOverride prompt(
+ FeatureSwitch::prompt_for_external_extensions(), true);
+ // This sets up the ExtensionPrefs used by our ExtensionService to be
+ // post-first run.
+ ExtensionServiceInitParams params = CreateDefaultInitParams();
+ params.is_first_run = false;
+ InitializeExtensionService(params);
+
+ MockExtensionProvider* provider =
+ new MockExtensionProvider(service(), Manifest::EXTERNAL_PREF);
+ AddMockExternalProvider(provider);
+
+ std::vector<BubbleErrorsTestData> data;
+ data.push_back(
+ BubbleErrorsTestData(updates_from_webstore, "1",
+ temp_dir().path().AppendASCII("webstore.crx"), 1u));
+ data.push_back(
+ BubbleErrorsTestData(updates_from_webstore2, "1",
+ temp_dir().path().AppendASCII("webstore2.crx"), 2u));
+
+ PackCRX(data_dir().AppendASCII("update_from_webstore"),
+ data_dir().AppendASCII("update_from_webstore.pem"), data[0].crx_path);
+ PackCRX(data_dir().AppendASCII("update_from_webstore2"),
+ data_dir().AppendASCII("update_from_webstore2.pem"),
+ data[1].crx_path);
+ {
+ content::WindowedNotificationObserver global_error_observer(
+ chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED,
+ content::NotificationService::AllSources());
+ content::WindowedNotificationObserver observer(
+ extensions::NOTIFICATION_CRX_INSTALLER_DONE,
+ content::NotificationService::AllSources());
+ provider->UpdateOrAddExtension(data[0].id, data[0].version,
+ data[0].crx_path);
+ service()->CheckForExternalUpdates();
+ observer.Wait();
+ // Make sure ExternalInstallError::OnDialogReady() fires.
+ global_error_observer.Wait();
+
+ std::vector<ExternalInstallError*> errors =
+ service_->external_install_manager()->GetErrorsForTesting();
+ EXPECT_EQ(1u, errors.size());
+ EXPECT_EQ(1u, GetExternalInstallBubbleCount(service()));
+ EXPECT_TRUE(service()
+ ->external_install_manager()
+ ->has_currently_visible_install_alert());
+ // Verify that the visible alert is for the current error.
+ EXPECT_EQ(errors[0], service()
+ ->external_install_manager()
+ ->currently_visible_install_alert_for_testing());
+ }
+
+ ExternalInstallError* first_extension_error = GetError(data[0].id);
+
+ // Close the bubble alert.
+ GlobalError* global_error =
+ GlobalErrorServiceFactory::GetForProfile(profile())
+ ->GetHighestSeverityGlobalErrorWithAppMenuItem();
+ first_extension_error->DidCloseBubbleView();
+
+ // Bring the bubble alert error again by clicking its menu item.
+ global_error->ExecuteMenuItem(nullptr);
+
+ // Install another webstore extension that will trigger an erorr of type
+ // BUBBLE_ALERT.
+ // Make sure that this bubble alert does not replace the current bubble alert.
+ {
+ content::WindowedNotificationObserver global_error_observer(
+ chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED,
+ content::NotificationService::AllSources());
+ content::WindowedNotificationObserver observer(
+ extensions::NOTIFICATION_CRX_INSTALLER_DONE,
+ content::NotificationService::AllSources());
+ provider->UpdateOrAddExtension(data[1].id, data[1].version,
+ data[1].crx_path);
+ service()->CheckForExternalUpdates();
+ observer.Wait();
+ // Make sure ExternalInstallError::OnDialogReady() fires.
+ global_error_observer.Wait();
+
+ std::vector<ExternalInstallError*> errors =
+ service_->external_install_manager()->GetErrorsForTesting();
+ EXPECT_EQ(2u, errors.size());
+ EXPECT_EQ(2u, GetExternalInstallBubbleCount(service()));
+ EXPECT_TRUE(service()
+ ->external_install_manager()
+ ->has_currently_visible_install_alert());
+ // Verify that the old bubble alert was *not* replaced by the new alert.
+ EXPECT_EQ(first_extension_error,
+ service()
+ ->external_install_manager()
+ ->currently_visible_install_alert_for_testing());
+ }
+}
+
// Test that there is a bubble for external extensions that update
// from the webstore if the profile is not new.
TEST_F(ExtensionServiceTest, ExternalInstallUpdatesFromWebstoreOldProfile) {