0

Adding feature FocusFollowsCursor on cros.

When enabled, focus will follow the cursor. Windows are not raised
when focused in this way. This implements the "sloppy focus" model
which means that focus remains with the most recent window if the
cursor is moved to the desktop (and similar).

Tested: unit tests + manual testing in emulator and on device.

Bug: 323298, 1138604
Change-Id: Ia613b04807d27ec92c3bfebad2bca32dbfe9d201
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2934417
Commit-Queue: Daniel Andersson <dandersson@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#889479}
This commit is contained in:
Daniel Andersson
2021-06-04 22:09:43 +00:00
committed by Chromium LUCI CQ
parent 1f94daf9d9
commit a36c4bcd84
10 changed files with 90 additions and 18 deletions

@ -7199,6 +7199,12 @@ const FeatureEntry kFeatureEntries[] = {
kOsMac | kOsLinux | kOsCrOS | kOsAndroid,
FEATURE_VALUE_TYPE(features::kDefaultPassthroughCommandDecoder)},
#if BUILDFLAG(IS_CHROMEOS_ASH)
{"focus-follows-cursor", flag_descriptions::kFocusFollowsCursorName,
flag_descriptions::kFocusFollowsCursorDescription, kOsCrOS,
FEATURE_VALUE_TYPE(::features::kFocusFollowsCursor)},
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// NOTE: Adding a new flag requires adding a corresponding entry to enum
// "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
// Histograms" in tools/metrics/histograms/README.md (run the

@ -2893,6 +2893,11 @@
"owners": [ "fhorschig", "redatawfik" ],
"expiry_milestone": 95
},
{
"name": "focus-follows-cursor",
"owners": [ "dandersson", "tclaiborne" ],
"expiry_milestone": 100
},
{
"name": "focus-mode",
"owners": [ "dfried", "pbos" ],

@ -4444,6 +4444,10 @@ const char kFiltersInRecentsDescription[] =
"Enable file-type filters (Audio, Images, Videos) in Files App Recents "
"view.";
const char kFocusFollowsCursorName[] = "Focus follows cursor";
const char kFocusFollowsCursorDescription[] =
"Enable window focusing by moving the cursor.";
const char kFrameThrottleFpsName[] = "Set frame throttling fps.";
const char kFrameThrottleFpsDescription[] =
"Set the throttle fps for compositor frame submission.";

@ -2572,6 +2572,9 @@ extern const char kFilesZipUnpackDescription[];
extern const char kFiltersInRecentsName[];
extern const char kFiltersInRecentsDescription[];
extern const char kFocusFollowsCursorName[];
extern const char kFocusFollowsCursorDescription[];
extern const char kFrameThrottleFpsName[];
extern const char kFrameThrottleFpsDescription[];
extern const char kFrameThrottleFpsDefault[];

@ -45555,6 +45555,7 @@ from previous Chrome versions.
<int value="-1572010356" label="enable-privet-v3"/>
<int value="-1571841513" label="enable-devtools-experiments"/>
<int value="-1571525676" label="FilesSWA:disabled"/>
<int value="-1570760081" label="FocusFollowsCursor:disabled"/>
<int value="-1569571629"
label="AutofillEnableOffersInClankKeyboardAccessory:disabled"/>
<int value="-1568737447" label="InsecureFormSubmissionInterstitial:enabled"/>
@ -46640,6 +46641,7 @@ from previous Chrome versions.
<int value="-695687521" label="double-buffer-compositing"/>
<int value="-694622753" label="VizHitTest:disabled"/>
<int value="-694187898" label="MashOopViz:disabled"/>
<int value="-688003116" label="FocusFollowsCursor:enabled"/>
<int value="-687302378" label="BluetoothFixA2dpPacketSize:enabled"/>
<int value="-686761381" label="UseHDRTransferFunction:enabled"/>
<int value="-684900739" label="disable-merge-key-char-events"/>

@ -147,8 +147,11 @@ const base::Feature kElasticOverscroll = {"ElasticOverscroll",
base::FEATURE_DISABLED_BY_DEFAULT};
#endif // defined(OS_WIN) || defined(OS_ANDROID)
#if defined(OS_WIN)
// Enables focus follow follow cursor (sloppyfocus).
const base::Feature kFocusFollowsCursor = {"FocusFollowsCursor",
base::FEATURE_DISABLED_BY_DEFAULT};
#if defined(OS_WIN)
// Enables InputPane API for controlling on screen keyboard.
const base::Feature kInputPaneOnScreenKeyboard = {
"InputPaneOnScreenKeyboard", base::FEATURE_ENABLED_BY_DEFAULT};

@ -21,6 +21,8 @@ COMPONENT_EXPORT(UI_BASE_FEATURES)
extern const base::Feature kCompositorThreadedScrollbarScrolling;
COMPONENT_EXPORT(UI_BASE_FEATURES)
extern const base::Feature kExperimentalFlingAnimation;
COMPONENT_EXPORT(UI_BASE_FEATURES)
extern const base::Feature kFocusFollowsCursor;
#if BUILDFLAG(IS_CHROMEOS_ASH)
COMPONENT_EXPORT(UI_BASE_FEATURES)
extern const base::Feature kSettingsShowsPerKeyboardSettings;

@ -10,6 +10,7 @@
#include "ui/aura/client/focus_change_observer.h"
#include "ui/aura/env.h"
#include "ui/aura/window_tracker.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/event.h"
#include "ui/wm/core/focus_rules.h"
#include "ui/wm/core/window_util.h"
@ -37,7 +38,10 @@ void StackTransientParentsBelowModalWindow(aura::Window* window) {
////////////////////////////////////////////////////////////////////////////////
// FocusController, public:
FocusController::FocusController(FocusRules* rules) : rules_(rules) {
FocusController::FocusController(FocusRules* rules)
: rules_(rules),
focus_follows_cursor_(
base::FeatureList::IsEnabled(features::kFocusFollowsCursor)) {
DCHECK(rules);
}
@ -95,7 +99,8 @@ void FocusController::RemoveObserver(
void FocusController::FocusWindow(aura::Window* window) {
FocusAndActivateWindow(
ActivationChangeObserver::ActivationReason::ACTIVATION_CLIENT, window);
ActivationChangeObserver::ActivationReason::ACTIVATION_CLIENT, window,
/*no_stacking=*/false);
}
void FocusController::ResetFocusWithinActiveWindow(aura::Window* window) {
@ -116,7 +121,9 @@ aura::Window* FocusController::GetFocusedWindow() {
void FocusController::OnKeyEvent(ui::KeyEvent* event) {}
void FocusController::OnMouseEvent(ui::MouseEvent* event) {
if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled())
if ((event->type() == ui::ET_MOUSE_PRESSED ||
(event->type() == ui::ET_MOUSE_ENTERED && focus_follows_cursor_)) &&
!event->handled())
WindowFocusedFromInputEvent(static_cast<aura::Window*>(event->target()),
event);
}
@ -185,10 +192,13 @@ void FocusController::OnWindowHierarchyChanged(
void FocusController::FocusAndActivateWindow(
ActivationChangeObserver::ActivationReason reason,
aura::Window* window) {
aura::Window* window,
bool no_stacking) {
if (window &&
(window->Contains(focused_window_) || window->Contains(active_window_))) {
StackActiveWindow();
if (!no_stacking) {
StackActiveWindow();
}
return;
}
@ -215,7 +225,7 @@ void FocusController::FocusAndActivateWindow(
focusable = nullptr;
}
if (!SetActiveWindow(reason, window, activatable))
if (!SetActiveWindow(reason, window, activatable, no_stacking))
return;
if (!focusable_window_tracker.windows().empty())
@ -302,7 +312,8 @@ void FocusController::SetFocusedWindow(aura::Window* window) {
bool FocusController::SetActiveWindow(
ActivationChangeObserver::ActivationReason reason,
aura::Window* requested_window,
aura::Window* window) {
aura::Window* window,
bool no_stacking) {
if (pending_activation_)
return false;
@ -346,7 +357,7 @@ bool FocusController::SetActiveWindow(
active_window_ = window;
if (active_window_)
if (active_window_ && !no_stacking)
StackActiveWindow();
MAYBE_ACTIVATION_INTERRUPTED();
@ -423,7 +434,8 @@ void FocusController::WindowLostFocusFromDispositionChange(aura::Window* window,
aura::Window* next_activatable = rules_->GetNextActivatableWindow(window);
if (!SetActiveWindow(ActivationChangeObserver::ActivationReason::
WINDOW_DISPOSITION_CHANGED,
nullptr, next_activatable)) {
nullptr, next_activatable,
/*no_stacking=*/false)) {
return;
}
@ -453,7 +465,8 @@ void FocusController::WindowFocusedFromInputEvent(aura::Window* window,
// currently focused one.
if (rules_->CanFocusWindow(GetToplevelWindow(window), event)) {
FocusAndActivateWindow(
ActivationChangeObserver::ActivationReason::INPUT_EVENT, window);
ActivationChangeObserver::ActivationReason::INPUT_EVENT, window,
event->type() == ui::ET_MOUSE_ENTERED);
}
}

@ -88,7 +88,8 @@ class WM_CORE_EXPORT FocusController : public ActivationClient,
// Internal implementation that coordinates window focus and activation
// changes.
void FocusAndActivateWindow(ActivationChangeObserver::ActivationReason reason,
aura::Window* window);
aura::Window* window,
bool no_stacking);
// Internal implementation that sets the focused window, fires events etc.
// This function must be called with a valid focusable window.
@ -102,10 +103,12 @@ class WM_CORE_EXPORT FocusController : public ActivationClient,
// refers to the actual window to be activated, which may be different.
// Returns true if activation should proceed, or false if activation was
// interrupted, e.g. by the destruction of the window gaining activation
// during the process, and therefore activation should be aborted.
// during the process, and therefore activation should be aborted. If
// |no_stacking| is true, the activated window is not stacked.
bool SetActiveWindow(ActivationChangeObserver::ActivationReason reason,
aura::Window* requested_window,
aura::Window* activatable_window);
aura::Window* activatable_window,
bool no_stacking);
// Stack the |active_window_| on top of the window stack. This function is
// called when activating a window or re-activating the current active window.
@ -141,6 +144,9 @@ class WM_CORE_EXPORT FocusController : public ActivationClient,
base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
observation_manager_{this};
// When true, windows can be activated (but not raised) without clicking.
bool focus_follows_cursor_ = false;
DISALLOW_COPY_AND_ASSIGN(FocusController);
};

@ -7,6 +7,7 @@
#include <map>
#include "base/macros.h"
#include "base/test/scoped_feature_list.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/default_capture_client.h"
#include "ui/aura/client/focus_change_observer.h"
@ -16,6 +17,7 @@
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tracker.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_handler.h"
@ -1220,6 +1222,28 @@ class FocusControllerMouseEventTest : public FocusControllerDirectTestBase {
DISALLOW_COPY_AND_ASSIGN(FocusControllerMouseEventTest);
};
class FocusControllerMouseEnterEventTest
: public FocusControllerMouseEventTest {
public:
FocusControllerMouseEnterEventTest() {
scoped_feature_list_.InitAndEnableFeature(features::kFocusFollowsCursor);
}
void MouseEnteredEvent() {
aura::Window::Windows children_before = root_window()->children();
aura::Window* w2 = root_window()->GetChildById(2);
ui::test::EventGenerator generator(root_window(), w2);
generator.SendMouseEnter();
// The enter event should activate the window, but not stack the window.
EXPECT_EQ(w2, GetActiveWindow());
EXPECT_EQ(children_before, root_window()->children());
}
base::test::ScopedFeatureList scoped_feature_list_;
};
class FocusControllerGestureEventTest : public FocusControllerDirectTestBase {
public:
FocusControllerGestureEventTest() {}
@ -1497,10 +1521,11 @@ class FocusControllerParentRemovalTest : public FocusControllerRemovalTest {
TEST_F(TESTCLASS, TESTNAME) { TESTNAME(); }
// Runs direct focus change tests (input events and API calls).
#define DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \
FOCUS_CONTROLLER_TEST(FocusControllerApiTest, TESTNAME) \
FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, TESTNAME) \
FOCUS_CONTROLLER_TEST(FocusControllerGestureEventTest, TESTNAME)
#define DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \
FOCUS_CONTROLLER_TEST(FocusControllerApiTest, TESTNAME) \
FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, TESTNAME) \
FOCUS_CONTROLLER_TEST(FocusControllerMouseEnterEventTest, TESTNAME) \
FOCUS_CONTROLLER_TEST(FocusControllerGestureEventTest, TESTNAME)
// Runs implicit focus change tests for disposition changes to target.
#define IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \
@ -1602,4 +1627,7 @@ FOCUS_CONTROLLER_TEST(FocusControllerParentHideTest,
// If a mouse event was handled, it should not activate a window.
FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, IgnoreHandledEvent)
// Mouse over window should activate it.
FOCUS_CONTROLLER_TEST(FocusControllerMouseEnterEventTest, MouseEnteredEvent)
} // namespace wm