blob: c0330b512a7488641ee9a80477d8e59b78a8e315 [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 "chrome/browser/ui/webui/app_home/app_home_page_handler.h"
#include <vector>
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/app_home/app_home.mojom.h"
#include "chrome/browser/ui/webui/app_home/mock_app_home_page.h"
#include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_web_ui.h"
#include "extensions/browser/extension_dialog_auto_confirm.h"
#include "extensions/common/extension_builder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/views/test/dialog_test.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"
using web_app::AppId;
using GetAppsCallback =
base::OnceCallback<void(std::vector<app_home::mojom::AppInfoPtr>)>;
namespace webapps {
namespace {
constexpr char kTestAppUrl[] = "https://www.example.com/";
constexpr char kTestManifestUrl[] = "https://www.example.com/manifest.json";
constexpr char kTestAppName[] = "Test App";
#if !BUILDFLAG(IS_MAC)
void FlushShortcutTasks() {
// Execute the UI thread task runner before and after the shortcut task runner
// to ensure that tasks get to the shortcut runner, and then any scheduled
// replies on the UI thread get run.
{
base::RunLoop loop;
content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
{
base::RunLoop loop;
web_app::internals::GetShortcutIOTaskRunner()->PostTask(FROM_HERE,
loop.QuitClosure());
loop.Run();
}
{
base::RunLoop loop;
content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, loop.QuitClosure());
loop.Run();
}
}
#endif
class TestAppHomePageHandler : public AppHomePageHandler {
public:
TestAppHomePageHandler(content::WebUI* web_ui,
Profile* profile,
mojo::PendingRemote<app_home::mojom::Page> page)
: AppHomePageHandler(
web_ui,
profile,
mojo::PendingReceiver<app_home::mojom::PageHandler>(),
std::move(page)) {
run_loop_ = std::make_unique<base::RunLoop>();
}
TestAppHomePageHandler(const TestAppHomePageHandler&) = delete;
TestAppHomePageHandler& operator=(const TestAppHomePageHandler&) = delete;
~TestAppHomePageHandler() override = default;
void Wait() {
run_loop_->Run();
run_loop_ = std::make_unique<base::RunLoop>();
}
private:
void OnWebAppInstalled(const web_app::AppId& app_id) override {
run_loop_->Quit();
AppHomePageHandler::OnWebAppInstalled(app_id);
}
void OnWebAppWillBeUninstalled(const web_app::AppId& app_id) override {
run_loop_->Quit();
AppHomePageHandler::OnWebAppWillBeUninstalled(app_id);
}
void OnExtensionLoaded(content::BrowserContext* browser_context,
const extensions::Extension* extension) override {
run_loop_->Quit();
AppHomePageHandler::OnExtensionLoaded(browser_context, extension);
}
void OnExtensionUninstalled(content::BrowserContext* browser_context,
const extensions::Extension* extension,
extensions::UninstallReason reason) override {
run_loop_->Quit();
AppHomePageHandler::OnExtensionUninstalled(browser_context, extension,
reason);
}
std::unique_ptr<base::RunLoop> run_loop_;
};
std::unique_ptr<WebAppInstallInfo> BuildWebAppInfo() {
auto app_info = std::make_unique<WebAppInstallInfo>();
app_info->start_url = GURL(kTestAppUrl);
app_info->scope = GURL(kTestAppUrl);
app_info->title = base::UTF8ToUTF16(base::StringPiece(kTestAppName));
app_info->manifest_url = GURL(kTestManifestUrl);
return app_info;
}
GetAppsCallback WrapGetAppsCallback(
std::vector<app_home::mojom::AppInfoPtr>* out,
base::OnceClosure quit_closure) {
return base::BindOnce(
[](base::OnceClosure quit_closure,
std::vector<app_home::mojom::AppInfoPtr>* out,
std::vector<app_home::mojom::AppInfoPtr> result) {
*out = std::move(result);
std::move(quit_closure).Run();
},
std::move(quit_closure), out);
}
} // namespace
class AppHomePageHandlerTest : public InProcessBrowserTest {
public:
AppHomePageHandlerTest() = default;
AppHomePageHandlerTest(const AppHomePageHandlerTest&) = delete;
AppHomePageHandlerTest& operator=(const AppHomePageHandlerTest&) = delete;
~AppHomePageHandlerTest() override = default;
protected:
std::unique_ptr<TestAppHomePageHandler> GetAppHomePageHandler() {
AddBlankTabAndShow(browser());
content::WebContents* contents =
browser()->tab_strip_model()->GetWebContentsAt(0);
test_web_ui_.set_web_contents(contents);
return std::make_unique<TestAppHomePageHandler>(&test_web_ui_, profile(),
page_.BindAndGetRemote());
}
extensions::ExtensionService* extension_service() {
return extensions::ExtensionSystem::Get(profile())->extension_service();
}
AppId InstallTestWebApp() {
AppId installed_app_id =
web_app::test::InstallWebApp(profile(), BuildWebAppInfo());
return installed_app_id;
}
Profile* profile() { return browser()->profile(); }
void UninstallTestWebApp(const web_app::AppId& app_id) {
web_app::test::UninstallWebApp(profile(), app_id);
}
scoped_refptr<const extensions::Extension> InstallTestExtensionApp() {
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder(kTestAppName).Build();
extension_service()->AddExtension(extension.get());
return extension;
}
void UninstallTestExtensionApp(const extensions::Extension* extension) {
std::u16string error;
base::RunLoop run_loop;
// `UninstallExtension` method synchronously removes the extension from the
// set of installed extensions stored in the ExtensionRegistry and later
// notifies interested observer of extension uninstall event. But it will
// asynchronously remove site-related data and the files stored on disk.
// It's common case that `WebappTest::TearDonw` invokes before
// `ExtensionService` completes delete related file, as a result, the
// `AppHome` test would finally fail delete testing-related file for file
// locking semantics on WinOS platfom. To workaround this case, make sure
// the task of uninstalling extension complete before the `AppHome` test
// tear down.
extension_service()->UninstallExtension(
extension->id(),
extensions::UninstallReason::UNINSTALL_REASON_FOR_TESTING, &error,
base::BindOnce(
[](base::OnceClosure quit_closure) {
std::move(quit_closure).Run();
},
run_loop.QuitClosure()));
run_loop.Run();
}
extensions::ExtensionService* CreateTestExtensionService() {
auto* extension_system = static_cast<extensions::TestExtensionSystem*>(
extensions::ExtensionSystem::Get(profile()));
extensions::ExtensionService* ext_service =
extension_system->CreateExtensionService(
base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
ext_service->Init();
return ext_service;
}
content::TestWebUI test_web_ui_;
testing::StrictMock<MockAppHomePage> page_;
};
MATCHER_P(MatchAppName, expected_app_name, "") {
if (expected_app_name == arg->name) {
return true;
}
return false;
}
MATCHER_P(MatchAppId, expected_app_id, "") {
if (expected_app_id == arg->id) {
return true;
}
return false;
}
IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, GetApps) {
AppId installed_app_id = InstallTestWebApp();
std::unique_ptr<TestAppHomePageHandler> page_handler =
GetAppHomePageHandler();
std::vector<app_home::mojom::AppInfoPtr> app_infos;
base::RunLoop run_loop;
page_handler->GetApps(
WrapGetAppsCallback(&app_infos, run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(kTestAppUrl, app_infos[0]->start_url);
EXPECT_EQ(kTestAppName, app_infos[0]->name);
}
IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, OnWebAppInstalled) {
std::unique_ptr<TestAppHomePageHandler> page_handler =
GetAppHomePageHandler();
EXPECT_CALL(page_, AddApp(MatchAppName(kTestAppName)));
AppId installed_app_id = InstallTestWebApp();
page_handler->Wait();
}
IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, OnExtensionLoaded) {
std::unique_ptr<TestAppHomePageHandler> page_handler =
GetAppHomePageHandler();
EXPECT_CALL(page_, AddApp(MatchAppName(kTestAppName)));
scoped_refptr<const extensions::Extension> extension =
InstallTestExtensionApp();
ASSERT_NE(extension, nullptr);
page_handler->Wait();
}
IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, OnWebAppUninstall) {
std::unique_ptr<TestAppHomePageHandler> page_handler =
GetAppHomePageHandler();
// First, install a web app for test.
EXPECT_CALL(page_, AddApp(MatchAppName(kTestAppName)));
AppId installed_app_id = InstallTestWebApp();
page_handler->Wait();
// Check uninstall previous web app will call `RemoveApp` API.
EXPECT_CALL(page_, RemoveApp(MatchAppId(installed_app_id)))
.Times(testing::AtLeast(1));
UninstallTestWebApp(installed_app_id);
page_handler->Wait();
}
IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, OnExtensionUninstall) {
std::unique_ptr<TestAppHomePageHandler> page_handler =
GetAppHomePageHandler();
// First, install a test extension app for test.
EXPECT_CALL(page_, AddApp(MatchAppName(kTestAppName)));
scoped_refptr<const extensions::Extension> extension =
InstallTestExtensionApp();
page_handler->Wait();
// Check uninstall previous extension will call `RemoveApp` API.
EXPECT_CALL(page_, RemoveApp(MatchAppId(extension->id())))
.Times(testing::AtLeast(1));
UninstallTestExtensionApp(extension.get());
page_handler->Wait();
}
IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, UninstallWebApp) {
std::unique_ptr<TestAppHomePageHandler> page_handler =
GetAppHomePageHandler();
// First, install a test web app for test.
EXPECT_CALL(page_, AddApp(MatchAppName(kTestAppName)));
AppId installed_app_id = InstallTestWebApp();
page_handler->Wait();
// Then, check uninstalling previous web app via using
// `AppHomePageHandler::UninstallApp`.
EXPECT_CALL(page_, RemoveApp(MatchAppId(installed_app_id)))
.Times(testing::AtLeast(1));
extensions::ScopedTestDialogAutoConfirm auto_confirm(
extensions::ScopedTestDialogAutoConfirm::ACCEPT);
page_handler->UninstallApp(installed_app_id);
page_handler->Wait();
}
IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, UninstallExtensionApp) {
std::unique_ptr<TestAppHomePageHandler> page_handler =
GetAppHomePageHandler();
// First, install a test extension app for test.
EXPECT_CALL(page_, AddApp(MatchAppName(kTestAppName)));
scoped_refptr<const extensions::Extension> extension =
InstallTestExtensionApp();
page_handler->Wait();
// Then, check uninstalling previous extension app via using
// `AppHomePageHandler::UninstallApp`.
EXPECT_CALL(page_, RemoveApp(MatchAppId(extension->id())))
.Times(testing::AtLeast(1));
extensions::ScopedTestDialogAutoConfirm auto_confirm(
extensions::ScopedTestDialogAutoConfirm::ACCEPT);
page_handler->UninstallApp(extension->id());
page_handler->Wait();
}
IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, ShowWebAppSettings) {
std::unique_ptr<TestAppHomePageHandler> page_handler =
GetAppHomePageHandler();
// First, install a test web app for test.
EXPECT_CALL(page_, AddApp(MatchAppName(kTestAppName)));
AppId installed_app_id = InstallTestWebApp();
page_handler->Wait();
content::WebContentsAddedObserver nav_observer;
page_handler->ShowAppSettings(installed_app_id);
// Wait for new web content to be created.
nav_observer.GetWebContents();
GURL url = browser()->tab_strip_model()->GetActiveWebContents()->GetURL();
EXPECT_EQ(url, GURL(chrome::kChromeUIWebAppSettingsURL + installed_app_id));
}
IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, CreateWebAppShortcut) {
std::unique_ptr<TestAppHomePageHandler> page_handler =
GetAppHomePageHandler();
// First, install a test web app for test.
EXPECT_CALL(page_, AddApp(MatchAppName(kTestAppName)));
AppId installed_app_id = InstallTestWebApp();
page_handler->Wait();
#if BUILDFLAG(IS_MAC)
base::RunLoop loop;
page_handler->CreateAppShortcut(installed_app_id, loop.QuitClosure());
loop.Run();
#else
views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
"CreateChromeApplicationShortcutView");
page_handler->CreateAppShortcut(installed_app_id, base::DoNothing());
FlushShortcutTasks();
views::Widget* widget = waiter.WaitIfNeededAndGet();
ASSERT_TRUE(widget != nullptr);
views::test::AcceptDialog(widget);
#endif
}
IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, CreateExtensionAppShortcut) {
std::unique_ptr<TestAppHomePageHandler> page_handler =
GetAppHomePageHandler();
// First, install a test extension app for test.
EXPECT_CALL(page_, AddApp(MatchAppName(kTestAppName)));
scoped_refptr<const extensions::Extension> extension =
InstallTestExtensionApp();
page_handler->Wait();
#if BUILDFLAG(IS_MAC)
base::RunLoop loop;
page_handler->CreateAppShortcut(extension->id(), loop.QuitClosure());
loop.Run();
#else
views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
"CreateChromeApplicationShortcutView");
page_handler->CreateAppShortcut(extension->id(), base::DoNothing());
FlushShortcutTasks();
views::Widget* widget = waiter.WaitIfNeededAndGet();
ASSERT_TRUE(widget != nullptr);
views::test::AcceptDialog(widget);
#endif
}
} // namespace webapps