blob: 4937c2108fecbd34570afdd57694d65d81a4d597 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/values_util.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/chromeos/app_mode/app_session.h"
#include "chrome/browser/chromeos/app_mode/app_session_browser_window_handler.h"
#include "chrome/browser/chromeos/app_mode/app_session_metrics_service.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
#if BUILDFLAG(ENABLE_PLUGINS)
#include "chrome/browser/chromeos/app_mode/kiosk_session_plugin_handler_delegate.h"
#include "content/public/browser/plugin_service.h"
#endif
namespace chromeos {
namespace {
using ::chromeos::FakePowerManagerClient;
constexpr char kTestAppId[] = "aaaabbbbaaaabbbbaaaabbbbaaaabbbb";
constexpr char kTestWebAppName1[] = "test_web_app_name1";
constexpr char kTestWebAppName2[] = "test_web_app_name2";
#if BUILDFLAG(ENABLE_PLUGINS)
constexpr char16_t kPepperPluginName1[] = u"pepper_plugin_name1";
constexpr char16_t kPepperPluginName2[] = u"pepper_plugin_name2";
constexpr char16_t kBrowserPluginName[] = u"browser_plugin_name";
constexpr char kPepperPluginFilePath1[] = "/path/to/pepper_plugin1";
constexpr char kPepperPluginFilePath2[] = "/path/to/pepper_plugin2";
constexpr char kBrowserPluginFilePath[] = "/path/to/browser_plugin";
constexpr char kUnregisteredPluginFilePath[] = "/path/to/unregistered_plugin";
#endif // BUILDFLAG(ENABLE_PLUGINS)
// A test browser window that can toggle fullscreen state.
class FullscreenTestBrowserWindow : public TestBrowserWindow,
ExclusiveAccessContext {
public:
explicit FullscreenTestBrowserWindow(TestingProfile* profile,
bool fullscreen = false)
: fullscreen_(fullscreen), profile_(profile) {}
FullscreenTestBrowserWindow(const FullscreenTestBrowserWindow&) = delete;
FullscreenTestBrowserWindow& operator=(const FullscreenTestBrowserWindow&) =
delete;
~FullscreenTestBrowserWindow() override = default;
// TestBrowserWindow:
bool ShouldHideUIForFullscreen() const override { return fullscreen_; }
bool IsFullscreen() const override { return fullscreen_; }
void EnterFullscreen(const GURL& url,
ExclusiveAccessBubbleType type,
int64_t display_id) override {
fullscreen_ = true;
}
void ExitFullscreen() override { fullscreen_ = false; }
bool IsToolbarShowing() const override { return false; }
bool IsLocationBarVisible() const override { return true; }
ExclusiveAccessContext* GetExclusiveAccessContext() override { return this; }
// ExclusiveAccessContext:
Profile* GetProfile() override { return profile_; }
content::WebContents* GetActiveWebContents() override {
NOTIMPLEMENTED();
return nullptr;
}
void UpdateExclusiveAccessExitBubbleContent(
const GURL& url,
ExclusiveAccessBubbleType bubble_type,
ExclusiveAccessBubbleHideCallback bubble_first_hide_callback,
bool notify_download,
bool force_update) override {}
bool IsExclusiveAccessBubbleDisplayed() const override { return false; }
void OnExclusiveAccessUserInput() override {}
bool CanUserExitFullscreen() const override { return true; }
private:
bool fullscreen_ = false;
raw_ptr<TestingProfile> profile_;
};
bool IsBrowserFullscreen(Browser& browser) {
return browser.exclusive_access_manager()
->fullscreen_controller()
->IsFullscreenForBrowser();
}
std::unique_ptr<Browser> CreateBrowserWithFullscreenTestWindowForParams(
Browser::CreateParams params,
TestingProfile* profile,
bool is_main_browser = false) {
// The main browser window for the kiosk is always fullscreen in the
// prodaction.
FullscreenTestBrowserWindow* window =
new FullscreenTestBrowserWindow(profile, /*fullscreen=*/is_main_browser);
new TestBrowserWindowOwner(window);
params.window = window;
return base::WrapUnique<Browser>(Browser::Create(params));
}
void EmulateDeviceReboot() {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
ash::switches::kFirstExecAfterBoot);
}
struct KioskSessionRestartTestCase {
std::string test_name;
bool run_with_reboot = false;
};
struct KioskSessionPowerManagerRequestRestartTestCase {
power_manager::RequestRestartReason power_manager_reason;
KioskSessionRestartReason restart_reason;
};
void CheckSessionRestartReasonHistogramDependingOnRebootStatus(
bool run_with_reboot,
const KioskSessionRestartReason& reasonWithoutReboot,
const KioskSessionRestartReason& reasonWithReboot,
const base::HistogramTester* histogram) {
if (run_with_reboot) {
histogram->ExpectBucketCount(kKioskSessionRestartReasonHistogram,
reasonWithReboot, 1);
} else {
histogram->ExpectBucketCount(kKioskSessionRestartReasonHistogram,
reasonWithoutReboot, 1);
}
histogram->ExpectTotalCount(kKioskSessionRestartReasonHistogram, 1);
}
} // namespace
template <typename AppSessionParamType = KioskSessionRestartTestCase>
class AppSessionBaseTest
: public ::testing::TestWithParam<AppSessionParamType> {
public:
explicit AppSessionBaseTest(
base::test::TaskEnvironment::TimeSource time_source =
base::test::TaskEnvironment::TimeSource::DEFAULT)
: task_environment_{time_source},
local_state_(std::make_unique<ScopedTestingLocalState>(
TestingBrowserProcess::GetGlobal())) {}
AppSessionBaseTest(const AppSessionBaseTest&) = delete;
AppSessionBaseTest& operator=(const AppSessionBaseTest&) = delete;
static void SetUpTestSuite() {
chromeos::PowerManagerClient::InitializeFake();
}
void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
static void TearDownTestSuite() { chromeos::PowerManagerClient::Shutdown(); }
TestingPrefServiceSimple* local_state() { return local_state_->Get(); }
TestingProfile* profile() { return &profile_; }
base::HistogramTester* histogram() { return &histogram_; }
base::test::TaskEnvironment* task_environment() { return &task_environment_; }
std::unique_ptr<Browser> CreateBrowserWithTestWindow() {
return CreateBrowserWithFullscreenTestWindowForParams(
Browser::CreateParams(profile(), true), profile());
}
std::unique_ptr<Browser> CreateBrowserForWebApp(
const std::string& web_app_name,
absl::optional<Browser::Type> browser_type = absl::nullopt) {
Browser::CreateParams params = Browser::CreateParams::CreateForAppPopup(
/*app_name=*/web_app_name, /*trusted_source=*/true,
/*window_bounds=*/gfx::Rect(), /*profile=*/profile(),
/*user_gesture=*/true);
if (browser_type.has_value()) {
params.type = browser_type.value();
}
return CreateBrowserWithFullscreenTestWindowForParams(params, profile());
}
// Simulate starting a web kiosk session.
void StartWebKioskSession(
const std::string& web_app_name = kTestWebAppName1) {
// Create the main kiosk browser window, which is normally auto-created when
// a web kiosk session starts.
web_kiosk_main_browser_ = CreateBrowserWithFullscreenTestWindowForParams(
Browser::CreateParams::CreateForApp(
/*app_name=*/web_app_name, /*trusted_source=*/true,
/*window_bounds=*/gfx::Rect(), /*profile=*/profile(),
/*user_gesture=*/true),
profile(), /*is_main_browser=*/true);
app_session_ = AppSession::CreateForTesting(
profile(), base::DoNothing(), local_state(), {crash_path().value()});
app_session_->InitForWebKiosk(web_app_name);
task_environment_.RunUntilIdle();
}
// Simulate starting a chrome app kiosk session.
void StartChromeAppKioskSession() {
app_session_ = std::make_unique<AppSession>(profile(), base::DoNothing(),
local_state());
app_session_->Init(kTestAppId);
}
// Waits until |app_session_| handles creation of |new_browser_window| and
// returns whether |new_browser_window| was asked to close. In this case we
// will also ensure that |new_browser_window| was automatically closed.
bool ShouldBrowserBeClosedByAppSessionBrowserHander(
BrowserWindow* new_browser_window) {
bool already_closed = false;
static_cast<TestBrowserWindow*>(new_browser_window)
->SetCloseCallback(base::BindLambdaForTesting(
[&already_closed]() { already_closed = true; }));
// Wait until the browser is handled by |app_session_|.
base::RunLoop handler_loop;
bool result = false;
app_session_->SetOnHandleBrowserCallbackForTesting(
base::BindLambdaForTesting([&handler_loop, &result](bool is_closing) {
result = is_closing;
handler_loop.Quit();
}));
handler_loop.Run();
if (result) {
EXPECT_TRUE(already_closed);
}
return result;
}
void CloseMainBrowser() {
// Close the main browser window.
web_kiosk_main_browser_.reset();
}
bool IsMainBrowserFullscreen() {
return IsBrowserFullscreen(*web_kiosk_main_browser_);
}
bool IsSessionShuttingDown() const {
return app_session_->is_shutting_down();
}
void ResetAppSession() { app_session_.reset(); }
PrefService* GetPrefs() { return profile()->GetPrefs(); }
KioskSessionPluginHandlerDelegate* GetPluginHandlerDelegate() {
return app_session_->GetPluginHandlerDelegateForTesting();
}
base::FilePath crash_path() const { return temp_dir_.GetPath(); }
private:
content::BrowserTaskEnvironment task_environment_;
base::ScopedTempDir temp_dir_;
std::unique_ptr<ScopedTestingLocalState> local_state_;
// Must outlive |app_session_|.
TestingProfile profile_;
// Main browser window created when launching a web kiosk app.
// Could be nullptr if |StartWebKioskSession| function was not called.
std::unique_ptr<Browser> web_kiosk_main_browser_;
base::HistogramTester histogram_;
std::unique_ptr<AppSession> app_session_;
};
using AppSessionTest = AppSessionBaseTest<>;
using AppSessionRestartReasonTest =
AppSessionBaseTest<KioskSessionRestartTestCase>;
TEST_F(AppSessionTest, WebKioskTracksBrowserCreation) {
{
base::Value::Dict value;
value.Set(kKioskSessionStartTime, base::TimeToValue(base::Time::Now()));
local_state()->SetDict(prefs::kKioskMetrics, std::move(value));
}
StartWebKioskSession();
histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
KioskSessionState::kWebStarted, 1);
histogram()->ExpectTotalCount(kKioskSessionCountPerDayHistogram, 1);
EXPECT_TRUE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateBrowserWithTestWindow()->window()));
// The main browser window still exists, the kiosk session should not
// shutdown.
EXPECT_FALSE(IsSessionShuttingDown());
// Opening a new browser should not be counted as a new session.
histogram()->ExpectTotalCount(kKioskSessionCountPerDayHistogram, 1);
CloseMainBrowser();
EXPECT_TRUE(IsSessionShuttingDown());
const base::Value::Dict& dict = local_state()->GetDict(prefs::kKioskMetrics);
const base::Value::List* sessions_list =
dict.FindList(kKioskSessionLastDayList);
ASSERT_TRUE(sessions_list);
EXPECT_EQ(1u, sessions_list->size());
histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
KioskSessionState::kStopped, 1);
EXPECT_EQ(2u, histogram()->GetAllSamples(kKioskSessionStateHistogram).size());
histogram()->ExpectTotalCount(kKioskSessionDurationNormalHistogram, 1);
histogram()->ExpectTotalCount(kKioskSessionDurationInDaysNormalHistogram, 0);
}
TEST_F(AppSessionTest, ChromeAppKioskSessionState) {
StartChromeAppKioskSession();
histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
KioskSessionState::kStarted, 1);
histogram()->ExpectTotalCount(kKioskSessionCountPerDayHistogram, 1);
}
TEST_F(AppSessionTest, ChromeAppKioskTracksBrowserCreation) {
StartChromeAppKioskSession();
EXPECT_TRUE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateBrowserWithTestWindow()->window()));
// Closing the browser should not shutdown the ChromeApp kiosk session.
EXPECT_FALSE(IsSessionShuttingDown());
histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
KioskBrowserWindowType::kClosedRegularBrowser,
1);
histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
const base::Value::Dict& dict = local_state()->GetDict(prefs::kKioskMetrics);
const base::Value::List* sessions_list =
dict.FindList(kKioskSessionLastDayList);
ASSERT_TRUE(sessions_list);
EXPECT_EQ(1u, sessions_list->size());
// Emulate exiting kiosk session.
ResetAppSession();
histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
KioskSessionState::kStopped, 1);
EXPECT_EQ(2u, histogram()->GetAllSamples(kKioskSessionStateHistogram).size());
histogram()->ExpectTotalCount(kKioskSessionDurationNormalHistogram, 1);
histogram()->ExpectTotalCount(kKioskSessionDurationInDaysNormalHistogram, 0);
}
// Check that sessions list in local_state contains only sessions within the
// last 24h.
TEST_F(AppSessionTest, WebKioskLastDaySessions) {
// Setup local_state with 5 more kiosk sessions happened prior to the current
// one: {now, 2,3,4,5 days ago}
{
base::Value::List session_list;
session_list.Append(base::TimeToValue(base::Time::Now()));
const size_t kMaxDays = 4;
for (size_t i = 0; i < kMaxDays; i++) {
session_list.Append(
base::TimeToValue(base::Time::Now() - base::Days(i + 2)));
}
base::Value::Dict value;
value.Set(kKioskSessionLastDayList, std::move(session_list));
value.Set(kKioskSessionStartTime,
base::TimeToValue(base::Time::Now() -
2 * kKioskSessionDurationHistogramLimit));
local_state()->SetDict(prefs::kKioskMetrics, std::move(value));
}
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
ash::switches::kLoginUser, "fake-user");
base::FilePath crash_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(crash_path(), &crash_file));
StartWebKioskSession();
// We set |kKioskSessionStartTime| for previous session and did not clear them
// up, so it emulates previous session crashes.
histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
KioskSessionState::kRestored, 1);
histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
KioskSessionState::kCrashed, 1);
histogram()->ExpectTotalCount(kKioskSessionDurationCrashedHistogram, 1);
histogram()->ExpectTotalCount(kKioskSessionDurationInDaysCrashedHistogram, 1);
histogram()->ExpectTotalCount(kKioskSessionCountPerDayHistogram, 1);
CloseMainBrowser();
EXPECT_TRUE(IsSessionShuttingDown());
const base::Value::Dict& dict = local_state()->GetDict(prefs::kKioskMetrics);
const base::Value::List* sessions_list =
dict.FindList(kKioskSessionLastDayList);
ASSERT_TRUE(sessions_list);
// There should be only two kiosk sessions on the list:
// the one that happened right before the current one and the current one.
EXPECT_EQ(2u, sessions_list->size());
for (const auto& time : *sessions_list) {
EXPECT_LE(base::Time::Now() - base::ValueToTime(time).value(),
base::Days(1));
}
histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
KioskSessionState::kStopped, 1);
EXPECT_EQ(3u, histogram()->GetAllSamples(kKioskSessionStateHistogram).size());
histogram()->ExpectTotalCount(kKioskSessionDurationNormalHistogram, 1);
histogram()->ExpectTotalCount(kKioskSessionDurationInDaysNormalHistogram, 0);
}
TEST_F(AppSessionTest, DoNotOpenSecondBrowserInWebKiosk) {
StartWebKioskSession(kTestWebAppName1);
EXPECT_TRUE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateBrowserForWebApp(kTestWebAppName1)->window()));
}
TEST_F(AppSessionTest, OpenSecondBrowserInWebKioskIfAllowed) {
GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
StartWebKioskSession(kTestWebAppName1);
EXPECT_FALSE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateBrowserForWebApp(kTestWebAppName1)->window()));
}
TEST_F(AppSessionTest, EnsureSecondBrowserIsFullscreenInWebKiosk) {
GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
StartWebKioskSession(kTestWebAppName1);
EXPECT_TRUE(IsMainBrowserFullscreen());
auto second_browser = CreateBrowserForWebApp(kTestWebAppName1);
ShouldBrowserBeClosedByAppSessionBrowserHander(second_browser->window());
EXPECT_TRUE(IsBrowserFullscreen(*second_browser));
}
TEST_F(AppSessionTest, DoNotOpenSecondBrowserInWebKioskIfTypeIsNotAppPopup) {
const std::vector<Browser::Type> not_app_popup_browser_types = {
Browser::Type::TYPE_NORMAL,
Browser::Type::TYPE_POPUP,
Browser::Type::TYPE_APP,
Browser::Type::TYPE_DEVTOOLS,
#if BUILDFLAG(IS_CHROMEOS_ASH)
Browser::Type::TYPE_CUSTOM_TAB,
#endif
Browser::Type::TYPE_PICTURE_IN_PICTURE,
};
GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
StartWebKioskSession(kTestWebAppName1);
for (auto browser_type : not_app_popup_browser_types) {
EXPECT_TRUE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateBrowserForWebApp(kTestWebAppName1, browser_type)->window()));
}
}
TEST_F(AppSessionTest, DoNotOpenSecondBrowserInWebKioskWithEmptyWebAppName) {
GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
StartWebKioskSession();
EXPECT_TRUE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateBrowserWithTestWindow()->window()));
}
TEST_F(AppSessionTest,
DoNotOpenSecondBrowserInWebKioskWithDifferentWebAppName) {
GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
StartWebKioskSession(kTestWebAppName1);
EXPECT_TRUE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateBrowserForWebApp(kTestWebAppName2)->window()));
}
TEST_F(AppSessionTest, DoNotOpenSecondBrowserInChromeAppKiosk) {
// This flag allows opening new windows only for the web kiosk session. For
// chrome app kiosk we still should block all new browsers.
GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
StartChromeAppKioskSession();
EXPECT_TRUE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateBrowserForWebApp(kTestWebAppName2)->window()));
}
TEST_F(AppSessionTest, NewOpenedRegularBrowserMetrics) {
GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
StartWebKioskSession(kTestWebAppName1);
ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateBrowserForWebApp(kTestWebAppName1)->window());
histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
KioskBrowserWindowType::kOpenedRegularBrowser,
1);
histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}
TEST_F(AppSessionTest, NewClosedRegularBrowserMetrics) {
GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, false);
StartWebKioskSession(kTestWebAppName1);
ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateBrowserForWebApp(kTestWebAppName1)->window());
histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
KioskBrowserWindowType::kClosedRegularBrowser,
1);
histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}
TEST_F(AppSessionTest, DoNotExitWebKioskSessionWhenSecondBrowserIsOpened) {
GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
StartWebKioskSession();
auto second_browser = CreateBrowserForWebApp(kTestWebAppName1);
EXPECT_FALSE(
ShouldBrowserBeClosedByAppSessionBrowserHander(second_browser->window()));
CloseMainBrowser();
EXPECT_FALSE(IsSessionShuttingDown());
second_browser.reset();
// Exit kioks session when the last browser is closed.
EXPECT_TRUE(IsSessionShuttingDown());
}
TEST_F(AppSessionTest, InitialBrowserShouldBeHandledAsRegularBrowser) {
GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
StartWebKioskSession();
auto second_browser = CreateBrowserForWebApp(kTestWebAppName1);
EXPECT_FALSE(
ShouldBrowserBeClosedByAppSessionBrowserHander(second_browser->window()));
second_browser.reset();
EXPECT_FALSE(IsSessionShuttingDown());
CloseMainBrowser();
// Exit kioks session when the last browser is closed.
EXPECT_TRUE(IsSessionShuttingDown());
}
TEST_P(AppSessionRestartReasonTest, StoppedMetric) {
const KioskSessionRestartTestCase& test_config = GetParam();
StartWebKioskSession();
// Emulate exiting the kiosk session.
CloseMainBrowser();
EXPECT_TRUE(IsSessionShuttingDown());
if (test_config.run_with_reboot) {
EmulateDeviceReboot();
}
histogram()->ExpectTotalCount(kKioskSessionRestartReasonHistogram, 0);
StartWebKioskSession();
if (test_config.run_with_reboot) {
histogram()->ExpectBucketCount(
kKioskSessionRestartReasonHistogram,
KioskSessionRestartReason::kStoppedWithReboot, 1);
} else {
histogram()->ExpectBucketCount(kKioskSessionRestartReasonHistogram,
KioskSessionRestartReason::kStopped, 1);
}
histogram()->ExpectTotalCount(kKioskSessionRestartReasonHistogram, 1);
}
TEST_P(AppSessionRestartReasonTest, CrashMetric) {
const KioskSessionRestartTestCase& test_config = GetParam();
// Setup `kKioskSessionStartTime` and add a file to the crash directory to
// emulate previous kiosk session crash.
base::Value::Dict value;
value.Set(kKioskSessionStartTime,
base::TimeToValue(base::Time::Now() - base::Hours(1)));
local_state()->SetDict(prefs::kKioskMetrics, std::move(value));
base::FilePath crash_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(crash_path(), &crash_file));
if (test_config.run_with_reboot) {
EmulateDeviceReboot();
}
StartWebKioskSession();
CheckSessionRestartReasonHistogramDependingOnRebootStatus(
test_config.run_with_reboot, KioskSessionRestartReason::kCrashed,
KioskSessionRestartReason::kCrashedWithReboot, histogram());
}
TEST_P(AppSessionRestartReasonTest, LocalStateWasNotSavedMetric) {
const KioskSessionRestartTestCase& test_config = GetParam();
// Setup `kKioskSessionStartTime` to emulate previous kiosk session stopped
// correctly, but because of race condition, `kKioskSessionStartTime` was not
// cleaned.
base::Value::Dict value;
value.Set(kKioskSessionStartTime,
base::TimeToValue(base::Time::Now() - base::Hours(1)));
local_state()->SetDict(prefs::kKioskMetrics, std::move(value));
if (test_config.run_with_reboot) {
EmulateDeviceReboot();
}
StartWebKioskSession();
CheckSessionRestartReasonHistogramDependingOnRebootStatus(
test_config.run_with_reboot,
KioskSessionRestartReason::kLocalStateWasNotSaved,
KioskSessionRestartReason::kLocalStateWasNotSavedWithReboot, histogram());
}
#if BUILDFLAG(ENABLE_PLUGINS)
TEST_P(AppSessionRestartReasonTest, PluginCrashedMetric) {
const KioskSessionRestartTestCase& test_config = GetParam();
StartWebKioskSession();
KioskSessionPluginHandlerDelegate* delegate = GetPluginHandlerDelegate();
delegate->OnPluginCrashed(base::FilePath(kBrowserPluginFilePath));
// Emulate exiting the kiosk session.
CloseMainBrowser();
EXPECT_TRUE(IsSessionShuttingDown());
if (test_config.run_with_reboot) {
EmulateDeviceReboot();
}
histogram()->ExpectTotalCount(kKioskSessionRestartReasonHistogram, 0);
StartWebKioskSession();
CheckSessionRestartReasonHistogramDependingOnRebootStatus(
test_config.run_with_reboot, KioskSessionRestartReason::kPluginCrashed,
KioskSessionRestartReason::kPluginCrashedWithReboot, histogram());
}
TEST_P(AppSessionRestartReasonTest, PluginHungMetric) {
const KioskSessionRestartTestCase& test_config = GetParam();
// Create a fake power manager client.
// FakePowerManagerClient client;
StartWebKioskSession();
KioskSessionPluginHandlerDelegate* delegate = GetPluginHandlerDelegate();
delegate->OnPluginHung(std::set<int>());
// Emulate exiting the kiosk session.
CloseMainBrowser();
EXPECT_TRUE(IsSessionShuttingDown());
if (test_config.run_with_reboot) {
EmulateDeviceReboot();
}
histogram()->ExpectTotalCount(kKioskSessionRestartReasonHistogram, 0);
StartWebKioskSession();
CheckSessionRestartReasonHistogramDependingOnRebootStatus(
test_config.run_with_reboot, KioskSessionRestartReason::kPluginHung,
KioskSessionRestartReason::kPluginHungWithReboot, histogram());
}
#endif // BUILDFLAG(ENABLE_PLUGINS)
INSTANTIATE_TEST_SUITE_P(
AppSessionRestartReasons,
AppSessionRestartReasonTest,
testing::ValuesIn<KioskSessionRestartTestCase>({
{/*test_name=*/"WithReboot", /*run_with_reboot=*/false},
{/*test_name=*/"WithoutReboot", /*run_with_reboot=*/true},
}),
[](const testing::TestParamInfo<AppSessionRestartReasonTest::ParamType>&
info) { return info.param.test_name; });
TEST_F(AppSessionRestartReasonTest, PowerManagerRequestRestart) {
std::vector<KioskSessionPowerManagerRequestRestartTestCase> test_cases = {
{/*power_manager_reason=*/power_manager::RequestRestartReason::
REQUEST_RESTART_SCHEDULED_REBOOT_POLICY,
/*restart_reason=*/KioskSessionRestartReason::kRebootPolicy},
{/*power_manager_reason=*/power_manager::RequestRestartReason::
REQUEST_RESTART_REMOTE_ACTION_REBOOT,
/*restart_reason=*/KioskSessionRestartReason::kRemoteActionReboot},
{/*power_manager_reason=*/power_manager::RequestRestartReason::
REQUEST_RESTART_API,
/*restart_reason=*/KioskSessionRestartReason::kRestartApi}};
for (auto test_case : test_cases) {
StartWebKioskSession();
chromeos::FakePowerManagerClient::Get()->RequestRestart(
test_case.power_manager_reason, "test reboot description");
// Emulate exiting the kiosk session.
CloseMainBrowser();
EXPECT_TRUE(IsSessionShuttingDown());
StartWebKioskSession();
histogram()->ExpectBucketCount(kKioskSessionRestartReasonHistogram,
test_case.restart_reason, 1);
}
}
// Kiosk type agnostic test class. Runs all tests for web and chrome app kiosks.
class AppSessionTroubleshootingTest : public AppSessionBaseTest<bool> {
public:
void SetUpKioskSession() {
bool is_web_kiosk = GetParam();
if (is_web_kiosk) {
StartWebKioskSession();
return;
}
StartChromeAppKioskSession();
}
std::unique_ptr<Browser> CreateDevToolsBrowserWithTestWindow() {
auto params = Browser::CreateParams::CreateForDevTools(profile());
params.window = &test_window_;
return base::WrapUnique<Browser>(Browser::Create(params));
}
private:
TestBrowserWindow test_window_;
};
TEST_P(AppSessionTroubleshootingTest, EnableTroubleshootingToolsDuringSession) {
SetUpKioskSession();
GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled, true);
// Kiosk session should shoutdown only if policy is changed from enable to
// disable.
EXPECT_FALSE(IsSessionShuttingDown());
GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled, false);
EXPECT_TRUE(IsSessionShuttingDown());
}
TEST_P(AppSessionTroubleshootingTest,
EnableTroubleshootingToolsBeforeSessionStarted) {
GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled, true);
SetUpKioskSession();
GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled, false);
EXPECT_TRUE(IsSessionShuttingDown());
}
TEST_P(AppSessionTroubleshootingTest, OpenDevToolsEnabledTroubleshootingTools) {
SetUpKioskSession();
GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled, true);
EXPECT_FALSE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateDevToolsBrowserWithTestWindow()->window()));
histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
KioskBrowserWindowType::kOpenedDevToolsBrowser,
1);
histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}
TEST_P(AppSessionTroubleshootingTest,
CloseDevToolsDisabledTroubleshootingTools) {
SetUpKioskSession();
// Kiosk troubleshooting tools are disabled by default.
EXPECT_TRUE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateDevToolsBrowserWithTestWindow()->window()));
histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
KioskBrowserWindowType::kClosedRegularBrowser,
1);
histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}
TEST_P(AppSessionTroubleshootingTest,
OpenDevToolsDisableTroubleshootingToolsDuringSession) {
SetUpKioskSession();
GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled, true);
// Kiosk session should shoutdown only if policy is changed from enable to
// disable.
EXPECT_FALSE(IsSessionShuttingDown());
EXPECT_FALSE(ShouldBrowserBeClosedByAppSessionBrowserHander(
CreateDevToolsBrowserWithTestWindow()->window()));
histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
KioskBrowserWindowType::kOpenedDevToolsBrowser,
1);
histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled, false);
EXPECT_TRUE(IsSessionShuttingDown());
}
INSTANTIATE_TEST_SUITE_P(AppSessionTroubleshootingTools,
AppSessionTroubleshootingTest,
::testing::Bool());
#if BUILDFLAG(ENABLE_PLUGINS)
TEST_F(AppSessionTest, ShouldHandlePlugin) {
// Create an out-of-process pepper plugin.
content::WebPluginInfo info1;
info1.name = kPepperPluginName1;
info1.path = base::FilePath(kPepperPluginFilePath1);
info1.type = content::WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS;
// Create an in-of-process pepper plugin.
content::WebPluginInfo info2;
info2.name = kPepperPluginName2;
info2.path = base::FilePath(kPepperPluginFilePath2);
info2.type = content::WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS;
// Create an in-of-process browser (non-pepper) plugin.
content::WebPluginInfo info3;
info3.name = kBrowserPluginName;
info3.path = base::FilePath(kBrowserPluginFilePath);
info3.type = content::WebPluginInfo::PLUGIN_TYPE_BROWSER_PLUGIN;
// Register two pepper plugins.
content::PluginService* service = content::PluginService::GetInstance();
service->RegisterInternalPlugin(info1, true);
service->RegisterInternalPlugin(info2, true);
service->RegisterInternalPlugin(info3, true);
service->Init();
service->RefreshPlugins();
// Force plugins to load and wait for completion.
base::RunLoop run_loop;
service->GetPlugins(base::BindOnce(
[](base::OnceClosure callback,
const std::vector<content::WebPluginInfo>& ignore) {
std::move(callback).Run();
},
run_loop.QuitClosure()));
run_loop.Run();
AppSession app_session(profile());
KioskSessionPluginHandlerDelegate* delegate =
app_session.GetPluginHandlerDelegateForTesting();
// The app session should handle two pepper plugins.
EXPECT_TRUE(
delegate->ShouldHandlePlugin(base::FilePath(kPepperPluginFilePath1)));
EXPECT_TRUE(
delegate->ShouldHandlePlugin(base::FilePath(kPepperPluginFilePath2)));
// The app session should not handle the browser plugin.
EXPECT_FALSE(
delegate->ShouldHandlePlugin(base::FilePath(kBrowserPluginFilePath)));
// The app session should not handle the unregistered plugin.
EXPECT_FALSE(delegate->ShouldHandlePlugin(
base::FilePath(kUnregisteredPluginFilePath)));
}
TEST_F(AppSessionTest, OnPluginCrashed) {
StartWebKioskSession();
KioskSessionPluginHandlerDelegate* delegate = GetPluginHandlerDelegate();
// Verified the number of restart calls.
EXPECT_EQ(
chromeos::FakePowerManagerClient::Get()->num_request_restart_calls(), 0);
delegate->OnPluginCrashed(base::FilePath(kBrowserPluginFilePath));
EXPECT_EQ(
chromeos::FakePowerManagerClient::Get()->num_request_restart_calls(), 1);
histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
KioskSessionState::kPluginCrashed, 1);
EXPECT_EQ(2u, histogram()->GetAllSamples(kKioskSessionStateHistogram).size());
}
TEST_F(AppSessionTest, OnPluginHung) {
StartWebKioskSession();
KioskSessionPluginHandlerDelegate* delegate = GetPluginHandlerDelegate();
// Only verify if this method can be called without error.
delegate->OnPluginHung(std::set<int>());
histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
KioskSessionState::kPluginHung, 1);
EXPECT_EQ(2u, histogram()->GetAllSamples(kKioskSessionStateHistogram).size());
}
#endif // BUILDFLAG(ENABLE_PLUGINS)
} // namespace chromeos