blob: 07ea699e657ab77435df509b07296d0cc566cadb [file] [log] [blame]
// 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 "base/run_loop.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/lacros/browser_test_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/web_applications/test/web_app_navigation_browsertest.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/test/app_registry_cache_waiter.h"
#include "chrome/browser/web_applications/test/service_worker_registration_waiter.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h"
#include "chromeos/crosapi/mojom/test_controller.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "components/app_constants/constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
using crosapi::mojom::ShelfItemState;
namespace {
constexpr char kFirstAppUrlHost[] = "first-pwa.test";
constexpr char kSecondAppUrlHost[] = "second-pwa.test";
// Polls until the command has the expected state.
void WaitForAppMenuCommandState(int command_id,
Browser* browser,
web_app::AppMenuCommandState command_state) {
base::RunLoop run_loop;
base::RepeatingTimer timer;
constexpr base::TimeDelta kPollingInterval = base::Milliseconds(1000);
timer.Start(FROM_HERE, kPollingInterval, base::BindLambdaForTesting([&]() {
if (web_app::GetAppMenuCommandState(command_id, browser) ==
command_state) {
timer.Stop();
run_loop.Quit();
}
}));
run_loop.Run();
}
} // namespace
namespace web_app {
class LacrosWebAppShelfBrowserTest : public WebAppNavigationBrowserTest {
public:
LacrosWebAppShelfBrowserTest() = default;
~LacrosWebAppShelfBrowserTest() override = default;
void SetUpOnMainThread() override {
WebAppNavigationBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(https_server().Start());
}
protected:
// If ash is does not contain the relevant test controller functionality, then
// there's nothing to do for this test.
bool IsServiceAvailable() {
DCHECK(IsWebAppsCrosapiEnabled());
if (chromeos::LacrosService::Get()
->GetInterfaceVersion<crosapi::mojom::TestController>() <
static_cast<int>(crosapi::mojom::TestController::MethodMinVersions::
kGetShelfItemStateMinVersion)) {
LOG(WARNING) << "Unsupported ash version.";
return false;
}
return true;
}
};
IN_PROC_BROWSER_TEST_F(LacrosWebAppShelfBrowserTest, Activation) {
if (!IsServiceAvailable())
GTEST_SKIP();
const GURL app1_url =
https_server().GetURL(kFirstAppUrlHost, "/web_apps/basic.html");
const AppId app1_id =
InstallWebAppFromPageAndCloseAppBrowser(browser(), app1_url);
const GURL app2_url = https_server().GetURL(
kSecondAppUrlHost, "/web_apps/standalone/basic.html");
const AppId app2_id =
InstallWebAppFromPageAndCloseAppBrowser(browser(), app2_url);
AppReadinessWaiter(profile(), app1_id).Await();
Browser* app_browser1 = LaunchWebAppBrowser(app1_id);
EXPECT_TRUE(AppBrowserController::IsForWebApp(app_browser1, app1_id));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kActive)));
ASSERT_TRUE(AddTabAtIndex(/*index=*/1, app1_url, ui::PAGE_TRANSITION_TYPED));
AppReadinessWaiter(profile(), app2_id).Await();
LaunchWebAppBrowser(app2_id);
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app2_id, static_cast<uint32_t>(ShelfItemState::kActive)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kRunning)));
CloseAndWait(app_browser1);
// A tab open at app1_url is not sufficient for the app to be considered
// running.
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kNormal)));
test::UninstallWebApp(profile(), app2_id);
AppReadinessWaiter(profile(), app2_id, apps::Readiness::kUninstalledByUser)
.Await();
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app2_id, static_cast<uint32_t>(ShelfItemState::kNormal)));
test::UninstallWebApp(profile(), app1_id);
}
// Navigating out of scope in an app window does not affect which app is
// considered running.
IN_PROC_BROWSER_TEST_F(LacrosWebAppShelfBrowserTest, Navigation) {
if (!IsServiceAvailable())
GTEST_SKIP();
const GURL app1_url =
https_server().GetURL(kFirstAppUrlHost, "/web_apps/basic.html");
const AppId app1_id =
InstallWebAppFromPageAndCloseAppBrowser(browser(), app1_url);
const GURL app2_url = https_server().GetURL(
kSecondAppUrlHost, "/web_app_shortcuts/shortcuts.html");
const AppId app2_id =
InstallWebAppFromPageAndCloseAppBrowser(browser(), app2_url);
GURL out_of_scope_url = https_server().GetURL("/empty.html");
Browser* app_browser1 = LaunchWebAppBrowser(app1_id);
{
NavigateParams params(app_browser1, out_of_scope_url,
ui::PAGE_TRANSITION_LINK);
params.tabstrip_index = app_browser1->tab_strip_model()->active_index();
params.disposition = WindowOpenDisposition::CURRENT_TAB;
Navigate(&params);
ASSERT_TRUE(
content::WaitForLoadStop(params.navigated_or_inserted_contents));
EXPECT_EQ(app_browser1->tab_strip_model()->count(), 1);
EXPECT_EQ(app_browser1->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL(),
out_of_scope_url);
}
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kActive)));
LaunchWebAppBrowser(app2_id);
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app2_id, static_cast<uint32_t>(ShelfItemState::kActive)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kRunning)));
app_browser1->window()->Activate();
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kActive)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app2_id, static_cast<uint32_t>(ShelfItemState::kRunning)));
test::UninstallWebApp(profile(), app1_id);
test::UninstallWebApp(profile(), app2_id);
}
IN_PROC_BROWSER_TEST_F(LacrosWebAppShelfBrowserTest, BadgeShown) {
if (!IsServiceAvailable())
GTEST_SKIP();
const GURL app_url = https_server().GetURL(kFirstAppUrlHost,
"/web_apps/minimal_ui/basic.html");
const AppId app_id =
InstallWebAppFromPageAndCloseAppBrowser(browser(), app_url);
AppReadinessWaiter(profile(), app_id).Await();
Browser* app_browser = LaunchWebAppBrowser(app_id);
content::WebContents* const web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(AppBrowserController::IsForWebApp(app_browser, app_id));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app_id, static_cast<uint32_t>(ShelfItemState::kActive)));
ASSERT_TRUE(content::ExecJs(web_contents, "navigator.setAppBadge();",
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app_id, static_cast<uint32_t>(ShelfItemState::kActive) |
static_cast<uint32_t>(ShelfItemState::kNotification)));
ASSERT_TRUE(content::ExecJs(web_contents, "navigator.clearAppBadge();",
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app_id, static_cast<uint32_t>(ShelfItemState::kActive)));
test::UninstallWebApp(profile(), app_id);
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app_id, static_cast<uint32_t>(ShelfItemState::kNormal)));
}
IN_PROC_BROWSER_TEST_F(LacrosWebAppShelfBrowserTest, RunningInTab) {
if (!IsServiceAvailable())
GTEST_SKIP();
crosapi::mojom::TestController* const test_controller =
chromeos::LacrosService::Get()
->GetRemote<crosapi::mojom::TestController>()
.get();
crosapi::mojom::TestControllerAsyncWaiter waiter(test_controller);
const GURL app1_url = https_server().GetURL(
kFirstAppUrlHost, "/web_apps/standalone/basic.html");
const AppId app1_id =
InstallWebAppFromPageAndCloseAppBrowser(browser(), app1_url);
const GURL app2_url =
https_server().GetURL(kSecondAppUrlHost, "/web_apps/basic.html");
const AppId app2_id =
InstallWebAppFromPageAndCloseAppBrowser(browser(), app2_url);
{
auto& sync_bridge =
WebAppProvider::GetForTest(profile())->sync_bridge_unsafe();
Browser* app_browser1 = LaunchWebAppBrowser(app1_id);
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kActive)));
waiter.PinOrUnpinItemInShelf(app1_id, /*pin=*/true);
CloseAndWait(app_browser1);
sync_bridge.SetAppUserDisplayMode(app1_id, mojom::UserDisplayMode::kBrowser,
/*is_user_action=*/true);
AppWindowModeWaiter(profile(), app1_id, apps::WindowMode::kBrowser).Await();
Browser* app_browser2 = LaunchWebAppBrowser(app2_id);
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app2_id, static_cast<uint32_t>(ShelfItemState::kActive)));
waiter.PinOrUnpinItemInShelf(app2_id, /*pin=*/true);
CloseAndWait(app_browser2);
sync_bridge.SetAppUserDisplayMode(app2_id, mojom::UserDisplayMode::kBrowser,
/*is_user_action=*/true);
AppWindowModeWaiter(profile(), app2_id, apps::WindowMode::kBrowser).Await();
}
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app_constants::kLacrosAppId,
static_cast<uint32_t>(ShelfItemState::kActive)));
test_controller->LaunchAppFromAppList(app1_id);
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kActive)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app_constants::kLacrosAppId,
static_cast<uint32_t>(ShelfItemState::kRunning)));
test_controller->LaunchAppFromAppList(app2_id);
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app2_id, static_cast<uint32_t>(ShelfItemState::kActive)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kRunning)));
EXPECT_EQ(BrowserList::GetInstance()->size(), 1U);
TabStripModel* tab_strip_model = browser()->tab_strip_model();
tab_strip_model->CloseWebContentsAt(tab_strip_model->active_index(),
TabCloseTypes::CLOSE_NONE);
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app2_id, static_cast<uint32_t>(ShelfItemState::kNormal)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kActive)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app_constants::kLacrosAppId,
static_cast<uint32_t>(ShelfItemState::kRunning)));
tab_strip_model->CloseWebContentsAt(tab_strip_model->active_index(),
TabCloseTypes::CLOSE_NONE);
ASSERT_TRUE(AddTabAtIndex(/*index=*/1, app2_url, ui::PAGE_TRANSITION_TYPED));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kNormal)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app2_id, static_cast<uint32_t>(ShelfItemState::kActive)));
// Navigation is sufficient to change which app is considered running.
{
NavigateParams params(browser(), app1_url, ui::PAGE_TRANSITION_TYPED);
params.tabstrip_index = tab_strip_model->active_index();
params.disposition = WindowOpenDisposition::CURRENT_TAB;
Navigate(&params);
ASSERT_TRUE(
content::WaitForLoadStop(params.navigated_or_inserted_contents));
}
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app2_id, static_cast<uint32_t>(ShelfItemState::kNormal)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kActive)));
test::UninstallWebApp(profile(), app1_id);
test::UninstallWebApp(profile(), app2_id);
}
// Tests that a web page without a manifest may be used to create a shortcut.
// Tests that a web page with a manifest etc. may be used to install a PWA.
// Tests that opening a shortcut in a tab does not make it appear in the Shelf.
// Tests that web apps opened in windows do appear in the Shelf.
IN_PROC_BROWSER_TEST_F(LacrosWebAppShelfBrowserTest, CreateShortcut) {
if (!IsServiceAvailable())
GTEST_SKIP();
ASSERT_TRUE(embedded_test_server()->Start());
crosapi::mojom::TestController* const test_controller =
chromeos::LacrosService::Get()
->GetRemote<crosapi::mojom::TestController>()
.get();
auto& sync_bridge =
WebAppProvider::GetForTest(profile())->sync_bridge_unsafe();
GURL app1_url(
embedded_test_server()->GetURL("/banners/scope_a/no_manifest.html"));
GURL app2_url(
embedded_test_server()->GetURL("/banners/scope_b/scope_b.html"));
AppId app1_id;
Browser* app1_browser;
{
web_app::ServiceWorkerRegistrationWaiter registration_waiter(profile(),
app1_url);
ASSERT_TRUE(
AddTabAtIndex(/*index=*/1, app1_url, ui::PAGE_TRANSITION_TYPED));
registration_waiter.AwaitRegistration();
ASSERT_TRUE(
AddTabAtIndex(/*index=*/2, app2_url, ui::PAGE_TRANSITION_TYPED));
WaitForAppMenuCommandState(IDC_INSTALL_PWA, browser(), kEnabled);
// Install app1 shortcut.
browser()->tab_strip_model()->ActivateTabAt(/*index=*/1);
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, browser()), kEnabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kNotPresent);
chrome::SetAutoAcceptWebAppDialogForTesting(
/*auto_accept=*/true,
/*auto_open_in_window=*/true);
ui_test_utils::BrowserChangeObserver browser_change_observer(
nullptr, ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
WebAppTestInstallObserver install_observer(profile());
install_observer.BeginListening();
CHECK(chrome::ExecuteCommand(browser(), IDC_CREATE_SHORTCUT));
app1_id = install_observer.Wait();
app1_browser = browser_change_observer.Wait();
EXPECT_TRUE(AppBrowserController::IsForWebApp(app1_browser, app1_id));
chrome::SetAutoAcceptWebAppDialogForTesting(
/*auto_accept=*/false,
/*auto_open_in_window=*/false);
}
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kActive)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app_constants::kLacrosAppId,
static_cast<uint32_t>(ShelfItemState::kRunning)));
// Launch app1 in a browser tab (only).
{
sync_bridge.SetAppUserDisplayMode(app1_id, mojom::UserDisplayMode::kBrowser,
/*is_user_action=*/false);
AppWindowModeWaiter(profile(), app1_id, apps::WindowMode::kBrowser).Await();
app1_browser->window()->Close();
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kNormal)));
ui_test_utils::TabAddedWaiter waiter(browser());
test_controller->LaunchAppFromAppList(app1_id);
waiter.Wait();
}
// Install app2 PWA.
AppId app2_id;
Browser* app2_browser;
{
browser()->tab_strip_model()->ActivateTabAt(/*index=*/1);
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, browser()), kEnabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kEnabled);
chrome::SetAutoAcceptPWAInstallConfirmationForTesting(/*auto_accept=*/true);
ui_test_utils::BrowserChangeObserver browser_change_observer(
nullptr, ui_test_utils::BrowserChangeObserver::ChangeType::kAdded);
WebAppTestInstallObserver observer(profile());
observer.BeginListening();
CHECK(chrome::ExecuteCommand(browser(), IDC_INSTALL_PWA));
app2_id = observer.Wait();
app2_browser = browser_change_observer.Wait();
EXPECT_TRUE(AppBrowserController::IsForWebApp(app2_browser, app2_id));
chrome::SetAutoAcceptPWAInstallConfirmationForTesting(
/*auto_accept=*/false);
}
// App1 is open in a tab, but does not appear in the shelf.
EXPECT_EQ(
browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
app1_url);
EXPECT_EQ(
app2_browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
app2_url);
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app2_id, static_cast<uint32_t>(ShelfItemState::kActive)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app1_id, static_cast<uint32_t>(ShelfItemState::kNormal)));
ASSERT_TRUE(browser_test_util::WaitForShelfItemState(
app_constants::kLacrosAppId,
static_cast<uint32_t>(ShelfItemState::kRunning)));
ASSERT_TRUE(browser_test_util::WaitForShelfItem(app1_id, /*exists=*/false));
}
} // namespace web_app