Bug 1351783 part 16 - Perform async scrolling for keyboard events when possible. r=kats,botond,dvander

This commit ties it all together by dispatching keyboard actions to scroll targets
in response to keyboard inputs when we have current and valid focus state.

MozReview-Commit-ID: G7rZiS3FH5e

--HG--
extra : rebase_source : 10129d417fe8ef576cac5bda3157dd8f65b5843a
extra : histedit_source : be651a33f787f68bc764988ddc073d346e854491
This commit is contained in:
Ryan Hunt 2017-06-05 19:46:06 -05:00
Родитель c24e099b23
Коммит db5f497c0f
18 изменённых файлов: 302 добавлений и 0 удалений

Просмотреть файл

@ -11,6 +11,7 @@
#include "mozilla/EventStateManager.h" // for WheelPrefs
#include "mozilla/layers/APZThreadUtils.h" // for AssertOnCompositorThread, etc
#include "mozilla/MouseEvents.h" // for WidgetMouseEvent
#include "mozilla/TextEvents.h" // for WidgetKeyboardEvent
#include "mozilla/TouchEvents.h" // for WidgetTouchEvent
namespace mozilla {
@ -144,6 +145,17 @@ IAPZCTreeManager::ReceiveInputEvent(
return nsEventStatus_eIgnore;
}
case eKeyboardEventClass: {
WidgetKeyboardEvent& keyboardEvent = *aEvent.AsKeyboardEvent();
KeyboardInput input(keyboardEvent);
nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId);
keyboardEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ;
keyboardEvent.mFocusSequenceNumber = input.mFocusSequenceNumber;
return status;
}
default: {
UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage);

Просмотреть файл

@ -1240,6 +1240,91 @@ APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
apzc->GetGuid(aOutTargetGuid);
tapInput.mPoint = *untransformedPoint;
}
break;
} case KEYBOARD_INPUT: {
// Disable async keyboard scrolling when accessibility.browsewithcaret is enabled
if (!gfxPrefs::APZKeyboardEnabled() ||
gfxPrefs::AccessibilityBrowseWithCaret()) {
return result;
}
KeyboardInput& keyInput = aEvent.AsKeyboardInput();
// Try and find a matching shortcut for this keyboard input
Maybe<KeyboardShortcut> shortcut = mKeyboardMap.FindMatch(keyInput);
if (!shortcut) {
// If we don't have a shortcut for this key event, then we can keep our focus
// only if we know there are no key event listeners for this target
if (mFocusState.CanIgnoreKeyboardShortcutMisses()) {
focusSetter.MarkAsNonFocusChanging();
}
return result;
}
// Check if this shortcut needs to be dispatched to content. Anything matching
// this is assumed to be able to change focus.
if (shortcut->mDispatchToContent) {
return result;
}
// We know we have an action to execute on whatever is the current focus target
const KeyboardScrollAction& action = shortcut->mAction;
// The current focus target depends on which direction the scroll is to happen
Maybe<ScrollableLayerGuid> targetGuid;
switch (action.mType)
{
case KeyboardScrollAction::eScrollCharacter: {
targetGuid = mFocusState.GetHorizontalTarget();
break;
}
case KeyboardScrollAction::eScrollLine:
case KeyboardScrollAction::eScrollPage:
case KeyboardScrollAction::eScrollComplete: {
targetGuid = mFocusState.GetVerticalTarget();
break;
}
case KeyboardScrollAction::eSentinel: {
MOZ_ASSERT_UNREACHABLE("Invalid KeyboardScrollActionType");
}
}
// If we don't have a scroll target then either we have a stale focus target,
// the focused element has event listeners, or the focused element doesn't have a
// layerized scroll frame. In any case we need to dispatch to content.
if (!targetGuid) {
return result;
}
RefPtr<AsyncPanZoomController> targetApzc = GetTargetAPZC(targetGuid->mLayersId,
targetGuid->mScrollId);
// Scroll snapping behavior with keyboard input is more complicated, so
// ignore any input events that are targeted at an Apzc with scroll snap
// points.
if (!targetApzc || targetApzc->HasScrollSnapping()) {
return result;
}
// Attach the keyboard scroll action to the input event for processing
// by the input queue.
keyInput.mAction = action;
// Dispatch the event to the input queue.
result = mInputQueue->ReceiveInputEvent(
targetApzc,
/* aTargetConfirmed = */ true,
keyInput, aOutInputBlockId);
// Any keyboard event that is dispatched to the input queue at this point
// should have been consumed
MOZ_ASSERT(result == nsEventStatus_eConsumeNoDefault);
keyInput.mHandledByAPZ = true;
focusSetter.MarkAsNonFocusChanging();
break;
} case SENTINEL_INPUT: {
MOZ_ASSERT_UNREACHABLE("Invalid InputType.");

Просмотреть файл

@ -340,6 +340,13 @@ public:
*/
bool HasScrollgrab() const { return mScrollMetadata.GetHasScrollgrab(); }
/**
* Returns whether this APZC has scroll snap points.
*/
bool HasScrollSnapping() const {
return mScrollMetadata.GetSnapInfo().HasScrollSnapping();
}
/**
* Returns whether this APZC has room to be panned (in any direction).
*/

Просмотреть файл

@ -113,5 +113,35 @@ FocusState::RemoveFocusTarget(uint64_t aLayersId)
mFocusTree.erase(aLayersId);
}
Maybe<ScrollableLayerGuid>
FocusState::GetHorizontalTarget() const
{
// There is not a scrollable layer to async scroll if
// 1. We aren't current
// 2. There are event listeners that could change the focus
// 3. The target has not been layerized
if (!IsCurrent() ||
mFocusHasKeyEventListeners ||
mFocusHorizontalTarget == FrameMetrics::NULL_SCROLL_ID) {
return Nothing();
}
return Some(ScrollableLayerGuid(mFocusLayersId, 0, mFocusHorizontalTarget));
}
Maybe<ScrollableLayerGuid>
FocusState::GetVerticalTarget() const
{
// There is not a scrollable layer to async scroll if:
// 1. We aren't current
// 2. There are event listeners that could change the focus
// 3. The target has not been layerized
if (!IsCurrent() ||
mFocusHasKeyEventListeners ||
mFocusVerticalTarget == FrameMetrics::NULL_SCROLL_ID) {
return Nothing();
}
return Some(ScrollableLayerGuid(mFocusLayersId, 0, mFocusVerticalTarget));
}
} // namespace layers
} // namespace mozilla

Просмотреть файл

@ -115,6 +115,31 @@ public:
*/
void RemoveFocusTarget(uint64_t aLayersId);
/**
* Gets the scrollable layer that should be horizontally scrolled for a key
* event, if any. The returned ScrollableLayerGuid doesn't contain a presShellId,
* and so it should not be used in comparisons.
*
* No scrollable layer is returned if any of the following are true:
* 1. We don't have a current focus target
* 2. There are event listeners that could change the focus
* 3. The target has not been layerized
*/
Maybe<ScrollableLayerGuid> GetHorizontalTarget() const;
/**
* The same as GetHorizontalTarget() but for vertical scrolling.
*/
Maybe<ScrollableLayerGuid> GetVerticalTarget() const;
/**
* Gets whether it is safe to not increment the focus sequence number for an
* unmatched keyboard event.
*/
bool CanIgnoreKeyboardShortcutMisses() const
{
return IsCurrent() && !mFocusHasKeyEventListeners;
}
private:
// The set of focus targets received indexed by their layer tree ID
std::unordered_map<uint64_t, FocusTarget> mFocusTree;

Просмотреть файл

@ -870,5 +870,10 @@ TouchBlockState::GetActiveTouchCount() const
return mTouchCounter.GetActiveTouchCount();
}
KeyboardBlockState::KeyboardBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc)
: InputBlockState(aTargetApzc, true)
{
}
} // namespace layers
} // namespace mozilla

Просмотреть файл

@ -27,6 +27,7 @@ class TouchBlockState;
class WheelBlockState;
class DragBlockState;
class PanGestureBlockState;
class KeyboardBlockState;
/**
* A base class that stores state common to various input blocks.
@ -68,6 +69,9 @@ public:
virtual PanGestureBlockState* AsPanGestureBlock() {
return nullptr;
}
virtual KeyboardBlockState* AsKeyboardBlock() {
return nullptr;
}
virtual bool SetConfirmedTargetApzc(const RefPtr<AsyncPanZoomController>& aTargetApzc,
TargetConfirmationState aState,
@ -486,6 +490,23 @@ private:
TouchCounter& mTouchCounter;
};
/**
* This class represents a set of keyboard inputs targeted at the same Apzc.
*/
class KeyboardBlockState : public InputBlockState
{
public:
explicit KeyboardBlockState(const RefPtr<AsyncPanZoomController>& aTargetApzc);
KeyboardBlockState* AsKeyboardBlock() override {
return this;
}
bool MustStayActive() override {
return false;
}
};
} // namespace layers
} // namespace mozilla

Просмотреть файл

@ -56,6 +56,14 @@ InputQueue::ReceiveInputEvent(const RefPtr<AsyncPanZoomController>& aTarget,
return ReceiveMouseInput(aTarget, aTargetConfirmed, event, aOutInputBlockId);
}
case KEYBOARD_INPUT: {
// Every keyboard input must have a confirmed target
MOZ_ASSERT(aTarget && aTargetConfirmed);
const KeyboardInput& event = aEvent.AsKeyboardInput();
return ReceiveKeyboardInput(aTarget, event, aOutInputBlockId);
}
default:
// The return value for non-touch input is only used by tests, so just pass
// through the return value for now. This can be changed later if needed.
@ -268,6 +276,39 @@ InputQueue::ReceiveScrollWheelInput(const RefPtr<AsyncPanZoomController>& aTarge
return nsEventStatus_eConsumeDoDefault;
}
nsEventStatus
InputQueue::ReceiveKeyboardInput(const RefPtr<AsyncPanZoomController>& aTarget,
const KeyboardInput& aEvent,
uint64_t* aOutInputBlockId) {
KeyboardBlockState* block = mActiveKeyboardBlock.get();
// If the block is targeting a different Apzc than this keyboard event then
// we'll create a new input block
if (block && block->GetTargetApzc() != aTarget) {
block = nullptr;
}
if (!block) {
block = new KeyboardBlockState(aTarget);
INPQ_LOG("started new keyboard block %p id %" PRIu64 " for target %p\n",
block, block->GetBlockId(), aTarget.get());
mActiveKeyboardBlock = block;
} else {
INPQ_LOG("received new event in block %p\n", block);
}
if (aOutInputBlockId) {
*aOutInputBlockId = block->GetBlockId();
}
mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block));
ProcessQueue();
return nsEventStatus_eConsumeNoDefault;
}
static bool
CanScrollTargetHorizontally(const PanGestureInput& aInitialEvent,
PanGestureBlockState* aBlock)
@ -461,6 +502,13 @@ InputQueue::GetCurrentPanGestureBlock() const
return block ? block->AsPanGestureBlock() : mActivePanGestureBlock.get();
}
KeyboardBlockState*
InputQueue::GetCurrentKeyboardBlock() const
{
InputBlockState* block = GetCurrentBlock();
return block ? block->AsKeyboardBlock() : mActiveKeyboardBlock.get();
}
WheelBlockState*
InputQueue::GetActiveWheelTransaction() const
{

Просмотреть файл

@ -30,6 +30,7 @@ class TouchBlockState;
class WheelBlockState;
class DragBlockState;
class PanGestureBlockState;
class KeyboardBlockState;
class AsyncDragMetrics;
class QueuedInput;
@ -106,6 +107,7 @@ public:
WheelBlockState* GetCurrentWheelBlock() const;
DragBlockState* GetCurrentDragBlock() const;
PanGestureBlockState* GetCurrentPanGestureBlock() const;
KeyboardBlockState* GetCurrentKeyboardBlock() const;
/**
* Returns true iff the pending block at the head of the queue is a touch
* block and is ready for handling.
@ -169,6 +171,9 @@ private:
bool aTargetConfirmed,
const PanGestureInput& aEvent,
uint64_t* aOutInputBlockId);
nsEventStatus ReceiveKeyboardInput(const RefPtr<AsyncPanZoomController>& aTarget,
const KeyboardInput& aEvent,
uint64_t* aOutInputBlockId);
/**
* Helper function that searches mQueuedInputs for the first block matching
@ -202,6 +207,7 @@ private:
RefPtr<WheelBlockState> mActiveWheelBlock;
RefPtr<DragBlockState> mActiveDragBlock;
RefPtr<PanGestureBlockState> mActivePanGestureBlock;
RefPtr<KeyboardBlockState> mActiveKeyboardBlock;
// The APZC to which the last event was delivered
RefPtr<AsyncPanZoomController> mLastActiveApzc;

Просмотреть файл

@ -38,6 +38,12 @@ QueuedInput::QueuedInput(const PanGestureInput& aInput, PanGestureBlockState& aB
{
}
QueuedInput::QueuedInput(const KeyboardInput& aInput, KeyboardBlockState& aBlock)
: mInput(MakeUnique<KeyboardInput>(aInput))
, mBlock(&aBlock)
{
}
InputData*
QueuedInput::Input()
{

Просмотреть файл

@ -17,6 +17,7 @@ class MultiTouchInput;
class ScrollWheelInput;
class MouseInput;
class PanGestureInput;
class KeyboardInput;
namespace layers {
@ -25,6 +26,7 @@ class TouchBlockState;
class WheelBlockState;
class DragBlockState;
class PanGestureBlockState;
class KeyboardBlockState;
/**
* This lightweight class holds a pointer to an input event that has not yet
@ -38,6 +40,7 @@ public:
QueuedInput(const ScrollWheelInput& aInput, WheelBlockState& aBlock);
QueuedInput(const MouseInput& aInput, DragBlockState& aBlock);
QueuedInput(const PanGestureInput& aInput, PanGestureBlockState& aBlock);
QueuedInput(const KeyboardInput& aInput, KeyboardBlockState& aBlock);
InputData* Input();
InputBlockState* Block();

Просмотреть файл

@ -119,6 +119,20 @@ APZCTreeManagerChild::ReceiveInputEvent(
event = processedEvent;
return res;
}
case KEYBOARD_INPUT: {
KeyboardInput& event = aEvent.AsKeyboardInput();
KeyboardInput processedEvent;
nsEventStatus res;
SendReceiveKeyboardInputEvent(event,
&res,
&processedEvent,
aOutTargetGuid,
aOutInputBlockId);
event = processedEvent;
return res;
}
default: {
MOZ_ASSERT_UNREACHABLE("Invalid InputData type.");
return nsEventStatus_eConsumeNoDefault;

Просмотреть файл

@ -144,6 +144,25 @@ APZCTreeManagerParent::RecvReceiveScrollWheelInputEvent(
return IPC_OK();
}
mozilla::ipc::IPCResult
APZCTreeManagerParent::RecvReceiveKeyboardInputEvent(
const KeyboardInput& aEvent,
nsEventStatus* aOutStatus,
KeyboardInput* aOutEvent,
ScrollableLayerGuid* aOutTargetGuid,
uint64_t* aOutInputBlockId)
{
KeyboardInput event = aEvent;
*aOutStatus = mTreeManager->ReceiveInputEvent(
event,
aOutTargetGuid,
aOutInputBlockId);
*aOutEvent = event;
return IPC_OK();
}
mozilla::ipc::IPCResult
APZCTreeManagerParent::RecvSetKeyboardMap(const KeyboardMap& aKeyboardMap)
{

Просмотреть файл

@ -78,6 +78,14 @@ public:
ScrollableLayerGuid* aOutTargetGuid,
uint64_t* aOutInputBlockId) override;
mozilla::ipc::IPCResult
RecvReceiveKeyboardInputEvent(
const KeyboardInput& aEvent,
nsEventStatus* aOutStatus,
KeyboardInput* aOutEvent,
ScrollableLayerGuid* aOutTargetGuid,
uint64_t* aOutInputBlockId) override;
mozilla::ipc::IPCResult
RecvSetKeyboardMap(const KeyboardMap& aKeyboardMap) override;

Просмотреть файл

@ -38,6 +38,7 @@ using class mozilla::PinchGestureInput from "InputData.h";
using mozilla::PinchGestureInput::PinchGestureType from "InputData.h";
using class mozilla::TapGestureInput from "InputData.h";
using class mozilla::ScrollWheelInput from "InputData.h";
using class mozilla::KeyboardInput from "InputData.h";
namespace mozilla {
namespace layers {
@ -121,6 +122,12 @@ parent:
ScrollableLayerGuid aOutTargetGuid,
uint64_t aOutInputBlockId);
sync ReceiveKeyboardInputEvent(KeyboardInput aEvent)
returns (nsEventStatus aOutStatus,
KeyboardInput aOutEvent,
ScrollableLayerGuid aOutTargetGuid,
uint64_t aOutInputBlockId);
async UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, EventMessage aEventMessage);
sync ProcessUnhandledEvent(LayoutDeviceIntPoint aRefPoint)

Просмотреть файл

@ -279,6 +279,8 @@ private:
// We will keep these in an alphabetical order to make it easier to see if
// a method accessing a pref already exists. Just add yours in the list.
DECL_GFX_PREF(Live, "accessibility.browsewithcaret", AccessibilityBrowseWithCaret, bool, false);
// The apz prefs are explained in AsyncPanZoomController.cpp
DECL_GFX_PREF(Live, "apz.allow_checkerboarding", APZAllowCheckerboarding, bool, true);
DECL_GFX_PREF(Live, "apz.allow_immediate_handoff", APZAllowImmediateHandoff, bool, true);
@ -310,6 +312,7 @@ private:
DECL_GFX_PREF(Live, "apz.fling_stop_on_tap_threshold", APZFlingStopOnTapThreshold, float, 0.05f);
DECL_GFX_PREF(Live, "apz.fling_stopped_threshold", APZFlingStoppedThreshold, float, 0.01f);
DECL_GFX_PREF(Live, "apz.highlight_checkerboarded_areas", APZHighlightCheckerboardedAreas, bool, false);
DECL_GFX_PREF(Once, "apz.keyboard.enabled", APZKeyboardEnabled, bool, false);
DECL_GFX_PREF(Live, "apz.max_velocity_inches_per_ms", APZMaxVelocity, float, -1.0f);
DECL_GFX_PREF(Once, "apz.max_velocity_queue_size", APZMaxVelocityQueueSize, uint32_t, 5);
DECL_GFX_PREF(Live, "apz.min_skate_speed", APZMinSkateSpeed, float, 1.0f);

Просмотреть файл

@ -969,6 +969,8 @@ description =
description =
[PAPZCTreeManager::ProcessUnhandledEvent]
description =
[PAPZCTreeManager::ReceiveKeyboardInputEvent]
description =
[PCompositorBridge::Initialize]
description =
[PCompositorBridge::GetFrameUniformity]

Просмотреть файл

@ -701,6 +701,7 @@ pref("apz.fling_min_velocity_threshold", "0.5");
pref("apz.fling_stop_on_tap_threshold", "0.05");
pref("apz.fling_stopped_threshold", "0.01");
pref("apz.highlight_checkerboarded_areas", false);
pref("apz.keyboard.enabled", false);
pref("apz.max_velocity_inches_per_ms", "-1.0");
pref("apz.max_velocity_queue_size", 5);
pref("apz.min_skate_speed", "1.0");