| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ptr_exclusion.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/new_tab_page/promos/promo_data.h" |
| #include "chrome/browser/new_tab_page/promos/promo_service.h" |
| #include "chrome/browser/new_tab_page/promos/promo_service_factory.h" |
| #include "chrome/browser/new_tab_page/promos/promo_service_observer.h" |
| #include "chrome/browser/search/background/ntp_background_data.h" |
| #include "chrome/browser/search/background/ntp_custom_background_service.h" |
| #include "chrome/browser/search/background/ntp_custom_background_service_observer.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/browser/themes/theme_helper.h" |
| #include "chrome/browser/themes/theme_properties.h" |
| #include "chrome/browser/themes/theme_service.h" |
| #include "chrome/browser/themes/theme_service_observer.h" |
| #include "chrome/browser/ui/color/chrome_color_id.h" |
| #include "chrome/browser/ui/hats/hats_service_factory.h" |
| #include "chrome/browser/ui/hats/mock_hats_service.h" |
| #include "chrome/browser/ui/side_panel/customize_chrome/customize_chrome_tab_helper.h" |
| #include "chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom.h" |
| #include "chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h" |
| #include "chrome/browser/ui/webui/webui_util.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/grit/theme_resources.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/keyed_service/content/browser_context_keyed_service_factory.h" |
| #include "components/keyed_service/core/keyed_service.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/search/ntp_features.h" |
| #include "components/search_provider_logos/logo_common.h" |
| #include "components/search_provider_logos/logo_service.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/test_web_contents_factory.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "ui/base/theme_provider.h" |
| #include "ui/color/color_mixer.h" |
| #include "ui/color/color_provider_source.h" |
| #include "ui/color/color_recipe.h" |
| #include "ui/color/color_transform.h" |
| #include "ui/gfx/color_palette.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| using testing::_; |
| using testing::DoAll; |
| using testing::ElementsAre; |
| using testing::Optional; |
| using testing::SaveArg; |
| |
| class MockPage : public new_tab_page::mojom::Page { |
| public: |
| MockPage() = default; |
| ~MockPage() override = default; |
| |
| mojo::PendingRemote<new_tab_page::mojom::Page> BindAndGetRemote() { |
| DCHECK(!receiver_.is_bound()); |
| return receiver_.BindNewPipeAndPassRemote(); |
| } |
| |
| void FlushForTesting() { receiver_.FlushForTesting(); } |
| |
| MOCK_METHOD1(SetTheme, void(new_tab_page::mojom::ThemePtr)); |
| MOCK_METHOD2(SetDisabledModules, void(bool, const std::vector<std::string>&)); |
| MOCK_METHOD1(SetModulesFreVisibility, void(bool)); |
| MOCK_METHOD1(SetCustomizeChromeSidePanelVisibility, void(bool)); |
| MOCK_METHOD1(SetPromo, void(new_tab_page::mojom::PromoPtr)); |
| |
| mojo::Receiver<new_tab_page::mojom::Page> receiver_{this}; |
| }; |
| |
| class MockLogoService : public search_provider_logos::LogoService { |
| public: |
| MOCK_METHOD2(GetLogo, void(search_provider_logos::LogoCallbacks, bool)); |
| MOCK_METHOD1(GetLogo, void(search_provider_logos::LogoObserver*)); |
| }; |
| |
| class MockColorProviderSource : public ui::ColorProviderSource { |
| public: |
| MockColorProviderSource() { color_provider_.GenerateColorMap(); } |
| MockColorProviderSource(const MockColorProviderSource&) = delete; |
| MockColorProviderSource& operator=(const MockColorProviderSource&) = delete; |
| ~MockColorProviderSource() override = default; |
| |
| const ui::ColorProvider* GetColorProvider() const override { |
| return &color_provider_; |
| } |
| |
| void SetColor(ui::ColorId id, SkColor color) { |
| color_provider_.SetColorForTesting(id, color); |
| } |
| |
| protected: |
| ui::ColorProviderManager::Key GetColorProviderKey() const override { |
| return ui::ColorProviderManager::Key(); |
| } |
| |
| private: |
| ui::ColorProvider color_provider_; |
| }; |
| |
| class MockThemeProvider : public ui::ThemeProvider { |
| public: |
| MOCK_CONST_METHOD1(GetImageSkiaNamed, gfx::ImageSkia*(int)); |
| MOCK_CONST_METHOD1(GetColor, SkColor(int)); |
| MOCK_CONST_METHOD1(GetTint, color_utils::HSL(int)); |
| MOCK_CONST_METHOD1(GetDisplayProperty, int(int)); |
| MOCK_CONST_METHOD0(ShouldUseNativeFrame, bool()); |
| MOCK_CONST_METHOD1(HasCustomImage, bool(int)); |
| MOCK_CONST_METHOD2(GetRawData, |
| base::RefCountedMemory*(int, ui::ResourceScaleFactor)); |
| }; |
| |
| class MockNtpCustomBackgroundService : public NtpCustomBackgroundService { |
| public: |
| explicit MockNtpCustomBackgroundService(Profile* profile) |
| : NtpCustomBackgroundService(profile) {} |
| MOCK_METHOD0(RefreshBackgroundIfNeeded, void()); |
| MOCK_METHOD0(GetCustomBackground, absl::optional<CustomBackground>()); |
| MOCK_METHOD1(AddObserver, void(NtpCustomBackgroundServiceObserver*)); |
| }; |
| |
| class MockThemeService : public ThemeService { |
| public: |
| MockThemeService() : ThemeService(nullptr, theme_helper_) {} |
| MOCK_CONST_METHOD0(GetThemeID, std::string()); |
| MOCK_CONST_METHOD0(UsingAutogeneratedTheme, bool()); |
| MOCK_CONST_METHOD0(UsingDefaultTheme, bool()); |
| MOCK_METHOD1(AddObserver, void(ThemeServiceObserver*)); |
| |
| private: |
| ThemeHelper theme_helper_; |
| }; |
| |
| class MockPromoService : public PromoService { |
| public: |
| MockPromoService() : PromoService(nullptr, nullptr) {} |
| MOCK_METHOD(const absl::optional<PromoData>&, |
| promo_data, |
| (), |
| (const, override)); |
| MOCK_METHOD(void, AddObserver, (PromoServiceObserver*), (override)); |
| MOCK_METHOD(void, Refresh, (), (override)); |
| }; |
| |
| std::unique_ptr<TestingProfile> MakeTestingProfile( |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) { |
| TestingProfile::Builder profile_builder; |
| profile_builder.AddTestingFactory( |
| PromoServiceFactory::GetInstance(), |
| base::BindRepeating([](content::BrowserContext* context) |
| -> std::unique_ptr<KeyedService> { |
| return std::make_unique<testing::NiceMock<MockPromoService>>(); |
| })); |
| profile_builder.SetSharedURLLoaderFactory(url_loader_factory); |
| auto profile = profile_builder.Build(); |
| TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse( |
| profile.get(), |
| base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor)); |
| return profile; |
| } |
| |
| } // namespace |
| |
| class NewTabPageHandlerTest : public testing::Test { |
| public: |
| NewTabPageHandlerTest() |
| : profile_(MakeTestingProfile( |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory_))), |
| mock_ntp_custom_background_service_(profile_.get()), |
| mock_promo_service_(*static_cast<MockPromoService*>( |
| PromoServiceFactory::GetForProfile(profile_.get()))), |
| web_contents_(factory_.CreateWebContents(profile_.get())) { |
| mock_hats_service_ = static_cast<MockHatsService*>( |
| HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse( |
| profile_.get(), base::BindRepeating(&BuildMockHatsService))); |
| EXPECT_CALL(*mock_hats_service(), CanShowAnySurvey(_)) |
| .WillRepeatedly(testing::Return(true)); |
| } |
| |
| ~NewTabPageHandlerTest() override = default; |
| |
| void SetUp() override { |
| EXPECT_CALL(mock_theme_service_, AddObserver) |
| .Times(1) |
| .WillOnce(testing::SaveArg<0>(&theme_service_observer_)); |
| EXPECT_CALL(mock_ntp_custom_background_service_, AddObserver) |
| .Times(1) |
| .WillOnce( |
| testing::SaveArg<0>(&ntp_custom_background_service_observer_)); |
| EXPECT_CALL(*mock_promo_service_, AddObserver) |
| .Times(1) |
| .WillOnce(testing::SaveArg<0>(&promo_service_observer_)); |
| EXPECT_CALL(mock_page_, SetTheme).Times(1); |
| EXPECT_CALL(mock_ntp_custom_background_service_, RefreshBackgroundIfNeeded) |
| .Times(1); |
| webui::SetThemeProviderForTesting(&mock_theme_provider_); |
| web_contents_->SetColorProviderSource(&mock_color_provider_source_); |
| const std::vector<std::pair<const std::string, int>> module_id_names = { |
| {"recipe_tasks", IDS_NTP_MODULES_RECIPE_TASKS_SENTENCE}}; |
| CustomizeChromeTabHelper::CreateForWebContents(web_contents_.get()); |
| auto* customize_chrome_tab_helper_ = |
| CustomizeChromeTabHelper::FromWebContents(web_contents_.get()); |
| EXPECT_FALSE(customize_chrome_tab_helper_->IsCustomizeChromeEntryShowing()); |
| handler_ = std::make_unique<NewTabPageHandler>( |
| mojo::PendingReceiver<new_tab_page::mojom::PageHandler>(), |
| mock_page_.BindAndGetRemote(), profile_.get(), |
| &mock_ntp_custom_background_service_, &mock_theme_service_, |
| &mock_logo_service_, web_contents_, base::Time::Now(), module_id_names); |
| mock_page_.FlushForTesting(); |
| EXPECT_EQ(handler_.get(), theme_service_observer_); |
| EXPECT_EQ(handler_.get(), ntp_custom_background_service_observer_); |
| testing::Mock::VerifyAndClearExpectations(&mock_page_); |
| testing::Mock::VerifyAndClearExpectations( |
| &mock_ntp_custom_background_service_); |
| } |
| |
| new_tab_page::mojom::DoodlePtr GetDoodle( |
| const search_provider_logos::EncodedLogo& logo) { |
| search_provider_logos::EncodedLogoCallback on_cached_encoded_logo_available; |
| EXPECT_CALL(mock_logo_service_, GetLogo(testing::_, testing::_)) |
| .Times(1) |
| .WillOnce( |
| testing::Invoke([&on_cached_encoded_logo_available]( |
| search_provider_logos::LogoCallbacks callbacks, |
| bool for_webui_ntp) { |
| on_cached_encoded_logo_available = |
| std::move(callbacks.on_cached_encoded_logo_available); |
| })); |
| base::MockCallback<NewTabPageHandler::GetDoodleCallback> callback; |
| new_tab_page::mojom::DoodlePtr doodle; |
| EXPECT_CALL(callback, Run(testing::_)) |
| .Times(1) |
| .WillOnce( |
| testing::Invoke([&doodle](new_tab_page::mojom::DoodlePtr arg) { |
| doodle = std::move(arg); |
| })); |
| handler_->GetDoodle(callback.Get()); |
| |
| std::move(on_cached_encoded_logo_available) |
| .Run(search_provider_logos::LogoCallbackReason::DETERMINED, |
| absl::make_optional(logo)); |
| |
| return doodle; |
| } |
| |
| protected: |
| testing::NiceMock<MockPage> mock_page_; |
| // NOTE: The initialization order of these members matters. |
| content::BrowserTaskEnvironment task_environment_; |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| std::unique_ptr<TestingProfile> profile_; |
| testing::NiceMock<MockNtpCustomBackgroundService> |
| mock_ntp_custom_background_service_; |
| testing::NiceMock<MockThemeService> mock_theme_service_; |
| MockLogoService mock_logo_service_; |
| MockColorProviderSource mock_color_provider_source_; |
| MockHatsService* mock_hats_service() { return mock_hats_service_; } |
| testing::NiceMock<MockThemeProvider> mock_theme_provider_; |
| const raw_ref<MockPromoService> mock_promo_service_; |
| content::TestWebContentsFactory factory_; |
| raw_ptr<content::WebContents> web_contents_; // Weak. Owned by factory_. |
| base::HistogramTester histogram_tester_; |
| std::unique_ptr<NewTabPageHandler> handler_; |
| // This field is not a raw_ptr<> because it was filtered by the rewriter for: |
| // #addr-of |
| RAW_PTR_EXCLUSION ThemeServiceObserver* theme_service_observer_; |
| // This field is not a raw_ptr<> because it was filtered by the rewriter for: |
| // #addr-of |
| RAW_PTR_EXCLUSION NtpCustomBackgroundServiceObserver* |
| ntp_custom_background_service_observer_; |
| // This field is not a raw_ptr<> because it was filtered by the rewriter for: |
| // #addr-of |
| RAW_PTR_EXCLUSION PromoServiceObserver* promo_service_observer_; |
| |
| private: |
| raw_ptr<MockHatsService> mock_hats_service_; |
| }; |
| |
| class NewTabPageHandlerThemeTest |
| : public NewTabPageHandlerTest, |
| public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> { |
| public: |
| NewTabPageHandlerThemeTest() { |
| std::vector<base::test::FeatureRef> enabled_features; |
| std::vector<base::test::FeatureRef> disabled_features; |
| |
| if (RemoveScrim()) { |
| enabled_features.push_back(ntp_features::kNtpRemoveScrim); |
| } else { |
| disabled_features.push_back(ntp_features::kNtpRemoveScrim); |
| } |
| |
| if (ComprehensiveTheme()) { |
| enabled_features.push_back(ntp_features::kNtpComprehensiveTheming); |
| } else { |
| disabled_features.push_back(ntp_features::kNtpComprehensiveTheming); |
| } |
| |
| if (CustomizeChromeSidePanel()) { |
| enabled_features.push_back(ntp_features::kCustomizeChromeSidePanel); |
| } else { |
| disabled_features.push_back(ntp_features::kCustomizeChromeSidePanel); |
| } |
| |
| feature_list_.InitWithFeatures(std::move(enabled_features), |
| std::move(disabled_features)); |
| } |
| |
| bool RemoveScrim() const { return std::get<0>(GetParam()); } |
| bool ComprehensiveTheme() const { return std::get<1>(GetParam()); } |
| bool CustomizeChromeSidePanel() const { return std::get<2>(GetParam()); } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| TEST_P(NewTabPageHandlerThemeTest, SetTheme) { |
| new_tab_page::mojom::ThemePtr theme; |
| EXPECT_CALL(mock_page_, SetTheme) |
| .Times(1) |
| .WillOnce(testing::Invoke([&theme](new_tab_page::mojom::ThemePtr arg) { |
| theme = std::move(arg); |
| })); |
| ON_CALL(mock_ntp_custom_background_service_, GetCustomBackground()) |
| .WillByDefault(testing::Return(absl::optional<CustomBackground>())); |
| mock_color_provider_source_.SetColor(kColorNewTabPageBackground, |
| SkColorSetRGB(0, 0, 1)); |
| mock_color_provider_source_.SetColor(kColorNewTabPageText, |
| SkColorSetRGB(0, 0, 2)); |
| mock_color_provider_source_.SetColor(kColorNewTabPageTextUnthemed, |
| SkColorSetRGB(0, 0, 3)); |
| ON_CALL(mock_theme_service_, UsingDefaultTheme()) |
| .WillByDefault(testing::Return(false)); |
| ON_CALL(mock_theme_service_, UsingAutogeneratedTheme()) |
| .WillByDefault(testing::Return(true)); |
| ON_CALL(mock_theme_provider_, |
| GetDisplayProperty(ThemeProperties::NTP_LOGO_ALTERNATE)) |
| .WillByDefault(testing::Return(1)); |
| mock_color_provider_source_.SetColor(kColorNewTabPageLogo, |
| SkColorSetRGB(0, 0, 4)); |
| mock_color_provider_source_.SetColor(kColorNewTabPageLogoUnthemedLight, |
| SkColorSetRGB(0, 0, 5)); |
| ON_CALL(mock_theme_service_, GetThemeID()) |
| .WillByDefault(testing::Return("bar")); |
| ON_CALL(mock_theme_provider_, |
| GetDisplayProperty(ThemeProperties::NTP_BACKGROUND_TILING)) |
| .WillByDefault(testing::Return(ThemeProperties::REPEAT_X)); |
| ON_CALL(mock_theme_provider_, |
| GetDisplayProperty(ThemeProperties::NTP_BACKGROUND_ALIGNMENT)) |
| .WillByDefault(testing::Return(ThemeProperties::ALIGN_TOP)); |
| ON_CALL(mock_theme_provider_, HasCustomImage(IDR_THEME_NTP_ATTRIBUTION)) |
| .WillByDefault(testing::Return(true)); |
| ON_CALL(mock_theme_provider_, HasCustomImage(IDR_THEME_NTP_BACKGROUND)) |
| .WillByDefault(testing::Return(true)); |
| mock_color_provider_source_.SetColor( |
| kColorNewTabPageMostVisitedTileBackground, SkColorSetRGB(0, 0, 6)); |
| mock_color_provider_source_.SetColor( |
| kColorNewTabPageMostVisitedTileBackgroundThemed, SkColorSetRGB(0, 0, 7)); |
| mock_color_provider_source_.SetColor( |
| kColorNewTabPageMostVisitedTileBackgroundUnthemed, |
| SkColorSetRGB(0, 0, 8)); |
| |
| theme_service_observer_->OnThemeChanged(); |
| mock_page_.FlushForTesting(); |
| |
| ASSERT_TRUE(theme); |
| EXPECT_EQ(SkColorSetRGB(0, 0, 1), theme->background_color); |
| EXPECT_FALSE(theme->is_custom_background); |
| EXPECT_FALSE(theme->is_dark); |
| EXPECT_FALSE(theme->daily_refresh_collection_id.has_value()); |
| ASSERT_TRUE(theme->background_image); |
| EXPECT_EQ("chrome-untrusted://theme/IDR_THEME_NTP_BACKGROUND?bar", |
| theme->background_image->url); |
| EXPECT_EQ("chrome-untrusted://theme/IDR_THEME_NTP_BACKGROUND@2x?bar", |
| theme->background_image->url_2x); |
| EXPECT_EQ("chrome://theme/IDR_THEME_NTP_ATTRIBUTION?bar", |
| theme->background_image->attribution_url); |
| EXPECT_EQ("initial", theme->background_image->size); |
| EXPECT_EQ("repeat", theme->background_image->repeat_x); |
| EXPECT_EQ("no-repeat", theme->background_image->repeat_y); |
| EXPECT_EQ("center", theme->background_image->position_x); |
| EXPECT_EQ("top", theme->background_image->position_y); |
| if (!RemoveScrim()) { |
| EXPECT_EQ(SkColorSetRGB(0, 0, 2), theme->text_color); |
| EXPECT_EQ(SkColorSetRGB(0, 0, 4), theme->logo_color); |
| EXPECT_EQ(RemoveScrim(), |
| theme->background_image->scrim_display.has_value()); |
| } else { |
| EXPECT_EQ(SkColorSetRGB(0, 0, 3), theme->text_color); |
| EXPECT_EQ(SkColorSetRGB(0, 0, 5), theme->logo_color); |
| EXPECT_TRUE(theme->background_image->scrim_display.has_value()); |
| EXPECT_EQ("none", theme->background_image->scrim_display.value()); |
| } |
| EXPECT_FALSE(theme->background_image_attribution_1.has_value()); |
| EXPECT_FALSE(theme->background_image_attribution_2.has_value()); |
| EXPECT_FALSE(theme->background_image_attribution_url.has_value()); |
| ASSERT_TRUE(theme->most_visited); |
| EXPECT_EQ(SkColorSetRGB(0, 0, 8), theme->most_visited->background_color); |
| if (RemoveScrim()) { |
| EXPECT_FALSE(theme->most_visited->use_title_pill); |
| } else { |
| EXPECT_TRUE(theme->most_visited->use_title_pill); |
| } |
| EXPECT_TRUE(theme->most_visited->use_white_tile_icon); |
| EXPECT_EQ(false, theme->most_visited->is_dark); |
| } |
| |
| TEST_P(NewTabPageHandlerThemeTest, SetCustomBackground) { |
| new_tab_page::mojom::ThemePtr theme; |
| EXPECT_CALL(mock_page_, SetTheme) |
| .Times(1) |
| .WillOnce(testing::Invoke([&theme](new_tab_page::mojom::ThemePtr arg) { |
| theme = std::move(arg); |
| })); |
| CustomBackground custom_background; |
| custom_background.custom_background_url = GURL("https://foo.com/img.png"); |
| custom_background.custom_background_attribution_line_1 = "foo line"; |
| custom_background.custom_background_attribution_line_2 = "bar line"; |
| custom_background.custom_background_attribution_action_url = |
| GURL("https://foo.com/action"); |
| custom_background.collection_id = "baz collection"; |
| ON_CALL(mock_ntp_custom_background_service_, GetCustomBackground()) |
| .WillByDefault(testing::Return(absl::make_optional(custom_background))); |
| ON_CALL(mock_theme_provider_, HasCustomImage(IDR_THEME_NTP_BACKGROUND)) |
| .WillByDefault(testing::Return(true)); |
| mock_color_provider_source_.SetColor(kColorNewTabPageBackground, |
| SkColorSetRGB(0, 0, 1)); |
| mock_color_provider_source_.SetColor(kColorNewTabPageTextUnthemed, |
| SkColorSetRGB(0, 0, 2)); |
| mock_color_provider_source_.SetColor(kColorNewTabPageLogoUnthemedLight, |
| SkColorSetRGB(0, 0, 3)); |
| mock_color_provider_source_.SetColor( |
| kColorNewTabPageMostVisitedTileBackground, SkColorSetRGB(0, 0, 4)); |
| mock_color_provider_source_.SetColor( |
| kColorNewTabPageMostVisitedTileBackgroundUnthemed, |
| SkColorSetRGB(0, 0, 5)); |
| |
| ntp_custom_background_service_observer_->OnCustomBackgroundImageUpdated(); |
| mock_page_.FlushForTesting(); |
| |
| ASSERT_TRUE(theme); |
| if (CustomizeChromeSidePanel()) { |
| EXPECT_FALSE(theme->is_custom_background); |
| EXPECT_FALSE(theme->background_image_attribution_1.has_value()); |
| EXPECT_FALSE(theme->background_image_attribution_2.has_value()); |
| EXPECT_FALSE(theme->background_image_attribution_url.has_value()); |
| } else { |
| ASSERT_TRUE(theme); |
| EXPECT_TRUE(theme->is_custom_background); |
| EXPECT_EQ(SkColorSetRGB(0, 0, 1), theme->background_color); |
| EXPECT_EQ(SkColorSetRGB(0, 0, 2), theme->text_color); |
| EXPECT_EQ(SkColorSetRGB(0, 0, 3), theme->logo_color); |
| EXPECT_EQ("https://foo.com/img.png", theme->background_image->url); |
| EXPECT_EQ("foo line", theme->background_image_attribution_1); |
| EXPECT_EQ("bar line", theme->background_image_attribution_2); |
| EXPECT_EQ("https://foo.com/action", |
| theme->background_image_attribution_url); |
| EXPECT_EQ("baz collection", theme->daily_refresh_collection_id); |
| } |
| if (RemoveScrim()) { |
| EXPECT_TRUE(theme->background_image->scrim_display.has_value()); |
| EXPECT_EQ("none", theme->background_image->scrim_display.value()); |
| } else { |
| EXPECT_FALSE(theme->background_image->scrim_display.has_value()); |
| } |
| |
| if (ComprehensiveTheme()) { |
| EXPECT_EQ(SkColorSetRGB(0, 0, 4), theme->most_visited->background_color); |
| } else { |
| EXPECT_EQ(SkColorSetRGB(0, 0, 5), theme->most_visited->background_color); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| NewTabPageHandlerThemeTest, |
| ::testing::Combine(::testing::Bool(), |
| ::testing::Bool(), |
| ::testing::Bool())); |
| |
| TEST_F(NewTabPageHandlerTest, Histograms) { |
| histogram_tester_.ExpectTotalCount( |
| NewTabPageHandler::kModuleDismissedHistogram, 0); |
| histogram_tester_.ExpectTotalCount( |
| NewTabPageHandler::kModuleRestoredHistogram, 0); |
| |
| handler_->OnDismissModule("shopping_tasks"); |
| histogram_tester_.ExpectTotalCount( |
| NewTabPageHandler::kModuleDismissedHistogram, 1); |
| histogram_tester_.ExpectTotalCount( |
| std::string(NewTabPageHandler::kModuleDismissedHistogram) + |
| ".shopping_tasks", |
| 1); |
| |
| handler_->OnRestoreModule("kaleidoscope"); |
| histogram_tester_.ExpectTotalCount( |
| NewTabPageHandler::kModuleRestoredHistogram, 1); |
| histogram_tester_.ExpectTotalCount( |
| std::string(NewTabPageHandler::kModuleRestoredHistogram) + |
| ".kaleidoscope", |
| 1); |
| |
| // NewTabPage.Modules.FreOptIn and NewTabPage.Modules.FreOptOut log how many |
| // times the FRE is shown, so we increment the shown count to make sure the |
| // histogram is logging correctly |
| handler_->IncrementModulesShownCount(); |
| handler_->LogModulesFreOptInStatus( |
| new_tab_page::mojom::OptInStatus::kExplicitOptIn); |
| histogram_tester_.ExpectTotalCount("NewTabPage.Modules.FreExplicitOptIn", 1); |
| ASSERT_EQ(1, histogram_tester_.GetBucketCount( |
| "NewTabPage.Modules.FreExplicitOptIn", 1)); |
| |
| handler_->IncrementModulesShownCount(); |
| handler_->LogModulesFreOptInStatus(new_tab_page::mojom::OptInStatus::kOptOut); |
| histogram_tester_.ExpectTotalCount("NewTabPage.Modules.FreOptOut", 1); |
| ASSERT_EQ( |
| 1, histogram_tester_.GetBucketCount("NewTabPage.Modules.FreOptOut", 2)); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, GetAnimatedDoodle) { |
| search_provider_logos::EncodedLogo logo; |
| logo.encoded_image = |
| base::MakeRefCounted<base::RefCountedString>(std::string("light image")); |
| logo.dark_encoded_image = |
| base::MakeRefCounted<base::RefCountedString>(std::string("dark image")); |
| logo.metadata.type = search_provider_logos::LogoType::ANIMATED; |
| logo.metadata.on_click_url = GURL("https://doodle.com/on_click_url"); |
| logo.metadata.alt_text = "alt text"; |
| logo.metadata.mime_type = "light_mime_type"; |
| logo.metadata.dark_mime_type = "dark_mime_type"; |
| logo.metadata.dark_background_color = "#000001"; |
| logo.metadata.animated_url = GURL("https://doodle.com/light_animation"); |
| logo.metadata.dark_animated_url = GURL("https://doodle.com/dark_animation"); |
| logo.metadata.cta_log_url = GURL("https://doodle.com/light_cta_log_url"); |
| logo.metadata.dark_cta_log_url = GURL("https://doodle.com/dark_cta_log_url"); |
| logo.metadata.log_url = GURL("https://doodle.com/light_log_url"); |
| logo.metadata.dark_log_url = GURL("https://doodle.com/dark_log_url"); |
| logo.metadata.short_link = GURL("https://doodle.com/short_link"); |
| logo.metadata.width_px = 1; |
| logo.metadata.height_px = 2; |
| logo.metadata.dark_width_px = 3; |
| logo.metadata.dark_height_px = 4; |
| logo.metadata.share_button_x = 5; |
| logo.metadata.dark_share_button_x = 6; |
| logo.metadata.share_button_y = 7; |
| logo.metadata.dark_share_button_y = 8; |
| logo.metadata.share_button_opacity = 0.5; |
| logo.metadata.dark_share_button_opacity = 0.7; |
| logo.metadata.share_button_icon = "light share button"; |
| logo.metadata.dark_share_button_icon = "dark share button"; |
| logo.metadata.share_button_bg = "#000100"; |
| logo.metadata.dark_share_button_bg = "#010000"; |
| |
| auto doodle = GetDoodle(logo); |
| |
| ASSERT_TRUE(doodle); |
| ASSERT_TRUE(doodle->image); |
| ASSERT_FALSE(doodle->interactive); |
| EXPECT_EQ("data:light_mime_type;base64,bGlnaHQgaW1hZ2U=", |
| doodle->image->light->image_url); |
| EXPECT_EQ("https://doodle.com/light_animation", |
| doodle->image->light->animation_url); |
| EXPECT_EQ(1u, doodle->image->light->width); |
| EXPECT_EQ(2u, doodle->image->light->height); |
| EXPECT_EQ(SK_ColorWHITE, doodle->image->light->background_color); |
| EXPECT_EQ(5, doodle->image->light->share_button->x); |
| EXPECT_EQ(7, doodle->image->light->share_button->y); |
| EXPECT_EQ(SkColorSetARGB(127, 0, 1, 0), |
| doodle->image->light->share_button->background_color); |
| EXPECT_EQ("data:image/png;base64,light share button", |
| doodle->image->light->share_button->icon_url); |
| EXPECT_EQ("https://doodle.com/light_cta_log_url", |
| doodle->image->light->image_impression_log_url); |
| EXPECT_EQ("https://doodle.com/light_log_url", |
| doodle->image->light->animation_impression_log_url); |
| EXPECT_EQ("data:dark_mime_type;base64,ZGFyayBpbWFnZQ==", |
| doodle->image->dark->image_url); |
| EXPECT_EQ("https://doodle.com/dark_animation", |
| doodle->image->dark->animation_url); |
| EXPECT_EQ(3u, doodle->image->dark->width); |
| EXPECT_EQ(4u, doodle->image->dark->height); |
| EXPECT_EQ(SkColorSetRGB(0, 0, 1), doodle->image->dark->background_color); |
| EXPECT_EQ(6, doodle->image->dark->share_button->x); |
| EXPECT_EQ(8, doodle->image->dark->share_button->y); |
| EXPECT_EQ(SkColorSetARGB(178, 1, 0, 0), |
| doodle->image->dark->share_button->background_color); |
| EXPECT_EQ("data:image/png;base64,dark share button", |
| doodle->image->dark->share_button->icon_url); |
| EXPECT_EQ("https://doodle.com/dark_cta_log_url", |
| doodle->image->dark->image_impression_log_url); |
| EXPECT_EQ("https://doodle.com/dark_log_url", |
| doodle->image->dark->animation_impression_log_url); |
| EXPECT_EQ("https://doodle.com/on_click_url", doodle->image->on_click_url); |
| EXPECT_EQ("https://doodle.com/short_link", doodle->image->share_url); |
| EXPECT_EQ("alt text", doodle->description); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, GetInteractiveDoodle) { |
| search_provider_logos::EncodedLogo logo; |
| logo.metadata.type = search_provider_logos::LogoType::INTERACTIVE; |
| logo.metadata.full_page_url = GURL("https://doodle.com/full_page_url"); |
| logo.metadata.iframe_width_px = 1; |
| logo.metadata.iframe_height_px = 2; |
| logo.metadata.alt_text = "alt text"; |
| |
| auto doodle = GetDoodle(logo); |
| |
| EXPECT_EQ("https://doodle.com/full_page_url", doodle->interactive->url); |
| EXPECT_EQ(1u, doodle->interactive->width); |
| EXPECT_EQ(2u, doodle->interactive->height); |
| EXPECT_EQ("alt text", doodle->description); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, UpdatePromoData) { |
| PromoData promo_data; |
| promo_data.middle_slot_json = R"({ |
| "part": [{ |
| "image": { |
| "image_url": "https://image.com/image", |
| "target": "https://image.com/target" |
| } |
| }, { |
| "link": { |
| "url": "https://link.com", |
| "text": "bar", |
| "color": "red" |
| } |
| }, { |
| "text": { |
| "text": "blub", |
| "color": "green" |
| } |
| }] |
| })"; |
| promo_data.promo_log_url = GURL("https://foo.com"); |
| promo_data.promo_id = "foo"; |
| auto promo_data_optional = absl::make_optional(promo_data); |
| ON_CALL(*mock_promo_service_, promo_data()) |
| .WillByDefault(testing::ReturnRef(promo_data_optional)); |
| EXPECT_CALL(*mock_promo_service_, Refresh).Times(1); |
| |
| new_tab_page::mojom::PromoPtr promo; |
| EXPECT_CALL(mock_page_, SetPromo) |
| .Times(1) |
| .WillOnce(testing::Invoke([&promo](new_tab_page::mojom::PromoPtr arg) { |
| promo = std::move(arg); |
| })); |
| handler_->UpdatePromoData(); |
| mock_page_.FlushForTesting(); |
| |
| ASSERT_TRUE(promo); |
| EXPECT_EQ("foo", promo->id); |
| EXPECT_EQ("https://foo.com/", promo->log_url); |
| ASSERT_EQ(3lu, promo->middle_slot_parts.size()); |
| ASSERT_TRUE(promo->middle_slot_parts[0]->is_image()); |
| const auto& image = promo->middle_slot_parts[0]->get_image(); |
| EXPECT_EQ("https://image.com/image", image->image_url); |
| EXPECT_EQ("https://image.com/target", image->target); |
| ASSERT_TRUE(promo->middle_slot_parts[1]->is_link()); |
| const auto& link = promo->middle_slot_parts[1]->get_link(); |
| EXPECT_EQ("bar", link->text); |
| EXPECT_EQ("https://link.com/", link->url); |
| ASSERT_TRUE(promo->middle_slot_parts[2]->is_text()); |
| const auto& text = promo->middle_slot_parts[2]->get_text(); |
| EXPECT_EQ("blub", text->text); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, OnDoodleImageClicked) { |
| handler_->OnDoodleImageClicked( |
| /*type=*/new_tab_page::mojom::DoodleImageType::kCta, |
| /*log_url=*/GURL("https://doodle.com/log")); |
| |
| histogram_tester_.ExpectTotalCount("NewTabPage.LogoClick", 1); |
| EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( |
| "https://doodle.com/log", "")); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, OnDoodleImageRendered) { |
| base::MockCallback<NewTabPageHandler::OnDoodleImageRenderedCallback> callback; |
| absl::optional<std::string> image_click_params; |
| absl::optional<GURL> interaction_log_url; |
| absl::optional<std::string> shared_id; |
| EXPECT_CALL(callback, Run(_, _, _)) |
| .Times(1) |
| .WillOnce(DoAll(SaveArg<0>(&image_click_params), |
| SaveArg<1>(&interaction_log_url), |
| SaveArg<2>(&shared_id))); |
| |
| handler_->OnDoodleImageRendered( |
| /*type=*/new_tab_page::mojom::DoodleImageType::kStatic, |
| /*time=*/0, |
| /*log_url=*/GURL("https://doodle.com/log"), callback.Get()); |
| |
| EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( |
| "https://doodle.com/log", R"()]}' |
| { |
| "ddllog": { |
| "target_url_params": "foo params", |
| "interaction_log_url": "/bar_log", |
| "encoded_ei": "baz ei" |
| } |
| })")); |
| EXPECT_THAT(image_click_params, Optional(std::string("foo params"))); |
| EXPECT_THAT(interaction_log_url, |
| Optional(GURL("https://www.google.com/bar_log"))); |
| EXPECT_THAT(shared_id, Optional(std::string("baz ei"))); |
| histogram_tester_.ExpectTotalCount("NewTabPage.LogoShown", 1); |
| histogram_tester_.ExpectTotalCount("NewTabPage.LogoShown.FromCache", 1); |
| histogram_tester_.ExpectTotalCount("NewTabPage.LogoShownTime2", 1); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, OnDoodleShared) { |
| handler_->OnDoodleShared(new_tab_page::mojom::DoodleShareChannel::kEmail, |
| "food_id", "bar_id"); |
| |
| EXPECT_TRUE(test_url_loader_factory_.IsPending( |
| "https://www.google.com/" |
| "gen_204?atype=i&ct=doodle&ntp=2&cad=sh,5,ct:food_id&ei=bar_id")); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, GetModulesIdNames) { |
| std::vector<new_tab_page::mojom::ModuleIdNamePtr> modules_details; |
| base::MockCallback<NewTabPageHandler::GetModulesIdNamesCallback> callback; |
| EXPECT_CALL(callback, Run(_)) |
| .Times(1) |
| .WillOnce(testing::Invoke( |
| [&modules_details]( |
| std::vector<new_tab_page::mojom::ModuleIdNamePtr> arg) { |
| modules_details = std::move(arg); |
| })); |
| base::test::ScopedFeatureList features; |
| features.InitWithFeatures( |
| /*enabled_features=*/{ntp_features::kNtpRecipeTasksModule}, |
| /*disabled_features=*/{}); |
| handler_->GetModulesIdNames(callback.Get()); |
| EXPECT_EQ(modules_details.size(), 1u); |
| EXPECT_EQ(modules_details.front()->id, "recipe_tasks"); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, GetModulesOrder) { |
| std::vector<std::string> module_ids; |
| base::MockCallback<NewTabPageHandler::GetModulesOrderCallback> callback; |
| EXPECT_CALL(callback, Run(_)).Times(1).WillOnce(SaveArg<0>(&module_ids)); |
| base::test::ScopedFeatureList features; |
| features.InitWithFeaturesAndParameters( |
| {{ntp_features::kNtpModulesOrder, |
| {{ntp_features::kNtpModulesOrderParam, "bar,baz"}}}, |
| {ntp_features::kNtpModulesDragAndDrop, {}}}, |
| {}); |
| base::Value module_ids_value(base::Value::Type::LIST); |
| module_ids_value.Append("foo"); |
| module_ids_value.Append("bar"); |
| profile_->GetPrefs()->Set(prefs::kNtpModulesOrder, module_ids_value); |
| |
| handler_->GetModulesOrder(callback.Get()); |
| EXPECT_THAT(module_ids, ElementsAre("foo", "bar", "baz")); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, SurveyLaunchedEligibleModulesCriteria) { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeaturesAndParameters( |
| { |
| {features::kHappinessTrackingSurveysForDesktopNtpModules, |
| {{ntp_features::kNtpModulesEligibleForHappinessTrackingSurveyParam, |
| "recipe_tasks,drive"}}}, |
| }, |
| {}); |
| |
| EXPECT_CALL(*mock_hats_service(), |
| LaunchDelayedSurveyForWebContents(_, _, _, _, _, _)) |
| .Times(1); |
| const std::vector<std::string> module_ids = {"recipe_tasks", "cart"}; |
| handler_->OnModulesLoadedWithData(module_ids); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, SurveyLaunchSkippedEligibleModulesCriteria) { |
| base::test::ScopedFeatureList features; |
| features.InitWithFeaturesAndParameters( |
| { |
| {features::kHappinessTrackingSurveysForDesktopNtpModules, |
| {{ntp_features::kNtpModulesEligibleForHappinessTrackingSurveyParam, |
| "drive"}}}, |
| }, |
| {}); |
| |
| EXPECT_CALL(*mock_hats_service(), |
| LaunchDelayedSurveyForWebContents(_, _, _, _, _, _)) |
| .Times(0); |
| const std::vector<std::string> module_ids = {"recipe_tasks"}; |
| handler_->OnModulesLoadedWithData(module_ids); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, UpdateNtpModulesFreVisibility) { |
| bool expected_visibility = true; |
| profile_->GetPrefs()->SetBoolean(prefs::kNtpModulesFreVisible, |
| expected_visibility); |
| |
| EXPECT_EQ(profile_->GetPrefs()->GetBoolean(prefs::kNtpModulesFreVisible), |
| expected_visibility); |
| |
| expected_visibility = false; |
| EXPECT_CALL(mock_page_, SetModulesFreVisibility) |
| .Times(1) |
| .WillOnce(testing::Invoke( |
| [&](bool arg) { EXPECT_EQ(expected_visibility, arg); })); |
| |
| handler_->SetModulesFreVisible(expected_visibility); |
| |
| EXPECT_EQ(profile_->GetPrefs()->GetBoolean(prefs::kNtpModulesFreVisible), |
| expected_visibility); |
| |
| mock_page_.FlushForTesting(); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, IncrementModulesShownCount) { |
| EXPECT_EQ(profile_->GetPrefs()->GetInteger(prefs::kNtpModulesShownCount), 0); |
| EXPECT_EQ(profile_->GetPrefs()->GetTime(prefs::kNtpModulesFirstShownTime), |
| base::Time()); |
| |
| handler_->IncrementModulesShownCount(); |
| |
| EXPECT_EQ(profile_->GetPrefs()->GetInteger(prefs::kNtpModulesShownCount), 1); |
| EXPECT_NE(profile_->GetPrefs()->GetTime(prefs::kNtpModulesFirstShownTime), |
| base::Time()); |
| |
| mock_page_.FlushForTesting(); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, |
| UpdateModulesFreVisibilityUsingModulesShownCount) { |
| handler_->SetModulesFreVisible(true); |
| profile_->GetPrefs()->SetInteger(prefs::kNtpModulesShownCount, 7); |
| |
| handler_->UpdateModulesFreVisibility(); |
| |
| EXPECT_EQ(profile_->GetPrefs()->GetBoolean(prefs::kNtpModulesFreVisible), |
| true); |
| |
| profile_->GetPrefs()->SetInteger(prefs::kNtpModulesShownCount, 8); |
| |
| handler_->UpdateModulesFreVisibility(); |
| |
| EXPECT_EQ(profile_->GetPrefs()->GetBoolean(prefs::kNtpModulesFreVisible), |
| false); |
| histogram_tester_.ExpectTotalCount("NewTabPage.Modules.FreImplicitOptIn", 1); |
| ASSERT_EQ(1, histogram_tester_.GetBucketCount( |
| "NewTabPage.Modules.FreImplicitOptIn", true)); |
| |
| mock_page_.FlushForTesting(); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, |
| UpdateModulesFreVisibilityUsingModulesFirstShownTime) { |
| handler_->SetModulesFreVisible(true); |
| profile_->GetPrefs()->SetTime(prefs::kNtpModulesFirstShownTime, |
| base::Time::Now()); |
| |
| handler_->UpdateModulesFreVisibility(); |
| |
| EXPECT_EQ(profile_->GetPrefs()->GetBoolean(prefs::kNtpModulesFreVisible), |
| true); |
| |
| profile_->GetPrefs()->SetTime(prefs::kNtpModulesFirstShownTime, |
| base::Time::Now() - base::Days(2)); |
| |
| handler_->UpdateModulesFreVisibility(); |
| |
| EXPECT_EQ(profile_->GetPrefs()->GetBoolean(prefs::kNtpModulesFreVisible), |
| false); |
| |
| mock_page_.FlushForTesting(); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, SetModuleDisabledTriggersPageCall) { |
| handler_->SetModuleDisabled("drive", true); |
| EXPECT_CALL(mock_page_, SetDisabledModules).Times(1); |
| mock_page_.FlushForTesting(); |
| } |
| |
| TEST_F(NewTabPageHandlerTest, ModulesVisiblePrefChangeTriggersPageCall) { |
| profile_->GetPrefs()->SetBoolean(prefs::kNtpModulesVisible, true); |
| EXPECT_CALL(mock_page_, SetDisabledModules).Times(1); |
| mock_page_.FlushForTesting(); |
| } |