[PwdCheckAndroid] Add compromised site count to dialog

This CL ensures that users of the local password check see how many
more sites are affected if the current, saved password is compromised.


IDS_CREDENTIAL_LEAK_SAVED_PASSWORDS_MESSAGE.png:
https://storage.cloud.google.com/chromium-translation-screenshots/dc7b365cedcbeffe5a3848a7db5092d38eb1738f

See screenshot in the linked bug.

Bug: 1102391
Change-Id: I9ae827da9b76ca3beac70fd69b0a62c75435f4c8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2297561
Commit-Queue: Friedrich [CET] <fhorschig@chromium.org>
Reviewed-by: Ioana Pandele <ioanap@chromium.org>
Reviewed-by: Jan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#788632}
diff --git a/chrome/browser/password_manager/android/credential_leak_controller_android.cc b/chrome/browser/password_manager/android/credential_leak_controller_android.cc
index 8831995b..0c87da7 100644
--- a/chrome/browser/password_manager/android/credential_leak_controller_android.cc
+++ b/chrome/browser/password_manager/android/credential_leak_controller_android.cc
@@ -20,10 +20,12 @@
 
 CredentialLeakControllerAndroid::CredentialLeakControllerAndroid(
     password_manager::CredentialLeakType leak_type,
+    password_manager::CompromisedSitesCount saved_sites,
     const GURL& origin,
     const base::string16& username,
     ui::WindowAndroid* window_android)
     : leak_type_(leak_type),
+      saved_sites_(saved_sites),
       origin_(origin),
       username_(username),
       window_android_(window_android) {}
@@ -94,6 +96,11 @@
 }
 
 base::string16 CredentialLeakControllerAndroid::GetDescription() const {
+  if (base::FeatureList::IsEnabled(
+          password_manager::features::kPasswordCheck)) {
+    return password_manager::GetDescriptionWithCount(leak_type_, origin_,
+                                                     saved_sites_);
+  }
   return password_manager::GetDescription(leak_type_, origin_);
 }
 
diff --git a/chrome/browser/password_manager/android/credential_leak_controller_android.h b/chrome/browser/password_manager/android/credential_leak_controller_android.h
index 8f6a7d8..f62dacd 100644
--- a/chrome/browser/password_manager/android/credential_leak_controller_android.h
+++ b/chrome/browser/password_manager/android/credential_leak_controller_android.h
@@ -23,6 +23,7 @@
  public:
   CredentialLeakControllerAndroid(
       password_manager::CredentialLeakType leak_type,
+      password_manager::CompromisedSitesCount saved_sites,
       const GURL& origin,
       const base::string16& username,
       ui::WindowAndroid* window_android);
@@ -70,6 +71,7 @@
  private:
   // Used to customize the UI.
   const password_manager::CredentialLeakType leak_type_;
+  const password_manager::CompromisedSitesCount saved_sites_;
 
   const GURL origin_;
 
diff --git a/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc b/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc
index 32795d4b5..0d66ef5 100644
--- a/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc
+++ b/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "components/password_manager/core/browser/compromised_credentials_table.h"
 #include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -27,8 +28,8 @@
                                                 IsSyncing is_syncing) {
   return new CredentialLeakControllerAndroid(
       CreateLeakType(is_saved, is_reused, is_syncing),
-      GURL("https://example.com"), base::ASCIIToUTF16("test_username"),
-      nullptr);
+      password_manager::CompromisedSitesCount(0), GURL("https://example.com"),
+      base::ASCIIToUTF16("test_username"), nullptr);
 }
 
 }  // namespace
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index 80c2030..e861f5a 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -541,6 +541,7 @@
 
 void ChromePasswordManagerClient::NotifyUserCredentialsWereLeaked(
     password_manager::CredentialLeakType leak_type,
+    password_manager::CompromisedSitesCount saved_sites,
     const GURL& origin,
     const base::string16& username) {
 #if defined(OS_ANDROID)
@@ -550,7 +551,8 @@
   }
   HideSavePasswordInfobar(web_contents());
   (new CredentialLeakControllerAndroid(
-       leak_type, origin, username, web_contents()->GetTopLevelNativeWindow()))
+       leak_type, saved_sites, origin, username,
+       web_contents()->GetTopLevelNativeWindow()))
       ->ShowDialog();
 #else   // !defined(OS_ANDROID)
   PasswordsClientUIDelegate* manage_passwords_ui_controller =
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.h b/chrome/browser/password_manager/chrome_password_manager_client.h
index cd09cf00..8a4619b5 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.h
+++ b/chrome/browser/password_manager/chrome_password_manager_client.h
@@ -141,6 +141,7 @@
       const password_manager::PasswordFormManagerForUI* form_manager) override;
   void NotifyUserCredentialsWereLeaked(
       password_manager::CredentialLeakType leak_type,
+      password_manager::CompromisedSitesCount saved_sites,
       const GURL& origin,
       const base::string16& username) override;
   void TriggerReauthForPrimaryAccount(
diff --git a/components/password_manager/core/browser/leak_detection_delegate.cc b/components/password_manager/core/browser/leak_detection_delegate.cc
index ef1e159..96a622c 100644
--- a/components/password_manager/core/browser/leak_detection_delegate.cc
+++ b/components/password_manager/core/browser/leak_detection_delegate.cc
@@ -98,7 +98,8 @@
     IsSaved is_saved,
     IsReused is_reused,
     GURL url,
-    base::string16 username) {
+    base::string16 username,
+    CompromisedSitesCount saved_sites) {
   bool force_dialog_for_testing = base::GetFieldTrialParamByFeatureAsBool(
       password_manager::features::kPasswordChange,
       password_manager::features::
@@ -111,7 +112,8 @@
         CreateLeakType(is_saved, IsReused(false),
                        IsSyncing(client_->GetPasswordSyncState() ==
                                  SYNCING_NORMAL_ENCRYPTION));
-    client_->NotifyUserCredentialsWereLeaked(leak_type, url, username);
+    client_->NotifyUserCredentialsWereLeaked(leak_type, saved_sites, url,
+                                             username);
     return;
   }
 
@@ -128,7 +130,8 @@
                             IsPasswordUsedOnOtherSites(leak_type));
   base::UmaHistogramBoolean("PasswordManager.LeakDetection.IsSyncing",
                             IsSyncingPasswordsNormally(leak_type));
-  client_->NotifyUserCredentialsWereLeaked(leak_type, url, username);
+  client_->NotifyUserCredentialsWereLeaked(leak_type, saved_sites, url,
+                                           username);
 }
 
 void LeakDetectionDelegate::OnError(LeakDetectionError error) {
diff --git a/components/password_manager/core/browser/leak_detection_delegate.h b/components/password_manager/core/browser/leak_detection_delegate.h
index d74bce3..c024fee 100644
--- a/components/password_manager/core/browser/leak_detection_delegate.h
+++ b/components/password_manager/core/browser/leak_detection_delegate.h
@@ -59,7 +59,8 @@
   void OnShowLeakDetectionNotification(IsSaved is_saved,
                                        IsReused is_reused,
                                        GURL url,
-                                       base::string16 username);
+                                       base::string16 username,
+                                       CompromisedSitesCount saved_sites);
 
   void OnError(LeakDetectionError error) override;
 
diff --git a/components/password_manager/core/browser/leak_detection_delegate_helper.cc b/components/password_manager/core/browser/leak_detection_delegate_helper.cc
index e9e7dc1..e5b412e 100644
--- a/components/password_manager/core/browser/leak_detection_delegate_helper.cc
+++ b/components/password_manager/core/browser/leak_detection_delegate_helper.cc
@@ -4,6 +4,7 @@
 
 #include "components/password_manager/core/browser/leak_detection_delegate_helper.h"
 
+#include "base/containers/flat_set.h"
 #include "base/feature_list.h"
 #include "components/password_manager/core/browser/leak_detection/encryption_utils.h"
 #include "components/password_manager/core/browser/password_store.h"
@@ -32,11 +33,13 @@
 
 void LeakDetectionDelegateHelper::OnGetPasswordStoreResults(
     std::vector<std::unique_ptr<autofill::PasswordForm>> results) {
+  base::flat_set<std::string> distinct_origins;
   if (base::FeatureList::IsEnabled(features::kPasswordCheck)) {
     base::string16 canonicalized_username = CanonicalizeUsername(username_);
     for (const auto& form : results) {
       if (CanonicalizeUsername(form->username_value) ==
           canonicalized_username) {
+        distinct_origins.insert(form->signon_realm);
         store_->AddCompromisedCredentials(
             {form->signon_realm, form->username_value, base::Time::Now(),
              CompromiseType::kLeaked});
@@ -49,9 +52,12 @@
         return form->url == url_ && form->username_value == username_;
       }));
 
+  // Number of compromised origins that the user saved.
+  CompromisedSitesCount saved_sites(distinct_origins.size());
+
   IsReused is_reused(results.size() > (is_saved ? 1 : 0));
   std::move(callback_).Run(is_saved, is_reused, std::move(url_),
-                           std::move(username_));
+                           std::move(username_), saved_sites);
 }
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/leak_detection_delegate_helper.h b/components/password_manager/core/browser/leak_detection_delegate_helper.h
index c8ddc35..e766e6d0 100644
--- a/components/password_manager/core/browser/leak_detection_delegate_helper.h
+++ b/components/password_manager/core/browser/leak_detection_delegate_helper.h
@@ -23,8 +23,8 @@
 class LeakDetectionDelegateHelper : public PasswordStoreConsumer {
  public:
   // Type alias for |callback_|.
-  using LeakTypeReply =
-      base::OnceCallback<void(IsSaved, IsReused, GURL, base::string16)>;
+  using LeakTypeReply = base::OnceCallback<
+      void(IsSaved, IsReused, GURL, base::string16, CompromisedSitesCount)>;
 
   LeakDetectionDelegateHelper(scoped_refptr<PasswordStore> store,
                               LeakTypeReply callback);
diff --git a/components/password_manager/core/browser/leak_detection_delegate_helper_unittest.cc b/components/password_manager/core/browser/leak_detection_delegate_helper_unittest.cc
index bdef280..6d53c66f 100644
--- a/components/password_manager/core/browser/leak_detection_delegate_helper_unittest.cc
+++ b/components/password_manager/core/browser/leak_detection_delegate_helper_unittest.cc
@@ -60,6 +60,8 @@
  protected:
   void SetUp() override {
     store_ = new testing::StrictMock<MockPasswordStore>;
+    feature_list_.InitAndEnableFeature(
+        password_manager::features::kPasswordCheck);
     CHECK(store_->Init(nullptr));
 
     delegate_helper_ =
@@ -91,10 +93,12 @@
   }
 
   // Set the expectation for the |CredentialLeakType| in the callback_.
-  void SetOnShowLeakDetectionNotificationExpectation(IsSaved is_saved,
-                                                     IsReused is_reused) {
+  void SetOnShowLeakDetectionNotificationExpectation(
+      IsSaved is_saved,
+      IsReused is_reused,
+      CompromisedSitesCount other_sites) {
     EXPECT_CALL(callback_, Run(is_saved, is_reused, GURL(kLeakedOrigin),
-                               ASCIIToUTF16(kLeakedUsername)))
+                               ASCIIToUTF16(kLeakedUsername), other_sites))
         .Times(1);
   }
 
@@ -103,6 +107,7 @@
   MockCallback<LeakDetectionDelegateHelper::LeakTypeReply> callback_;
   scoped_refptr<MockPasswordStore> store_;
   std::unique_ptr<LeakDetectionDelegateHelper> delegate_helper_;
+  base::test::ScopedFeatureList feature_list_;
 };
 
 // Credentials are neither saved nor is the password reused.
@@ -110,8 +115,8 @@
   std::vector<PasswordForm> password_forms;
 
   SetGetLoginByPasswordConsumerInvocation(std::move(password_forms));
-  SetOnShowLeakDetectionNotificationExpectation(IsSaved(false),
-                                                IsReused(false));
+  SetOnShowLeakDetectionNotificationExpectation(IsSaved(false), IsReused(false),
+                                                CompromisedSitesCount(0));
   InitiateGetCredentialLeakType();
 }
 
@@ -121,7 +126,8 @@
       CreateForm(kLeakedOrigin, kLeakedUsername)};
 
   SetGetLoginByPasswordConsumerInvocation(std::move(password_forms));
-  SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(false));
+  SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(false),
+                                                CompromisedSitesCount(1));
   EXPECT_CALL(*store_, AddCompromisedCredentialsImpl)
       .Times(base::FeatureList::IsEnabled(features::kPasswordCheck));
   InitiateGetCredentialLeakType();
@@ -135,7 +141,8 @@
       CreateForm(kOtherOrigin, kLeakedUsername)};
 
   SetGetLoginByPasswordConsumerInvocation(std::move(password_forms));
-  SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(true));
+  SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(true),
+                                                CompromisedSitesCount(2));
   EXPECT_CALL(*store_, AddCompromisedCredentialsImpl)
       .Times(2 * base::FeatureList::IsEnabled(features::kPasswordCheck));
   InitiateGetCredentialLeakType();
@@ -150,7 +157,8 @@
       CreateForm(kLeakedOrigin, kOtherUsername)};
 
   SetGetLoginByPasswordConsumerInvocation(std::move(password_forms));
-  SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(true));
+  SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(true),
+                                                CompromisedSitesCount(1));
   EXPECT_CALL(*store_, AddCompromisedCredentialsImpl)
       .Times(base::FeatureList::IsEnabled(features::kPasswordCheck));
   InitiateGetCredentialLeakType();
@@ -162,7 +170,8 @@
       CreateForm(kLeakedOrigin, kOtherUsername)};
 
   SetGetLoginByPasswordConsumerInvocation(std::move(password_forms));
-  SetOnShowLeakDetectionNotificationExpectation(IsSaved(false), IsReused(true));
+  SetOnShowLeakDetectionNotificationExpectation(IsSaved(false), IsReused(true),
+                                                CompromisedSitesCount(0));
   InitiateGetCredentialLeakType();
 }
 
@@ -172,7 +181,8 @@
       CreateForm(kOtherOrigin, kLeakedUsername)};
 
   SetGetLoginByPasswordConsumerInvocation(std::move(password_forms));
-  SetOnShowLeakDetectionNotificationExpectation(IsSaved(false), IsReused(true));
+  SetOnShowLeakDetectionNotificationExpectation(IsSaved(false), IsReused(true),
+                                                CompromisedSitesCount(1));
   EXPECT_CALL(*store_, AddCompromisedCredentialsImpl)
       .Times(base::FeatureList::IsEnabled(features::kPasswordCheck));
   InitiateGetCredentialLeakType();
@@ -185,7 +195,8 @@
       CreateForm(kOtherOrigin, kOtherUsername)};
 
   SetGetLoginByPasswordConsumerInvocation(std::move(password_forms));
-  SetOnShowLeakDetectionNotificationExpectation(IsSaved(false), IsReused(true));
+  SetOnShowLeakDetectionNotificationExpectation(IsSaved(false), IsReused(true),
+                                                CompromisedSitesCount(0));
   InitiateGetCredentialLeakType();
 }
 
@@ -198,7 +209,8 @@
       {CreateForm(kLeakedOrigin, kLeakedUsername, kLeakedPassword),
        CreateForm(kOtherOrigin, kLeakedUsername, kLeakedPassword),
        CreateForm(kLeakedOrigin, kOtherUsername, kLeakedPassword)});
-  SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(true));
+  SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(true),
+                                                CompromisedSitesCount(2));
   EXPECT_CALL(*store_, AddCompromisedCredentialsImpl(CompromisedCredentials{
                            GetSignonRealm(GURL(kLeakedOrigin)),
                            ASCIIToUTF16(kLeakedUsername), base::Time::Now(),
@@ -217,7 +229,8 @@
 
   SetGetLoginByPasswordConsumerInvocation({CreateForm(
       kOtherOrigin, kLeakedUsernameNonCanonicalized, kLeakedPassword)});
-  SetOnShowLeakDetectionNotificationExpectation(IsSaved(false), IsReused(true));
+  SetOnShowLeakDetectionNotificationExpectation(IsSaved(false), IsReused(true),
+                                                CompromisedSitesCount(1));
 
   EXPECT_CALL(*store_, AddCompromisedCredentialsImpl(CompromisedCredentials{
                            GetSignonRealm(GURL(kOtherOrigin)),
diff --git a/components/password_manager/core/browser/leak_detection_delegate_unittest.cc b/components/password_manager/core/browser/leak_detection_delegate_unittest.cc
index 40c5066..6d0bae1e 100644
--- a/components/password_manager/core/browser/leak_detection_delegate_unittest.cc
+++ b/components/password_manager/core/browser/leak_detection_delegate_unittest.cc
@@ -13,6 +13,7 @@
 #include "components/password_manager/core/browser/leak_detection/leak_detection_check.h"
 #include "components/password_manager/core/browser/leak_detection/mock_leak_detection_check_factory.h"
 #include "components/password_manager/core/browser/leak_detection_delegate.h"
+#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 #include "components/password_manager/core/browser/mock_password_store.h"
 #include "components/password_manager/core/browser/password_store_consumer.h"
 #include "components/password_manager/core/browser/stub_password_manager_client.h"
@@ -54,8 +55,9 @@
 
   MOCK_CONST_METHOD0(IsIncognito, bool());
   MOCK_CONST_METHOD0(GetPrefs, PrefService*());
-  MOCK_METHOD3(NotifyUserCredentialsWereLeaked,
+  MOCK_METHOD4(NotifyUserCredentialsWereLeaked,
                void(password_manager::CredentialLeakType,
+                    password_manager::CompromisedSitesCount,
                     const GURL&,
                     const base::string16& username));
   MOCK_CONST_METHOD0(GetProfilePasswordStore, PasswordStore*());
@@ -290,7 +292,7 @@
               NotifyUserCredentialsWereLeaked(
                   password_manager::CreateLeakType(
                       IsSaved(false), IsReused(false), IsSyncing(false)),
-                  form.url, form.username_value));
+                  CompromisedSitesCount(0), form.url, form.username_value));
 
   delegate_interface->OnLeakDetectionDone(
       /*is_leaked=*/false, form.url, form.username_value, form.password_value);
@@ -314,7 +316,7 @@
               NotifyUserCredentialsWereLeaked(
                   password_manager::CreateLeakType(
                       IsSaved(false), IsReused(false), IsSyncing(false)),
-                  form.url, form.username_value));
+                  CompromisedSitesCount(0), form.url, form.username_value));
   delegate_interface->OnLeakDetectionDone(
       /*is_leaked=*/true, form.url, form.username_value, form.password_value);
   WaitForPasswordStore();
@@ -339,8 +341,9 @@
           Return(ByMove(std::make_unique<NiceMock<MockLeakDetectionCheck>>())));
   delegate().StartLeakCheck(form);
 
-  EXPECT_CALL(client(), NotifyUserCredentialsWereLeaked(_, form.url,
-                                                        form.username_value));
+  EXPECT_CALL(client(),
+              NotifyUserCredentialsWereLeaked(_, CompromisedSitesCount(1),
+                                              form.url, form.username_value));
   delegate_interface->OnLeakDetectionDone(
       /*is_leaked=*/true, form.url, form.username_value, form.password_value);
 
diff --git a/components/password_manager/core/browser/leak_detection_dialog_utils.cc b/components/password_manager/core/browser/leak_detection_dialog_utils.cc
index f3b9ac7..1c4358b7 100644
--- a/components/password_manager/core/browser/leak_detection_dialog_utils.cc
+++ b/components/password_manager/core/browser/leak_detection_dialog_utils.cc
@@ -5,6 +5,7 @@
 #include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 
 #include "base/feature_list.h"
+#include "base/i18n/message_formatter.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
@@ -88,6 +89,21 @@
   }
 }
 
+base::string16 GetDescriptionWithCount(CredentialLeakType leak_type,
+                                       const GURL& origin,
+                                       CompromisedSitesCount saved_sites) {
+  IsSaved is_saved(IsPasswordSaved(leak_type));
+  if (ShouldCheckPasswords(leak_type) || is_saved) {
+    DCHECK_GE(saved_sites.value(), 1);
+    // saved_sites must be reduced by 1 if the saved sites include the origin as
+    // the origin is mentioned explicitly in the message.
+    return base::i18n::MessageFormatter::FormatWithNumberedArgs(
+        l10n_util::GetStringUTF16(IDS_CREDENTIAL_LEAK_SAVED_PASSWORDS_MESSAGE),
+        GetFormattedUrl(origin), saved_sites.value() - (is_saved ? 1 : 0));
+  }
+  return GetDescription(leak_type, origin);
+}
+
 base::string16 GetTitle(CredentialLeakType leak_type) {
   return l10n_util::GetStringUTF16(ShouldCheckPasswords(leak_type)
                                        ? IDS_CREDENTIAL_LEAK_TITLE_CHECK
diff --git a/components/password_manager/core/browser/leak_detection_dialog_utils.h b/components/password_manager/core/browser/leak_detection_dialog_utils.h
index bb90727..fc271ab 100644
--- a/components/password_manager/core/browser/leak_detection_dialog_utils.h
+++ b/components/password_manager/core/browser/leak_detection_dialog_utils.h
@@ -36,10 +36,13 @@
 // Contains combination of CredentialLeakFlags values.
 using CredentialLeakType = std::underlying_type_t<CredentialLeakFlags>;
 
+// Contains a number of compromised sites.
+using CompromisedSitesCount =
+    util::StrongAlias<class CompromisedSitesCountTag, int>;
+
 using IsSaved = util::StrongAlias<class IsSavedTag, bool>;
 using IsReused = util::StrongAlias<class IsReusedTag, bool>;
 using IsSyncing = util::StrongAlias<class IsSyncingTag, bool>;
-
 // Creates CredentialLeakType from strong booleans.
 CredentialLeakType CreateLeakType(IsSaved is_saved,
                                   IsReused is_reused,
@@ -65,6 +68,12 @@
 base::string16 GetDescription(password_manager::CredentialLeakType leak_type,
                               const GURL& origin);
 
+// Returns the leak dialog message based on leak type and count of leaked sites.
+base::string16 GetDescriptionWithCount(
+    password_manager::CredentialLeakType leak_type,
+    const GURL& origin,
+    CompromisedSitesCount saved_sites);
+
 // Returns the leak dialog title based on leak type.
 base::string16 GetTitle(password_manager::CredentialLeakType leak_type);
 
diff --git a/components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc b/components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc
index 861ec2ae..bd3754b 100644
--- a/components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc
+++ b/components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc
@@ -4,9 +4,11 @@
 
 #include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 
+#include "base/i18n/message_formatter.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
+#include "components/password_manager/core/browser/compromised_credentials_table.h"
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/url_formatter/elide_url.h"
@@ -15,6 +17,7 @@
 #include "url/gurl.h"
 #include "url/origin.h"
 
+using password_manager::CompromisedSitesCount;
 using password_manager::CreateLeakType;
 using password_manager::CredentialLeakFlags;
 using password_manager::CredentialLeakType;
@@ -195,6 +198,76 @@
             GetTitle(GetParam().leak_type));
 }
 
+TEST_F(BulkCheckCredentialLeakDialogUtilsTest,
+       GetChangeDescriptionWitCountForSingleLeak) {
+  const CredentialLeakType leak_type =
+      CreateLeakType(IsSaved(true), IsReused(false), IsSyncing(true));
+  const GURL origin("https://example.com");
+  base::string16 expected_message =
+      base::i18n::MessageFormatter::FormatWithNumberedArgs(
+          l10n_util::GetStringUTF16(
+              IDS_CREDENTIAL_LEAK_SAVED_PASSWORDS_MESSAGE),
+          url_formatter::FormatOriginForSecurityDisplay(
+              url::Origin::Create(origin),
+              url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS),
+          0);
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_CREDENTIAL_LEAK_TITLE_CHANGE),
+            GetTitle(leak_type));
+  EXPECT_FALSE(ShouldCheckPasswords(leak_type));
+  EXPECT_FALSE(ShouldShowCancelButton(leak_type));
+  EXPECT_EQ(expected_message, GetDescriptionWithCount(
+                                  leak_type, origin, CompromisedSitesCount(1)));
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_OK), GetAcceptButtonLabel(leak_type));
+}
+
+TEST_F(BulkCheckCredentialLeakDialogUtilsTest,
+       GetCheckDescriptionWitCountForMultipleLeaks) {
+  const CredentialLeakType leak_type =
+      CreateLeakType(IsSaved(true), IsReused(true), IsSyncing(true));
+  const GURL origin("https://example.com");
+  base::string16 expected_message =
+      base::i18n::MessageFormatter::FormatWithNumberedArgs(
+          l10n_util::GetStringUTF16(
+              IDS_CREDENTIAL_LEAK_SAVED_PASSWORDS_MESSAGE),
+          url_formatter::FormatOriginForSecurityDisplay(
+              url::Origin::Create(origin),
+              url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS),
+          2);
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_CREDENTIAL_LEAK_TITLE_CHECK),
+            GetTitle(leak_type));
+  EXPECT_TRUE(ShouldCheckPasswords(leak_type));
+  EXPECT_TRUE(ShouldShowCancelButton(leak_type));
+  EXPECT_EQ(expected_message, GetDescriptionWithCount(
+                                  leak_type, origin, CompromisedSitesCount(3)));
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_LEAK_CHECK_CREDENTIALS),
+            GetAcceptButtonLabel(leak_type));
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_CLOSE), GetCancelButtonLabel());
+}
+
+TEST_F(BulkCheckCredentialLeakDialogUtilsTest,
+       GetCheckDescriptionWitCountForUnsavedOriginWithMultipleLeaks) {
+  const CredentialLeakType leak_type =
+      CreateLeakType(IsSaved(false), IsReused(true), IsSyncing(true));
+  const GURL origin("https://example.com");
+  base::string16 expected_message =
+      base::i18n::MessageFormatter::FormatWithNumberedArgs(
+          l10n_util::GetStringUTF16(
+              IDS_CREDENTIAL_LEAK_SAVED_PASSWORDS_MESSAGE),
+          url_formatter::FormatOriginForSecurityDisplay(
+              url::Origin::Create(origin),
+              url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS),
+          3);
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_CREDENTIAL_LEAK_TITLE_CHECK),
+            GetTitle(leak_type));
+  EXPECT_TRUE(ShouldCheckPasswords(leak_type));
+  EXPECT_TRUE(ShouldShowCancelButton(leak_type));
+  EXPECT_EQ(expected_message, GetDescriptionWithCount(
+                                  leak_type, origin, CompromisedSitesCount(3)));
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_LEAK_CHECK_CREDENTIALS),
+            GetAcceptButtonLabel(leak_type));
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_CLOSE), GetCancelButtonLabel());
+}
+
 INSTANTIATE_TEST_SUITE_P(InstantiationName,
                          BulkCheckCredentialLeakDialogUtilsTest,
                          testing::ValuesIn(kBulkCheckTestCases));
diff --git a/components/password_manager/core/browser/password_manager_client.cc b/components/password_manager/core/browser/password_manager_client.cc
index ac7fd054..0e81070 100644
--- a/components/password_manager/core/browser/password_manager_client.cc
+++ b/components/password_manager/core/browser/password_manager_client.cc
@@ -54,6 +54,7 @@
 
 void PasswordManagerClient::NotifyUserCredentialsWereLeaked(
     password_manager::CredentialLeakType leak_type,
+    password_manager::CompromisedSitesCount saved_sites,
     const GURL& origin,
     const base::string16& username) {}
 
diff --git a/components/password_manager/core/browser/password_manager_client.h b/components/password_manager/core/browser/password_manager_client.h
index 7892425a..0bc497cf 100644
--- a/components/password_manager/core/browser/password_manager_client.h
+++ b/components/password_manager/core/browser/password_manager_client.h
@@ -243,9 +243,11 @@
                                 const PasswordFormManagerForUI* form_manager);
 
   // Informs the embedder that user credentials were leaked.
-  virtual void NotifyUserCredentialsWereLeaked(CredentialLeakType leak_type,
-                                               const GURL& origin,
-                                               const base::string16& username);
+  virtual void NotifyUserCredentialsWereLeaked(
+      CredentialLeakType leak_type,
+      CompromisedSitesCount saved_sites,
+      const GURL& origin,
+      const base::string16& username);
 
   // Requests a reauth for the primary account with |access_point| representing
   // where the reauth was triggered.
diff --git a/components/password_manager_strings.grdp b/components/password_manager_strings.grdp
index d66f56e..fe14620 100644
--- a/components/password_manager_strings.grdp
+++ b/components/password_manager_strings.grdp
@@ -25,6 +25,12 @@
   <message name="IDS_CREDENTIAL_LEAK_CHANGE_AND_CHECK_PASSWORDS_MESSAGE" desc="The text that is used in credential leak detection dialog when the leaked credentials were not saved but used on multiple sites. The leaked credentials may have been leaked by the current website, some other third-party website or even a third-party app used by the user. It could also be coincidental reuse of a trivial password used by some other users in the world and exposed in a public leak.">
     A data breach on a site or app exposed your password. Chrome recommends checking your saved passwords and changing your password on <ph name="ORIGIN">$1<ex>example.com</ex></ph> now.
   </message>
+  <message name="IDS_CREDENTIAL_LEAK_SAVED_PASSWORDS_MESSAGE" desc="The text that is used in credential leak detection dialog when the leaked credentials were saved for at least one and X other sites. The leaked credentials may have been leaked by the current website, some other third-party website or even a third-party app used by the user. It could also be coincidental reuse of a trivial password used by some other users in the world and exposed in a public leak.">
+  {1, plural,
+     =0 {A data breach on a site or app exposed your saved password for <ph name="ORIGIN">{0}<ex>example.com</ex></ph>. Chrome recommends changing your password on <ph name="ORIGIN">{0}<ex>example.com</ex></ph> now.}
+     =1 {A data breach on a site or app exposed your saved password for <ph name="ORIGIN">{0}<ex>example.com</ex></ph> and one other site. Chrome recommends checking your saved passwords now.}
+     other {A data breach on a site or app exposed your saved password for <ph name="ORIGIN">{0}<ex>example.com</ex></ph> and <ph name="SITES_COUNT">#<ex>2</ex></ph> other sites. Chrome recommends checking your saved passwords now.}}
+  </message>
   <if expr="is_ios">
     <message name="IDS_IOS_SUGGEST_PASSWORD" desc="Button title in the keyboard accessory bar to show a dialog with a generated password. [Length: 20em] [iOS only]">
       Suggest Password...
diff --git a/components/password_manager_strings_grdp/IDS_CREDENTIAL_LEAK_SAVED_PASSWORDS_MESSAGE.png.sha1 b/components/password_manager_strings_grdp/IDS_CREDENTIAL_LEAK_SAVED_PASSWORDS_MESSAGE.png.sha1
new file mode 100644
index 0000000..b7a7014
--- /dev/null
+++ b/components/password_manager_strings_grdp/IDS_CREDENTIAL_LEAK_SAVED_PASSWORDS_MESSAGE.png.sha1
@@ -0,0 +1 @@
+dc7b365cedcbeffe5a3848a7db5092d38eb1738f
\ No newline at end of file
diff --git a/ios/chrome/browser/passwords/ios_chrome_password_manager_client.h b/ios/chrome/browser/passwords/ios_chrome_password_manager_client.h
index 4243713..2f8d43f 100644
--- a/ios/chrome/browser/passwords/ios_chrome_password_manager_client.h
+++ b/ios/chrome/browser/passwords/ios_chrome_password_manager_client.h
@@ -95,6 +95,7 @@
   void NotifyStorePasswordCalled() override;
   void NotifyUserCredentialsWereLeaked(
       password_manager::CredentialLeakType leak_type,
+      password_manager::CompromisedSitesCount saved_sites,
       const GURL& origin,
       const base::string16& username) override;
   bool IsSavingAndFillingEnabled(const GURL& url) const override;
diff --git a/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm b/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm
index ec4f763..a140157 100644
--- a/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm
+++ b/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm
@@ -205,6 +205,7 @@
 
 void IOSChromePasswordManagerClient::NotifyUserCredentialsWereLeaked(
     password_manager::CredentialLeakType leak_type,
+    password_manager::CompromisedSitesCount saved_sites,
     const GURL& origin,
     const base::string16& username) {
   [bridge_ showPasswordBreachForLeakType:leak_type URL:origin];