blob: c8a056471f14d3b0d161f5f144fb3b7c18b84fc5 [file] [log] [blame]
// Copyright 2025 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/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "content/public/test/browser_test.h"
#include "extensions/common/extension.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/base_window.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_features.h"
#include "ash/webui/boca_ui/url_constants.h"
#include "ash/wm/window_pin_util.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/extensions/window_controller.h"
#include "chrome/browser/extensions/window_controller_list.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser_command_controller.h"
#include "chromeos/ui/base/window_pin_type.h"
#include "content/public/test/test_navigation_observer.h"
#include "ui/aura/window.h"
#endif
using ::testing::NotNull;
namespace extensions {
namespace {
class LockedFullscreenWindowApiTestBase : public ExtensionApiTest {
protected:
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
};
#if !BUILDFLAG(IS_CHROMEOS)
using LockedFullscreenWindowApiTestNonChromeOS =
LockedFullscreenWindowApiTestBase;
// Loading an extension requiring the 'lockWindowFullscreenPrivate' permission
// on non Chrome OS platforms should always fail since the API is available only
// on Chrome OS.
IN_PROC_BROWSER_TEST_F(LockedFullscreenWindowApiTestNonChromeOS,
OpenLockedFullscreenWindow) {
const extensions::Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("locked_fullscreen/with_permission"),
{.ignore_manifest_warnings = true});
ASSERT_TRUE(extension);
ASSERT_EQ(1u, extension->install_warnings().size());
EXPECT_EQ(std::string("'lockWindowFullscreenPrivate' "
"is not allowed for specified platform."),
extension->install_warnings()[0].message);
}
#endif // !BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_CHROMEOS)
class LockedFullscreenWindowApiTestChromeOS
: public LockedFullscreenWindowApiTestBase,
public ::testing::WithParamInterface<bool> {
protected:
LockedFullscreenWindowApiTestChromeOS() {
// TODO(crbug.com/438844429): Remove `kBoca` and `kBocaConsumer` feature
// flags once Boca SWA is installed even when Class Tools policy is not set.
if (IsLockedQuizMigrationEnabled()) {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{ash::features::kBocaOnTaskLockedQuizMigration,
ash::features::kBoca,
ash::features::kBocaConsumer},
/*disabled_features=*/{});
} else {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{ash::features::kBoca,
ash::features::kBocaConsumer},
/*disabled_features=*/{
ash::features::kBocaOnTaskLockedQuizMigration});
}
}
void SetUpOnMainThread() override {
LockedFullscreenWindowApiTestBase::SetUpOnMainThread();
ash::SystemWebAppManager::GetForTest(browser()->profile())
->InstallSystemAppsForTesting();
}
// Launch Boca app and wait for it to open.
void LaunchBocaAppAndWait() {
content::TestNavigationObserver observer(
(GURL(ash::boca::kChromeBocaAppUntrustedIndexURL)));
observer.StartWatchingNewWebContents();
ash::LaunchSystemWebAppAsync(browser()->profile(),
ash::SystemWebAppType::BOCA);
observer.Wait();
}
chromeos::WindowPinType GetCurrentWindowPinType() {
chromeos::WindowPinType type = ash::GetWindowPinType(GetCurrentWindow());
return type;
}
void SetCurrentWindowPinType(chromeos::WindowPinType type) {
if (type == chromeos::WindowPinType::kNone) {
ash::UnpinWindow(GetCurrentWindow());
} else {
ash::PinWindow(GetCurrentWindow(), /*trusted=*/true);
}
}
Browser* FindBocaSystemWebAppBrowser() {
return ash::FindSystemWebAppBrowser(browser()->profile(),
ash::SystemWebAppType::BOCA);
}
bool IsLockedQuizMigrationEnabled() const { return GetParam(); }
private:
aura::Window* GetCurrentWindow() {
extensions::WindowController* controller = nullptr;
for (extensions::WindowController* window :
*extensions::WindowControllerList::GetInstance()) {
if (window->window()->IsActive()) {
controller = window;
break;
}
}
EXPECT_TRUE(controller);
return controller->window()->GetNativeWindow();
}
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_P(LockedFullscreenWindowApiTestChromeOS,
OpenLockedFullscreenWindow) {
ASSERT_TRUE(RunExtensionTest("locked_fullscreen/with_permission",
{.custom_arg = "openLockedFullscreenWindow"}))
<< message_;
// Make sure the newly created window is "trusted pinned" (which means that
// it's in locked fullscreen mode).
EXPECT_EQ(chromeos::WindowPinType::kTrustedPinned, GetCurrentWindowPinType());
}
IN_PROC_BROWSER_TEST_P(LockedFullscreenWindowApiTestChromeOS,
OpenLockedFullscreenWindowWithIncorrectUrlCount) {
if (!IsLockedQuizMigrationEnabled()) {
GTEST_SKIP()
<< "This test is only relevant for the new SWA-based migration case.";
}
ASSERT_TRUE(RunExtensionTest(
"locked_fullscreen/with_permission",
{.custom_arg = "openLockedFullscreenWindowWithIncorrectUrlCount"}))
<< message_;
// Make sure no new windows get created (so only the one created by default
// exists) since the call to chrome.windows.create fails on the javascript
// side.
EXPECT_EQ(1u, extensions::WindowControllerList::GetInstance()->size());
}
IN_PROC_BROWSER_TEST_P(LockedFullscreenWindowApiTestChromeOS,
UpdateWindowToLockedFullscreen) {
if (IsLockedQuizMigrationEnabled()) {
LaunchBocaAppAndWait();
}
ASSERT_TRUE(
RunExtensionTest("locked_fullscreen/with_permission",
{.custom_arg = "updateWindowToLockedFullscreen"}))
<< message_;
// Make sure the current window is put into the "trusted pinned" state.
EXPECT_EQ(chromeos::WindowPinType::kTrustedPinned, GetCurrentWindowPinType());
}
IN_PROC_BROWSER_TEST_P(LockedFullscreenWindowApiTestChromeOS,
UpdateIncompatibleWindowToLockedFullscreen) {
if (!IsLockedQuizMigrationEnabled()) {
GTEST_SKIP()
<< "This test is only relevant for the new SWA-based migration case.";
}
ASSERT_TRUE(RunExtensionTest(
"locked_fullscreen/with_permission",
{.custom_arg = "updateIncompatibleWindowToLockedFullscreen"}))
<< message_;
// chrome.windows.update call fails since the new SWA-based migration does not
// support set locked fullscreen on regular browser window.
EXPECT_EQ(chromeos::WindowPinType::kNone, GetCurrentWindowPinType());
}
IN_PROC_BROWSER_TEST_P(LockedFullscreenWindowApiTestChromeOS,
RemoveLockedFullscreenFromWindow) {
Browser* current_browser = browser();
if (IsLockedQuizMigrationEnabled()) {
LaunchBocaAppAndWait();
current_browser = FindBocaSystemWebAppBrowser();
}
ASSERT_THAT(current_browser, NotNull());
// After locking the window, do a LockedFullscreenStateChanged so the
// command_controller state catches up as well.
SetCurrentWindowPinType(chromeos::WindowPinType::kTrustedPinned);
current_browser->command_controller()->LockedFullscreenStateChanged();
ASSERT_TRUE(
RunExtensionTest("locked_fullscreen/with_permission",
{.custom_arg = "removeLockedFullscreenFromWindow"}))
<< message_;
// Make sure the current window is removed from locked-fullscreen state.
EXPECT_EQ(chromeos::WindowPinType::kNone, GetCurrentWindowPinType());
}
IN_PROC_BROWSER_TEST_P(LockedFullscreenWindowApiTestChromeOS,
RemoveLockedFullscreenFromIncompatibleWindow) {
if (!IsLockedQuizMigrationEnabled()) {
GTEST_SKIP()
<< "This test is only relevant for the new SWA-based migration case.";
}
// After locking the window, do a LockedFullscreenStateChanged so the
// command_controller state catches up as well.
SetCurrentWindowPinType(chromeos::WindowPinType::kTrustedPinned);
browser()->command_controller()->LockedFullscreenStateChanged();
ASSERT_TRUE(RunExtensionTest(
"locked_fullscreen/with_permission",
{.custom_arg = "removeLockedFullscreenFromIncompatibleWindow"}))
<< message_;
// chrome.windows.update call fails since the new SWA-based migration does not
// support set locked fullscreen on regular browser window.
EXPECT_EQ(chromeos::WindowPinType::kTrustedPinned, GetCurrentWindowPinType());
}
// Make sure that commands disabling code works in locked fullscreen mode.
IN_PROC_BROWSER_TEST_P(LockedFullscreenWindowApiTestChromeOS,
VerifyCommandsInLockedFullscreen) {
Browser* current_browser = browser();
if (IsLockedQuizMigrationEnabled()) {
LaunchBocaAppAndWait();
Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
current_browser = boca_app_browser;
}
ASSERT_THAT(current_browser, NotNull());
// IDC_EXIT is always enabled in regular mode so it's a perfect candidate for
// testing.
EXPECT_TRUE(
current_browser->command_controller()->IsCommandEnabled(IDC_EXIT));
ASSERT_TRUE(
RunExtensionTest("locked_fullscreen/with_permission",
{.custom_arg = "updateWindowToLockedFullscreen"}))
<< message_;
// Verify some disabled commands.
EXPECT_FALSE(
current_browser->command_controller()->IsCommandEnabled(IDC_EXIT));
EXPECT_FALSE(
current_browser->command_controller()->IsCommandEnabled(IDC_ZOOM_PLUS));
// Verify some allowlisted commands.
EXPECT_TRUE(
current_browser->command_controller()->IsCommandEnabled(IDC_COPY));
EXPECT_TRUE(
current_browser->command_controller()->IsCommandEnabled(IDC_PASTE));
// IDC_FIND should be disabled for the legacy locked fullscreen, but enabled
// for new SWA-based migration locked fullscreen.
EXPECT_EQ(current_browser->command_controller()->IsCommandEnabled(IDC_FIND),
IsLockedQuizMigrationEnabled());
}
IN_PROC_BROWSER_TEST_P(LockedFullscreenWindowApiTestChromeOS,
OpenLockedFullscreenWindowWithoutPermission) {
ASSERT_TRUE(RunExtensionTest("locked_fullscreen/without_permission",
{.custom_arg = "openLockedFullscreenWindow"}))
<< message_;
// Make sure no new windows get created (so only the one created by default
// exists) since the call to chrome.windows.create fails on the javascript
// side.
EXPECT_EQ(1u, extensions::WindowControllerList::GetInstance()->size());
}
IN_PROC_BROWSER_TEST_P(LockedFullscreenWindowApiTestChromeOS,
UpdateWindowToLockedFullscreenWithoutPermission) {
if (IsLockedQuizMigrationEnabled()) {
LaunchBocaAppAndWait();
}
ASSERT_TRUE(
RunExtensionTest("locked_fullscreen/without_permission",
{.custom_arg = "updateWindowToLockedFullscreen"}))
<< message_;
// chrome.windows.update call fails since this extension doesn't have the
// correct permission and hence the current window has NONE as WindowPinType.
EXPECT_EQ(chromeos::WindowPinType::kNone, GetCurrentWindowPinType());
}
IN_PROC_BROWSER_TEST_P(LockedFullscreenWindowApiTestChromeOS,
RemoveLockedFullscreenFromWindowWithoutPermission) {
Browser* current_browser = browser();
if (IsLockedQuizMigrationEnabled()) {
LaunchBocaAppAndWait();
current_browser = FindBocaSystemWebAppBrowser();
}
ASSERT_THAT(current_browser, NotNull());
// After locking the window, do a LockedFullscreenStateChanged so the
// command_controller state catches up as well.
SetCurrentWindowPinType(chromeos::WindowPinType::kTrustedPinned);
current_browser->command_controller()->LockedFullscreenStateChanged();
ASSERT_TRUE(
RunExtensionTest("locked_fullscreen/without_permission",
{.custom_arg = "removeLockedFullscreenFromWindow"}))
<< message_;
// The current window is still locked-fullscreen.
EXPECT_EQ(chromeos::WindowPinType::kTrustedPinned, GetCurrentWindowPinType());
}
INSTANTIATE_TEST_SUITE_P(LockedFullscreenWindowApiChromeOSTests,
LockedFullscreenWindowApiTestChromeOS,
testing::Bool());
#endif // BUILDFLAG (IS_CHROMEOS)
} // namespace
} // namespace extensions