| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <optional> |
| #include <string> |
| |
| #include "base/feature_list.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/test/bind.h" |
| #include "base/test/gtest_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "build/build_config.h" |
| #include "chrome/app/vector_icons/vector_icons.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/extensions/unpacked_installer.h" |
| #include "chrome/browser/media/router/media_router_feature.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h" |
| #include "chrome/browser/ui/accelerator_utils.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_element_identifiers.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h" |
| #include "chrome/browser/ui/chrome_pages.h" |
| #include "chrome/browser/ui/toolbar/app_menu_model.h" |
| #include "chrome/browser/ui/ui_features.h" |
| #include "chrome/browser/web_applications/proto/web_app_install_state.pb.h" |
| #include "chrome/browser/web_applications/test/web_app_icon_test_utils.h" |
| #include "chrome/browser/web_applications/test/web_app_test_utils.h" |
| #include "chrome/browser/web_applications/web_app_command_scheduler.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/browser/web_applications/web_app_registrar.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/test/base/interactive_test_utils.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "chrome/test/interaction/interactive_browser_test.h" |
| #include "chrome/test/interaction/tracked_element_webcontents.h" |
| #include "chrome/test/interaction/webcontents_interaction_test_util.h" |
| #include "components/crx_file/id_util.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| #include "components/performance_manager/public/features.h" |
| #include "components/signin/public/identity_manager/identity_test_environment.h" |
| #include "components/supervised_user/core/common/features.h" |
| #include "components/supervised_user/test_support/supervised_user_signin_test_utils.h" |
| #include "components/webapps/browser/banners/app_banner_manager.h" |
| #include "components/webapps/browser/banners/installable_web_app_check_result.h" |
| #include "components/webapps/browser/banners/web_app_banner_data.h" |
| #include "components/webapps/browser/installable/installable_metrics.h" |
| #include "content/public/test/browser_test.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/test_extension_registry_observer.h" |
| #include "extensions/common/extension_urls.h" |
| #include "extensions/test/test_extension_dir.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/accelerators/menu_label_accelerator_util.h" |
| #include "ui/base/interaction/element_identifier.h" |
| #include "ui/base/interaction/element_tracker.h" |
| #include "ui/base/interaction/expect_call_in_scope.h" |
| #include "ui/base/interaction/interaction_sequence.h" |
| #include "ui/base/interaction/state_observer.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| #include "ui/menus/simple_menu_model.h" |
| #include "ui/views/controls/menu/menu_item_view.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "base/mac/mac_util.h" |
| #include "chrome/browser/ui/browser_commands_mac.h" |
| #include "chrome/browser/ui/fullscreen_util_mac.h" |
| #endif // BUILDFLAG(IS_MAC) |
| |
| namespace { |
| DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kPrimaryTabPageElementId); |
| |
| #if BUILDFLAG(IS_MAC) |
| bool kTestDisabledForVirtualMachineMac = |
| (base::mac::MacOSMajorVersion() == 15) && base::mac::IsVirtualMachine(); |
| #endif // BUILDFLAG(IS_MAC) |
| |
| } // namespace |
| |
| class AppMenuModelInteractiveTest : public InteractiveBrowserTest { |
| public: |
| AppMenuModelInteractiveTest() = default; |
| ~AppMenuModelInteractiveTest() override = default; |
| AppMenuModelInteractiveTest(const AppMenuModelInteractiveTest&) = delete; |
| void operator=(const AppMenuModelInteractiveTest&) = delete; |
| |
| void SetUp() override { |
| set_open_about_blank_on_browser_launch(true); |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| InteractiveBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| InteractiveBrowserTest::SetUpOnMainThread(); |
| embedded_test_server()->StartAcceptingConnections(); |
| } |
| |
| void TearDownOnMainThread() override { |
| EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); |
| InteractiveBrowserTest::TearDownOnMainThread(); |
| } |
| |
| protected: |
| auto CheckIncognitoWindowOpened(const Browser* default_browser) { |
| return Check(base::BindLambdaForTesting([default_browser]() { |
| BrowserWindowInterface* new_browser = nullptr; |
| if (BrowserList::GetIncognitoBrowserCount() == 1) { |
| EXPECT_EQ(2u, BrowserList::GetInstance()->size()); |
| ForEachCurrentBrowserWindowInterfaceOrderedByActivation( |
| [default_browser, &new_browser](BrowserWindowInterface* browser) { |
| if (browser != default_browser) { |
| new_browser = browser; |
| } |
| return !new_browser; |
| }); |
| CHECK(new_browser); |
| } else { |
| new_browser = ui_test_utils::WaitForBrowserToOpen(); |
| } |
| return new_browser->GetProfile()->IsIncognitoProfile(); |
| })); |
| } |
| |
| auto CheckGuestWindowOpened(const Browser* default_browser) { |
| return Check(base::BindLambdaForTesting([default_browser]() { |
| BrowserWindowInterface* new_browser = nullptr; |
| if (BrowserList::GetGuestBrowserCount() == 1) { |
| EXPECT_EQ(2u, BrowserList::GetInstance()->size()); |
| ForEachCurrentBrowserWindowInterfaceOrderedByActivation( |
| [default_browser, &new_browser](BrowserWindowInterface* browser) { |
| if (browser != default_browser) { |
| new_browser = browser; |
| } |
| return !new_browser; |
| }); |
| CHECK(new_browser); |
| } else { |
| new_browser = ui_test_utils::WaitForBrowserToOpen(); |
| } |
| return new_browser->GetProfile()->IsGuestSession(); |
| })); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(AppMenuModelInteractiveTest, PerformanceNavigation) { |
| RunTestSequence( |
| InstrumentTab(kPrimaryTabPageElementId), |
| PressButton(kToolbarAppMenuButtonElementId), |
| ScrollIntoView(AppMenuModel::kMoreToolsMenuItem), |
| SelectMenuItem(AppMenuModel::kMoreToolsMenuItem), |
| ScrollIntoView(ToolsMenuModel::kPerformanceMenuItem), |
| SelectMenuItem(ToolsMenuModel::kPerformanceMenuItem), |
| WaitForWebContentsNavigation( |
| kPrimaryTabPageElementId, |
| GURL(chrome::GetSettingsUrl(chrome::kPerformanceSubPage)))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppMenuModelInteractiveTest, IncognitoMenuItem) { |
| RunTestSequence(PressButton(kToolbarAppMenuButtonElementId), |
| SelectMenuItem(AppMenuModel::kIncognitoMenuItem), |
| CheckIncognitoWindowOpened(browser())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppMenuModelInteractiveTest, IncognitoAccelerator) { |
| ui::Accelerator incognito_accelerator; |
| AcceleratorProviderForBrowser(browser())->GetAcceleratorForCommandId( |
| IDC_NEW_INCOGNITO_WINDOW, &incognito_accelerator); |
| |
| RunTestSequence( |
| SendAccelerator(kToolbarAppMenuButtonElementId, incognito_accelerator), |
| CheckIncognitoWindowOpened(browser())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(AppMenuModelInteractiveTest, |
| CastSaveShareSubMenuItemText) { |
| // TODO(crbug.com/445214951): Flaky on mac-vm builder for macOS 15. |
| #if BUILDFLAG(IS_MAC) |
| if (kTestDisabledForVirtualMachineMac) { |
| GTEST_SKIP() << "Disabled on macOS Sequoia for virtual machines."; |
| } |
| #endif // BUILDFLAG(IS_MAC) |
| |
| if (!media_router::MediaRouterEnabled(browser()->profile())) { |
| GTEST_SKIP() << "The cast item only exists if cast is enabled."; |
| } |
| RunTestSequence( |
| InstrumentTab(kPrimaryTabPageElementId), |
| PressButton(kToolbarAppMenuButtonElementId), |
| EnsurePresent(AppMenuModel::kSaveAndShareMenuItem), |
| CheckViewProperty( |
| AppMenuModel::kSaveAndShareMenuItem, &views::MenuItemView::title, |
| l10n_util::GetStringUTF16(IDS_CAST_SAVE_AND_SHARE_MENU)), |
| ScrollIntoView(AppMenuModel::kSaveAndShareMenuItem), |
| SelectMenuItem(AppMenuModel::kSaveAndShareMenuItem), |
| EnsurePresent(AppMenuModel::kCastTitleItem)); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| IN_PROC_BROWSER_TEST_F(AppMenuModelInteractiveTest, |
| ShowAppMenuInImmersiveFullscreen) { |
| chrome::SetAlwaysShowToolbarInFullscreenForTesting(browser(), false); |
| ASSERT_TRUE(!fullscreen_utils::IsAlwaysShowToolbarEnabled(browser())); |
| ui_test_utils::ToggleFullscreenModeAndWait(browser()); |
| chrome::RevealToolbarForTesting(browser()); |
| RunTestSequence(WaitForShow(kToolbarAppMenuButtonElementId), |
| PressButton(kToolbarAppMenuButtonElementId), |
| WaitForShow(AppMenuModel::kMoreToolsMenuItem)); |
| } |
| #endif // BUILDFLAG(IS_MAC) |
| |
| namespace { |
| |
| enum ExtensionsTestMode { |
| kDoNotCollapse, |
| kCollapseNoExtensions, |
| kCollapseWithExtensions |
| }; |
| |
| } // namespace |
| |
| class AppMenuModelExtensionsInteractiveTest |
| : public AppMenuModelInteractiveTest, |
| public testing::WithParamInterface<ExtensionsTestMode> { |
| public: |
| AppMenuModelExtensionsInteractiveTest() = default; |
| ~AppMenuModelExtensionsInteractiveTest() override = default; |
| |
| bool MenuShouldCollapse() const { |
| return GetParam() == ExtensionsTestMode::kCollapseNoExtensions; |
| } |
| |
| void SetUp() override { |
| scoped_feature_list_.InitWithFeatureState( |
| features::kExtensionsCollapseMainMenu, |
| GetParam() != ExtensionsTestMode::kDoNotCollapse); |
| set_open_about_blank_on_browser_launch(true); |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| InteractiveBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| if (GetParam() != ExtensionsTestMode::kDoNotCollapse) { |
| // Enable promotions. |
| promotions_enabled_value_to_restore_opt_ = |
| g_browser_process->local_state()->GetBoolean( |
| prefs::kPromotionsEnabled); |
| g_browser_process->local_state()->SetBoolean(prefs::kPromotionsEnabled, |
| true); |
| } |
| if (GetParam() == ExtensionsTestMode::kCollapseWithExtensions) { |
| // Create and load a dummy extension. |
| constexpr char kExtensionManifest[] = R"( |
| { |
| "name": "an extension", |
| "version": "1.0", |
| "manifest_version": 3, |
| "action": {} |
| } |
| )"; |
| extensions::TestExtensionDir dir; |
| dir.WriteManifest(kExtensionManifest); |
| const auto id = crx_file::id_util::GenerateIdForPath( |
| base::MakeAbsoluteFilePath(dir.UnpackedPath())); |
| auto* const registry = |
| extensions::ExtensionRegistry::Get(browser()->profile()); |
| CHECK(registry); |
| extensions::TestExtensionRegistryObserver observer(registry, id); |
| extensions::UnpackedInstaller::Create(browser()->profile()) |
| ->Load(dir.UnpackedPath()); |
| observer.WaitForExtensionLoaded(); |
| } |
| AppMenuModelInteractiveTest::SetUpOnMainThread(); |
| } |
| |
| void TearDownOnMainThread() override { |
| InteractiveBrowserTest::TearDownOnMainThread(); |
| if (promotions_enabled_value_to_restore_opt_.has_value()) { |
| g_browser_process->local_state()->SetBoolean( |
| prefs::kPromotionsEnabled, |
| promotions_enabled_value_to_restore_opt_.value()); |
| } |
| } |
| |
| protected: |
| base::HistogramTester histograms_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| private: |
| std::optional<bool> promotions_enabled_value_to_restore_opt_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| AppMenuModelExtensionsInteractiveTest, |
| testing::Values(ExtensionsTestMode::kDoNotCollapse, |
| ExtensionsTestMode::kCollapseNoExtensions, |
| ExtensionsTestMode::kCollapseWithExtensions), |
| [](const testing::TestParamInfo<ExtensionsTestMode>& param) { |
| switch (param.param) { |
| case ExtensionsTestMode::kDoNotCollapse: |
| return "DoNotCollapse"; |
| case ExtensionsTestMode::kCollapseNoExtensions: |
| return "CollapseNoExtensions"; |
| case ExtensionsTestMode::kCollapseWithExtensions: |
| return "CollapseWithExtensions"; |
| } |
| }); |
| |
| // Test to confirm that the manage extensions menu item navigates when selected |
| // and emit histograms that it did so. |
| IN_PROC_BROWSER_TEST_P(AppMenuModelExtensionsInteractiveTest, |
| ManageExtensions) { |
| if (MenuShouldCollapse()) { |
| GTEST_SKIP() |
| << "Manage extensions cannot be accessed through collapsed menu."; |
| } |
| |
| RunTestSequence( |
| InstrumentTab(kPrimaryTabPageElementId), |
| PressButton(kToolbarAppMenuButtonElementId), |
| SelectMenuItem(AppMenuModel::kExtensionsMenuItem), |
| SelectMenuItem(ExtensionsMenuModel::kManageExtensionsMenuItem), |
| WaitForWebContentsNavigation(kPrimaryTabPageElementId, |
| GURL(chrome::kChromeUIExtensionsURL))); |
| |
| histograms_.ExpectTotalCount("WrenchMenu.TimeToAction.ManageExtensions", 1); |
| histograms_.ExpectTotalCount("WrenchMenu.TimeToAction.VisitChromeWebStore", |
| 0); |
| histograms_.ExpectTotalCount("WrenchMenu.TimeToAction.FindExtensions", 0); |
| histograms_.ExpectBucketCount("WrenchMenu.MenuAction", |
| MENU_ACTION_MANAGE_EXTENSIONS, 1); |
| histograms_.ExpectBucketCount("WrenchMenu.MenuAction", |
| MENU_ACTION_VISIT_CHROME_WEB_STORE, 0); |
| histograms_.ExpectBucketCount("WrenchMenu.MenuAction", |
| MENU_ACTION_FIND_EXTENSIONS, 0); |
| } |
| |
| // Test to confirm that the visit Chrome Web Store menu item navigates to the |
| // correct chrome webstore URL when selected and emits histograms that it did |
| // so. |
| IN_PROC_BROWSER_TEST_P(AppMenuModelExtensionsInteractiveTest, |
| VisitChromeWebStore) { |
| const bool collapse = MenuShouldCollapse(); |
| const GURL expected_webstore_launch_url = |
| extension_urls::GetNewWebstoreLaunchURL(); |
| RunTestSequence( |
| InstrumentTab(kPrimaryTabPageElementId), |
| PressButton(kToolbarAppMenuButtonElementId), |
| // If not collapsed, then the web store item is in the extensions submenu. |
| If([collapse]() { return !collapse; }, |
| Then(SelectMenuItem(AppMenuModel::kExtensionsMenuItem))), |
| SelectMenuItem(ExtensionsMenuModel::kVisitChromeWebStoreMenuItem), |
| WaitForWebContentsNavigation( |
| kPrimaryTabPageElementId, |
| extension_urls::AppendUtmSource(expected_webstore_launch_url, |
| extension_urls::kAppMenuUtmSource))); |
| |
| histograms_.ExpectTotalCount("WrenchMenu.TimeToAction.VisitChromeWebStore", |
| collapse ? 0 : 1); |
| histograms_.ExpectTotalCount("WrenchMenu.TimeToAction.FindExtensions", |
| collapse ? 1 : 0); |
| histograms_.ExpectTotalCount("WrenchMenu.TimeToAction.ManageExtensions", 0); |
| histograms_.ExpectBucketCount("WrenchMenu.MenuAction", |
| MENU_ACTION_VISIT_CHROME_WEB_STORE, |
| collapse ? 0 : 1); |
| histograms_.ExpectBucketCount("WrenchMenu.MenuAction", |
| MENU_ACTION_FIND_EXTENSIONS, collapse ? 1 : 0); |
| histograms_.ExpectBucketCount("WrenchMenu.MenuAction", |
| MENU_ACTION_MANAGE_EXTENSIONS, 0); |
| } |
| |
| class AppMenuModelCreateNewTabGroupTest : public AppMenuModelInteractiveTest { |
| public: |
| AppMenuModelCreateNewTabGroupTest() { |
| scoped_feature_list_.InitWithFeatures( |
| {features::kCreateNewTabGroupAppMenuTopLevel}, {}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(AppMenuModelCreateNewTabGroupTest, |
| CheckCreateNewTabGroupAppMenuTopLevel) { |
| RunTestSequence(InstrumentTab(kPrimaryTabPageElementId), |
| PressButton(kToolbarAppMenuButtonElementId), |
| EnsurePresent(AppMenuModel::kCreateNewTabGroupTopLevel)); |
| } |
| |
| class AppMenuModelCreateNewTabGroupDisabled |
| : public AppMenuModelInteractiveTest { |
| public: |
| AppMenuModelCreateNewTabGroupDisabled() { |
| scoped_feature_list_.InitWithFeatures( |
| {}, {features::kCreateNewTabGroupAppMenuTopLevel}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(AppMenuModelCreateNewTabGroupDisabled, |
| CheckCreateNewTabGroupAppMenuTopLevelNotPresent) { |
| RunTestSequence(InstrumentTab(kPrimaryTabPageElementId), |
| PressButton(kToolbarAppMenuButtonElementId), |
| EnsureNotPresent(AppMenuModel::kCreateNewTabGroupTopLevel)); |
| } |
| |
| class PasswordManagerMenuItemInteractiveTest |
| : public AppMenuModelInteractiveTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| PasswordManagerMenuItemInteractiveTest() = default; |
| PasswordManagerMenuItemInteractiveTest( |
| const PasswordManagerMenuItemInteractiveTest&) = delete; |
| void operator=(const PasswordManagerMenuItemInteractiveTest&) = delete; |
| |
| ~PasswordManagerMenuItemInteractiveTest() override = default; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PasswordManagerMenuItemInteractiveTest, |
| PasswordManagerMenuItem) { |
| base::HistogramTester histograms; |
| |
| RunTestSequence(InstrumentTab(kPrimaryTabPageElementId), |
| PressButton(kToolbarAppMenuButtonElementId), |
| SelectMenuItem(AppMenuModel::kPasswordAndAutofillMenuItem), |
| SelectMenuItem(AppMenuModel::kPasswordManagerMenuItem), |
| WaitForWebContentsNavigation( |
| kPrimaryTabPageElementId, |
| GURL("chrome://password-manager/passwords"))); |
| |
| histograms.ExpectTotalCount("WrenchMenu.TimeToAction.ShowPasswordManager", 1); |
| histograms.ExpectBucketCount("WrenchMenu.MenuAction", |
| MENU_ACTION_SHOW_PASSWORD_MANAGER, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PasswordManagerMenuItemInteractiveTest, |
| NoMenuItemOnPasswordManagerPage) { |
| RunTestSequence( |
| AddInstrumentedTab(kPrimaryTabPageElementId, |
| GURL("chrome://password-manager/passwords")), |
| WaitForWebContentsReady(kPrimaryTabPageElementId, |
| GURL("chrome://password-manager/passwords")), |
| PressButton(kToolbarAppMenuButtonElementId), |
| EnsureNotPresent(AppMenuModel::kPasswordManagerMenuItem)); |
| } |
| |
| using ui::test::ObservationStateObserver; |
| using webapps::AppBannerManager; |
| using webapps::InstallableWebAppCheckResult; |
| using webapps::WebAppBannerData; |
| |
| class AppBannerManagerInstallStateObserver |
| : public ObservationStateObserver<InstallableWebAppCheckResult, |
| AppBannerManager, |
| AppBannerManager::Observer> { |
| public: |
| explicit AppBannerManagerInstallStateObserver( |
| AppBannerManager* app_banner_manager) |
| : ObservationStateObserver(app_banner_manager) {} |
| ~AppBannerManagerInstallStateObserver() override = default; |
| |
| // ObservationStateObserver: |
| InstallableWebAppCheckResult GetStateObserverInitialState() const override { |
| return source()->GetInstallableWebAppCheckResult(); |
| } |
| |
| // AppBannerManager::Observer: |
| void OnInstallableWebAppStatusUpdated( |
| InstallableWebAppCheckResult result, |
| const std::optional<WebAppBannerData>& data) override { |
| OnStateObserverStateChanged(result); |
| } |
| }; |
| |
| namespace { |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(AppBannerManagerInstallStateObserver, |
| kAppBannerManagerState); |
| } // namespace |
| |
| class UniversalInstallAppMenuModelInteractiveTest |
| : public AppMenuModelInteractiveTest { |
| public: |
| UniversalInstallAppMenuModelInteractiveTest() = default; |
| UniversalInstallAppMenuModelInteractiveTest( |
| const UniversalInstallAppMenuModelInteractiveTest&) = delete; |
| void operator=(const UniversalInstallAppMenuModelInteractiveTest&) = delete; |
| ~UniversalInstallAppMenuModelInteractiveTest() override = default; |
| |
| protected: |
| GURL GetNonInstallableAppUrl() { |
| return embedded_test_server()->GetURL( |
| "/banners/no_manifest_test_page.html"); |
| } |
| |
| GURL GetInstallableAppUrl() { |
| return embedded_test_server()->GetURL("/banners/manifest_test_page.html"); |
| } |
| |
| GURL GetInvalidManifestParsingAppUrl() { |
| return embedded_test_server()->GetURL( |
| "/banners/invalid_manifest_test_page.html"); |
| } |
| |
| // If universal install is enabled, non installable sites (DIY apps) will have |
| // a corresponding menu item entry for installation, as well as the default |
| // install icon next to them. |
| auto VerifyDiyAppMenuItemViews() { |
| const ui::ImageModel icon_image = ui::ImageModel::FromVectorIcon( |
| kInstallDesktopChromeRefreshIcon, ui::kColorMenuIcon, |
| ui::SimpleMenuModel::kDefaultIconSize); |
| return Steps( |
| EnsurePresent(AppMenuModel::kInstallAppItem), |
| CheckViewProperty( |
| AppMenuModel::kInstallAppItem, &views::MenuItemView::title, |
| l10n_util::GetStringUTF16(IDS_INSTALL_DIY_TO_OS_LAUNCH_SURFACE)), |
| CheckViewProperty(AppMenuModel::kInstallAppItem, |
| &views::MenuItemView::GetIcon, icon_image)); |
| } |
| |
| AppBannerManager* GetManager() { |
| return AppBannerManager::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| } |
| |
| // If universal install is enabled, the app icon should be shown in the menu |
| // entry, so compare the middle most color as an indicator of similarity (as |
| // is the case for most web_applications/ based tests). For the other case, |
| // the default chrome refresh icon is a vector icon that is easy to compare, |
| // so we do a 1:1 comparison. |
| auto CompareIcons() { |
| return base::BindLambdaForTesting([&](views::MenuItemView* item_view) { |
| EXPECT_TRUE(item_view->GetIcon().IsImage()); |
| EXPECT_EQ( |
| GetMidColorFromBitmap(item_view->GetIcon().GetImage().AsBitmap()), |
| GetAppIconColorBasedOnBannerData()); |
| }); |
| } |
| |
| bool InstallNonLocallyInstalledApp(const GURL& url) { |
| auto install_info = |
| web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(url); |
| install_info->title = u"Test App"; |
| install_info->user_display_mode = |
| web_app::mojom::UserDisplayMode::kStandalone; |
| web_app::WebAppInstallParams params; |
| params.install_state = |
| web_app::proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE; |
| params.add_to_applications_menu = false; |
| params.add_to_desktop = false; |
| params.add_to_quick_launch_bar = false; |
| params.add_to_search = false; |
| base::test::TestFuture<const webapps::AppId&, webapps::InstallResultCode> |
| result; |
| auto* provider = web_app::WebAppProvider::GetForTest(browser()->profile()); |
| provider->scheduler().InstallFromInfoWithParams( |
| std::move(install_info), /*overwrite_existing_manifest_fields=*/true, |
| webapps::WebappInstallSource::SYNC, result.GetCallback(), params); |
| bool success = result.Wait(); |
| const webapps::AppId& app_id = result.Get<webapps::AppId>(); |
| EXPECT_EQ(provider->registrar_unsafe().GetInstallState(app_id), |
| web_app::proto::SUGGESTED_FROM_ANOTHER_DEVICE); |
| return success; |
| } |
| |
| private: |
| SkColor GetAppIconColorBasedOnBannerData() { |
| std::optional<WebAppBannerData> banner_data = |
| GetManager()->GetCurrentWebAppBannerData(); |
| |
| if (banner_data->primary_icon.empty()) { |
| return SK_ColorTRANSPARENT; |
| } |
| |
| gfx::ImageSkia primary_icon = |
| gfx::ImageSkia::CreateFrom1xBitmap(banner_data->primary_icon); |
| gfx::ImageSkia resized_app_icon = |
| gfx::ImageSkiaOperations::CreateResizedImage( |
| primary_icon, skia::ImageOperations::RESIZE_BEST, |
| gfx::Size(ui::SimpleMenuModel::kDefaultIconSize, |
| ui::SimpleMenuModel::kDefaultIconSize)); |
| |
| return GetMidColorFromBitmap(*resized_app_icon.bitmap()); |
| } |
| |
| SkColor GetMidColorFromBitmap(const SkBitmap& bitmap) { |
| return bitmap.getColor(bitmap.width() / 2, bitmap.height() / 2); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(UniversalInstallAppMenuModelInteractiveTest, |
| DIYAppMenuWorksCorrectly) { |
| // TODO(crbug.com/445214951): Flaky on mac-vm builder for macOS 15. |
| #if BUILDFLAG(IS_MAC) |
| if (kTestDisabledForVirtualMachineMac) { |
| GTEST_SKIP() << "Disabled on macOS Sequoia for virtual machines."; |
| } |
| #endif // BUILDFLAG(IS_MAC) |
| |
| RunTestSequence( |
| InstrumentTab(kPrimaryTabPageElementId), |
| ObserveState(kAppBannerManagerState, GetManager()), |
| NavigateWebContents(kPrimaryTabPageElementId, GetNonInstallableAppUrl()), |
| WaitForWebContentsReady(kPrimaryTabPageElementId), |
| WaitForState(kAppBannerManagerState, InstallableWebAppCheckResult::kNo), |
| PressButton(kToolbarAppMenuButtonElementId), |
| EnsurePresent(AppMenuModel::kSaveAndShareMenuItem), |
| ScrollIntoView(AppMenuModel::kSaveAndShareMenuItem), |
| SelectMenuItem(AppMenuModel::kSaveAndShareMenuItem), |
| VerifyDiyAppMenuItemViews()); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_DIYAppMenuWorksCorrectlyInvalidManifestParsingSites \ |
| DISABLED_DIYAppMenuWorksCorrectlyInvalidManifestParsingSites |
| #else |
| #define MAYBE_DIYAppMenuWorksCorrectlyInvalidManifestParsingSites \ |
| DIYAppMenuWorksCorrectlyInvalidManifestParsingSites |
| #endif |
| IN_PROC_BROWSER_TEST_F( |
| UniversalInstallAppMenuModelInteractiveTest, |
| MAYBE_DIYAppMenuWorksCorrectlyInvalidManifestParsingSites) { |
| RunTestSequence(InstrumentTab(kPrimaryTabPageElementId), |
| ObserveState(kAppBannerManagerState, GetManager()), |
| NavigateWebContents(kPrimaryTabPageElementId, |
| GetInvalidManifestParsingAppUrl()), |
| WaitForWebContentsReady(kPrimaryTabPageElementId), |
| // Invalid parsing currently leads the AppBannerManager to |
| // early exit the pipeline without modifying the default value |
| // of `InstallableWebAppCheckResult`, which is `kUnknown`. |
| // This should almost never trigger a wait, but it's better to |
| // be safe than introduce flakiness. |
| WaitForState(kAppBannerManagerState, |
| InstallableWebAppCheckResult::kUnknown), |
| PressButton(kToolbarAppMenuButtonElementId), |
| EnsurePresent(AppMenuModel::kSaveAndShareMenuItem), |
| SelectMenuItem(AppMenuModel::kSaveAndShareMenuItem), |
| VerifyDiyAppMenuItemViews()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(UniversalInstallAppMenuModelInteractiveTest, |
| InstallAppMenuWorksCorrectly) { |
| RunTestSequence( |
| InstrumentTab(kPrimaryTabPageElementId), |
| ObserveState(kAppBannerManagerState, GetManager()), |
| NavigateWebContents(kPrimaryTabPageElementId, GetInstallableAppUrl()), |
| WaitForWebContentsReady(kPrimaryTabPageElementId), |
| WaitForState(kAppBannerManagerState, |
| InstallableWebAppCheckResult::kYes_Promotable), |
| PressButton(kToolbarAppMenuButtonElementId), |
| EnsurePresent(AppMenuModel::kSaveAndShareMenuItem), |
| ScrollIntoView(AppMenuModel::kSaveAndShareMenuItem), |
| SelectMenuItem(AppMenuModel::kSaveAndShareMenuItem), |
| EnsurePresent(AppMenuModel::kInstallAppItem), |
| CheckViewProperty( |
| AppMenuModel::kInstallAppItem, &views::MenuItemView::title, |
| l10n_util::GetStringFUTF16( |
| IDS_INSTALL_TO_OS_LAUNCH_SURFACE, |
| ui::EscapeMenuLabelAmpersands(u"Manifest test app"))), |
| WithView(AppMenuModel::kInstallAppItem, CompareIcons())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(UniversalInstallAppMenuModelInteractiveTest, |
| InstallAppMenuShowsForNonLocallyInstalledApps) { |
| EXPECT_TRUE(InstallNonLocallyInstalledApp(GetInstallableAppUrl())); |
| RunTestSequence( |
| InstrumentTab(kPrimaryTabPageElementId), |
| ObserveState(kAppBannerManagerState, GetManager()), |
| NavigateWebContents(kPrimaryTabPageElementId, GetInstallableAppUrl()), |
| WaitForWebContentsReady(kPrimaryTabPageElementId), |
| WaitForState(kAppBannerManagerState, |
| InstallableWebAppCheckResult::kYes_Promotable), |
| PressButton(kToolbarAppMenuButtonElementId), |
| EnsurePresent(AppMenuModel::kSaveAndShareMenuItem), |
| ScrollIntoView(AppMenuModel::kSaveAndShareMenuItem), |
| SelectMenuItem(AppMenuModel::kSaveAndShareMenuItem), |
| EnsurePresent(AppMenuModel::kInstallAppItem)); |
| } |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) |
| class SupervisedUserAppMenuModelInteractiveTest |
| : public AppMenuModelInteractiveTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| unused_subscription_ = |
| BrowserContextDependencyManager::GetInstance() |
| ->RegisterCreateServicesCallbackForTesting( |
| base::BindRepeating([](content::BrowserContext* context) { |
| // Required to use IdentityTestEnvironmentAdaptor. |
| IdentityTestEnvironmentProfileAdaptor:: |
| SetIdentityTestEnvironmentFactoriesOnBrowserContext( |
| context); |
| })); |
| } |
| |
| protected: |
| void OnWillCreateBrowserContextServices(content::BrowserContext* context) { |
| IdentityTestEnvironmentProfileAdaptor:: |
| SetIdentityTestEnvironmentFactoriesOnBrowserContext(context); |
| } |
| |
| void SetUpOnMainThread() override { |
| InteractiveBrowserTest::SetUpOnMainThread(); |
| identity_test_environment_adaptor_ = |
| std::make_unique<IdentityTestEnvironmentProfileAdaptor>( |
| browser()->profile()); |
| } |
| |
| void SignIn(bool is_supervised_user) { |
| AccountInfo account_info = |
| identity_test_environment_adaptor_->identity_test_env() |
| ->MakePrimaryAccountAvailable("name@gmail.com", |
| signin::ConsentLevel::kSignin); |
| supervised_user::UpdateSupervisionStatusForAccount( |
| account_info, |
| identity_test_environment_adaptor_->identity_test_env() |
| ->identity_manager(), |
| is_supervised_user); |
| } |
| |
| private: |
| base::CallbackListSubscription unused_subscription_; |
| std::unique_ptr<IdentityTestEnvironmentProfileAdaptor> |
| identity_test_environment_adaptor_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserAppMenuModelInteractiveTest, |
| OpenGuestSessionForSignedOutUser) { |
| RunTestSequence(PressButton(kToolbarAppMenuButtonElementId), |
| SelectMenuItem(AppMenuModel::kProfileMenuItem), |
| SelectMenuItem(AppMenuModel::kProfileOpenGuestItem), |
| CheckGuestWindowOpened(browser())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserAppMenuModelInteractiveTest, |
| OpenGuestSessionForSignedInRegularUser) { |
| SignIn(/*is_supervised_user=*/false); |
| RunTestSequence(PressButton(kToolbarAppMenuButtonElementId), |
| SelectMenuItem(AppMenuModel::kProfileMenuItem), |
| SelectMenuItem(AppMenuModel::kProfileOpenGuestItem), |
| CheckGuestWindowOpened(browser())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SupervisedUserAppMenuModelInteractiveTest, |
| OpenGuestSessionForSignedInSupervisedUser) { |
| SignIn(/*is_supervised_user=*/true); |
| |
| RunTestSequence(PressButton(kToolbarAppMenuButtonElementId), |
| SelectMenuItem(AppMenuModel::kProfileMenuItem), |
| EnsureNotPresent(AppMenuModel::kProfileOpenGuestItem)); |
| } |
| |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) |