| // Copyright 2020 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 "chrome/browser/ui/views/crostini/crostini_recovery_view.h" |
| |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_base.h" |
| #include "base/run_loop.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "chrome/browser/chromeos/crostini/crostini_manager.h" |
| #include "chrome/browser/chromeos/crostini/crostini_test_helper.h" |
| #include "chrome/browser/chromeos/crostini/crostini_util.h" |
| #include "chrome/browser/chromeos/guest_os/guest_os_registry_service.h" |
| #include "chrome/browser/chromeos/guest_os/guest_os_registry_service_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/views/crostini/crostini_browser_test_util.h" |
| #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/fake_concierge_client.h" |
| #include "content/public/test/browser_test.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| constexpr crostini::CrostiniUISurface kUiSurface = |
| crostini::CrostiniUISurface::kAppList; |
| constexpr char kDesktopFileId[] = "test_app"; |
| constexpr int kDisplayId = 0; |
| |
| namespace { |
| |
| void ExpectFailure(const std::string& expected_failure_reason, |
| bool success, |
| const std::string& failure_reason) { |
| EXPECT_FALSE(success); |
| EXPECT_EQ(expected_failure_reason, failure_reason); |
| } |
| } // namespace |
| |
| class CrostiniRecoveryViewBrowserTest : public CrostiniDialogBrowserTest { |
| public: |
| CrostiniRecoveryViewBrowserTest() |
| : CrostiniDialogBrowserTest(true /*register_termina*/), |
| app_id_(crostini::CrostiniTestHelper::GenerateAppId(kDesktopFileId)) {} |
| |
| void SetUpOnMainThread() override { |
| CrostiniDialogBrowserTest::SetUpOnMainThread(); |
| } |
| |
| // DialogBrowserTest: |
| void ShowUi(const std::string& name) override { |
| ShowCrostiniRecoveryView(browser()->profile(), kUiSurface, app_id(), |
| kDisplayId, {}, base::DoNothing()); |
| } |
| |
| void SetUncleanStartup() { |
| crostini::CrostiniManager::GetForProfile(browser()->profile()) |
| ->SetUncleanStartupForTesting(true); |
| } |
| |
| CrostiniRecoveryView* ActiveView() { |
| return CrostiniRecoveryView::GetActiveViewForTesting(); |
| } |
| |
| void WaitForViewDestroyed() { |
| base::RunLoop().RunUntilIdle(); |
| ExpectNoView(); |
| } |
| |
| void ExpectView() { |
| // A new Widget was created in ShowUi() or since the last VerifyUi(). |
| EXPECT_TRUE(VerifyUi()); |
| // There is one view, and it's ours. |
| EXPECT_NE(nullptr, ActiveView()); |
| EXPECT_EQ(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL, |
| ActiveView()->GetDialogButtons()); |
| |
| EXPECT_NE(ActiveView()->GetOkButton(), nullptr); |
| EXPECT_NE(ActiveView()->GetCancelButton(), nullptr); |
| EXPECT_TRUE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK)); |
| EXPECT_TRUE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_CANCEL)); |
| } |
| |
| void ExpectNoView() { |
| // No new Widget was created in ShowUi() or since the last VerifyUi(). |
| EXPECT_FALSE(VerifyUi()); |
| // Our view has really been deleted. |
| EXPECT_EQ(nullptr, ActiveView()); |
| } |
| |
| bool IsUncleanStartup() { |
| return crostini::CrostiniManager::GetForProfile(browser()->profile()) |
| ->IsUncleanStartup(); |
| } |
| |
| void RegisterApp() { |
| vm_tools::apps::ApplicationList app_list = |
| crostini::CrostiniTestHelper::BasicAppList( |
| kDesktopFileId, crostini::kCrostiniDefaultVmName, |
| crostini::kCrostiniDefaultContainerName); |
| guest_os::GuestOsRegistryServiceFactory::GetForProfile(browser()->profile()) |
| ->UpdateApplicationList(app_list); |
| } |
| |
| const std::string& app_id() const { return app_id_; } |
| |
| private: |
| std::string app_id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CrostiniRecoveryViewBrowserTest); |
| }; |
| |
| // Test the dialog is actually launched. |
| IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, InvokeUi_default) { |
| ShowAndVerifyUi(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, NoViewOnNormalStartup) { |
| base::HistogramTester histogram_tester; |
| RegisterApp(); |
| |
| crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId); |
| ExpectNoView(); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Crostini.RecoverySource", |
| static_cast<base::HistogramBase::Sample>(kUiSurface), 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, Cancel) { |
| base::HistogramTester histogram_tester; |
| |
| SetUncleanStartup(); |
| RegisterApp(); |
| // Ensure Terminal System App is installed. |
| web_app::WebAppProvider::Get(browser()->profile()) |
| ->system_web_app_manager() |
| .InstallSystemAppsForTesting(); |
| |
| // First app should fail with 'cancelled for recovery'. |
| crostini::LaunchCrostiniApp( |
| browser()->profile(), app_id(), kDisplayId, {}, |
| base::BindOnce(&ExpectFailure, "cancelled for recovery")); |
| ExpectView(); |
| |
| // Apps launched while dialog is shown should fail with 'recovery in |
| // progress'. |
| crostini::LaunchCrostiniApp( |
| browser()->profile(), app_id(), kDisplayId, {}, |
| base::BindOnce(&ExpectFailure, "recovery in progress")); |
| |
| // Click 'Cancel'. |
| ActiveView()->CancelDialog(); |
| WaitForViewDestroyed(); |
| |
| // Terminal should launch after use clicks 'Cancel'. |
| Browser* terminal_browser = web_app::FindSystemWebAppBrowser( |
| browser()->profile(), web_app::SystemAppType::TERMINAL); |
| CHECK_NE(nullptr, terminal_browser); |
| WaitForLoadFinished(terminal_browser->tab_strip_model()->GetWebContentsAt(0)); |
| |
| // Any new apps launched should show the dialog again. |
| crostini::LaunchCrostiniApp( |
| browser()->profile(), app_id(), kDisplayId, {}, |
| base::BindOnce(&ExpectFailure, "cancelled for recovery")); |
| ExpectView(); |
| |
| ActiveView()->CancelDialog(); |
| WaitForViewDestroyed(); |
| |
| EXPECT_TRUE(IsUncleanStartup()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Crostini.RecoverySource", |
| static_cast<base::HistogramBase::Sample>(kUiSurface), 3); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CrostiniRecoveryViewBrowserTest, Accept) { |
| base::HistogramTester histogram_tester; |
| |
| SetUncleanStartup(); |
| RegisterApp(); |
| |
| crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId); |
| ExpectView(); |
| |
| // Apps launched while dialog is shown should fail with 'recovery in |
| // progress'. |
| crostini::LaunchCrostiniApp( |
| browser()->profile(), app_id(), kDisplayId, {}, |
| base::BindOnce(&ExpectFailure, "recovery in progress")); |
| |
| // Click 'Accept'. |
| ActiveView()->AcceptDialog(); |
| |
| // Buttons should be disabled after clicking Accept. |
| EXPECT_FALSE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK)); |
| EXPECT_FALSE(ActiveView()->IsDialogButtonEnabled(ui::DIALOG_BUTTON_CANCEL)); |
| |
| WaitForViewDestroyed(); |
| |
| EXPECT_FALSE(IsUncleanStartup()); |
| |
| // Apps now launch successfully. |
| crostini::LaunchCrostiniApp(browser()->profile(), app_id(), kDisplayId); |
| ExpectNoView(); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Crostini.RecoverySource", |
| static_cast<base::HistogramBase::Sample>(kUiSurface), 2); |
| } |