|  | // Copyright 2015 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "chrome/browser/ui/passwords/ui_utils.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <string> | 
|  |  | 
|  | #include "base/memory/raw_ref.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "chrome/browser/browser_process.h" | 
|  | #include "chrome/grit/generated_resources.h" | 
|  | #include "components/strings/grit/components_strings.h" | 
|  | #include "testing/gmock/include/gmock/gmock.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "ui/base/l10n/l10n_util.h" | 
|  | #include "ui/base/resource/resource_bundle.h" | 
|  | #include "ui/gfx/range/range.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // ScopedResourceOverride allows overriding localised strings in the shared | 
|  | // instance of the resource bundle, while restoring the bundle state on | 
|  | // destruction. | 
|  | class ScopedResourceOverride { | 
|  | public: | 
|  | ScopedResourceOverride() | 
|  | : bundle_(ui::ResourceBundle::GetSharedInstance()), | 
|  | app_locale_(g_browser_process->GetApplicationLocale()) {} | 
|  |  | 
|  | ScopedResourceOverride(const ScopedResourceOverride&) = delete; | 
|  | ScopedResourceOverride& operator=(const ScopedResourceOverride&) = delete; | 
|  |  | 
|  | ~ScopedResourceOverride() { | 
|  | // Reloading the resources will discard all overrides. | 
|  | bundle_->ReloadLocaleResources(app_locale_); | 
|  | } | 
|  |  | 
|  | void OverrideLocaleStringResource(int string_id, const std::u16string& str) { | 
|  | bundle_->OverrideLocaleStringResource(string_id, str); | 
|  | } | 
|  |  | 
|  | private: | 
|  | const raw_ref<ui::ResourceBundle> bundle_;  // The shared bundle. | 
|  | const std::string app_locale_; | 
|  | }; | 
|  |  | 
|  | const struct { | 
|  | const char* const user_visible_url; | 
|  | const char* const form_origin_url; | 
|  | PasswordTitleType bubble_type; | 
|  | const char* const expected_domain_placeholder;  // domain name | 
|  | } kDomainsTestCases[] = { | 
|  | // Same domains. | 
|  | {"http://example.com/landing", "http://example.com/login#form?value=3", | 
|  | PasswordTitleType::SAVE_PASSWORD, ""}, | 
|  | {"http://example.com/landing", "http://example.com/login#form?value=3", | 
|  | PasswordTitleType::SAVE_PASSWORD, ""}, | 
|  |  | 
|  | // Different subdomains. | 
|  | {"https://a.example.com/landing", | 
|  | "https://b.example.com/login#form?value=3", | 
|  | PasswordTitleType::SAVE_PASSWORD, ""}, | 
|  | {"https://a.example.com/landing", | 
|  | "https://b.example.com/login#form?value=3", | 
|  | PasswordTitleType::SAVE_PASSWORD, ""}, | 
|  |  | 
|  | // Different domains. | 
|  | {"https://another.org", "https://example.com:/login#form?value=3", | 
|  | PasswordTitleType::SAVE_PASSWORD, "example.com"}, | 
|  | {"https://another.org", "https://example.com/login#form?value=3", | 
|  | PasswordTitleType::SAVE_PASSWORD, "example.com"}, | 
|  |  | 
|  | // Different domains and password form origin url with | 
|  | // default port for the scheme. | 
|  | {"https://another.org", "https://example.com:443/login#form?value=3", | 
|  | PasswordTitleType::SAVE_PASSWORD, "example.com"}, | 
|  | {"https://another.org", "http://example.com:80/login#form?value=3", | 
|  | PasswordTitleType::SAVE_PASSWORD, "example.com"}, | 
|  |  | 
|  | // Different domains and password form origin url with | 
|  | // non-default port for the scheme. | 
|  | {"https://another.org", "https://example.com:8001/login#form?value=3", | 
|  | PasswordTitleType::SAVE_PASSWORD, "example.com:8001"}, | 
|  | {"https://another.org", "https://example.com:8001/login#form?value=3", | 
|  | PasswordTitleType::SAVE_PASSWORD, "example.com:8001"}, | 
|  |  | 
|  | // Update bubble, same domains. | 
|  | {"http://example.com/landing", "http://example.com/login#form?value=3", | 
|  | PasswordTitleType::UPDATE_PASSWORD, ""}, | 
|  | {"http://example.com/landing", "http://example.com/login#form?value=3", | 
|  | PasswordTitleType::UPDATE_PASSWORD, ""}, | 
|  |  | 
|  | // Update bubble, different domains. | 
|  | {"https://another.org", "http://example.com/login#form?value=3", | 
|  | PasswordTitleType::UPDATE_PASSWORD, "example.com"}, | 
|  | {"https://another.org", "http://example.com/login#form?value=3", | 
|  | PasswordTitleType::UPDATE_PASSWORD, "example.com"}, | 
|  |  | 
|  | // Same domains, federated credential. | 
|  | {"http://example.com/landing", "http://example.com/login#form?value=3", | 
|  | PasswordTitleType::SAVE_ACCOUNT, ""}, | 
|  | {"http://example.com/landing", "http://example.com/login#form?value=3", | 
|  | PasswordTitleType::SAVE_ACCOUNT, ""}, | 
|  |  | 
|  | // Different subdomains, federated credential. | 
|  | {"https://a.example.com/landing", | 
|  | "https://b.example.com/login#form?value=3", | 
|  | PasswordTitleType::SAVE_ACCOUNT, ""}, | 
|  | {"https://a.example.com/landing", | 
|  | "https://b.example.com/login#form?value=3", | 
|  | PasswordTitleType::SAVE_ACCOUNT, ""}}; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // Test for GetSavePasswordDialogTitleText(). | 
|  | TEST(ManagePasswordsViewUtilTest, GetSavePasswordDialogTitleText) { | 
|  | for (size_t i = 0; i < std::size(kDomainsTestCases); ++i) { | 
|  | SCOPED_TRACE(testing::Message() << "user_visible_url = " | 
|  | << kDomainsTestCases[i].user_visible_url | 
|  | << ", form_origin_url = " | 
|  | << kDomainsTestCases[i].form_origin_url); | 
|  |  | 
|  | std::u16string title = GetSavePasswordDialogTitleText( | 
|  | GURL(kDomainsTestCases[i].user_visible_url), | 
|  | url::Origin::Create(GURL(kDomainsTestCases[i].form_origin_url)), | 
|  | kDomainsTestCases[i].bubble_type); | 
|  |  | 
|  | // Verify against expectations. | 
|  | std::u16string domain = | 
|  | base::ASCIIToUTF16(kDomainsTestCases[i].expected_domain_placeholder); | 
|  | EXPECT_TRUE(title.find(domain) != std::u16string::npos); | 
|  | if (kDomainsTestCases[i].bubble_type == | 
|  | PasswordTitleType::UPDATE_PASSWORD) { | 
|  | EXPECT_TRUE(title.find(u"Update") != std::u16string::npos); | 
|  | } else { | 
|  | EXPECT_TRUE(title.find(u"Save") != std::u16string::npos); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check that empty localised strings do not cause a crash. | 
|  | TEST(ManagePasswordsViewUtilTest, GetSavePasswordDialogTitleText_EmptyStrings) { | 
|  | ScopedResourceOverride resource_override; | 
|  |  | 
|  | // Ensure that the resource bundle returns an empty string for the UI. | 
|  | resource_override.OverrideLocaleStringResource(IDS_SAVE_PASSWORD, | 
|  | std::u16string()); | 
|  |  | 
|  | const GURL kExample("http://example.org"); | 
|  | // The arguments passed below have this importance for the codepath: | 
|  | // * The first two URLs need to be the same, otherwise | 
|  | //   IDS_SAVE_PASSWORD_DIFFERENT_DOMAINS_TITLE will be used instead of | 
|  | //   IDS_SAVE_PASSWORD overridden above. | 
|  | // * |kBrandingEnabled| needs to be true, otherwise the code won't try to | 
|  | //   dereference placeholder offsets from the localised string, which | 
|  | //   triggers the crash in http://crbug.com/658902. | 
|  | // * SAVE_PASSWORD dialog type needs to be passed to match the | 
|  | //   IDS_SAVE_PASSWORD overridden above. | 
|  | std::u16string title = | 
|  | GetSavePasswordDialogTitleText(kExample, url::Origin::Create(kExample), | 
|  | PasswordTitleType::SAVE_PASSWORD); | 
|  | // Verify that the test did not pass just because | 
|  | // GetSavePasswordDialogTitleText changed the resource IDs it uses | 
|  | // (and hence did not get the overridden empty string). If the empty localised | 
|  | // string was used, the title and the range will be empty as well. | 
|  | EXPECT_THAT(title, testing::IsEmpty()); | 
|  | } | 
|  |  | 
|  | TEST(ManagePasswordsViewUtilTest, GetManagePasswordsDialogTitleText) { | 
|  | for (size_t i = 0; i < std::size(kDomainsTestCases); ++i) { | 
|  | SCOPED_TRACE(testing::Message() << "user_visible_url = " | 
|  | << kDomainsTestCases[i].user_visible_url | 
|  | << ", password_origin_url = " | 
|  | << kDomainsTestCases[i].form_origin_url); | 
|  |  | 
|  | std::u16string title = GetManagePasswordsDialogTitleText( | 
|  | GURL(kDomainsTestCases[i].user_visible_url), | 
|  | url::Origin::Create(GURL(kDomainsTestCases[i].form_origin_url)), true); | 
|  |  | 
|  | // Verify against expectations. | 
|  | std::u16string domain = | 
|  | base::ASCIIToUTF16(kDomainsTestCases[i].expected_domain_placeholder); | 
|  | EXPECT_TRUE(title.find(domain) != std::u16string::npos); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(ManagePasswordsViewUtilTest, | 
|  | GetConfirmationManagePasswordsDialogTitleText) { | 
|  | EXPECT_NE(std::u16string(), GetConfirmationManagePasswordsDialogTitleText()); | 
|  | } |