blob: b1ce930f903b8bb7ac11a153c17004dc67e32f60 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_VIEWS_INTERACTION_INTERACTION_TEST_UTIL_MOUSE_H_
#define UI_VIEWS_INTERACTION_INTERACTION_TEST_UTIL_MOUSE_H_
#include <list>
#include <memory>
#include <set>
#include <utility>
#include <variant>
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
#include "ui/base/test/ui_controls.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/native_ui_types.h"
namespace views {
class Widget;
namespace test {
// Class which provides useful primitives for controlling the mouse and then
// cleaning up mouse state (even if a test fails). As this object does control
// the mouse, do not create multiple simultaneous instances, and strongly prefer
// to use it only in test suites such as interactive_ui_tests where a single
// test can control the mouse at a time.
class InteractionTestUtilMouse {
public:
// Construct for a particular window or browser. This is required because the
// util object may need access to a drag controller, which is most easily
// accessed via the window.
explicit InteractionTestUtilMouse(Widget* widget);
~InteractionTestUtilMouse();
InteractionTestUtilMouse(const InteractionTestUtilMouse&) = delete;
void operator=(const InteractionTestUtilMouse&) = delete;
// These represent mouse gestures of different types. They are implementation
// details; prefer to use the static factory methods below.
struct MouseButtonGesture {
MouseButtonGesture(ui_controls::MouseButton button_,
ui_controls::MouseButtonState button_state_,
int modifier_keys_)
: button(button_),
button_state(button_state_),
modifier_keys(modifier_keys_) {}
ui_controls::MouseButton button;
ui_controls::MouseButtonState button_state;
/* ui_controls::AcceleratorState */ int modifier_keys;
};
using MouseMoveGesture = gfx::Point;
using MouseGesture = std::variant<MouseMoveGesture, MouseButtonGesture>;
using MouseGestures = std::list<MouseGesture>;
// Parameters for performing a sequence of gestures.
struct GestureParams {
GestureParams();
GestureParams(gfx::NativeWindow window_hint, bool force_async);
GestureParams(const GestureParams&);
GestureParams& operator=(const GestureParams&);
GestureParams(GestureParams&&) noexcept;
GestureParams& operator=(GestureParams&&) noexcept;
~GestureParams();
// The native window the input will be sent to. If null, the OS decides.
//
// Note: explicit init is required on Aura platforms, but not on Mac.
gfx::NativeWindow window_hint = gfx::NativeWindow(); // NOLINT
// If true, mouse input will be sent asynchronously, which may be required
// if the input would put the system into an OS-based message pump; this
// happens for native context menus on Mac and drag-drop on Windows.
//
// For all other cases, this should be `false`.
bool force_async = false;
};
// These factory methods create individual or compound gestures. They can be
// chained together. Prefer these to directly constructing a MouseGesture.
static MouseGesture MoveTo(gfx::Point point);
static MouseGesture MouseDown(
ui_controls::MouseButton button,
int modifier_keys = ui_controls::kNoAccelerator);
static MouseGesture MouseUp(ui_controls::MouseButton button,
int modifier_keys = ui_controls::kNoAccelerator);
static MouseGestures Click(ui_controls::MouseButton button,
int modifier_keys = ui_controls::kNoAccelerator);
static MouseGestures DragAndHold(gfx::Point destination);
static MouseGestures DragAndRelease(gfx::Point destination);
// Set or get touch mode.
//
// `SetTouchMode(true)` returns false if touch is not supported. If it
// succeeds, subsequent mouse inputs will be converted to equivalent touch
// inputs.
//
// Notes:
// - This is an experimental feature and the API is subject to change.
// - See tracking bug at crbug.com/1428292 for current status.
// - Currently only Ash Chrome is supported.
// - Hover is not yet supported, only tap [up/down] and drag.
// - Moves without a finger down affect the next tap input but do not send
// events.
//
// To use this in an InteractiveViewsTest or InteractiveBrowserTest, use the
// following syntax:
//
// Check([this](){ return test_impl().mouse_util().SetTouchMode(true); })
//
// Afterwards, you can use mouse verbs as normal and they will convert to
// equivalent touch inputs. We suggest using `Check()` so that the test will
// fail if it's accidentally run on a system that doesn't yet support it.
//
// Alternatively, you can write a parameterized test which selectively tries
// the test in touch-on and touch-off modes for platforms that support it, but
// only in touch-off mode for those that don't. In these cases, the `Check()`
// above changes to something like `...SetTouchMode(GetParam())`.
bool SetTouchMode(bool touch_mode);
bool GetTouchMode() const;
// Perform the gesture or gestures specified, returns true on success.
template <typename... Args>
bool PerformGestures(const GestureParams& window_hint, Args... gestures);
// Cancels any pending actions and cleans up any resulting mouse state (i.e.
// releases any buttons which were pressed).
void CancelAllGestures();
private:
explicit InteractionTestUtilMouse(gfx::NativeWindow window);
// Helper methods for adding gestures to a gesture list.
static void AddGestures(MouseGestures& gestures, MouseGesture to_add);
static void AddGestures(MouseGestures& gestures, MouseGestures to_add);
bool PerformGesturesImpl(const GestureParams& params, MouseGestures gestures);
bool ShouldCancelDrag() const;
void CancelFutureDrag();
void CancelDragNow();
bool SendButtonPress(const MouseButtonGesture& gesture,
const GestureParams& params,
base::OnceClosure on_complete);
bool SendMove(const MouseMoveGesture& gesture,
const GestureParams& params,
base::OnceClosure on_complete);
// The set of mouse buttons currently depressed. Used to clean up on abort.
std::set<ui_controls::MouseButton> buttons_down_;
// Whether gestures are being executed.
bool performing_gestures_ = false;
// Whether the current sequence is canceled.
bool canceled_ = false;
// Whether we're in touch mode. In touch mode, touch events are sent instead
// of mouse events. Moves without fingers down will not send events (but see
// `touch_hover_point_` below).
bool touch_mode_ = false;
// Tracks the next place touch input should take place. It is affected by all
// moves, regardless of whether any fingers are down, and you can use MoveTo
// with no fingers to reposition the point.
gfx::Point touch_hover_point_;
#if defined(USE_AURA)
// Whether the mouse is currently being dragged.
bool dragging_ = false;
// These are used in order to clean up extraneous drags on Aura platforms;
// without this it is possible for a drag loop to start and not exit,
// preventing a test from completing.
class DragEnder;
const std::unique_ptr<DragEnder> drag_ender_;
#endif
base::WeakPtrFactory<InteractionTestUtilMouse> weak_ptr_factory_{this};
};
template <typename... Args>
bool InteractionTestUtilMouse::PerformGestures(const GestureParams& params,
Args... gestures) {
MouseGestures gesture_list;
(AddGestures(gesture_list, std::move(gestures)), ...);
return PerformGesturesImpl(params, std::move(gesture_list));
}
} // namespace test
} // namespace views
#endif // UI_VIEWS_INTERACTION_INTERACTION_TEST_UTIL_MOUSE_H_