blob: 8d43feda2a8c7f917a43f035e35de6e9390551d4 [file]
// Copyright 2026 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/devtools/protocol/browser_handler_android.h"
#include <cstdint>
#include <memory>
#include <utility>
#include "base/android/jni_android.h"
#include "base/callback_list.h"
#include "base/functional/callback.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/ui/android/tab_model/tab_model_test_helper.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/test/native_unit_test_support_jni/BrowserWindowInterfaceIteratorAndroidNativeUnitTestSupport_jni.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/sessions/content/session_tab_helper.h"
#include "components/sessions/core/session_id.h"
#include "content/public/browser/web_contents.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/base_window.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/gfx/geometry/rect.h"
namespace {
// Minimal ui::BaseWindow stub for bounds and state reads and mutations.
class FakeBaseWindow : public ui::BaseWindow {
public:
FakeBaseWindow(gfx::Rect bounds,
gfx::Rect restored_bounds,
bool fullscreen,
bool maximized,
bool minimized,
bool can_resize = true)
: bounds_(bounds),
restored_bounds_(restored_bounds),
fullscreen_(fullscreen),
maximized_(maximized),
minimized_(minimized),
resize_precheck_result_(
can_resize
? ui::WindowResizePrecheckResult::kOk
: ui::WindowResizePrecheckResult::kAndroidNotAFreeformWindow) {}
bool IsActive() const override { return false; }
bool IsMaximized() const override { return maximized_; }
bool IsMinimized() const override { return minimized_; }
bool IsFullscreen() const override { return fullscreen_; }
gfx::NativeWindow GetNativeWindow() const override { return {}; }
gfx::Rect GetRestoredBounds() const override { return restored_bounds_; }
ui::mojom::WindowShowState GetRestoredState() const override {
return ui::mojom::WindowShowState::kNormal;
}
gfx::Rect GetBounds() const override { return bounds_; }
void Show() override {}
void Hide() override {}
bool IsVisible() const override { return true; }
void ShowInactive() override {}
void Close() override {}
void Activate() override {}
void Deactivate() override {}
bool CanResize(ui::WindowResizePrecheckResult& result) const override {
result = resize_precheck_result_;
return result == ui::WindowResizePrecheckResult::kOk;
}
void Maximize() override {
maximized_ = true;
minimized_ = false;
}
void Minimize() override {
maximized_ = false;
minimized_ = true;
}
void Restore() override {
maximized_ = false;
minimized_ = false;
}
void SetBounds(const gfx::Rect& bounds) override {
bounds_ = bounds;
maximized_ = false;
minimized_ = false;
}
void FlashFrame(bool flash) override {}
ui::ZOrderLevel GetZOrderLevel() const override {
return ui::ZOrderLevel::kNormal;
}
void SetZOrderLevel(ui::ZOrderLevel order) override {}
void SetResizePrecheckResult(ui::WindowResizePrecheckResult result) {
resize_precheck_result_ = result;
}
private:
gfx::Rect bounds_;
gfx::Rect restored_bounds_;
bool fullscreen_;
bool maximized_;
bool minimized_;
ui::WindowResizePrecheckResult resize_precheck_result_;
};
class FakeBrowserWindowInterface : public BrowserWindowInterface {
public:
FakeBrowserWindowInterface(Profile* profile,
SessionID session_id,
ui::BaseWindow* window)
: profile_(profile), session_id_(session_id), window_(window) {}
~FakeBrowserWindowInterface() override {
browser_did_close_callback_list_.Notify(this);
}
Profile* GetProfile() override { return profile_; }
const Profile* GetProfile() const override { return profile_; }
const SessionID& GetSessionID() const override { return session_id_; }
bool IsDeleteScheduled() const override { return false; }
base::CallbackListSubscription RegisterBrowserDidClose(
BrowserDidCloseCallback callback) override {
return browser_did_close_callback_list_.Add(std::move(callback));
}
ui::UnownedUserDataHost& GetUnownedUserDataHost() override {
return user_data_host_;
}
const ui::UnownedUserDataHost& GetUnownedUserDataHost() const override {
return user_data_host_;
}
Type GetType() const override { return Type::TYPE_NORMAL; }
ui::BaseWindow* GetWindow() override { return window_; }
const ui::BaseWindow* GetWindow() const override { return window_; }
content::WebContents* OpenURL(
const content::OpenURLParams&,
base::OnceCallback<void(content::NavigationHandle&)>) override {
return nullptr;
}
base::WeakPtr<BrowserWindowInterface> GetWeakPtr() override {
return weak_ptr_factory_.GetWeakPtr();
}
private:
raw_ptr<Profile> profile_;
const SessionID session_id_;
raw_ptr<ui::BaseWindow> window_;
ui::UnownedUserDataHost user_data_host_;
base::RepeatingCallbackList<void(BrowserWindowInterface*)>
browser_did_close_callback_list_;
base::WeakPtrFactory<FakeBrowserWindowInterface> weak_ptr_factory_{this};
};
class TestFrontendChannel : public protocol::FrontendChannel {
public:
void SendProtocolResponse(
int call_id,
std::unique_ptr<protocol::Serializable> message) override {}
void SendProtocolNotification(
std::unique_ptr<protocol::Serializable> message) override {}
void FlushProtocolNotifications() override {}
};
} // namespace
class BrowserHandlerAndroidTest : public ChromeRenderViewHostTestHarness {};
TEST_F(BrowserHandlerAndroidTest, PrefersTabWindowIdOverTabModelSessionId) {
OwningTestTabModel tab_model(profile());
TabAndroid* tab =
tab_model.AddTabFromWebContents(CreateTestWebContents(), /*index=*/0);
sessions::SessionTabHelper::CreateForWebContents(tab->web_contents(),
base::NullCallback());
SessionID tab_window_id = SessionID::NewUnique();
tab->SetWindowSessionID(tab_window_id);
ASSERT_NE(tab_model.GetSessionId(), tab_window_id);
std::optional<int> resolved =
BrowserHandlerAndroid::ResolveWindowIdForWebContents(tab->web_contents());
ASSERT_TRUE(resolved.has_value());
EXPECT_EQ(tab_window_id.id(), resolved.value());
}
TEST_F(BrowserHandlerAndroidTest,
FallsBackToTabModelSessionIdWhenTabHasNoWindowId) {
OwningTestTabModel tab_model(profile());
TabAndroid* tab =
tab_model.AddTabFromWebContents(CreateTestWebContents(), /*index=*/0);
// Tab carries no explicit window id; resolver should fall back to the
// owning TabModel's session id.
ASSERT_FALSE(tab->GetWindowId().is_valid());
std::optional<int> resolved =
BrowserHandlerAndroid::ResolveWindowIdForWebContents(tab->web_contents());
ASSERT_TRUE(resolved.has_value());
EXPECT_EQ(tab_model.GetSessionId().id(), resolved.value());
}
TEST_F(BrowserHandlerAndroidTest, ReturnsNulloptWhenWebContentsUnknown) {
std::unique_ptr<content::WebContents> standalone = CreateTestWebContents();
std::optional<int> resolved =
BrowserHandlerAndroid::ResolveWindowIdForWebContents(standalone.get());
EXPECT_FALSE(resolved.has_value());
}
TEST(BrowserHandlerAndroidStateTest, FullscreenWinsOverEverything) {
EXPECT_EQ("fullscreen", BrowserHandlerAndroid::ComputeWindowStateString(
/*is_fullscreen=*/true,
/*is_maximized=*/false,
/*is_minimized=*/false));
EXPECT_EQ("fullscreen", BrowserHandlerAndroid::ComputeWindowStateString(
/*is_fullscreen=*/true,
/*is_maximized=*/true,
/*is_minimized=*/false));
EXPECT_EQ("fullscreen", BrowserHandlerAndroid::ComputeWindowStateString(
/*is_fullscreen=*/true,
/*is_maximized=*/false,
/*is_minimized=*/true));
EXPECT_EQ("fullscreen", BrowserHandlerAndroid::ComputeWindowStateString(
/*is_fullscreen=*/true,
/*is_maximized=*/true,
/*is_minimized=*/true));
}
TEST(BrowserHandlerAndroidStateTest, MaximizedBeatsMinimizedWhenNotFullscreen) {
EXPECT_EQ("maximized", BrowserHandlerAndroid::ComputeWindowStateString(
/*is_fullscreen=*/false,
/*is_maximized=*/true,
/*is_minimized=*/true));
}
TEST(BrowserHandlerAndroidStateTest, MinimizedWhenOnlyMinimized) {
EXPECT_EQ("minimized", BrowserHandlerAndroid::ComputeWindowStateString(
/*is_fullscreen=*/false,
/*is_maximized=*/false,
/*is_minimized=*/true));
}
TEST(BrowserHandlerAndroidStateTest, NormalWhenAllFalse) {
EXPECT_EQ("normal", BrowserHandlerAndroid::ComputeWindowStateString(
/*is_fullscreen=*/false,
/*is_maximized=*/false,
/*is_minimized=*/false));
}
TEST_F(BrowserHandlerAndroidTest,
FindBrowserWindowByIdReturnsWindowForRegisteredBwi) {
// Positive case: when a live AndroidBrowserWindow owns a session id,
// FindBrowserWindowById returns its ui::BaseWindow.
BrowserWindowInterface* live_window = reinterpret_cast<
BrowserWindowInterface*>(
Java_BrowserWindowInterfaceIteratorAndroidNativeUnitTestSupport_createBrowserWindow(
base::android::AttachCurrentThread(), /*taskId=*/1,
profile()->GetJavaObject()));
ASSERT_NE(nullptr, live_window);
SessionID live_id = live_window->GetSessionID();
ui::BaseWindow* resolved =
BrowserHandlerAndroid::FindBrowserWindowById(live_id.id());
EXPECT_EQ(live_window->GetWindow(), resolved);
Java_BrowserWindowInterfaceIteratorAndroidNativeUnitTestSupport_destroyBrowserWindow(
base::android::AttachCurrentThread(), /*taskId=*/1);
}
TEST_F(BrowserHandlerAndroidTest,
FindBrowserWindowByIdReturnsNullForUnregisteredWindowId) {
// An id without a matching BWI returns nullptr even when other BWIs are
// registered.
BrowserWindowInterface* live_window = reinterpret_cast<
BrowserWindowInterface*>(
Java_BrowserWindowInterfaceIteratorAndroidNativeUnitTestSupport_createBrowserWindow(
base::android::AttachCurrentThread(), /*taskId=*/1,
profile()->GetJavaObject()));
ASSERT_NE(nullptr, live_window);
SessionID unregistered_window_id = SessionID::NewUnique();
ASSERT_NE(live_window->GetSessionID(), unregistered_window_id);
EXPECT_EQ(nullptr, BrowserHandlerAndroid::FindBrowserWindowById(
unregistered_window_id.id()));
Java_BrowserWindowInterfaceIteratorAndroidNativeUnitTestSupport_destroyBrowserWindow(
base::android::AttachCurrentThread(), /*taskId=*/1);
}
TEST_F(BrowserHandlerAndroidTest,
GetWindowBoundsReturnsErrorForUnknownWindowId) {
TestFrontendChannel channel;
protocol::UberDispatcher dispatcher(&channel);
BrowserHandlerAndroid handler(&dispatcher, /*target_id=*/"");
std::unique_ptr<protocol::Browser::Bounds> bounds;
protocol::Response response =
handler.GetWindowBounds(SessionID::NewUnique().id(), &bounds);
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ("Browser window not found", response.Message());
EXPECT_EQ(nullptr, bounds);
}
TEST_F(BrowserHandlerAndroidTest,
GetWindowBoundsUsesWindowTrackedByDevToolsSession) {
TestFrontendChannel channel;
protocol::UberDispatcher dispatcher(&channel);
BrowserHandlerAndroid handler(&dispatcher, /*target_id=*/"");
FakeBaseWindow base_window(/*bounds=*/gfx::Rect(10, 20, 800, 600),
/*restored_bounds=*/gfx::Rect(),
/*fullscreen=*/false,
/*maximized=*/true,
/*minimized=*/false);
const SessionID session_id = SessionID::NewUnique();
FakeBrowserWindowInterface browser_window(profile(), session_id,
&base_window);
ASSERT_EQ(nullptr,
BrowserHandlerAndroid::FindBrowserWindowById(session_id.id()));
handler.TrackBrowserWindow(&browser_window);
std::unique_ptr<protocol::Browser::Bounds> bounds;
protocol::Response response =
handler.GetWindowBounds(session_id.id(), &bounds);
EXPECT_TRUE(response.IsSuccess());
ASSERT_NE(nullptr, bounds);
EXPECT_EQ(10, bounds->GetLeft());
EXPECT_EQ(20, bounds->GetTop());
EXPECT_EQ(800, bounds->GetWidth());
EXPECT_EQ(600, bounds->GetHeight());
EXPECT_EQ("maximized", bounds->GetWindowState());
}
TEST_F(BrowserHandlerAndroidTest,
GetWindowBoundsRejectsDestroyedTrackedWindow) {
TestFrontendChannel channel;
protocol::UberDispatcher dispatcher(&channel);
BrowserHandlerAndroid handler(&dispatcher, /*target_id=*/"");
FakeBaseWindow base_window(/*bounds=*/gfx::Rect(10, 20, 800, 600),
/*restored_bounds=*/gfx::Rect(),
/*fullscreen=*/false,
/*maximized=*/true,
/*minimized=*/false);
const SessionID session_id = SessionID::NewUnique();
auto browser_window = std::make_unique<FakeBrowserWindowInterface>(
profile(), session_id, &base_window);
handler.TrackBrowserWindow(browser_window.get());
browser_window.reset();
std::unique_ptr<protocol::Browser::Bounds> bounds;
protocol::Response response =
handler.GetWindowBounds(session_id.id(), &bounds);
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ("Browser window not found", response.Message());
EXPECT_EQ(nullptr, bounds);
}
TEST_F(BrowserHandlerAndroidTest,
SetWindowBoundsPrefersRegisteredWindowOverTrackedFallback) {
BrowserWindowInterface* live_window = reinterpret_cast<
BrowserWindowInterface*>(
Java_BrowserWindowInterfaceIteratorAndroidNativeUnitTestSupport_createBrowserWindow(
base::android::AttachCurrentThread(), /*taskId=*/1,
profile()->GetJavaObject()));
ASSERT_NE(nullptr, live_window);
TestFrontendChannel channel;
protocol::UberDispatcher dispatcher(&channel);
BrowserHandlerAndroid handler(&dispatcher, /*target_id=*/"");
FakeBaseWindow fallback_window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false,
false, false);
fallback_window.SetResizePrecheckResult(
ui::WindowResizePrecheckResult::kAndroidNoActivity);
FakeBrowserWindowInterface tracked_window(
profile(), live_window->GetSessionID(), &fallback_window);
handler.TrackBrowserWindow(&tracked_window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("minimized").Build();
protocol::Response response = handler.SetWindowBounds(
live_window->GetSessionID().id(), std::move(bounds));
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ(
"Window state or bounds cannot be changed in the current Android "
"configuration",
response.Message());
EXPECT_FALSE(fallback_window.IsMinimized());
Java_BrowserWindowInterfaceIteratorAndroidNativeUnitTestSupport_destroyBrowserWindow(
base::android::AttachCurrentThread(), /*taskId=*/1);
}
TEST(BrowserHandlerAndroidBoundsTest, BuildsBoundsAndStateFromWindow) {
FakeBaseWindow window(/*bounds=*/gfx::Rect(10, 20, 800, 600),
/*restored_bounds=*/gfx::Rect(0, 0, 400, 300),
/*fullscreen=*/false,
/*maximized=*/false,
/*minimized=*/false);
auto bounds = BrowserHandlerAndroid::BuildBrowserWindowBounds(&window);
ASSERT_NE(nullptr, bounds);
EXPECT_EQ(10, bounds->GetLeft());
EXPECT_EQ(20, bounds->GetTop());
EXPECT_EQ(800, bounds->GetWidth());
EXPECT_EQ(600, bounds->GetHeight());
EXPECT_EQ("normal", bounds->GetWindowState(""));
}
TEST(BrowserHandlerAndroidBoundsTest, BuildsRestoredBoundsWhenMinimized) {
FakeBaseWindow window(/*bounds=*/gfx::Rect(10, 20, 800, 600),
/*restored_bounds=*/gfx::Rect(0, 0, 400, 300),
/*fullscreen=*/false,
/*maximized=*/false,
/*minimized=*/true);
auto bounds = BrowserHandlerAndroid::BuildBrowserWindowBounds(&window);
ASSERT_NE(nullptr, bounds);
// Minimized windows report their restored bounds, not current bounds.
EXPECT_EQ(0, bounds->GetLeft());
EXPECT_EQ(0, bounds->GetTop());
EXPECT_EQ(400, bounds->GetWidth());
EXPECT_EQ(300, bounds->GetHeight());
EXPECT_EQ("minimized", bounds->GetWindowState(""));
}
TEST(BrowserHandlerAndroidBoundsTest, BuildsFullscreenStateAndBounds) {
FakeBaseWindow window(/*bounds=*/gfx::Rect(0, 0, 1920, 1080),
/*restored_bounds=*/gfx::Rect(100, 100, 800, 600),
/*fullscreen=*/true,
/*maximized=*/false,
/*minimized=*/false);
auto bounds = BrowserHandlerAndroid::BuildBrowserWindowBounds(&window);
ASSERT_NE(nullptr, bounds);
EXPECT_EQ(1920, bounds->GetWidth());
EXPECT_EQ(1080, bounds->GetHeight());
EXPECT_EQ("fullscreen", bounds->GetWindowState(""));
}
TEST(BrowserHandlerAndroidBoundsTest,
BuildsRestoredBoundsWhenMinimizedAndMaximized) {
FakeBaseWindow window(/*bounds=*/gfx::Rect(10, 20, 800, 600),
/*restored_bounds=*/gfx::Rect(0, 0, 400, 300),
/*fullscreen=*/false,
/*maximized=*/true,
/*minimized=*/true);
auto bounds = BrowserHandlerAndroid::BuildBrowserWindowBounds(&window);
ASSERT_NE(nullptr, bounds);
EXPECT_EQ(0, bounds->GetLeft());
EXPECT_EQ(0, bounds->GetTop());
EXPECT_EQ(400, bounds->GetWidth());
EXPECT_EQ(300, bounds->GetHeight());
EXPECT_EQ("maximized", bounds->GetWindowState(""));
}
TEST(BrowserHandlerAndroidBoundsTest,
BuildsMaximizedBoundsForNonResizableMinimizedTask) {
FakeBaseWindow window(/*bounds=*/gfx::Rect(10, 20, 800, 600),
/*restored_bounds=*/gfx::Rect(0, 0, 400, 300),
/*fullscreen=*/false,
/*maximized=*/false,
/*minimized=*/true,
/*can_resize=*/false);
auto bounds = BrowserHandlerAndroid::BuildBrowserWindowBounds(&window);
ASSERT_NE(nullptr, bounds);
EXPECT_EQ(10, bounds->GetLeft());
EXPECT_EQ(20, bounds->GetTop());
EXPECT_EQ(800, bounds->GetWidth());
EXPECT_EQ(600, bounds->GetHeight());
EXPECT_EQ("maximized", bounds->GetWindowState(""));
}
class BrowserHandlerAndroidMutationTest
: public ChromeRenderViewHostTestHarness {
protected:
void TrackWindow(FakeBaseWindow* window) {
session_id_ = SessionID::NewUnique();
browser_window_ = std::make_unique<FakeBrowserWindowInterface>(
profile(), session_id_, window);
handler_.TrackBrowserWindow(browser_window_.get());
}
protocol::Response SetWindowBounds(
std::unique_ptr<protocol::Browser::Bounds> bounds) {
return handler_.SetWindowBounds(session_id_.id(), std::move(bounds));
}
TestFrontendChannel channel_;
protocol::UberDispatcher dispatcher_{&channel_};
BrowserHandlerAndroid handler_{&dispatcher_, /*target_id=*/""};
SessionID session_id_ = SessionID::NewUnique();
std::unique_ptr<FakeBrowserWindowInterface> browser_window_;
};
TEST_F(BrowserHandlerAndroidMutationTest, RejectsUnknownWindow) {
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("maximized").Build();
protocol::Response response =
handler_.SetWindowBounds(SessionID::NewUnique().id(), std::move(bounds));
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ("Browser window not found", response.Message());
}
TEST_F(BrowserHandlerAndroidMutationTest, RejectsBoundsWithNonNormalState) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, false,
false);
TrackWindow(&window);
auto bounds = protocol::Browser::Bounds::Create()
.SetWidth(400)
.SetWindowState("maximized")
.Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ(
"The 'minimized', 'maximized' and 'fullscreen' states cannot be "
"combined with 'left', 'top', 'width' or 'height'",
response.Message());
EXPECT_FALSE(window.IsMaximized());
}
TEST_F(BrowserHandlerAndroidMutationTest, RejectsFullscreen) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, false,
false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("fullscreen").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ("Fullscreen not supported on Android", response.Message());
}
TEST_F(BrowserHandlerAndroidMutationTest, RejectsInvalidWindowState) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, false,
false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("invalid").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ("Invalid windowState: invalid", response.Message());
}
TEST_F(BrowserHandlerAndroidMutationTest, RejectsMaximizeFromMinimized) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, false,
true);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("maximized").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ(
"To maximize a minimized or fullscreen window, restore it to normal "
"state first.",
response.Message());
EXPECT_FALSE(window.IsMaximized());
}
TEST_F(BrowserHandlerAndroidMutationTest, RejectsMaximizeFromFullscreen) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), true, false,
false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("maximized").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ(
"To maximize a minimized or fullscreen window, restore it to normal "
"state first.",
response.Message());
EXPECT_FALSE(window.IsMaximized());
}
TEST_F(BrowserHandlerAndroidMutationTest, MaximizesWindow) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, false,
false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("maximized").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_TRUE(response.IsSuccess());
EXPECT_TRUE(window.IsMaximized());
}
TEST_F(BrowserHandlerAndroidMutationTest,
MaximizeIsIdempotentForFixedSizeWindow) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, true,
false, /*can_resize=*/false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("maximized").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_TRUE(response.IsSuccess());
EXPECT_TRUE(window.IsMaximized());
}
TEST_F(BrowserHandlerAndroidMutationTest, MinimizesWindow) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, false,
false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("minimized").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_TRUE(response.IsSuccess());
EXPECT_TRUE(window.IsMinimized());
}
TEST_F(BrowserHandlerAndroidMutationTest, MinimizeIsIdempotent) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, false,
true);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("minimized").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_TRUE(response.IsSuccess());
EXPECT_TRUE(window.IsMinimized());
}
TEST_F(BrowserHandlerAndroidMutationTest, MinimizesMaximizedWindow) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, true,
false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("minimized").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_TRUE(response.IsSuccess());
EXPECT_FALSE(window.IsMaximized());
EXPECT_TRUE(window.IsMinimized());
}
TEST_F(BrowserHandlerAndroidMutationTest, RejectsMinimizeFromFullscreen) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), true, false,
false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("minimized").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ(
"To minimize a fullscreen window, restore it to normal state first.",
response.Message());
EXPECT_FALSE(window.IsMinimized());
}
TEST_F(BrowserHandlerAndroidMutationTest,
RejectsMutationForFixedSizeAndroidWindow) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, true,
false, /*can_resize=*/false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("minimized").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ(
"Window state or bounds cannot be changed in the current Android "
"configuration",
response.Message());
EXPECT_FALSE(window.IsMinimized());
}
TEST_F(BrowserHandlerAndroidMutationTest,
AllowsMutationWhileAndroidWindowIsPending) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(10, 20, 400, 300),
false, false, false,
/*can_resize=*/false);
window.SetResizePrecheckResult(
ui::WindowResizePrecheckResult::kAndroidNoActivity);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("minimized").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_TRUE(response.IsSuccess());
EXPECT_TRUE(window.IsMinimized());
auto reported_bounds =
BrowserHandlerAndroid::BuildBrowserWindowBounds(&window);
EXPECT_EQ("minimized", reported_bounds->GetWindowState(""));
EXPECT_EQ(400, reported_bounds->GetWidth());
EXPECT_EQ(300, reported_bounds->GetHeight());
}
TEST_F(BrowserHandlerAndroidMutationTest, RestoresWindow) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), false, true,
false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("normal").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_TRUE(response.IsSuccess());
EXPECT_FALSE(window.IsMaximized());
EXPECT_FALSE(window.IsMinimized());
}
TEST_F(BrowserHandlerAndroidMutationTest,
PartialBoundsPreserveUnspecifiedValuesAndRestoreWindow) {
FakeBaseWindow window(gfx::Rect(10, 20, 800, 600), gfx::Rect(), false, true,
false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetLeft(30).SetWidth(400).Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_TRUE(response.IsSuccess());
EXPECT_EQ(gfx::Rect(30, 20, 400, 600), window.GetBounds());
EXPECT_FALSE(window.IsMaximized());
}
TEST_F(BrowserHandlerAndroidMutationTest, RejectsNormalFromFullscreen) {
FakeBaseWindow window(gfx::Rect(0, 0, 800, 600), gfx::Rect(), true, false,
false);
TrackWindow(&window);
auto bounds =
protocol::Browser::Bounds::Create().SetWindowState("normal").Build();
protocol::Response response = SetWindowBounds(std::move(bounds));
EXPECT_FALSE(response.IsSuccess());
EXPECT_EQ("Cannot exit fullscreen on Android", response.Message());
}