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"
+}