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) {
diff --git a/chrome/browser/extensions/external_install_error.cc b/chrome/browser/extensions/external_install_error.cc
index 8280d3e..640faa9 100644
--- a/chrome/browser/extensions/external_install_error.cc
+++ b/chrome/browser/extensions/external_install_error.cc
@@ -197,7 +197,10 @@
}
void ExternalInstallBubbleAlert::ExecuteMenuItem(Browser* browser) {
- ShowBubbleView(browser);
+ // |browser| is nullptr in unit test.
+ if (browser)
+ ShowBubbleView(browser);
+ error_->DidOpenBubbleView();
}
gfx::Image ExternalInstallBubbleAlert::GetBubbleViewIcon() {
@@ -264,6 +267,7 @@
}
void ExternalInstallBubbleAlert::OnBubbleViewDidClose(Browser* browser) {
+ error_->DidCloseBubbleView();
}
void ExternalInstallBubbleAlert::BubbleViewAcceptButtonPressed(
@@ -339,11 +343,20 @@
}
break;
case ExtensionInstallPrompt::Result::ABORTED:
+ manager_->DidChangeInstallAlertVisibility(this, false);
break;
}
// NOTE: We may be deleted here!
}
+void ExternalInstallError::DidOpenBubbleView() {
+ manager_->DidChangeInstallAlertVisibility(this, true);
+}
+
+void ExternalInstallError::DidCloseBubbleView() {
+ manager_->DidChangeInstallAlertVisibility(this, false);
+}
+
void ExternalInstallError::ShowDialog(Browser* browser) {
DCHECK(install_ui_.get());
DCHECK(prompt_.get());
@@ -352,6 +365,7 @@
web_contents = browser->tab_strip_model()->GetActiveWebContents();
install_ui_show_params_.reset(
new ExtensionInstallPromptShowParams(web_contents));
+ manager_->DidChangeInstallAlertVisibility(this, true);
ExtensionInstallPrompt::GetDefaultShowDialogCallback().Run(
install_ui_show_params_.get(),
base::Bind(&ExternalInstallError::OnInstallPromptDone,
@@ -422,10 +436,16 @@
global_error_.reset(new ExternalInstallBubbleAlert(this, prompt_.get()));
error_service_->AddGlobalError(global_error_.get());
- Browser* browser = chrome::FindTabbedBrowser(
- Profile::FromBrowserContext(browser_context_), true);
- if (browser)
- global_error_->ShowBubbleView(browser);
+ if (!manager_->has_currently_visible_install_alert()) {
+ // |browser| is nullptr during unit tests, so call
+ // DidChangeInstallAlertVisibility() regardless because we depend on this
+ // in unit tests.
+ manager_->DidChangeInstallAlertVisibility(this, true);
+ Browser* browser = chrome::FindTabbedBrowser(
+ Profile::FromBrowserContext(browser_context_), true);
+ if (browser)
+ global_error_->ShowBubbleView(browser);
+ }
} else {
DCHECK(alert_type_ == MENU_ALERT);
global_error_.reset(new ExternalInstallMenuAlert(this));
diff --git a/chrome/browser/extensions/external_install_error.h b/chrome/browser/extensions/external_install_error.h
index abb06974..a74288b8 100644
--- a/chrome/browser/extensions/external_install_error.h
+++ b/chrome/browser/extensions/external_install_error.h
@@ -52,6 +52,9 @@
void OnInstallPromptDone(ExtensionInstallPrompt::Result result);
+ void DidOpenBubbleView();
+ void DidCloseBubbleView();
+
// Show the associated dialog. This should only be called once the dialog is
// ready.
void ShowDialog(Browser* browser);
diff --git a/chrome/browser/extensions/external_install_manager.cc b/chrome/browser/extensions/external_install_manager.cc
index 3908e49..70737e83 100644
--- a/chrome/browser/extensions/external_install_manager.cc
+++ b/chrome/browser/extensions/external_install_manager.cc
@@ -62,6 +62,7 @@
: browser_context_(browser_context),
is_first_run_(is_first_run),
extension_prefs_(ExtensionPrefs::Get(browser_context_)),
+ currently_visible_install_alert_(nullptr),
extension_registry_observer_(this) {
DCHECK(browser_context_);
extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
@@ -94,8 +95,14 @@
void ExternalInstallManager::RemoveExternalInstallError(
const std::string& extension_id) {
- if (errors_.erase(extension_id) > 0)
+ std::map<std::string, scoped_ptr<ExternalInstallError>>::iterator iter =
+ errors_.find(extension_id);
+ if (iter != errors_.end()) {
+ if (iter->second.get() == currently_visible_install_alert_)
+ currently_visible_install_alert_ = nullptr;
+ errors_.erase(iter);
UpdateExternalExtensionAlert();
+ }
}
void ExternalInstallManager::UpdateExternalExtensionAlert() {
@@ -141,6 +148,17 @@
UpdateExternalExtensionAlert();
}
+void ExternalInstallManager::DidChangeInstallAlertVisibility(
+ ExternalInstallError* external_install_error,
+ bool visible) {
+ if (visible) {
+ currently_visible_install_alert_ = external_install_error;
+ } else if (!visible &&
+ currently_visible_install_alert_ == external_install_error) {
+ currently_visible_install_alert_ = nullptr;
+ }
+}
+
std::vector<ExternalInstallError*>
ExternalInstallManager::GetErrorsForTesting() {
std::vector<ExternalInstallError*> errors;
diff --git a/chrome/browser/extensions/external_install_manager.h b/chrome/browser/extensions/external_install_manager.h
index afc7cb9..82732fac 100644
--- a/chrome/browser/extensions/external_install_manager.h
+++ b/chrome/browser/extensions/external_install_manager.h
@@ -43,6 +43,20 @@
// acknowledged.
void AcknowledgeExternalExtension(const std::string& extension_id);
+ // Notifies the manager that |external_install_error| has changed its alert
+ // visibility.
+ void DidChangeInstallAlertVisibility(
+ ExternalInstallError* external_install_error,
+ bool visible);
+
+ bool has_currently_visible_install_alert() {
+ return currently_visible_install_alert_ != nullptr;
+ }
+
+ ExternalInstallError* currently_visible_install_alert_for_testing() const {
+ return currently_visible_install_alert_;
+ }
+
// Returns a mutable copy of the list of global errors for testing purposes.
std::vector<ExternalInstallError*> GetErrorsForTesting();
@@ -85,6 +99,9 @@
std::set<std::string> shown_ids_;
+ // The error that is currently showing an alert dialog/bubble.
+ ExternalInstallError* currently_visible_install_alert_;
+
content::NotificationRegistrar registrar_;
ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
diff --git a/chrome/test/data/extensions/update_from_webstore2.pem b/chrome/test/data/extensions/update_from_webstore2.pem
new file mode 100644
index 0000000..f93c2bc
--- /dev/null
+++ b/chrome/test/data/extensions/update_from_webstore2.pem
@@ -0,0 +1,15 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMWHeR/rCAn3Bk8NV
+Rbet1j7x6pZ8u80IJDw26ejxnwDL1iQc8GFbcejNxDNZOa+j82dniqL25sQfoP8KZ
+7p9EoT6iK+YoLQDxeZCskEK+lxe2e6h+51qEN8ASAHpq6KJ2kIruClixl/0rWrMIp
+w8KiYNoYxZhHy7nMgu8RQD2bvAgMBAAECgYANi4Kky6sbc9L87L8e+Fq+QVWpus1L
+1dk91yZY0oQz0MwYWJ93b7zdabXG8y6uG/ckdGB6Lmj6syDZ0BrSiNx1cRlz5DSH2
+kGpSTrYRj4/c5s8E7VESTbvqa9GersHjD7VVm7t0gcgzkEENS6Xqi+qxS1d0iQdXN
+8VLXHAn9g9kQJBAOe/N4mWu0D3fmQt2WYFbL+O7ujWphrvpKiiS7/aso/vvhccSAn
+hrLJByEFhavyWEgml1EUOkxOPHPSMHXDEFesCQQDaM4VfYbUr4xpaHE8kjsAJJ56X
+Dl0P6eboZNuvgDSHIHMo7WI1httZmIjNVol6i0uoMQ0YUoLNzUal7IsYql4NAkApE
+1tkp7LI8RUV7Ip76ro7ubneyyzl2VP2D84dU0YS/QtUTU73cWiKr94uuHWeD3ti+m
+GFW+/1p4gaUDtnoVnBAkBvd1hPxHMvq0E//Sw06bOSOaobUpwZ0AxEdZmASwcKbHp
+H0xSTcYPGflSMSQPMFTKeBgjBfw0RIydPBcp83DNVAkB0r8S2vQyMQzlyq8L05qXg
+SHel0mIgxqLmCC6wi2sPKuKauqztTpRavfR060tnNz2otXX3hENxtu0chpsSRJov
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/extensions/update_from_webstore2/manifest.json b/chrome/test/data/extensions/update_from_webstore2/manifest.json
new file mode 100644
index 0000000..b35bb03
--- /dev/null
+++ b/chrome/test/data/extensions/update_from_webstore2/manifest.json
@@ -0,0 +1,6 @@
+{
+ "name": "Second test extension that updates from webstore",
+ "version": "1",
+ "manifest_version": 2,
+ "update_url": "https://clients2.google.com/service/update2/crx"
+}
diff --git a/chrome/test/data/extensions/update_from_webstore3.pem b/chrome/test/data/extensions/update_from_webstore3.pem
new file mode 100644
index 0000000..f69f4964
--- /dev/null
+++ b/chrome/test/data/extensions/update_from_webstore3.pem
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMRkvle1bZcmaeGv1
+/Ia8WRIosC5dr7E70gP3XOfwE+70EnC+Ytj3aU99VBbFow2tTgC2uAY+0P5HXvH9K
+woeJdPtOOPdRZ+VvE9JqwsEKps+C915YPb7iYIIoU3IXEe3O3ox8F4bPu1iiNabtr
+n6lopCH7E22QeSR5hPdVO8fq5AgMBAAECgYEAn0I23HdClHTRHfPzwN+6aqFAYdrE
+AXU/uQcshKvCTqY2BOq4ZCGqxmoU+YG0KiXfFLmz9lAryfZEw3Dl54m2J0oKHLgpX
+Fh2NsNPBAFTIgz8LLqXZxQ/30luxPLipTOX4mcN0ULX0MkQsxf/TgS0PrCPfgzFBU
+SKDETsBO7g/dUCQQDjE2zhcxMrmuqpF1Cy0G28+IzBjI0YooM2ZHiaSh/r/yO8ywN
+DLS3TTD0RggHJiqRCZnlrYgzDeqPW1aPv2tSXAkEA3WjRFEhAjtB5Ct4npVIyqGzW
+q+hQncG48T7PmOXdN+jL72XafEkKTJ0N0wghr9YwFda2EyBD0HD1FKnW+xIFLwJAW
+p3A4IMUjl0m8c1tFb6ZXETvnrlhAQixRf54JlIYRQwvDcMSDTe1RtHwuNDht7TM8f
+aE07ZwE34Ybb4ZyrjQBwJAfW7MRDlKmZ3xdP62ZypSGKjQVUOfqD//jmyPH4fZ87q
+nDlEdnhujAhRXqJ6KtxsY0sZ5EAzPXl8f+Tze1g43cQJAQa/Ycwm+IuaSkYeOM7oJ
+by7PRPZ7CqQknxBLfGgCnfmhEIcfNaa1+o+Fbf8GZNlQNzkxmq3zD9TDEn5zCdRa/
+w==
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/extensions/update_from_webstore3/manifest.json b/chrome/test/data/extensions/update_from_webstore3/manifest.json
new file mode 100644
index 0000000..dfba7e5
--- /dev/null
+++ b/chrome/test/data/extensions/update_from_webstore3/manifest.json
@@ -0,0 +1,6 @@
+{
+ "name": "Third test extension that updates from webstore",
+ "version": "1",
+ "manifest_version": 2,
+ "update_url": "https://clients2.google.com/service/update2/crx"
+}