blob: c9e1c422869e3d8503bc34f3b8d4e7f8dfbcc451 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <codecvt>
#include <string>
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "build/os_buildflags.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/browser_app_launcher.h"
#include "chrome/browser/banners/app_banner_manager_desktop.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/devtools/protocol/browser_handler.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/scoped_disable_client_side_decorations_for_test.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/themes/custom_theme_supplier.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_test.h"
#include "chrome/browser/ui/page_info/page_info_dialog.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/toolbar/app_menu_model.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
#include "chrome/browser/ui/web_applications/web_app_launch_manager.h"
#include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
#include "chrome/browser/ui/web_applications/web_app_menu_model.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/externally_installed_web_app_prefs.h"
#include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/web_applications/system_web_apps/system_web_app_types.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_install_finalizer.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_application_info.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/services/app_service/public/mojom/types.mojom.h"
#include "components/sessions/core/tab_restore_service.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "content/public/common/content_features.h"
#include "content/public/test/background_color_change_waiter.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "net/base/filename_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#endif
#if defined(OS_MAC)
#include "ui/base/test/scoped_fake_nswindow_fullscreen.h"
#endif
#if defined(OS_WIN)
#include "base/test/test_reg_util_win.h"
#include "base/win/windows_version.h"
#include "chrome/browser/web_applications/web_app_handler_registration_utils_win.h"
#include "chrome/browser/web_applications/web_app_shortcuts_menu_win.h"
#include "chrome/browser/win/jumplist_updater.h"
#include "chrome/installer/util/shell_util.h"
#endif
namespace {
constexpr const char kExampleURL[] = "http://example.org/";
constexpr const char16_t kExampleURL16[] = u"http://example.org/";
constexpr const char kExampleManifestURL[] = "http://example.org/manifest";
constexpr char kLaunchWebAppDisplayModeHistogram[] = "Launch.WebAppDisplayMode";
// Opens |url| in a new popup window with the dimensions |popup_size|.
Browser* OpenPopupAndWait(Browser* browser,
const GURL& url,
const gfx::Size& popup_size) {
content::WebContents* const web_contents =
browser->tab_strip_model()->GetActiveWebContents();
ui_test_utils::BrowserChangeObserver browser_change_observer(
nullptr, ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
std::string open_window_script = base::StringPrintf(
"window.open('%s', '_blank', 'toolbar=none,width=%i,height=%i')",
url.spec().c_str(), popup_size.width(), popup_size.height());
EXPECT_TRUE(content::ExecJs(web_contents, open_window_script));
// The navigation should happen in a new window.
Browser* popup_browser = browser_change_observer.Wait();
EXPECT_NE(browser, popup_browser);
content::WebContents* popup_contents =
popup_browser->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(popup_contents));
EXPECT_EQ(popup_contents->GetLastCommittedURL(), url);
return popup_browser;
}
#if defined(OS_WIN)
std::vector<std::wstring> GetFileExtensionsForProgId(
const std::wstring& file_handler_prog_id) {
const std::wstring prog_id_path =
base::StrCat({ShellUtil::kRegClasses, L"\\", file_handler_prog_id});
// Get list of handled file extensions from value FileExtensions at
// HKEY_CURRENT_USER\Software\Classes\<file_handler_prog_id>.
base::win::RegKey file_extensions_key(HKEY_CURRENT_USER, prog_id_path.c_str(),
KEY_QUERY_VALUE);
std::wstring handled_file_extensions;
EXPECT_EQ(file_extensions_key.ReadValue(L"FileExtensions",
&handled_file_extensions),
ERROR_SUCCESS);
return base::SplitString(handled_file_extensions, std::wstring(L";"),
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
}
#endif // defined(OS_WIN)
} // namespace
namespace web_app {
class WebAppBrowserTest : public WebAppControllerBrowserTest {
public:
GURL GetSecureAppURL() {
return https_server()->GetURL("app.com", "/ssl/google.html");
}
GURL GetURLForPath(const std::string& path) {
return https_server()->GetURL("app.com", path);
}
bool HasMinimalUiButtons(DisplayMode display_mode,
absl::optional<DisplayMode> display_override_mode,
bool open_as_window) {
static int index = 0;
base::HistogramTester tester;
const GURL app_url = https_server()->GetURL(
base::StringPrintf("/web_apps/basic.html?index=%d", index++));
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = app_url;
web_app_info->scope = app_url;
web_app_info->display_mode = display_mode;
web_app_info->user_display_mode =
open_as_window ? DisplayMode::kStandalone : DisplayMode::kBrowser;
if (display_override_mode)
web_app_info->display_override.push_back(*display_override_mode);
AppId app_id = InstallWebApp(std::move(web_app_info));
Browser* app_browser = LaunchWebAppBrowser(app_id);
DCHECK(app_browser->is_type_app());
DCHECK(app_browser->app_controller());
tester.ExpectUniqueSample(
kLaunchWebAppDisplayModeHistogram,
display_override_mode ? *display_override_mode : display_mode, 1);
content::WebContents* const web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(WaitForLoadStop(web_contents));
EXPECT_EQ(app_url, web_contents->GetVisibleURL());
bool matches;
const bool result = app_browser->app_controller()->HasMinimalUiButtons();
EXPECT_TRUE(ExecuteScriptAndExtractBool(
web_contents,
"window.domAutomationController.send(window.matchMedia('(display-mode: "
"minimal-ui)').matches)",
&matches));
EXPECT_EQ(result, matches);
CloseAndWait(app_browser);
return result;
}
};
// A dedicated test fixture for WindowControlsOverlay, which requires a command
// line switch to enable manifest parsing.
class WebAppBrowserTest_WindowControlsOverlay : public WebAppBrowserTest {
public:
WebAppBrowserTest_WindowControlsOverlay() = default;
private:
base::test::ScopedFeatureList scoped_feature_list_{
features::kWebAppWindowControlsOverlay};
};
// A dedicated test fixture for tabbed display override, which requires a
// command line switch to enable manifest parsing.
class WebAppBrowserTest_Tabbed : public WebAppBrowserTest {
public:
WebAppBrowserTest_Tabbed() = default;
private:
base::test::ScopedFeatureList scoped_feature_list_{
features::kDesktopPWAsTabStrip};
};
// TODO(crbug.com/1257751): Stabilize the test.
#if defined(OS_POSIX)
#define DISABLE_POSIX(TEST) DISABLED_##TEST
#else
#define DISABLE_POSIX(TEST) TEST
#endif
#if defined(OS_WIN)
using WebAppBrowserTest_ShortcutMenu = WebAppBrowserTest;
#endif
using WebAppTabRestoreBrowserTest = WebAppBrowserTest;
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ThemeColor) {
{
const SkColor theme_color = SkColorSetA(SK_ColorBLUE, 0xF0);
blink::mojom::Manifest manifest;
manifest.start_url = GURL(kExampleURL);
manifest.scope = GURL(kExampleURL);
manifest.has_theme_color = true;
manifest.theme_color = theme_color;
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app::UpdateWebAppInfoFromManifest(manifest, GURL(kExampleManifestURL),
web_app_info.get());
AppId app_id = InstallWebApp(std::move(web_app_info));
Browser* app_browser = LaunchWebAppBrowser(app_id);
EXPECT_EQ(GetAppIdFromApplicationName(app_browser->app_name()), app_id);
EXPECT_EQ(SkColorSetA(theme_color, SK_AlphaOPAQUE),
app_browser->app_controller()->GetThemeColor());
}
{
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = GURL("http://example.org/2");
web_app_info->scope = GURL("http://example.org/");
web_app_info->theme_color = absl::optional<SkColor>();
AppId app_id = InstallWebApp(std::move(web_app_info));
Browser* app_browser = LaunchWebAppBrowser(app_id);
EXPECT_EQ(GetAppIdFromApplicationName(app_browser->app_name()), app_id);
EXPECT_EQ(absl::nullopt, app_browser->app_controller()->GetThemeColor());
}
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, BackgroundColor) {
blink::mojom::Manifest manifest;
manifest.start_url = GURL(kExampleURL);
manifest.scope = GURL(kExampleURL);
manifest.has_background_color = true;
manifest.background_color = SkColorSetA(SK_ColorBLUE, 0xF0);
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app::UpdateWebAppInfoFromManifest(manifest, GURL(kExampleManifestURL),
web_app_info.get());
AppId app_id = InstallWebApp(std::move(web_app_info));
auto* provider = WebAppProvider::GetForTest(profile());
EXPECT_EQ(provider->registrar().GetAppBackgroundColor(app_id), SK_ColorBLUE);
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ShortcutBackgroundColor) {
const GURL app_url = https_server()->GetURL("/banners/background-color.html");
const AppId app_id = InstallWebAppFromPage(browser(), app_url);
auto* provider = WebAppProvider::GetForTest(profile());
EXPECT_EQ(provider->registrar().GetAppBackgroundColor(app_id), SK_ColorBLUE);
}
// Base class for background color change browser tests parameterized by whether
// to use a SWA or a non-SWA.
class WebAppBackgroundColorChangeBrowserTest
: public WebAppBrowserTest,
public testing::WithParamInterface</*use_swa=*/bool> {
public:
// Returns whether to use a SWA given test parameterization.
bool UseSwa() const { return GetParam(); }
// Installs an SWA or a non-SWA depending on test parameterization, returning
// the `AppId` of the installed app. Note that this method may only be invoked
// once per test.
AppId InstallWebApp() {
DCHECK(!app_id_.has_value());
if (UseSwa()) {
web_app::WebAppProvider::GetForSystemWebApps(profile())
->system_web_app_manager()
.InstallSystemAppsForTesting();
app_id_ = *web_app::GetAppIdForSystemWebApp(profile(),
web_app::SystemAppType::HELP);
} else {
const GURL app_url = GetSecureAppURL();
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = app_url;
web_app_info->scope = app_url.GetWithoutFilename();
web_app_info->theme_color = SK_ColorBLUE;
web_app_info->background_color = SK_ColorBLUE;
app_id_ = WebAppBrowserTest::InstallWebApp(std::move(web_app_info));
}
return app_id_.value();
}
// Returns the background color for the installed SWA or non-SWA depending on
// test parameterization. Note that this method may only be invoked after
// having called `InstallWebApp()`.
absl::optional<SkColor> GetBackgroundColorFromRegistrar() {
auto* provider = WebAppProvider::GetForTest(profile());
return provider->registrar().GetAppBackgroundColor(app_id_.value());
}
private:
absl::optional<AppId> app_id_;
};
#if BUILDFLAG(IS_CHROMEOS_ASH)
INSTANTIATE_TEST_SUITE_P(All,
WebAppBackgroundColorChangeBrowserTest,
/*use_swa=*/testing::Bool());
#else
INSTANTIATE_TEST_SUITE_P(All,
WebAppBackgroundColorChangeBrowserTest,
/*use_swa=*/testing::Values(false));
#endif
IN_PROC_BROWSER_TEST_P(WebAppBackgroundColorChangeBrowserTest,
BackgroundColorChange) {
const AppId app_id = WebAppBackgroundColorChangeBrowserTest::InstallWebApp();
Browser* const app_browser = LaunchWebAppBrowser(app_id);
content::WebContents* const web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
// Verify background color is resolved from the registrar prior to the web
// contents background color being loaded.
const SkColor registrar_color = GetBackgroundColorFromRegistrar().value();
EXPECT_EQ(app_browser->app_controller()->GetBackgroundColor().value(),
registrar_color);
// Wait for the web contents background color to load. For SWAs, the app
// controller should still resolve background color from the registrar while
// for non-SWAs the app controller should resolve background color from the
// web contents.
{
content::BackgroundColorChangeWaiter waiter(web_contents);
waiter.Wait();
EXPECT_EQ(app_browser->app_controller()->GetBackgroundColor().value(),
UseSwa() ? registrar_color
: web_contents->GetBackgroundColor().value());
}
content::AwaitDocumentOnLoadCompleted(web_contents);
// Changing web contents background color should update download shelf theme
// for non-SWAs. For SWAs, background color and shelf theme should still be
// resolved from the registrar.
{
content::BackgroundColorChangeWaiter waiter(web_contents);
EXPECT_TRUE(content::ExecuteScript(
web_contents, "document.body.style.backgroundColor = 'cyan';"));
waiter.Wait();
const SkColor web_contents_color =
web_contents->GetBackgroundColor().value();
EXPECT_EQ(web_contents_color, SK_ColorCYAN);
EXPECT_EQ(app_browser->app_controller()->GetBackgroundColor().value(),
UseSwa() ? registrar_color : web_contents_color);
SkColor download_shelf_color;
app_browser->app_controller()->GetThemeSupplier()->GetColor(
ThemeProperties::COLOR_DOWNLOAD_SHELF, &download_shelf_color);
EXPECT_EQ(download_shelf_color,
UseSwa() ? registrar_color : web_contents_color);
}
}
// This tests that we don't crash when launching a PWA window with an
// autogenerated user theme set.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, AutoGeneratedUserThemeCrash) {
ThemeServiceFactory::GetForProfile(browser()->profile())
->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = GURL(kExampleURL);
AppId app_id = InstallWebApp(std::move(web_app_info));
LaunchWebAppBrowser(app_id);
}
// Check the 'Open in Chrome' menu button for web app windows.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, OpenInChrome) {
const GURL app_url(kExampleURL);
const AppId app_id = InstallPWA(app_url);
{
Browser* const app_browser = LaunchWebAppBrowser(app_id);
EXPECT_EQ(1, app_browser->tab_strip_model()->count());
EXPECT_EQ(1, browser()->tab_strip_model()->count());
ASSERT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
chrome::ExecuteCommand(app_browser, IDC_OPEN_IN_CHROME);
// The browser frame is closed next event loop so it's still safe to access
// here.
EXPECT_EQ(0, app_browser->tab_strip_model()->count());
EXPECT_EQ(2, browser()->tab_strip_model()->count());
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
EXPECT_EQ(
app_url,
browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());
}
// Wait until the browser actually gets closed. This invalidates
// |app_browser|.
content::RunAllPendingInMessageLoop();
ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
}
// Check the 'App info' menu button for web app windows.
#if defined(OS_LINUX)
// Disabled on Linux because the test only completes unless unrelated
// events are received to wake up the message loop.
#define MAYBE_AppInfoOpensPageInfo DISABLED_AppInfoOpensPageInfo
#else
#define MAYBE_AppInfoOpensPageInfo AppInfoOpensPageInfo
#endif
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, MAYBE_AppInfoOpensPageInfo) {
const GURL app_url(kExampleURL);
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowser(app_id);
base::RunLoop run_loop_dialog_created;
GetPageInfoDialogCreatedCallbackForTesting() =
run_loop_dialog_created.QuitClosure();
chrome::ExecuteCommand(app_browser, IDC_WEB_APP_MENU_APP_INFO);
// Wait for dialog to be created, timeout will trigger the test to fail.
run_loop_dialog_created.Run();
// The test closure should have run. But clear the global in case it hasn't.
EXPECT_FALSE(GetPageInfoDialogCreatedCallbackForTesting());
GetPageInfoDialogCreatedCallbackForTesting().Reset();
}
// Check that last launch time is set after launch.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, AppLastLaunchTime) {
const GURL app_url(kExampleURL);
const AppId app_id = InstallPWA(app_url);
auto* provider = WebAppProvider::GetForTest(profile());
// last_launch_time is not set before launch
EXPECT_TRUE(provider->registrar().GetAppLastLaunchTime(app_id).is_null());
auto before_launch = base::Time::Now();
LaunchWebAppBrowser(app_id);
EXPECT_TRUE(provider->registrar().GetAppLastLaunchTime(app_id) >=
before_launch);
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, DISABLE_POSIX(WithMinimalUiButtons)) {
EXPECT_TRUE(HasMinimalUiButtons(DisplayMode::kBrowser, absl::nullopt,
/*open_as_window=*/true));
EXPECT_TRUE(HasMinimalUiButtons(DisplayMode::kMinimalUi, absl::nullopt,
/*open_as_window=*/true));
EXPECT_TRUE(HasMinimalUiButtons(DisplayMode::kBrowser, absl::nullopt,
/*open_as_window=*/false));
EXPECT_TRUE(HasMinimalUiButtons(DisplayMode::kMinimalUi, absl::nullopt,
/*open_as_window=*/false));
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, WithoutMinimalUiButtons) {
EXPECT_FALSE(HasMinimalUiButtons(DisplayMode::kStandalone, absl::nullopt,
/*open_as_window=*/true));
EXPECT_FALSE(HasMinimalUiButtons(DisplayMode::kFullscreen, absl::nullopt,
/*open_as_window=*/true));
EXPECT_FALSE(HasMinimalUiButtons(DisplayMode::kStandalone, absl::nullopt,
/*open_as_window=*/false));
EXPECT_FALSE(HasMinimalUiButtons(DisplayMode::kFullscreen, absl::nullopt,
/*open_as_window=*/false));
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, DISABLE_POSIX(DisplayOverride)) {
GURL test_url = https_server()->GetURL(
"/banners/"
"manifest_test_page.html?manifest=manifest_display_override.json");
NavigateToURLAndWait(browser(), test_url);
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
auto* provider = WebAppProvider::GetForTest(profile());
std::vector<DisplayMode> app_display_mode_override =
provider->registrar().GetAppDisplayModeOverride(app_id);
ASSERT_EQ(2u, app_display_mode_override.size());
EXPECT_EQ(DisplayMode::kMinimalUi, app_display_mode_override[0]);
EXPECT_EQ(DisplayMode::kStandalone, app_display_mode_override[1]);
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
WithMinimalUiButtons_DisplayOverride) {
EXPECT_TRUE(HasMinimalUiButtons(DisplayMode::kStandalone,
DisplayMode::kBrowser,
/*open_as_window=*/true));
EXPECT_TRUE(HasMinimalUiButtons(DisplayMode::kStandalone,
DisplayMode::kMinimalUi,
/*open_as_window=*/true));
EXPECT_TRUE(HasMinimalUiButtons(DisplayMode::kStandalone,
DisplayMode::kBrowser,
/*open_as_window=*/false));
EXPECT_TRUE(HasMinimalUiButtons(DisplayMode::kStandalone,
DisplayMode::kMinimalUi,
/*open_as_window=*/false));
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
WithoutMinimalUiButtons_DisplayOverride) {
EXPECT_FALSE(HasMinimalUiButtons(DisplayMode::kMinimalUi,
DisplayMode::kStandalone,
/*open_as_window=*/true));
EXPECT_FALSE(HasMinimalUiButtons(DisplayMode::kMinimalUi,
DisplayMode::kFullscreen,
/*open_as_window=*/true));
EXPECT_FALSE(HasMinimalUiButtons(DisplayMode::kMinimalUi,
DisplayMode::kStandalone,
/*open_as_window=*/false));
EXPECT_FALSE(HasMinimalUiButtons(DisplayMode::kMinimalUi,
DisplayMode::kFullscreen,
/*open_as_window=*/false));
}
// Tests that desktop PWAs open out-of-scope links with a custom toolbar.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, DesktopPWAsOpenLinksInApp) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
NavigateToURLAndWait(app_browser, app_url);
ASSERT_TRUE(app_browser->app_controller());
NavigateAndCheckForToolbar(app_browser, GURL(kExampleURL), true);
}
// Tests that desktop PWAs open links in a new tab at the end of the tabstrip of
// the last active browser.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, DesktopPWAsOpenLinksInNewTab) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
NavigateToURLAndWait(app_browser, app_url);
ASSERT_TRUE(app_browser->app_controller());
EXPECT_EQ(chrome::GetTotalBrowserCount(), 2u);
Browser* browser2 = CreateBrowser(app_browser->profile());
EXPECT_EQ(chrome::GetTotalBrowserCount(), 3u);
TabStripModel* model2 = browser2->tab_strip_model();
chrome::AddTabAt(browser2, GURL(), -1, true);
EXPECT_EQ(model2->count(), 2);
model2->SelectPreviousTab();
EXPECT_EQ(model2->active_index(), 0);
NavigateParams param(app_browser, GURL("http://www.google.com/"),
ui::PAGE_TRANSITION_LINK);
param.window_action = NavigateParams::SHOW_WINDOW;
param.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
ui_test_utils::NavigateToURL(&param);
EXPECT_EQ(chrome::GetTotalBrowserCount(), 3u);
EXPECT_EQ(model2->count(), 3);
EXPECT_EQ(param.browser, browser2);
EXPECT_EQ(model2->active_index(), 2);
EXPECT_EQ(param.navigated_or_inserted_contents,
model2->GetActiveWebContents());
}
// Tests that desktop PWAs are opened at the correct size.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, PWASizeIsCorrectlyRestored) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
EXPECT_TRUE(AppBrowserController::IsWebApp(app_browser));
NavigateToURLAndWait(app_browser, app_url);
const gfx::Rect bounds = gfx::Rect(50, 50, 550, 500);
app_browser->window()->SetBounds(bounds);
app_browser->window()->Close();
Browser* const new_browser = LaunchWebAppBrowser(app_id);
EXPECT_EQ(new_browser->window()->GetBounds(), bounds);
}
// Tests that desktop PWAs are reopened at the correct size.
IN_PROC_BROWSER_TEST_F(WebAppTabRestoreBrowserTest,
ReopenedPWASizeIsCorrectlyRestored) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
EXPECT_TRUE(AppBrowserController::IsWebApp(app_browser));
NavigateToURLAndWait(app_browser, app_url);
const gfx::Rect bounds = gfx::Rect(50, 50, 550, 500);
app_browser->window()->SetBounds(bounds);
app_browser->window()->Close();
content::WebContentsAddedObserver new_contents_observer;
sessions::TabRestoreService* const service =
TabRestoreServiceFactory::GetForProfile(profile());
ASSERT_GT(service->entries().size(), 0U);
sessions::TabRestoreService::Entry* entry = service->entries().front().get();
ASSERT_EQ(sessions::TabRestoreService::WINDOW, entry->type);
const auto* entry_win =
static_cast<sessions::TabRestoreService::Window*>(entry);
EXPECT_EQ(bounds, entry_win->bounds);
service->RestoreMostRecentEntry(nullptr);
content::WebContents* const restored_web_contents =
new_contents_observer.GetWebContents();
Browser* const restored_browser =
chrome::FindBrowserWithWebContents(restored_web_contents);
EXPECT_EQ(restored_browser->override_bounds(), bounds);
}
// Tests that using window.open to create a popup window out of scope results in
// a correctly sized window.
// TODO(crbug.com/1234260): Stabilize the test.
#if defined(OS_LINUX)
#define MAYBE_OffScopePWAPopupsHaveCorrectSize \
DISABLED_OffScopePWAPopupsHaveCorrectSize
#else
#define MAYBE_OffScopePWAPopupsHaveCorrectSize OffScopePWAPopupsHaveCorrectSize
#endif
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
MAYBE_OffScopePWAPopupsHaveCorrectSize) {
// TODO(crbug.com/1240482): the test expectations fail if the window gets CSD
// and becomes smaller because of that. Investigate this and remove the line
// below if possible.
ui::ScopedDisableClientSideDecorationsForTest scoped_disabled_csd;
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowser(app_id);
EXPECT_TRUE(AppBrowserController::IsWebApp(app_browser));
const GURL offscope_url =
https_server()->GetURL("offscope.site.test", "/simple.html");
const gfx::Size size(500, 500);
Browser* const popup_browser =
OpenPopupAndWait(app_browser, offscope_url, size);
// The navigation should have happened in a new window.
EXPECT_NE(popup_browser, app_browser);
// The popup browser should be a PWA.
EXPECT_TRUE(AppBrowserController::IsWebApp(popup_browser));
// Toolbar should be shown, as the popup is out of scope.
EXPECT_TRUE(popup_browser->app_controller()->ShouldShowCustomTabBar());
// Skip animating the toolbar visibility.
popup_browser->app_controller()->UpdateCustomTabBarVisibility(false);
// The popup window should be the size we specified.
EXPECT_EQ(size, popup_browser->window()->GetContentsSize());
}
// Tests that using window.open to create a popup window in scope results in
// a correctly sized window.
// TODO(crbug.com/1234260): Stabilize the test.
#if defined(OS_LINUX)
#define MAYBE_InScopePWAPopupsHaveCorrectSize \
DISABLED_InScopePWAPopupsHaveCorrectSize
#else
#define MAYBE_InScopePWAPopupsHaveCorrectSize InScopePWAPopupsHaveCorrectSize
#endif
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
MAYBE_InScopePWAPopupsHaveCorrectSize) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowser(app_id);
EXPECT_TRUE(AppBrowserController::IsWebApp(app_browser));
const gfx::Size size(500, 500);
Browser* const popup_browser = OpenPopupAndWait(app_browser, app_url, size);
// The navigation should have happened in a new window.
EXPECT_NE(popup_browser, app_browser);
// The popup browser should be a PWA.
EXPECT_TRUE(AppBrowserController::IsWebApp(popup_browser));
// Toolbar should not be shown, as the popup is in scope.
EXPECT_FALSE(popup_browser->app_controller()->ShouldShowCustomTabBar());
// Skip animating the toolbar visibility.
popup_browser->app_controller()->UpdateCustomTabBarVisibility(false);
// The popup window should be the size we specified.
EXPECT_EQ(size, popup_browser->window()->GetContentsSize());
}
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
// Tests that app windows are correctly restored.
IN_PROC_BROWSER_TEST_F(WebAppTabRestoreBrowserTest, RestoreAppWindow) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
ASSERT_TRUE(app_browser->is_type_app());
app_browser->window()->Close();
content::WebContentsAddedObserver new_contents_observer;
sessions::TabRestoreService* const service =
TabRestoreServiceFactory::GetForProfile(profile());
service->RestoreMostRecentEntry(nullptr);
content::WebContents* const restored_web_contents =
new_contents_observer.GetWebContents();
Browser* const restored_browser =
chrome::FindBrowserWithWebContents(restored_web_contents);
EXPECT_TRUE(restored_browser->is_type_app());
}
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)
// Test navigating to an out of scope url on the same origin causes the url
// to be shown to the user.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
LocationBarIsVisibleOffScopeOnSameOrigin) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
// Toolbar should not be visible in the app.
ASSERT_FALSE(app_browser->app_controller()->ShouldShowCustomTabBar());
// The installed PWA's scope is app.com:{PORT}/ssl,
// so app.com:{PORT}/accessibility_fail.html is out of scope.
const GURL out_of_scope = GetURLForPath("/accessibility_fail.html");
NavigateToURLAndWait(app_browser, out_of_scope);
// Location should be visible off scope.
ASSERT_TRUE(app_browser->app_controller()->ShouldShowCustomTabBar());
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, UpgradeWithoutCustomTabBar) {
const GURL secure_app_url =
https_server()->GetURL("app.site.test", "/empty.html");
GURL::Replacements rep;
rep.SetSchemeStr(url::kHttpScheme);
const GURL app_url = secure_app_url.ReplaceComponents(rep);
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowser(app_id);
NavigateToURLAndWait(app_browser, secure_app_url);
EXPECT_FALSE(app_browser->app_controller()->ShouldShowCustomTabBar());
const GURL off_origin_url =
https_server()->GetURL("example.org", "/empty.html");
NavigateToURLAndWait(app_browser, off_origin_url);
EXPECT_EQ(app_browser->app_controller()->ShouldShowCustomTabBar(), true);
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, OverscrollEnabled) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
// Overscroll is only enabled on Aura platforms currently.
#if defined(USE_AURA)
EXPECT_TRUE(app_browser->CanOverscrollContent());
#else
EXPECT_FALSE(app_browser->CanOverscrollContent());
#endif
}
// Check the 'Copy URL' menu button for Web App windows.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, CopyURL) {
const GURL app_url(kExampleURL);
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
content::BrowserTestClipboardScope test_clipboard_scope;
chrome::ExecuteCommand(app_browser, IDC_COPY_URL);
ui::Clipboard* const clipboard = ui::Clipboard::GetForCurrentThread();
std::u16string result;
clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
&result);
EXPECT_EQ(result, kExampleURL16);
}
// Tests that the command for popping a tab out to a PWA window is disabled in
// incognito.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, PopOutDisabledInIncognito) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const incognito_browser = OpenURLOffTheRecord(profile(), app_url);
auto app_menu_model =
std::make_unique<AppMenuModel>(nullptr, incognito_browser);
app_menu_model->Init();
ui::MenuModel* model = app_menu_model.get();
int index = -1;
ASSERT_TRUE(app_menu_model->GetModelAndIndexForCommandId(
IDC_OPEN_IN_PWA_WINDOW, &model, &index));
EXPECT_FALSE(model->IsEnabledAt(index));
}
// Tests that web app menus don't crash when no tabs are selected.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, NoTabSelectedMenuCrash) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
app_browser->tab_strip_model()->CloseAllTabs();
auto app_menu_model = std::make_unique<WebAppMenuModel>(
/*provider=*/nullptr, app_browser);
app_menu_model->Init();
}
// Tests that PWA menus have an uninstall option.
// TODO(crbug.com/1271118): Flaky on mac arm64.
#if defined(OS_MAC) && defined(ARCH_CPU_ARM64)
#define MAYBE_UninstallMenuOption DISABLED_UninstallMenuOption
#else
#define MAYBE_UninstallMenuOption UninstallMenuOption
#endif
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, MAYBE_UninstallMenuOption) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
auto app_menu_model = std::make_unique<WebAppMenuModel>(
/*provider=*/nullptr, app_browser);
app_menu_model->Init();
ui::MenuModel* model = app_menu_model.get();
int index = -1;
const bool found = app_menu_model->GetModelAndIndexForCommandId(
WebAppMenuModel::kUninstallAppCommandId, &model, &index);
#if defined(OS_CHROMEOS)
EXPECT_FALSE(found);
#else
EXPECT_TRUE(found);
EXPECT_TRUE(model->IsEnabledAt(index));
base::HistogramTester tester;
app_menu_model->ExecuteCommand(WebAppMenuModel::kUninstallAppCommandId,
/*event_flags=*/0);
tester.ExpectUniqueSample("HostedAppFrame.WrenchMenu.MenuAction",
MENU_ACTION_UNINSTALL_APP, 1);
tester.ExpectUniqueSample("WrenchMenu.MenuAction", MENU_ACTION_UNINSTALL_APP,
1);
#endif // defined(OS_CHROMEOS)
}
// Tests that both installing a PWA and creating a shortcut app are disabled for
// incognito windows.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ShortcutMenuOptionsInIncognito) {
Browser* const incognito_browser = CreateIncognitoBrowser(profile());
EXPECT_EQ(webapps::AppBannerManagerDesktop::FromWebContents(
incognito_browser->tab_strip_model()->GetActiveWebContents()),
nullptr);
NavigateToURLAndWait(incognito_browser, GetInstallableAppURL());
// Wait sufficient time for an installability check to occur.
EXPECT_TRUE(
NavigateAndAwaitInstallabilityCheck(browser(), GetInstallableAppURL()));
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, incognito_browser),
kDisabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, incognito_browser),
kNotPresent);
}
// Tests that both installing a PWA and creating a shortcut app are disabled for
// an error page.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ShortcutMenuOptionsForErrorPage) {
EXPECT_FALSE(NavigateAndAwaitInstallabilityCheck(
browser(), https_server()->GetURL("/invalid_path.html")));
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, browser()), kDisabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kNotPresent);
}
// Tests that both installing a PWA and creating a shortcut app are available
// for an installable PWA.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
ShortcutMenuOptionsForInstallablePWA) {
EXPECT_TRUE(
NavigateAndAwaitInstallabilityCheck(browser(), GetInstallableAppURL()));
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, browser()), kEnabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kEnabled);
}
// Tests that both installing a PWA and creating a shortcut app are disabled
// when page crashes.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ShortcutMenuOptionsForCrashedTab) {
EXPECT_TRUE(
NavigateAndAwaitInstallabilityCheck(browser(), GetInstallableAppURL()));
content::WebContents* tab_contents =
browser()->tab_strip_model()->GetActiveWebContents();
{
content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
content::RenderFrameDeletedObserver crash_observer(
tab_contents->GetMainFrame());
tab_contents->GetMainFrame()->GetProcess()->Shutdown(1);
crash_observer.WaitUntilDeleted();
}
ASSERT_TRUE(tab_contents->IsCrashed());
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, browser()), kDisabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kDisabled);
}
// Tests that an installed PWA is not used when out of scope by one path level.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
DISABLE_POSIX(MenuOptionsOutsideInstalledPwaScope)) {
NavigateToURLAndWait(
browser(),
https_server()->GetURL("/banners/scope_is_start_url/index.html"));
test::InstallPwaForCurrentUrl(browser());
// Open a page that is one directory up from the installed PWA.
Browser* const new_browser = NavigateInNewWindowAndAwaitInstallabilityCheck(
https_server()->GetURL("/banners/no_manifest_test_page.html"));
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, new_browser), kEnabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, new_browser), kNotPresent);
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, new_browser),
kNotPresent);
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
DISABLE_POSIX(InstallInstallableSite)) {
base::Time before_install_time = base::Time::Now();
base::UserActionTester user_action_tester;
NavigateToURLAndWait(browser(), GetInstallableAppURL());
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
auto* provider = WebAppProvider::GetForTest(profile());
EXPECT_EQ(provider->registrar().GetAppShortName(app_id),
GetInstallableAppName());
// Installed PWAs should launch in their own window.
EXPECT_EQ(provider->registrar().GetAppUserDisplayMode(app_id),
blink::mojom::DisplayMode::kStandalone);
// Installed PWAs should have install time set.
EXPECT_TRUE(provider->registrar().GetAppInstallTime(app_id) >=
before_install_time);
EXPECT_EQ(1, user_action_tester.GetActionCount("InstallWebAppFromMenu"));
EXPECT_EQ(0, user_action_tester.GetActionCount("CreateShortcut"));
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Apps on Chrome OS should not be pinned after install.
EXPECT_FALSE(ChromeShelfController::instance()->IsAppPinned(app_id));
#endif
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
DISABLE_POSIX(CanInstallOverBrowserTabPwa)) {
NavigateToURLAndWait(browser(), GetInstallableAppURL());
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
// Change display mode to open in tab.
auto* provider = WebAppProvider::GetForTest(profile());
provider->sync_bridge().SetAppUserDisplayMode(
app_id, blink::mojom::DisplayMode::kBrowser, /*is_user_action=*/false);
Browser* const new_browser =
NavigateInNewWindowAndAwaitInstallabilityCheck(GetInstallableAppURL());
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, new_browser), kEnabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, new_browser), kEnabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, new_browser),
kNotPresent);
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
DISABLE_POSIX(CannotInstallOverWindowPwa)) {
NavigateToURLAndWait(browser(), GetInstallableAppURL());
test::InstallPwaForCurrentUrl(browser());
// Avoid any interference if active browser was changed by PWA install.
Browser* const new_browser =
NavigateInNewWindowAndAwaitInstallabilityCheck(GetInstallableAppURL());
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, new_browser), kEnabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, new_browser), kNotPresent);
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, new_browser),
kEnabled);
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, NoOpenInAppForBrowserTabPwa) {
GURL app_url = https_server()->GetURL(
"/web_apps/get_manifest.html?display_browser.json");
AppId app_id = InstallWebAppFromPage(browser(), app_url);
// Change display mode to open in tab.
auto* provider = WebAppProvider::GetForTest(profile());
provider->sync_bridge().SetAppUserDisplayMode(
app_id, blink::mojom::DisplayMode::kBrowser, /*is_user_action=*/false);
NavigateToURLAndWait(browser(), app_url);
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, browser()), kEnabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kNotPresent);
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser()),
kNotPresent);
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, CanInstallWithPolicyPwa) {
ExternalInstallOptions options = CreateInstallOptions(GetInstallableAppURL());
options.install_source = ExternalInstallSource::kExternalPolicy;
ExternallyManagedAppManagerInstall(profile(), options);
// Avoid any interference if active browser was changed by PWA install.
Browser* const new_browser =
NavigateInNewWindowAndAwaitInstallabilityCheck(GetInstallableAppURL());
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, new_browser), kEnabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, new_browser), kNotPresent);
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, new_browser),
kEnabled);
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
CannotUninstallPolicyWebAppAfterUserInstall) {
GURL install_url = GetInstallableAppURL();
ExternalInstallOptions options = CreateInstallOptions(install_url);
options.install_source = ExternalInstallSource::kExternalPolicy;
ExternallyManagedAppManagerInstall(profile(), options);
auto* provider = WebAppProvider::GetForTest(browser()->profile());
ExternallyInstalledWebAppPrefs prefs(browser()->profile()->GetPrefs());
AppId app_id = prefs.LookupAppId(install_url).value();
EXPECT_FALSE(provider->install_finalizer().CanUserUninstallWebApp(app_id));
InstallWebAppFromPage(browser(), install_url);
// Performing a user install on the page should not override the "policy"
// install source.
EXPECT_FALSE(provider->install_finalizer().CanUserUninstallWebApp(app_id));
const WebApp& web_app = *provider->registrar().GetAppById(app_id);
EXPECT_TRUE(web_app.IsSynced());
EXPECT_TRUE(web_app.IsPolicyInstalledApp());
}
// Tests that the command for OpenActiveTabInPwaWindow is available for secure
// pages in an app's scope.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ReparentWebAppForSecureActiveTab) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
NavigateToURLAndWait(browser(), app_url);
content::WebContents* tab_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_EQ(tab_contents->GetLastCommittedURL(), app_url);
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser()),
kEnabled);
Browser* const app_browser = ReparentWebAppForActiveTab(browser());
ASSERT_EQ(app_browser->app_controller()->app_id(), app_id);
}
#if defined(OS_MAC) || defined(OS_WIN)
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest,
DISABLE_POSIX(ShortcutIconCorrectColor)) {
os_hooks_suppress_.reset();
base::ScopedAllowBlockingForTesting allow_blocking;
std::unique_ptr<ScopedShortcutOverrideForTesting> shortcut_override =
OverrideShortcutsForTesting();
NavigateToURLAndWait(
browser(),
https_server()->GetURL(
"/banners/manifest_test_page.html?manifest=manifest_one_icon.json"));
// Wait for OS hooks and installation to complete and the app to launch.
base::RunLoop run_loop_install;
WebAppTestRegistryObserverAdapter observer(profile());
observer.SetWebAppInstalledWithOsHooksDelegate(base::BindLambdaForTesting(
[&](const AppId& installed_app_id) { run_loop_install.Quit(); }));
content::WindowedNotificationObserver app_loaded_observer(
content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
content::NotificationService::AllSources());
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
run_loop_install.Run();
app_loaded_observer.Wait();
base::FilePath shortcut_path;
auto* provider = WebAppProvider::GetForTest(profile());
std::vector<SkColor> expected_pixel_colors = {SkColorSetRGB(92, 92, 92)};
#if defined(OS_MAC)
shortcut_path = shortcut_override->chrome_apps_folder.GetPath().Append(
provider->registrar().GetAppShortName(app_id) + ".app");
#elif defined(OS_WIN)
shortcut_path = shortcut_override->application_menu.GetPath().AppendASCII(
provider->registrar().GetAppShortName(app_id) + ".lnk");
expected_pixel_colors.push_back(SkColorSetRGB(91, 91, 91));
expected_pixel_colors.push_back(SkColorSetRGB(90, 90, 90));
#endif
SkColor icon_pixel_color = GetIconTopLeftColor(shortcut_path);
EXPECT_TRUE(std::find(expected_pixel_colors.begin(),
expected_pixel_colors.end(),
icon_pixel_color) != expected_pixel_colors.end())
<< "Actual color (RGB) is: "
<< color_utils::SkColorToRgbString(icon_pixel_color);
base::RunLoop run_loop_uninstall;
provider->install_finalizer().UninstallWebApp(
app_id, webapps::WebappUninstallSource::kAppMenu,
base::BindLambdaForTesting([&](bool uninstalled) {
DCHECK(uninstalled);
run_loop_uninstall.Quit();
}));
run_loop_uninstall.Run();
}
#endif
#if defined(OS_WIN)
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_ShortcutMenu, ShortcutsMenu) {
struct ShortcutsMenuItem {
public:
ShortcutsMenuItem() : command_line(base::CommandLine::NO_PROGRAM) {}
// The string to be displayed in a shortcut menu item.
std::u16string title;
// Used for storing and appending command-line arguments.
base::CommandLine command_line;
// The absolute path to an icon to be displayed in a shortcut menu item.
base::FilePath icon_path;
};
os_hooks_suppress_.reset();
base::ScopedAllowBlockingForTesting allow_blocking;
std::unique_ptr<ScopedShortcutOverrideForTesting> shortcut_override =
OverrideShortcutsForTesting();
NavigateToURLAndWait(
browser(),
https_server()->GetURL(
"/banners/"
"manifest_test_page.html?manifest=manifest_with_shortcuts.json"));
std::vector<ShortcutsMenuItem> shortcuts_menu_items;
auto SaveJumpList = base::BindLambdaForTesting(
[&](std::wstring,
const std::vector<scoped_refptr<ShellLinkItem>>& link_items) -> bool {
for (auto& shell_item : link_items) {
ShortcutsMenuItem item;
item.title = shell_item->title();
item.icon_path = shell_item->icon_path();
item.command_line = *shell_item->GetCommandLine();
shortcuts_menu_items.push_back(item);
}
return true;
});
SetUpdateJumpListForTesting(SaveJumpList);
// Wait for OS hooks and installation to complete and the app to launch.
base::RunLoop run_loop_install;
WebAppTestRegistryObserverAdapter observer(profile());
observer.SetWebAppInstalledWithOsHooksDelegate(base::BindLambdaForTesting(
[&](const AppId& installed_app_id) { run_loop_install.Quit(); }));
content::WindowedNotificationObserver app_loaded_observer(
content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
content::NotificationService::AllSources());
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
run_loop_install.Run();
app_loaded_observer.Wait();
EXPECT_EQ(2U, shortcuts_menu_items.size());
EXPECT_EQ(u"shortcut1", shortcuts_menu_items[0].title);
EXPECT_EQ(u"shortcut2", shortcuts_menu_items[1].title);
EXPECT_TRUE(base::PathExists(shortcuts_menu_items[0].icon_path));
EXPECT_TRUE(base::PathExists(shortcuts_menu_items[1].icon_path));
EXPECT_EQ(app_id, shortcuts_menu_items[0].command_line.GetSwitchValueASCII(
switches::kAppId));
EXPECT_EQ(app_id, shortcuts_menu_items[1].command_line.GetSwitchValueASCII(
switches::kAppId));
EXPECT_NE(
std::string::npos,
shortcuts_menu_items[0]
.command_line
.GetSwitchValueASCII(switches::kAppLaunchUrlForShortcutsMenuItem)
.find("/banners/launch_url1"));
EXPECT_NE(
std::string::npos,
shortcuts_menu_items[1]
.command_line
.GetSwitchValueASCII(switches::kAppLaunchUrlForShortcutsMenuItem)
.find("/banners/launch_url2"));
base::RunLoop run_loop_uninstall;
WebAppProvider::GetForTest(profile())->install_finalizer().UninstallWebApp(
app_id, webapps::WebappUninstallSource::kAppMenu,
base::BindLambdaForTesting([&](bool uninstalled) {
DCHECK(uninstalled);
run_loop_uninstall.Quit();
}));
run_loop_uninstall.Run();
}
#endif
#if defined(OS_MAC) || defined(OS_WIN) || defined(OS_LINUX)
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, WebAppCreateAndDeleteShortcut) {
os_hooks_suppress_.reset();
base::ScopedAllowBlockingForTesting allow_blocking;
std::unique_ptr<ScopedShortcutOverrideForTesting> shortcut_override =
OverrideShortcutsForTesting();
auto* provider = WebAppProvider::GetForTest(profile());
NavigateToURLAndWait(browser(), GetInstallableAppURL());
// Wait for OS hooks and installation to complete and the app to launch.
base::RunLoop run_loop_install;
WebAppTestRegistryObserverAdapter observer(profile());
observer.SetWebAppInstalledWithOsHooksDelegate(base::BindLambdaForTesting(
[&](const AppId& installed_app_id) { run_loop_install.Quit(); }));
content::WindowedNotificationObserver app_loaded_observer(
content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
content::NotificationService::AllSources());
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
run_loop_install.Run();
app_loaded_observer.Wait();
EXPECT_TRUE(provider->registrar().IsInstalled(app_id));
EXPECT_EQ(provider->registrar().GetAppShortName(app_id),
GetInstallableAppName());
#if defined(OS_WIN)
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::wstring shortcut_filename = converter.from_bytes(
provider->registrar().GetAppShortName(app_id) + ".lnk");
base::FilePath desktop_shortcut_path =
shortcut_override->desktop.GetPath().Append(shortcut_filename);
base::FilePath app_menu_shortcut_path =
shortcut_override->application_menu.GetPath().Append(shortcut_filename);
EXPECT_TRUE(base::PathExists(desktop_shortcut_path));
EXPECT_TRUE(base::PathExists(app_menu_shortcut_path));
#elif defined(OS_MAC)
std::string shortcut_filename =
provider->registrar().GetAppShortName(app_id) + ".app";
base::FilePath app_shortcut_path =
shortcut_override->chrome_apps_folder.GetPath().Append(shortcut_filename);
EXPECT_TRUE(base::PathExists(app_shortcut_path));
#elif defined(OS_LINUX)
std::string shortcut_filename = "chrome-" + app_id + "-Default.desktop";
base::FilePath desktop_shortcut_path =
shortcut_override->desktop.GetPath().Append(shortcut_filename);
EXPECT_TRUE(base::PathExists(desktop_shortcut_path));
#endif
// Unistall the web app
base::RunLoop run_loop_uninstall;
provider->install_finalizer().UninstallWebApp(
app_id, webapps::WebappUninstallSource::kAppMenu,
base::BindLambdaForTesting([&](bool uninstalled) {
DCHECK(uninstalled);
run_loop_uninstall.Quit();
}));
run_loop_uninstall.Run();
#if defined(OS_WIN)
EXPECT_FALSE(base::PathExists(desktop_shortcut_path));
EXPECT_FALSE(base::PathExists(app_menu_shortcut_path));
#elif defined(OS_MAC)
EXPECT_FALSE(base::PathExists(app_shortcut_path));
#elif defined(OS_LINUX)
EXPECT_FALSE(base::PathExists(desktop_shortcut_path));
#endif
} // namespace web_app
#endif
// Tests that reparenting the last browser tab doesn't close the browser window.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ReparentLastBrowserTab) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
NavigateToURLAndWait(browser(), app_url);
Browser* const app_browser = ReparentWebAppForActiveTab(browser());
ASSERT_EQ(app_browser->app_controller()->app_id(), app_id);
ASSERT_TRUE(IsBrowserOpen(browser()));
EXPECT_EQ(browser()->tab_strip_model()->count(), 1);
}
// Tests that reparenting a display: browser app tab results in a minimal-ui
// app window.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ReparentDisplayBrowserApp) {
const GURL app_url = GetSecureAppURL();
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = app_url;
web_app_info->scope = app_url.GetWithoutFilename();
web_app_info->display_mode = DisplayMode::kBrowser;
web_app_info->user_display_mode = DisplayMode::kStandalone;
web_app_info->title = u"A Shortcut App";
const AppId app_id = InstallWebApp(std::move(web_app_info));
NavigateToURLAndWait(browser(), app_url);
content::WebContents* tab_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_EQ(tab_contents->GetLastCommittedURL(), app_url);
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser()),
kEnabled);
EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_OPEN_IN_PWA_WINDOW));
Browser* const app_browser = BrowserList::GetInstance()->GetLastActive();
ASSERT_EQ(app_browser->app_controller()->app_id(), app_id);
EXPECT_TRUE(app_browser->app_controller()->HasMinimalUiButtons());
auto* provider = WebAppProvider::GetForTest(profile());
EXPECT_EQ(provider->registrar().GetAppUserDisplayMode(app_id),
DisplayMode::kStandalone);
EXPECT_EQ(provider->registrar().GetAppEffectiveDisplayMode(app_id),
DisplayMode::kMinimalUi);
}
// Tests that the manifest name of the current installable site is used in the
// installation menu text.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, InstallToShelfContainsAppName) {
EXPECT_TRUE(
NavigateAndAwaitInstallabilityCheck(browser(), GetInstallableAppURL()));
auto app_menu_model = std::make_unique<AppMenuModel>(nullptr, browser());
app_menu_model->Init();
ui::MenuModel* model = app_menu_model.get();
int index = -1;
EXPECT_TRUE(app_menu_model->GetModelAndIndexForCommandId(IDC_INSTALL_PWA,
&model, &index));
EXPECT_EQ(app_menu_model.get(), model);
EXPECT_EQ(model->GetLabelAt(index), u"Install Manifest test app…");
}
// Check that no assertions are hit when showing a permission request bubble.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, PermissionBubble) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
content::RenderFrameHost* const render_frame_host =
app_browser->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
EXPECT_TRUE(content::ExecuteScript(
render_frame_host,
"navigator.geolocation.getCurrentPosition(function(){});"));
}
class WebAppBrowserTest_PrefixInTitle
: public WebAppBrowserTest,
public testing::WithParamInterface<bool> {
public:
WebAppBrowserTest_PrefixInTitle() {
if (GetParam()) {
scoped_feature_list_.InitAndEnableFeature(
features::kPrefixWebAppWindowsWithAppName);
} else {
scoped_feature_list_.InitAndDisableFeature(
features::kPrefixWebAppWindowsWithAppName);
}
}
bool ExpectPrefixInTitle() { return GetParam(); }
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Ensure that web app windows don't duplicate the app name in the title, when
// the page's title already starts with the app name.
IN_PROC_BROWSER_TEST_P(WebAppBrowserTest_PrefixInTitle, PrefixExistsInTitle) {
const GURL app_url =
https_server()->GetURL("app.com", "/web_apps/title_appname_prefix.html");
const std::u16string app_title = u"A Web App";
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = app_url;
web_app_info->scope = app_url.GetWithoutFilename();
web_app_info->title = app_title;
const AppId app_id = InstallWebApp(std::move(web_app_info));
Browser* const app_browser = LaunchWebAppBrowser(app_id);
content::WebContents* const web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
// The window title should not repeat "A Web App".
EXPECT_EQ(u"A Web App - funny cat video",
app_browser->GetWindowTitleForCurrentTab(false));
}
// Ensure that web app windows with blank titles don't display the URL as a
// default window title.
IN_PROC_BROWSER_TEST_P(WebAppBrowserTest_PrefixInTitle,
WebAppWindowTitleForEmptyAndSimpleWebContentTitles) {
// Ensure web app windows show the expected title when the contents have an
// empty or simple title.
const GURL app_url = https_server()->GetURL("app.site.test", "/empty.html");
const std::u16string app_title = u"A Web App";
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = app_url;
web_app_info->scope = app_url.GetWithoutFilename();
web_app_info->title = app_title;
const AppId app_id = InstallWebApp(std::move(web_app_info));
Browser* const app_browser = LaunchWebAppBrowser(app_id);
content::WebContents* const web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
if (ExpectPrefixInTitle()) {
EXPECT_EQ(app_title, app_browser->GetWindowTitleForCurrentTab(false));
} else {
EXPECT_EQ(std::u16string(),
app_browser->GetWindowTitleForCurrentTab(false));
}
NavigateToURLAndWait(app_browser,
https_server()->GetURL("app.site.test", "/simple.html"));
if (ExpectPrefixInTitle()) {
EXPECT_EQ(u"A Web App - OK",
app_browser->GetWindowTitleForCurrentTab(false));
} else {
EXPECT_EQ(u"OK", app_browser->GetWindowTitleForCurrentTab(false));
}
}
// Ensure that web app windows display the app title instead of the page
// title when off scope.
IN_PROC_BROWSER_TEST_P(WebAppBrowserTest_PrefixInTitle,
OffScopeUrlsDisplayAppTitle) {
const GURL app_url = GetSecureAppURL();
const std::u16string app_title = u"A Web App";
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = app_url;
web_app_info->scope = app_url.GetWithoutFilename();
web_app_info->title = app_title;
const AppId app_id = InstallWebApp(std::move(web_app_info));
Browser* const app_browser = LaunchWebAppBrowser(app_id);
content::WebContents* const web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
// When we are within scope, show the page title.
if (ExpectPrefixInTitle()) {
EXPECT_EQ(u"A Web App - Google",
app_browser->GetWindowTitleForCurrentTab(false));
} else {
EXPECT_EQ(u"Google", app_browser->GetWindowTitleForCurrentTab(false));
}
NavigateToURLAndWait(app_browser,
https_server()->GetURL("app.site.test", "/simple.html"));
// When we are off scope, show the app title.
EXPECT_EQ(app_title, app_browser->GetWindowTitleForCurrentTab(false));
}
INSTANTIATE_TEST_SUITE_P(WebAppBrowserTestTitlePrefix,
WebAppBrowserTest_PrefixInTitle,
::testing::Values(true, false));
// Ensure that web app windows display the app title instead of the page
// title when using http.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, InScopeHttpUrlsDisplayAppTitle) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL app_url =
embedded_test_server()->GetURL("app.site.test", "/simple.html");
const std::u16string app_title = u"A Web App";
auto web_app_info = std::make_unique<WebApplicationInfo>();
web_app_info->start_url = app_url;
web_app_info->title = app_title;
const AppId app_id = InstallWebApp(std::move(web_app_info));
Browser* const app_browser = LaunchWebAppBrowser(app_id);
content::WebContents* const web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
// The page title is "OK" but the page is being served over HTTP, so the app
// title should be used instead.
EXPECT_EQ(app_title, app_browser->GetWindowTitleForCurrentTab(false));
}
class WebAppBrowserTest_HideOrigin : public WebAppBrowserTest {
public:
WebAppBrowserTest_HideOrigin() = default;
private:
base::test::ScopedFeatureList scoped_feature_list_{
features::kHideWebAppOriginText};
};
// WebApps should not have origin text with this feature on.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_HideOrigin, OriginTextRemoved) {
const GURL app_url = GetInstallableAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowserAndWait(app_id);
EXPECT_FALSE(app_browser->app_controller()->HasTitlebarAppOriginText());
}
// Check that a subframe on a regular web page can navigate to a URL that
// redirects to a web app. https://crbug.com/721949.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, SubframeRedirectsToWebApp) {
ASSERT_TRUE(embedded_test_server()->Start());
// Set up a web app which covers app.com URLs.
GURL app_url = embedded_test_server()->GetURL("app.com", "/title1.html");
const AppId app_id = InstallPWA(app_url);
// Navigate a regular tab to a page with a subframe.
const GURL url = embedded_test_server()->GetURL("foo.com", "/iframe.html");
content::WebContents* const tab =
browser()->tab_strip_model()->GetActiveWebContents();
NavigateToURLAndWait(browser(), url);
// Navigate the subframe to a URL that redirects to a URL in the web app's
// web extent.
const GURL redirect_url = embedded_test_server()->GetURL(
"bar.com", "/server-redirect?" + app_url.spec());
EXPECT_TRUE(NavigateIframeToURL(tab, "test", redirect_url));
// Ensure that the frame navigated successfully and that it has correct
// content.
content::RenderFrameHost* const subframe =
content::ChildFrameAt(tab->GetMainFrame(), 0);
EXPECT_EQ(app_url, subframe->GetLastCommittedURL());
EXPECT_EQ(
"This page has no title.",
EvalJs(subframe, "document.body.innerText.trim();").ExtractString());
}
#if defined(OS_MAC)
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, NewAppWindow) {
BrowserList* const browser_list = BrowserList::GetInstance();
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
Browser* const app_browser = LaunchWebAppBrowser(app_id);
EXPECT_EQ(browser_list->size(), 2U);
EXPECT_TRUE(chrome::ExecuteCommand(app_browser, IDC_NEW_WINDOW));
EXPECT_EQ(browser_list->size(), 3U);
Browser* const new_browser = browser_list->GetLastActive();
EXPECT_NE(new_browser, browser());
EXPECT_NE(new_browser, app_browser);
EXPECT_TRUE(new_browser->is_type_app());
EXPECT_EQ(new_browser->app_controller()->app_id(), app_id);
WebAppProvider::GetForTest(profile())->sync_bridge().SetAppUserDisplayMode(
app_id, DisplayMode::kBrowser,
/*is_user_action=*/false);
EXPECT_EQ(browser()->tab_strip_model()->count(), 1);
EXPECT_TRUE(chrome::ExecuteCommand(app_browser, IDC_NEW_WINDOW));
EXPECT_EQ(browser_list->GetLastActive(), browser());
EXPECT_EQ(browser()->tab_strip_model()->count(), 2);
EXPECT_EQ(
browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
app_url);
}
#endif
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, PopupLocationBar) {
#if defined(OS_MAC)
ui::test::ScopedFakeNSWindowFullscreen fake_fullscreen;
#endif
const GURL app_url = GetSecureAppURL();
const GURL in_scope =
https_server()->GetURL("app.com", "/ssl/page_with_subresource.html");
const AppId app_id = InstallPWA(app_url);
Browser* const popup_browser = web_app::CreateWebApplicationWindow(
profile(), app_id, WindowOpenDisposition::NEW_POPUP, /*restore_id=*/0);
EXPECT_TRUE(
popup_browser->CanSupportWindowFeature(Browser::FEATURE_LOCATIONBAR));
EXPECT_TRUE(
popup_browser->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR));
FullscreenNotificationObserver waiter(popup_browser);
chrome::ToggleFullscreenMode(popup_browser);
waiter.Wait();
EXPECT_TRUE(
popup_browser->CanSupportWindowFeature(Browser::FEATURE_LOCATIONBAR));
}
// Make sure chrome://web-app-internals page loads fine.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, WebAppInternalsPage) {
// Loads with no web app.
NavigateToURLAndWait(browser(), GURL("chrome://web-app-internals"));
const GURL app_url = GetSecureAppURL();
InstallPWA(app_url);
// Loads with one web app.
NavigateToURLAndWait(browser(), GURL("chrome://web-app-internals"));
// Install a non-promotable web app.
NavigateToURLAndWait(
browser(), https_server()->GetURL("/banners/no_manifest_test_page.html"));
chrome::SetAutoAcceptWebAppDialogForTesting(/*auto_accept=*/true,
/*auto_open_in_window=*/false);
WebAppTestInstallObserver observer(profile());
observer.BeginListening();
CHECK(chrome::ExecuteCommand(browser(), IDC_CREATE_SHORTCUT));
observer.Wait();
chrome::SetAutoAcceptWebAppDialogForTesting(false, false);
// Loads with two apps.
NavigateToURLAndWait(browser(), GURL("chrome://web-app-internals"));
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, BrowserDisplayNotInstallable) {
GURL url = https_server()->GetURL(
"/banners/"
"manifest_test_page.html?manifest=manifest_display_browser.json");
NavigateAndAwaitInstallabilityCheck(browser(), url);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kNotPresent);
// Install using Create Shortcut.
chrome::SetAutoAcceptWebAppDialogForTesting(/*auto_accept=*/true,
/*auto_open_in_window=*/false);
WebAppTestInstallObserver observer(profile());
observer.BeginListening();
CHECK(chrome::ExecuteCommand(browser(), IDC_CREATE_SHORTCUT));
observer.Wait();
chrome::SetAutoAcceptWebAppDialogForTesting(false, false);
// Navigate to this site again and install should still be disabled.
Browser* new_browser = NavigateInNewWindowAndAwaitInstallabilityCheck(url);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, new_browser), kNotPresent);
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_WindowControlsOverlay,
DISABLE_POSIX(WindowControlsOverlay)) {
GURL test_url = https_server()->GetURL(
"/banners/"
"manifest_test_page.html?manifest=manifest_window_controls_overlay.json");
NavigateToURLAndWait(browser(), test_url);
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
auto* provider = WebAppProvider::GetForTest(profile());
std::vector<DisplayMode> app_display_mode_override =
provider->registrar().GetAppDisplayModeOverride(app_id);
ASSERT_EQ(1u, app_display_mode_override.size());
EXPECT_EQ(DisplayMode::kWindowControlsOverlay, app_display_mode_override[0]);
Browser* const app_browser = LaunchWebAppBrowser(app_id);
EXPECT_EQ(true,
app_browser->app_controller()->AppUsesWindowControlsOverlay());
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_Tabbed,
DISABLE_POSIX(TabbedDisplayOverride)) {
GURL test_url = https_server()->GetURL(
"/banners/"
"manifest_test_page.html?manifest=manifest_tabbed_display_override.json");
NavigateToURLAndWait(browser(), test_url);
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
auto* provider = WebAppProvider::GetForTest(profile());
std::vector<DisplayMode> app_display_mode_override =
provider->registrar().GetAppDisplayModeOverride(app_id);
ASSERT_EQ(1u, app_display_mode_override.size());
EXPECT_EQ(DisplayMode::kTabbed, app_display_mode_override[0]);
EXPECT_EQ(true, provider->registrar().IsTabbedWindowModeEnabled(app_id));
}
class WebAppBrowserTest_RemoveStatusBar : public WebAppBrowserTest {
public:
WebAppBrowserTest_RemoveStatusBar() = default;
private:
base::test::ScopedFeatureList scoped_feature_list_{
features::kRemoveStatusBarInWebApps};
};
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_RemoveStatusBar,
DISABLE_POSIX(RemoveStatusBar)) {
NavigateToURLAndWait(browser(), GetInstallableAppURL());
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
Browser* const app_browser = LaunchWebAppBrowser(app_id);
EXPECT_EQ(nullptr, app_browser->GetStatusBubbleForTesting());
}
class WebAppBrowserTest_NoDestroyProfile : public WebAppBrowserTest {
public:
WebAppBrowserTest_NoDestroyProfile() {
// This test only makes sense when DestroyProfileOnBrowserClose is
// disabled.
feature_list_.InitAndDisableFeature(
features::kDestroyProfileOnBrowserClose);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Check that no web app is launched during shutdown.
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_NoDestroyProfile, Shutdown) {
const GURL app_url = GetSecureAppURL();
const AppId app_id = InstallPWA(app_url);
apps::AppLaunchParams params(
app_id, apps::mojom::LaunchContainer::kLaunchContainerWindow,
WindowOpenDisposition::NEW_WINDOW, apps::mojom::LaunchSource::kFromTest);
BrowserHandler handler(nullptr, std::string());
handler.Close();
ui_test_utils::WaitForBrowserToClose();
content::WebContents* const web_contents =
apps::AppServiceProxyFactory::GetForProfile(
ProfileManager::GetActiveUserProfile())
->BrowserAppLauncher()
->LaunchAppWithParams(std::move(params));
EXPECT_EQ(web_contents, nullptr);
}
class WebAppBrowserTest_ManifestId : public WebAppBrowserTest {
public:
WebAppBrowserTest_ManifestId() = default;
private:
base::test::ScopedFeatureList scoped_feature_list_{
blink::features::kWebAppEnableManifestId};
};
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_ManifestId,
DISABLE_POSIX(NoManifestId)) {
NavigateToURLAndWait(browser(), GetInstallableAppURL());
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
auto* provider = WebAppProvider::GetForTest(profile());
auto* app = provider->registrar().GetAppById(app_id);
EXPECT_EQ(
web_app::GenerateAppId(/*manifest_id=*/absl::nullopt,
provider->registrar().GetAppStartUrl(app_id)),
app_id);
EXPECT_EQ(app->start_url().spec().substr(
app->start_url().DeprecatedGetOriginAsURL().spec().size()),
app->manifest_id());
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_ManifestId, ManifestIdSpecified) {
NavigateAndAwaitInstallabilityCheck(
browser(),
https_server()->GetURL(
"/banners/manifest_test_page.html?manifest=manifest_with_id.json"));
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
auto* provider = WebAppProvider::GetForTest(profile());
auto* app = provider->registrar().GetAppById(app_id);
EXPECT_EQ(web_app::GenerateAppId(app->manifest_id(), app->start_url()),
app_id);
EXPECT_NE(
web_app::GenerateAppId(/*manifest_id=*/absl::nullopt, app->start_url()),
app_id);
}
#if defined(OS_MAC) || defined(OS_WIN)
class WebAppBrowserTest_FileHandler : public WebAppBrowserTest {
public:
WebAppBrowserTest_FileHandler() {
feature_list_.InitAndEnableFeature(blink::features::kFileHandlingAPI);
}
protected:
#if BUILDFLAG(IS_WIN)
void SetUp() override {
// Don't pollute Windows registry of machine running tests.
registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER);
WebAppBrowserTest::SetUp();
}
registry_util::RegistryOverrideManager registry_override_manager_;
#endif // BUILDFLAG(IS_WIN)
private:
base::test::ScopedFeatureList feature_list_;
};
// TODO(crbug.com/1270961): Flaky.
#if defined(OS_WIN) || (defined(OS_MAC) && defined(ARCH_CPU_ARM64))
#define MAYBE_WebAppFileHandler DISABLED_WebAppFileHandler
#else
#define MAYBE_WebAppFileHandler WebAppFileHandler
#endif
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest_FileHandler, MAYBE_WebAppFileHandler) {
os_hooks_suppress_.reset();
base::ScopedAllowBlockingForTesting allow_blocking;
std::unique_ptr<ScopedShortcutOverrideForTesting> shortcut_override =
OverrideShortcutsForTesting(base::GetHomeDir());
std::vector<std::string> expected_extensions{"bar", "baz", "foo", "foobar"};
ASSERT_TRUE(embedded_test_server()->Start());
GURL app_url(embedded_test_server()->GetURL(
"/banners/"
"manifest_test_page.html?manifest=manifest_with_file_handlers.json"));
NavigateToURLAndWait(browser(), app_url);
// Wait for OS hooks and installation to complete.
chrome::SetAutoAcceptWebAppDialogForTesting(true, true);
base::RunLoop run_loop_install;
WebAppTestRegistryObserverAdapter observer(profile());
observer.SetWebAppInstalledWithOsHooksDelegate(base::BindLambdaForTesting(
[&](const AppId& installed_app_id) { run_loop_install.Quit(); }));
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
run_loop_install.Run();
content::RunAllTasksUntilIdle();
chrome::SetAutoAcceptWebAppDialogForTesting(false, false);
#if defined(OS_WIN)
const std::wstring prog_id =
GetProgIdForApp(browser()->profile()->GetPath(), app_id);
const std::vector<std::wstring> file_handler_prog_ids =
ShellUtil::GetFileHandlerProgIdsForAppId(prog_id);
base::flat_map<std::wstring, std::wstring> reg_key_prog_id_map;
std::vector<std::wstring> file_ext_reg_keys;
base::win::RegKey key;
for (const auto& file_handler_prog_id : file_handler_prog_ids) {
const std::vector<std::wstring> file_extensions =
GetFileExtensionsForProgId(file_handler_prog_id);
for (const auto& file_extension : file_extensions) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
const std::string extension =
converter.to_bytes(file_extension.substr(1));
EXPECT_TRUE(std::find(expected_extensions.begin(),
expected_extensions.end(),
extension) != expected_extensions.end())
<< "Missing file extension: " << extension;
const std::wstring reg_key =
L"Software\\Classes\\" + file_extension + L"\\OpenWithProgids";
reg_key_prog_id_map[reg_key] = file_handler_prog_id;
ASSERT_EQ(ERROR_SUCCESS,
key.Open(HKEY_CURRENT_USER, reg_key.data(), KEY_READ));
EXPECT_TRUE(key.HasValue(file_handler_prog_id.data()));
}
}
#elif defined(OS_MAC)
for (auto extension : expected_extensions) {
const base::FilePath test_file_path =
shortcut_override->chrome_apps_folder.GetPath().AppendASCII("test." +
extension);
const base::File test_file(test_file_path, base::File::FLAG_CREATE_ALWAYS |
base::File::FLAG_WRITE);
const GURL test_file_url = net::FilePathToFileURL(test_file_path);
EXPECT_EQ(u"Manifest with file handlers",
shell_integration::GetApplicationNameForProtocol(test_file_url))
<< "The default app to open the file is wrong. "
<< "File extension: " + extension;
}
ASSERT_TRUE(shortcut_override->chrome_apps_folder.Delete());
#endif
// Unistall the web app
NavigateToURLAndWait(browser(), GURL(chrome::kChromeUIAppsURL));
base::RunLoop run_loop_uninstall;
WebAppProvider::GetForTest(profile())->install_finalizer().UninstallWebApp(
app_id, webapps::WebappUninstallSource::kAppsPage,
base::BindLambdaForTesting([&](bool uninstalled) {
DCHECK(uninstalled);
run_loop_uninstall.Quit();
}));
run_loop_uninstall.Run();
#if defined(OS_WIN)
// Check file associations after the web app is uninstalled.
// Check that HKCU/Software Classes/<filext>/ doesn't have the ProgId.
for (const auto& reg_key_prog_id : reg_key_prog_id_map) {
ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER,
reg_key_prog_id.first.data(), KEY_READ));
EXPECT_FALSE(key.HasValue(reg_key_prog_id.second.data()));
}
#endif
}
#endif
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, PRE_UninstallIncompleteUninstall) {
auto* provider = WebAppProvider::GetForTest(profile());
NavigateToURLAndWait(browser(), GetInstallableAppURL());
// Wait for OS hooks and installation to complete and the app to launch.
base::RunLoop run_loop_install;
WebAppTestRegistryObserverAdapter observer(profile());
observer.SetWebAppInstalledWithOsHooksDelegate(base::BindLambdaForTesting(
[&](const AppId& installed_app_id) { run_loop_install.Quit(); }));
const AppId app_id = test::InstallPwaForCurrentUrl(browser());
run_loop_install.Run();
EXPECT_TRUE(provider->registrar().IsInstalled(app_id));
EXPECT_EQ(provider->registrar().GetAppShortName(app_id),
GetInstallableAppName());
// This does NOT uninstall the web app, it just flags it for uninstall on
// startup.
{
ScopedRegistryUpdate update(&provider->sync_bridge());
WebApp* web_app = update->UpdateApp(app_id);
ASSERT_TRUE(web_app);
web_app->SetIsUninstalling(true);
}
}
IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, UninstallIncompleteUninstall) {
auto* provider = WebAppProvider::GetForTest(profile());
// The uninstall-on-startup code schedules tasks to uninstall flagged apps on
// startup. For this test, either:
// 1) The webapp was uninstalled during test startup, when it is waiting for
// the WebAppProvider to be ready, or
// 2) It hasn't been uninstalled yet.
// The test body here handles both cases, and ensures that the app has been
// uninstalled.
std::set<AppId> apps;
for (const auto& web_app : provider->registrar().GetAppsIncludingStubs()) {
EXPECT_TRUE(web_app.is_uninstalling());
apps.insert(web_app.app_id());
}
EXPECT_TRUE(apps.size() == 0 || apps.size() == 1);
if (apps.size() != 0) {
WebAppTestUninstallObserver observer(profile());
observer.BeginListeningAndWait(apps);
}
// TODO(dmurph): Remove AppSet, it's too hard to use.
int app_count = 0;
const web_app::WebAppRegistrar::AppSet& app_set =
provider->registrar().GetAppsIncludingStubs();
for (auto it = app_set.begin(); it != app_set.end(); ++it) {
++app_count;
}
EXPECT_EQ(app_count, 0);
}
} // namespace web_app