chromium/ui/views/widget/widget_interactive_uitest.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>

#include <memory>
#include <utility>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/test/ui_controls.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"
#include "ui/events/event_processor.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_test_api.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/test/focus_manager_test.h"
#include "ui/views/test/native_widget_factory.h"
#include "ui/views/test/widget_activation_waiter.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/touchui/touch_selection_controller_impl.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_interactive_uitest_utils.h"
#include "ui/views/widget/widget_utils.h"
#include "ui/views/window/dialog_delegate.h"
#include "ui/wm/public/activation_client.h"

#if BUILDFLAG(ENABLE_DESKTOP_AURA)
#include "ui/aura/env.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/views/win/hwnd_util.h"
#endif

namespace views::test {

namespace {

template <class T>
class UniqueWidgetPtrT : public views::UniqueWidgetPtr {};

// A View that closes the Widget and exits the current message-loop when it
// receives a mouse-release event.
class ExitLoopOnRelease : public View {};

BEGIN_METADATA()

// A view that does a capture on ui::EventType::kGestureTapDown events.
class GestureCaptureView : public View {};

BEGIN_METADATA()

// A view that always processes all mouse events.
class MouseView : public View {};

BEGIN_METADATA()

// A View that shows a different widget, sets capture on that widget, and
// initiates a nested message-loop when it receives a mouse-press event.
class NestedLoopCaptureView : public View {};

BEGIN_METADATA()

#if BUILDFLAG(ENABLE_DESKTOP_AURA)
// A view that runs closures in response to drag events.
class DragView : public View, public DragController {};

BEGIN_METADATA()
#endif  // BUILDFLAG(ENABLE_DESKTOP_AURA)

ui::WindowShowState GetWidgetShowState(const Widget* widget) {}

// Give the OS an opportunity to process messages for an activation change, when
// there is actually no change expected (e.g. ShowInactive()).
void RunPendingMessagesForActiveStatusChange() {}

// Activate a widget, and wait for it to become active. On non-desktop Aura
// this is just an activation. For other widgets, it means activating and then
// spinning the run loop until the OS has activated the window.
void ActivateSync(Widget* widget) {}

// Like for ActivateSync(), wait for a widget to become active, but Show() the
// widget rather than calling Activate().
void ShowSync(Widget* widget) {}

void DeactivateSync(Widget* widget) {}

#if BUILDFLAG(IS_WIN)
void ActivatePlatformWindow(Widget* widget) {
  ::SetActiveWindow(
      widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
}
#endif

// Calls ShowInactive() on a Widget, and spins a run loop. The goal is to give
// the OS a chance to activate a widget. However, for this case, the test
// doesn't expect that to happen, so there is nothing to wait for.
void ShowInactiveSync(Widget* widget) {}

std::unique_ptr<Textfield> CreateTextfield() {}

}  // namespace

class WidgetTestInteractive : public WidgetTest {};

#if BUILDFLAG(IS_WIN)
// Tests whether activation and focus change works correctly in Windows.
// We test the following:-
// 1. If the active aura window is correctly set when a top level widget is
//    created.
// 2. If the active aura window in widget 1 created above, is set to NULL when
//    another top level widget is created and focused.
// 3. On focusing the native platform window for widget 1, the active aura
//    window for widget 1 should be set and that for widget 2 should reset.
// TODO(ananta): Discuss with erg on how to write this test for linux x11 aura.
TEST_F(DesktopWidgetTestInteractive,
       DesktopNativeWidgetAuraActivationAndFocusTest) {
  // Create widget 1 and expect the active window to be its window.
  View* focusable_view1 = new View;
  focusable_view1->SetFocusBehavior(View::FocusBehavior::ALWAYS);
  WidgetAutoclosePtr widget1(CreateTopLevelNativeWidget());
  widget1->GetContentsView()->AddChildView(focusable_view1);
  widget1->Show();
  aura::Window* root_window1 = GetRootWindow(widget1.get());
  focusable_view1->RequestFocus();

  EXPECT_TRUE(root_window1 != nullptr);
  wm::ActivationClient* activation_client1 =
      wm::GetActivationClient(root_window1);
  EXPECT_TRUE(activation_client1 != nullptr);
  EXPECT_EQ(activation_client1->GetActiveWindow(), widget1->GetNativeView());

  // Create widget 2 and expect the active window to be its window.
  View* focusable_view2 = new View;
  WidgetAutoclosePtr widget2(CreateTopLevelNativeWidget());
  widget1->GetContentsView()->AddChildView(focusable_view2);
  widget2->Show();
  aura::Window* root_window2 = GetRootWindow(widget2.get());
  focusable_view2->RequestFocus();
  ActivatePlatformWindow(widget2.get());

  wm::ActivationClient* activation_client2 =
      wm::GetActivationClient(root_window2);
  EXPECT_TRUE(activation_client2 != nullptr);
  EXPECT_EQ(activation_client2->GetActiveWindow(), widget2->GetNativeView());
  EXPECT_EQ(activation_client1->GetActiveWindow(),
            reinterpret_cast<aura::Window*>(NULL));

  // Now set focus back to widget 1 and expect the active window to be its
  // window.
  focusable_view1->RequestFocus();
  ActivatePlatformWindow(widget1.get());
  EXPECT_EQ(activation_client2->GetActiveWindow(),
            reinterpret_cast<aura::Window*>(NULL));
  EXPECT_EQ(activation_client1->GetActiveWindow(), widget1->GetNativeView());
}

// Verifies bubbles result in a focus lost when shown.
TEST_F(DesktopWidgetTestInteractive, FocusChangesOnBubble) {
  WidgetAutoclosePtr widget(CreateTopLevelNativeWidget());
  View* focusable_view =
      widget->GetContentsView()->AddChildView(std::make_unique<View>());
  focusable_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
  widget->Show();
  focusable_view->RequestFocus();
  EXPECT_TRUE(focusable_view->HasFocus());

  // Show a bubble.
  auto owned_bubble_delegate_view =
      std::make_unique<views::BubbleDialogDelegateView>(focusable_view,
                                                        BubbleBorder::NONE);
  owned_bubble_delegate_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
  BubbleDialogDelegateView* bubble_delegate_view =
      owned_bubble_delegate_view.get();
  BubbleDialogDelegateView::CreateBubble(std::move(owned_bubble_delegate_view))
      ->Show();
  bubble_delegate_view->RequestFocus();

  // |focusable_view| should no longer have focus.
  EXPECT_FALSE(focusable_view->HasFocus());
  EXPECT_TRUE(bubble_delegate_view->HasFocus());

  bubble_delegate_view->GetWidget()->CloseNow();

  // Closing the bubble should result in focus going back to the contents view.
  EXPECT_TRUE(focusable_view->HasFocus());
}

class TouchEventHandler : public ui::EventHandler {
 public:
  explicit TouchEventHandler(Widget* widget) : widget_(widget) {
    widget_->GetNativeWindow()->GetHost()->window()->AddPreTargetHandler(this);
  }

  TouchEventHandler(const TouchEventHandler&) = delete;
  TouchEventHandler& operator=(const TouchEventHandler&) = delete;

  ~TouchEventHandler() override {
    widget_->GetNativeWindow()->GetHost()->window()->RemovePreTargetHandler(
        this);
  }

  void WaitForEvents() {
    base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
    quit_closure_ = run_loop.QuitClosure();
    run_loop.Run();
  }

  static void __stdcall AsyncActivateMouse(HWND hwnd,
                                           UINT msg,
                                           ULONG_PTR data,
                                           LRESULT result) {
    EXPECT_EQ(MA_NOACTIVATE, result);
    std::move(reinterpret_cast<TouchEventHandler*>(data)->quit_closure_).Run();
  }

  void ActivateViaMouse() {
    SendMessageCallback(
        widget_->GetNativeWindow()->GetHost()->GetAcceleratedWidget(),
        WM_MOUSEACTIVATE, 0, 0, AsyncActivateMouse,
        reinterpret_cast<ULONG_PTR>(this));
  }

 private:
  // ui::EventHandler:
  void OnTouchEvent(ui::TouchEvent* event) override {
    if (event->type() == ui::EventType::kTouchPressed) {
      ActivateViaMouse();
    }
  }

  raw_ptr<Widget> widget_;
  base::OnceClosure quit_closure_;
};

// TODO(dtapuska): Disabled due to it being flaky crbug.com/817531
TEST_F(DesktopWidgetTestInteractive, DISABLED_TouchNoActivateWindow) {
  View* focusable_view = new View;
  focusable_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
  WidgetAutoclosePtr widget(CreateTopLevelNativeWidget());
  widget->GetContentsView()->AddChildView(focusable_view);
  widget->Show();

  {
    TouchEventHandler touch_event_handler(widget.get());
    ASSERT_TRUE(
        ui_controls::SendTouchEvents(ui_controls::kTouchPress, 1, 100, 100));
    touch_event_handler.WaitForEvents();
  }
}

#endif  // BUILDFLAG(IS_WIN)

// Tests mouse move outside of the window into the "resize controller" and back
// will still generate an OnMouseEntered and OnMouseExited event..
TEST_F(WidgetTestInteractive, CheckResizeControllerEvents) {}

// Test view focus restoration when a widget is deactivated and re-activated.
TEST_F(WidgetTestInteractive, ViewFocusOnWidgetActivationChanges) {}

TEST_F(WidgetTestInteractive, ZOrderCheckBetweenTopWindows) {}

// Test z-order of child widgets relative to their parent.
// TODO(crbug.com/40776787): Disabled on Mac due to flake
#if BUILDFLAG(IS_MAC)
#define MAYBE_ChildStackedRelativeToParent
#else
#define MAYBE_ChildStackedRelativeToParent
#endif
TEST_F(WidgetTestInteractive, MAYBE_ChildStackedRelativeToParent) {}

TEST_F(WidgetTestInteractive, ChildWidgetStackAbove) {}

TEST_F(WidgetTestInteractive, ChildWidgetStackAtTop) {}

#if BUILDFLAG(IS_WIN)

// Test view focus retention when a widget's HWND is disabled and re-enabled.
TEST_F(WidgetTestInteractive, ViewFocusOnHWNDEnabledChanges) {
  WidgetAutoclosePtr widget(CreateTopLevelFramelessPlatformWidget());
  widget->SetContentsView(std::make_unique<View>());
  for (size_t i = 0; i < 2; ++i) {
    auto child = std::make_unique<View>();
    child->SetFocusBehavior(View::FocusBehavior::ALWAYS);
    widget->GetContentsView()->AddChildView(std::move(child));
  }

  widget->Show();
  widget->GetNativeWindow()->GetHost()->Show();
  const HWND hwnd = HWNDForWidget(widget.get());
  EXPECT_TRUE(::IsWindow(hwnd));
  EXPECT_TRUE(::IsWindowEnabled(hwnd));
  EXPECT_EQ(hwnd, ::GetActiveWindow());

  for (View* view : widget->GetContentsView()->children()) {
    SCOPED_TRACE("Child view " +
                 base::NumberToString(
                     widget->GetContentsView()->GetIndexOf(view).value()));

    view->RequestFocus();
    EXPECT_EQ(view, widget->GetFocusManager()->GetFocusedView());
    EXPECT_FALSE(::EnableWindow(hwnd, FALSE));
    EXPECT_FALSE(::IsWindowEnabled(hwnd));

    // Oddly, disabling the HWND leaves it active with the focus unchanged.
    EXPECT_EQ(hwnd, ::GetActiveWindow());
    EXPECT_TRUE(widget->IsActive());
    EXPECT_EQ(view, widget->GetFocusManager()->GetFocusedView());

    EXPECT_TRUE(::EnableWindow(hwnd, TRUE));
    EXPECT_TRUE(::IsWindowEnabled(hwnd));
    EXPECT_EQ(hwnd, ::GetActiveWindow());
    EXPECT_TRUE(widget->IsActive());
    EXPECT_EQ(view, widget->GetFocusManager()->GetFocusedView());
  }
}

// This class subclasses the Widget class to listen for activation change
// notifications and provides accessors to return information as to whether
// the widget is active. We need this to ensure that users of the widget
// class activate the widget only when the underlying window becomes really
// active. Previously we would activate the widget in the WM_NCACTIVATE
// message which is incorrect because APIs like FlashWindowEx flash the
// window caption by sending fake WM_NCACTIVATE messages.
class WidgetActivationTest : public Widget {
 public:
  WidgetActivationTest() = default;

  WidgetActivationTest(const WidgetActivationTest&) = delete;
  WidgetActivationTest& operator=(const WidgetActivationTest&) = delete;

  ~WidgetActivationTest() override = default;

  bool OnNativeWidgetActivationChanged(bool active) override {
    active_ = active;
    return true;
  }

  bool active() const { return active_; }

 private:
  bool active_ = false;
};

// Tests whether the widget only becomes active when the underlying window
// is really active.
TEST_F(WidgetTestInteractive, WidgetNotActivatedOnFakeActivationMessages) {
  UniqueWidgetPtrT widget1 = std::make_unique<WidgetActivationTest>();
  Widget::InitParams init_params =
      CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  init_params.native_widget = new DesktopNativeWidgetAura(widget1.get());
  init_params.bounds = gfx::Rect(0, 0, 200, 200);
  widget1->Init(std::move(init_params));
  widget1->Show();
  EXPECT_EQ(true, widget1->active());

  UniqueWidgetPtrT widget2 = std::make_unique<WidgetActivationTest>();
  init_params = CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  init_params.native_widget = new DesktopNativeWidgetAura(widget2.get());
  widget2->Init(std::move(init_params));
  widget2->Show();
  EXPECT_EQ(true, widget2->active());
  EXPECT_EQ(false, widget1->active());

  HWND win32_native_window1 = HWNDForWidget(widget1.get());
  EXPECT_TRUE(::IsWindow(win32_native_window1));

  ::SendMessage(win32_native_window1, WM_NCACTIVATE, 1, 0);
  EXPECT_EQ(false, widget1->active());
  EXPECT_EQ(true, widget2->active());

  ::SetActiveWindow(win32_native_window1);
  EXPECT_EQ(true, widget1->active());
  EXPECT_EQ(false, widget2->active());
}

// On Windows if we create a fullscreen window on a thread, then it affects the
// way other windows on the thread interact with the taskbar. To workaround
// this we reduce the bounds of a fullscreen window by 1px when it loses
// activation. This test verifies the same.
TEST_F(WidgetTestInteractive, FullscreenBoundsReducedOnActivationLoss) {
  UniqueWidgetPtr widget1 = std::make_unique<Widget>();
  Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
  params.native_widget = new DesktopNativeWidgetAura(widget1.get());
  widget1->Init(std::move(params));
  widget1->SetBounds(gfx::Rect(0, 0, 200, 200));
  widget1->Show();

  widget1->Activate();
  RunPendingMessages();
  EXPECT_EQ(::GetActiveWindow(),
            widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget());

  widget1->SetFullscreen(true);
  EXPECT_TRUE(widget1->IsFullscreen());
  // Ensure that the StopIgnoringPosChanges task in HWNDMessageHandler runs.
  // This task is queued when a widget becomes fullscreen.
  RunPendingMessages();
  EXPECT_EQ(::GetActiveWindow(),
            widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
  gfx::Rect fullscreen_bounds = widget1->GetWindowBoundsInScreen();

  UniqueWidgetPtr widget2 = std::make_unique<Widget>();
  params = CreateParams(Widget::InitParams::TYPE_WINDOW);
  params.native_widget = new DesktopNativeWidgetAura(widget2.get());
  widget2->Init(std::move(params));
  widget2->SetBounds(gfx::Rect(0, 0, 200, 200));
  widget2->Show();

  widget2->Activate();
  RunPendingMessages();
  EXPECT_EQ(::GetActiveWindow(),
            widget2->GetNativeWindow()->GetHost()->GetAcceleratedWidget());

  gfx::Rect fullscreen_bounds_after_activation_loss =
      widget1->GetWindowBoundsInScreen();

  // After deactivation loss the bounds of the fullscreen widget should be
  // reduced by 1px.
  EXPECT_EQ(fullscreen_bounds.height() -
                fullscreen_bounds_after_activation_loss.height(),
            1);

  widget1->Activate();
  RunPendingMessages();
  EXPECT_EQ(::GetActiveWindow(),
            widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget());

  gfx::Rect fullscreen_bounds_after_activate =
      widget1->GetWindowBoundsInScreen();

  // After activation the bounds of the fullscreen widget should be restored.
  EXPECT_EQ(fullscreen_bounds, fullscreen_bounds_after_activate);
}

// Ensure the window rect and client rects are correct with a window that was
// maximized.
TEST_F(WidgetTestInteractive, FullscreenMaximizedWindowBounds) {
  UniqueWidgetPtr widget = std::make_unique<Widget>();
  Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
  params.native_widget = new DesktopNativeWidgetAura(widget.get());
  widget->set_frame_type(Widget::FrameType::kForceCustom);
  widget->Init(std::move(params));
  widget->SetBounds(gfx::Rect(0, 0, 200, 200));
  widget->Show();

  widget->Maximize();
  EXPECT_TRUE(widget->IsMaximized());

  widget->SetFullscreen(true);
  EXPECT_TRUE(widget->IsFullscreen());
  EXPECT_FALSE(widget->IsMaximized());
  // Ensure that the StopIgnoringPosChanges task in HWNDMessageHandler runs.
  // This task is queued when a widget becomes fullscreen.
  RunPendingMessages();

  aura::WindowTreeHost* host = widget->GetNativeWindow()->GetHost();
  HWND hwnd = host->GetAcceleratedWidget();

  HMONITOR monitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
  ASSERT_TRUE(!!monitor);
  MONITORINFO monitor_info;
  monitor_info.cbSize = sizeof(monitor_info);
  ASSERT_TRUE(::GetMonitorInfo(monitor, &monitor_info));

  gfx::Rect monitor_bounds(monitor_info.rcMonitor);
  gfx::Rect window_bounds = widget->GetWindowBoundsInScreen();
  gfx::Rect client_area_bounds = host->GetBoundsInPixels();

  EXPECT_EQ(window_bounds, monitor_bounds);
  EXPECT_EQ(monitor_bounds, client_area_bounds);

  // Setting not fullscreen should return it to maximized.
  widget->SetFullscreen(false);
  EXPECT_FALSE(widget->IsFullscreen());
  EXPECT_TRUE(widget->IsMaximized());

  client_area_bounds = host->GetBoundsInPixels();
  EXPECT_TRUE(monitor_bounds.Contains(client_area_bounds));
  EXPECT_NE(monitor_bounds, client_area_bounds);
}
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)
// Tests whether the focused window is set correctly when a modal window is
// created and destroyed. When it is destroyed it should focus the owner window.
TEST_F(DesktopWidgetTestInteractive, WindowModalWindowDestroyedActivationTest) {}
#endif

TEST_F(DesktopWidgetTestInteractive, CanActivateFlagIsHonored) {}

#if defined(USE_AURA)

// Test that touch selection quick menu is not activated when opened.
TEST_F(DesktopWidgetTestInteractive, TouchSelectionQuickMenuIsNotActivated) {}
#endif  // defined(USE_AURA)

#if BUILDFLAG(IS_WIN)
TEST_F(DesktopWidgetTestInteractive, DisableViewDoesNotActivateWidget) {
#else
TEST_F(WidgetTestInteractive, DisableViewDoesNotActivateWidget) {}

TEST_F(WidgetTestInteractive, ShowCreatesActiveWindow) {}

TEST_F(WidgetTestInteractive, ShowInactive) {}

TEST_F(WidgetTestInteractive, InactiveBeforeShow) {}

TEST_F(WidgetTestInteractive, ShowInactiveAfterShow) {}

TEST_F(WidgetTestInteractive, ShowAfterShowInactive) {}

TEST_F(WidgetTestInteractive, WidgetShouldBeActiveWhenShow) {}

#if BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)
TEST_F(WidgetTestInteractive, InactiveWidgetDoesNotGrabActivation) {}
#endif  // BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)

// ExitFullscreenRestoreState doesn't use DesktopAura widgets. On Mac, there are
// currently only Desktop widgets and fullscreen changes have to coordinate with
// the OS. See BridgedNativeWidgetUITest for native Mac fullscreen tests.
// Maximize on mac is also (intentionally) a no-op.
#if BUILDFLAG(IS_MAC)
#define MAYBE_ExitFullscreenRestoreState
#else
#define MAYBE_ExitFullscreenRestoreState
#endif

// Test that window state is not changed after getting out of full screen.
TEST_F(WidgetTestInteractive, MAYBE_ExitFullscreenRestoreState) {}

// Testing initial focus is assigned properly for normal top-level widgets,
// and subclasses that specify a initially focused child view.
TEST_F(WidgetTestInteractive, InitialFocus) {}

TEST_F(DesktopWidgetTestInteractive, RestoreAfterMinimize) {}

// Maximize is not implemented on macOS, see crbug.com/868599
#if !BUILDFLAG(IS_MAC)
// Widget::Show/ShowInactive should not restore a maximized window
TEST_F(DesktopWidgetTestInteractive, ShowAfterMaximize) {}
#endif

#if BUILDFLAG(IS_WIN)
// TODO(davidbienvenu): Get this test to pass on Linux and ChromeOS by hiding
// the root window when desktop widget is minimized.
// Tests that root window visibility toggles correctly when the desktop widget
// is minimized and maximized on Windows, and the Widget remains visible.
TEST_F(DesktopWidgetTestInteractive, RestoreAndMinimizeVisibility) {
  WidgetAutoclosePtr widget(CreateTopLevelNativeWidget());
  aura::Window* root_window = GetRootWindow(widget.get());
  ShowSync(widget.get());
  ASSERT_FALSE(widget->IsMinimized());
  EXPECT_TRUE(root_window->IsVisible());

  PropertyWaiter minimize_widget_waiter(
      base::BindRepeating(&Widget::IsMinimized, base::Unretained(widget.get())),
      true);
  widget->Minimize();
  EXPECT_TRUE(minimize_widget_waiter.Wait());
  EXPECT_TRUE(widget->IsVisible());
  EXPECT_FALSE(root_window->IsVisible());

  PropertyWaiter restore_widget_waiter(
      base::BindRepeating(&Widget::IsMinimized, base::Unretained(widget.get())),
      false);
  widget->Restore();
  EXPECT_TRUE(restore_widget_waiter.Wait());
  EXPECT_TRUE(widget->IsVisible());
  EXPECT_TRUE(root_window->IsVisible());
}

// Test that focus is restored to the widget after a minimized window
// is activated.
TEST_F(DesktopWidgetTestInteractive, MinimizeAndActivateFocus) {
  WidgetAutoclosePtr widget(CreateTopLevelNativeWidget());
  aura::Window* root_window = GetRootWindow(widget.get());
  auto* widget_window = widget->GetNativeWindow();
  ShowSync(widget.get());
  ASSERT_FALSE(widget->IsMinimized());
  EXPECT_TRUE(root_window->IsVisible());
  widget_window->Focus();
  EXPECT_TRUE(widget_window->HasFocus());
  widget->GetContentsView()->SetFocusBehavior(View::FocusBehavior::ALWAYS);
  widget->GetContentsView()->RequestFocus();
  EXPECT_TRUE(widget->GetContentsView()->HasFocus());

  PropertyWaiter minimize_widget_waiter(
      base::BindRepeating(&Widget::IsMinimized, base::Unretained(widget.get())),
      true);
  widget->Minimize();
  EXPECT_TRUE(minimize_widget_waiter.Wait());
  EXPECT_TRUE(widget->IsVisible());
  EXPECT_FALSE(root_window->IsVisible());

  PropertyWaiter restore_widget_waiter(
      base::BindRepeating(&Widget::IsMinimized, base::Unretained(widget.get())),
      false);
  widget->Activate();
  EXPECT_TRUE(widget->GetContentsView()->HasFocus());
  EXPECT_TRUE(restore_widget_waiter.Wait());
  EXPECT_TRUE(widget->IsVisible());
  EXPECT_TRUE(root_window->IsVisible());
  EXPECT_TRUE(widget_window->CanFocus());
}

class SyntheticMouseMoveCounter : public ui::EventHandler {
 public:
  explicit SyntheticMouseMoveCounter(Widget* widget) : widget_(widget) {
    widget_->GetNativeWindow()->AddPreTargetHandler(this);
  }

  SyntheticMouseMoveCounter(const SyntheticMouseMoveCounter&) = delete;
  SyntheticMouseMoveCounter& operator=(const SyntheticMouseMoveCounter&) =
      delete;

  ~SyntheticMouseMoveCounter() override {
    widget_->GetNativeWindow()->RemovePreTargetHandler(this);
  }

  // ui::EventHandler:
  void OnMouseEvent(ui::MouseEvent* event) override {
    if (event->type() == ui::EventType::kMouseMoved && event->IsSynthesized()) {
      ++count_;
    }
  }

  int num_synthetic_mouse_moves() const { return count_; }

 private:
  int count_ = 0;
  raw_ptr<Widget> widget_;
};

#if BUILDFLAG(ENABLE_DESKTOP_AURA)
TEST_F(DesktopWidgetTestInteractive,
       // TODO(crbug.com/335767870): Re-enable this test
       DISABLED_DoNotSynthesizeMouseMoveOnVisibilityChangeIfOccluded) {
  // Create a top-level widget.
  WidgetAutoclosePtr widget_below(CreateTopLevelPlatformDesktopWidget());
  widget_below->SetBounds(gfx::Rect(300, 300));
  widget_below->Show();

  // Dispatch a mouse event to place cursor inside window bounds.
  base::RunLoop run_loop;
  ui_controls::SendMouseMoveNotifyWhenDone(150, 150, run_loop.QuitClosure());
  run_loop.Run();

  // Create a child widget.
  UniqueWidgetPtr child = std::make_unique<Widget>();
  Widget::InitParams child_params =
      CreateParams(Widget::InitParams::TYPE_WINDOW);
  child_params.parent = widget_below->GetNativeView();
  child_params.context = widget_below->GetNativeWindow();
  child->Init(std::move(child_params));
  child->SetBounds(gfx::Rect(300, 300));
  child->Show();
  base::RunLoop().RunUntilIdle();

  SyntheticMouseMoveCounter counter_below(widget_below.get());
  EXPECT_EQ(0, counter_below.num_synthetic_mouse_moves());

  // Update the child window's visibility. This should trigger a synthetic
  // mouse move event.
  child->Hide();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, counter_below.num_synthetic_mouse_moves());

  // Occlude the existing widget with a new top-level widget.
  WidgetAutoclosePtr widget_above(CreateTopLevelPlatformDesktopWidget());
  widget_above->SetBounds(gfx::Rect(300, 300));
  widget_above->Show();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(widget_above->AsWidget()->IsStackedAbove(
      widget_below->AsWidget()->GetNativeView()));

  // Update the child window's visibility again, but this should not trigger a
  // synthetic mouse move event, since there's another widget under the cursor.
  child->Show();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, counter_below.num_synthetic_mouse_moves());
}
#endif  // BUILDFLAG(ENABLE_DESKTOP_AURA)
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)
// Tests that minimizing a widget causes the gesture_handler
// to be cleared when the widget is minimized.
TEST_F(DesktopWidgetTestInteractive, EventHandlersClearedOnWidgetMinimize) {}
#endif

#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && \
    BUILDFLAG(ENABLE_DESKTOP_AURA)
// Tests that when a desktop native widget has modal transient child, it should
// avoid restore focused view itself as the modal transient child window will do
// that, thus avoids having multiple focused view visually (crbug.com/727641).
TEST_F(DesktopWidgetTestInteractive,
       DesktopNativeWidgetWithModalTransientChild) {}
#endif  // (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) &&
        // BUILDFLAG(ENABLE_DESKTOP_AURA)

namespace {

// Helper class for CaptureLostTrackingWidget to store whether
// OnMouseCaptureLost has been invoked for a widget.
class CaptureLostState {};

// Used to verify OnMouseCaptureLost() has been invoked.
class CaptureLostTrackingWidget : public Widget {};

}  // namespace

class WidgetCaptureTest : public DesktopWidgetTestInteractive {};

// See description in TestCapture().
TEST_F(WidgetCaptureTest, Capture) {}

#if BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)
// See description in TestCapture(). Creates DesktopNativeWidget.
TEST_F(WidgetCaptureTest, CaptureDesktopNativeWidget) {}
#endif

// Tests to ensure capture is correctly released from a Widget with capture when
// it is destroyed. Test for crbug.com/622201.
TEST_F(WidgetCaptureTest, DestroyWithCapture_CloseNow) {}

TEST_F(WidgetCaptureTest, DestroyWithCapture_Close) {}

// TODO(kylixrd): Remove this test once Widget ownership is normalized.
TEST_F(WidgetCaptureTest, DestroyWithCapture_WidgetOwnsNativeWidget) {}

// Test that no state is set if capture fails.
TEST_F(WidgetCaptureTest, FailedCaptureRequestIsNoop) {}

TEST_F(WidgetCaptureTest, CaptureAutoReset) {}

TEST_F(WidgetCaptureTest, ResetCaptureOnGestureEnd) {}

// Checks that if a mouse-press triggers a capture on a different widget (which
// consumes the mouse-release event), then the target of the press does not have
// capture.
TEST_F(WidgetCaptureTest, DisableCaptureWidgetFromMousePress) {}

// Tests some grab/ungrab events. Only one Widget can have capture at any given
// time.
TEST_F(WidgetCaptureTest, GrabUngrab) {}

// Disabled on Mac. Desktop Mac doesn't have system modal windows since Carbon
// was deprecated. It does have application modal windows, but only Ash requests
// those.
#if BUILDFLAG(IS_MAC)
#define MAYBE_SystemModalWindowReleasesCapture
#elif BUILDFLAG(IS_CHROMEOS_ASH)
// Investigate enabling for Chrome OS. It probably requires help from the window
// service.
#define MAYBE_SystemModalWindowReleasesCapture
#else
#define MAYBE_SystemModalWindowReleasesCapture
#endif

// Test that when opening a system-modal window, capture is released.
TEST_F(WidgetCaptureTest, MAYBE_SystemModalWindowReleasesCapture) {}

// Regression test for http://crbug.com/382421 (Linux-Aura issue).
// TODO(pkotwicz): Make test pass on CrOS and Windows.
// TODO(tapted): Investigate for toolkit-views on Mac http;//crbug.com/441064.
#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_MAC)
#define MAYBE_MouseExitOnCaptureGrab
#else
#define MAYBE_MouseExitOnCaptureGrab
#endif

// Test that a synthetic mouse exit is sent to the widget which was handling
// mouse events when a different widget grabs capture. Except for Windows,
// which does not send a synthetic mouse exit.
TEST_F(WidgetCaptureTest, MAYBE_MouseExitOnCaptureGrab) {}

namespace {

// Widget observer which grabs capture when the widget is activated.
class CaptureOnActivationObserver : public WidgetObserver {};

}  // namespace

// Test that setting capture on widget activation of a non-toplevel widget
// (e.g. a bubble on Linux) succeeds.
TEST_F(WidgetCaptureTest, SetCaptureToNonToplevel) {}

#if BUILDFLAG(IS_WIN)
namespace {

// Used to verify OnMouseEvent() has been invoked.
class MouseEventTrackingWidget : public Widget {
 public:
  MouseEventTrackingWidget() = default;

  MouseEventTrackingWidget(const MouseEventTrackingWidget&) = delete;
  MouseEventTrackingWidget& operator=(const MouseEventTrackingWidget&) = delete;

  ~MouseEventTrackingWidget() override = default;

  bool GetAndClearGotMouseEvent() {
    bool value = got_mouse_event_;
    got_mouse_event_ = false;
    return value;
  }

  // Widget:
  void OnMouseEvent(ui::MouseEvent* event) override {
    got_mouse_event_ = true;
    Widget::OnMouseEvent(event);
  }

 private:
  bool got_mouse_event_ = false;
};

}  // namespace

// Verifies if a mouse event is received on a widget that doesn't have capture
// on Windows that it is correctly processed by the widget that doesn't have
// capture. This behavior is not desired on OSes other than Windows.
TEST_F(WidgetCaptureTest, MouseEventDispatchedToRightWindow) {
  UniqueWidgetPtrT widget1 = std::make_unique<MouseEventTrackingWidget>();
  Widget::InitParams params1 =
      CreateParams(views::Widget::InitParams::TYPE_WINDOW);
  // Not setting bounds on Win64 Arm results in a 0 height window, which
  // won't get mouse events. See https://crbug.com/1418180.
  params1.bounds = gfx::Rect(0, 0, 200, 200);
  params1.native_widget = new DesktopNativeWidgetAura(widget1.get());
  widget1->Init(std::move(params1));
  widget1->Show();

  UniqueWidgetPtrT widget2 = std::make_unique<MouseEventTrackingWidget>();
  Widget::InitParams params2 =
      CreateParams(views::Widget::InitParams::TYPE_WINDOW);
  params2.bounds = gfx::Rect(0, 0, 200, 200);
  params2.native_widget = new DesktopNativeWidgetAura(widget2.get());
  widget2->Init(std::move(params2));
  widget2->Show();

  // Set capture to widget2 and verity it gets it.
  widget2->SetCapture(widget2->GetRootView());
  EXPECT_FALSE(widget1->HasCapture());
  EXPECT_TRUE(widget2->HasCapture());

  widget1->GetAndClearGotMouseEvent();
  widget2->GetAndClearGotMouseEvent();
  // Send a mouse event to the RootWindow associated with |widget1|. Even though
  // |widget2| has capture, |widget1| should still get the event.
  ui::MouseEvent mouse_event(ui::EventType::kMouseExited, gfx::Point(),
                             gfx::Point(), ui::EventTimeForNow(), ui::EF_NONE,
                             ui::EF_NONE);
  ui::EventDispatchDetails details =
      widget1->GetNativeWindow()->GetHost()->GetEventSink()->OnEventFromSource(
          &mouse_event);
  ASSERT_FALSE(details.dispatcher_destroyed);
  EXPECT_TRUE(widget1->GetAndClearGotMouseEvent());
  EXPECT_FALSE(widget2->GetAndClearGotMouseEvent());
}
#endif  // BUILDFLAG(IS_WIN)

class WidgetInputMethodInteractiveTest : public DesktopWidgetTestInteractive {};

#if BUILDFLAG(IS_MAC)
#define MAYBE_Activation
#else
#define MAYBE_Activation
#endif
// Test input method focus changes affected by top window activaction.
TEST_F(WidgetInputMethodInteractiveTest, MAYBE_Activation) {}

// Test input method focus changes affected by focus changes within 1 window.
TEST_F(WidgetInputMethodInteractiveTest, OneWindow) {}

// Test input method focus changes affected by focus changes cross 2 windows
// which shares the same top window.
TEST_F(WidgetInputMethodInteractiveTest, TwoWindows) {}

// Test input method focus changes affected by textfield's state changes.
TEST_F(WidgetInputMethodInteractiveTest, TextField) {}

// Test input method should not work for accelerator.
TEST_F(WidgetInputMethodInteractiveTest, AcceleratorInTextfield) {}

#if BUILDFLAG(ENABLE_DESKTOP_AURA)

class DesktopWidgetDragTestInteractive : public DesktopWidgetTestInteractive,
                                         public WidgetObserver {};

// Cancels a DnD session started by `RunShellDrag()`.
TEST_F(DesktopWidgetDragTestInteractive, CancelShellDrag) {}

#endif  // BUILDFLAG(ENABLE_DESKTOP_AURA)

}  // namespace views::test