diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /layout/base/gtest | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/base/gtest')
-rw-r--r-- | layout/base/gtest/TestAccessibleCaretEventHub.cpp | 785 | ||||
-rw-r--r-- | layout/base/gtest/TestAccessibleCaretManager.cpp | 848 | ||||
-rw-r--r-- | layout/base/gtest/moz.build | 26 |
3 files changed, 1659 insertions, 0 deletions
diff --git a/layout/base/gtest/TestAccessibleCaretEventHub.cpp b/layout/base/gtest/TestAccessibleCaretEventHub.cpp new file mode 100644 index 0000000000..6a3c9b61ac --- /dev/null +++ b/layout/base/gtest/TestAccessibleCaretEventHub.cpp @@ -0,0 +1,785 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include <iostream> +#include <string> + +#include "AccessibleCaretManager.h" + +#include "mozilla/AccessibleCaretEventHub.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TouchEvents.h" + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::DefaultValue; +using ::testing::Eq; +using ::testing::InSequence; +using ::testing::MockFunction; +using ::testing::Return; + +// ----------------------------------------------------------------------------- +// This file test the state transitions of AccessibleCaretEventHub under +// various combination of events and callbacks. + +namespace mozilla { + +class MockAccessibleCaretManager : public AccessibleCaretManager { + public: + MockAccessibleCaretManager() : AccessibleCaretManager(nullptr) {} + + MOCK_METHOD2(PressCaret, + nsresult(const nsPoint& aPoint, EventClassID aEventClass)); + MOCK_METHOD1(DragCaret, nsresult(const nsPoint& aPoint)); + MOCK_METHOD0(ReleaseCaret, nsresult()); + MOCK_METHOD1(TapCaret, nsresult(const nsPoint& aPoint)); + MOCK_METHOD1(SelectWordOrShortcut, nsresult(const nsPoint& aPoint)); + MOCK_METHOD0(OnScrollStart, void()); + MOCK_METHOD0(OnScrollEnd, void()); + MOCK_METHOD0(OnScrollPositionChanged, void()); + MOCK_METHOD0(OnBlur, void()); +}; + +class MockAccessibleCaretEventHub : public AccessibleCaretEventHub { + public: + using AccessibleCaretEventHub::DragCaretState; + using AccessibleCaretEventHub::LongTapState; + using AccessibleCaretEventHub::NoActionState; + using AccessibleCaretEventHub::PressCaretState; + using AccessibleCaretEventHub::PressNoCaretState; + using AccessibleCaretEventHub::ScrollState; + + MockAccessibleCaretEventHub() : AccessibleCaretEventHub(nullptr) { + mManager = MakeUnique<MockAccessibleCaretManager>(); + mInitialized = true; + } + + nsPoint GetTouchEventPosition(WidgetTouchEvent* aEvent, + int32_t aIdentifier) const override { + // Return the device point directly. + LayoutDeviceIntPoint touchIntPoint = aEvent->mTouches[0]->mRefPoint; + return nsPoint(touchIntPoint.x, touchIntPoint.y); + } + + nsPoint GetMouseEventPosition(WidgetMouseEvent* aEvent) const override { + // Return the device point directly. + LayoutDeviceIntPoint mouseIntPoint = aEvent->AsGUIEvent()->mRefPoint; + return nsPoint(mouseIntPoint.x, mouseIntPoint.y); + } + + MockAccessibleCaretManager* GetMockAccessibleCaretManager() { + return static_cast<MockAccessibleCaretManager*>(mManager.get()); + } +}; + +// Print the name of the state for debugging. +static ::std::ostream& operator<<( + ::std::ostream& aOstream, + const MockAccessibleCaretEventHub::State* aState) { + return aOstream << aState->Name(); +} + +class AccessibleCaretEventHubTester : public ::testing::Test { + public: + AccessibleCaretEventHubTester() { + DefaultValue<nsresult>::Set(NS_OK); + EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::NoActionState()); + + // AccessibleCaretEventHub requires the caller to hold a ref to it. We just + // add ref here for the sake of convenience. + mHub.get()->AddRef(); + } + + ~AccessibleCaretEventHubTester() override { + // Release the ref added in the constructor. + mHub.get()->Release(); + } + + static UniquePtr<WidgetEvent> CreateMouseEvent(EventMessage aMessage, + nscoord aX, nscoord aY) { + auto event = MakeUnique<WidgetMouseEvent>(true, aMessage, nullptr, + WidgetMouseEvent::eReal); + + event->mButton = MouseButton::ePrimary; + event->mRefPoint = LayoutDeviceIntPoint(aX, aY); + + return std::move(event); + } + + static UniquePtr<WidgetEvent> CreateMousePressEvent(nscoord aX, nscoord aY) { + return CreateMouseEvent(eMouseDown, aX, aY); + } + + static UniquePtr<WidgetEvent> CreateMouseMoveEvent(nscoord aX, nscoord aY) { + return CreateMouseEvent(eMouseMove, aX, aY); + } + + static UniquePtr<WidgetEvent> CreateMouseReleaseEvent(nscoord aX, + nscoord aY) { + return CreateMouseEvent(eMouseUp, aX, aY); + } + + static UniquePtr<WidgetEvent> CreateLongTapEvent(nscoord aX, nscoord aY) { + return CreateMouseEvent(eMouseLongTap, aX, aY); + } + + static UniquePtr<WidgetEvent> CreateTouchEvent(EventMessage aMessage, + nscoord aX, nscoord aY) { + auto event = MakeUnique<WidgetTouchEvent>(true, aMessage, nullptr); + int32_t identifier = 0; + LayoutDeviceIntPoint point(aX, aY); + LayoutDeviceIntPoint radius(19, 19); + float rotationAngle = 0; + float force = 1; + + RefPtr<dom::Touch> touch( + new dom::Touch(identifier, point, radius, rotationAngle, force)); + event->mTouches.AppendElement(touch); + + return std::move(event); + } + + static UniquePtr<WidgetEvent> CreateTouchStartEvent(nscoord aX, nscoord aY) { + return CreateTouchEvent(eTouchStart, aX, aY); + } + + static UniquePtr<WidgetEvent> CreateTouchMoveEvent(nscoord aX, nscoord aY) { + return CreateTouchEvent(eTouchMove, aX, aY); + } + + static UniquePtr<WidgetEvent> CreateTouchEndEvent(nscoord aX, nscoord aY) { + return CreateTouchEvent(eTouchEnd, aX, aY); + } + + static UniquePtr<WidgetEvent> CreateTouchCancelEvent(nscoord aX, nscoord aY) { + return CreateTouchEvent(eTouchCancel, aX, aY); + } + + static UniquePtr<WidgetEvent> CreateWheelEvent(EventMessage aMessage) { + auto event = MakeUnique<WidgetWheelEvent>(true, aMessage, nullptr); + + return std::move(event); + } + + MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestAsyncPanZoomScroll(); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + void HandleEventAndCheckState( + UniquePtr<WidgetEvent> aEvent, + MockAccessibleCaretEventHub::State* aExpectedState, + nsEventStatus aExpectedEventStatus) { + RefPtr<MockAccessibleCaretEventHub> hub(mHub); + nsEventStatus rv = hub->HandleEvent(aEvent.get()); + EXPECT_EQ(hub->GetState(), aExpectedState); + EXPECT_EQ(rv, aExpectedEventStatus); + } + + void CheckState(MockAccessibleCaretEventHub::State* aExpectedState) { + EXPECT_EQ(mHub->GetState(), aExpectedState); + } + + template <typename PressEventCreator, typename ReleaseEventCreator> + MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestPressReleaseOnNoCaret( + PressEventCreator aPressEventCreator, + ReleaseEventCreator aReleaseEventCreator); + + template <typename PressEventCreator, typename ReleaseEventCreator> + MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestPressReleaseOnCaret( + PressEventCreator aPressEventCreator, + ReleaseEventCreator aReleaseEventCreator); + + template <typename PressEventCreator, typename MoveEventCreator, + typename ReleaseEventCreator> + MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestPressMoveReleaseOnNoCaret( + PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator, + ReleaseEventCreator aReleaseEventCreator); + + template <typename PressEventCreator, typename MoveEventCreator, + typename ReleaseEventCreator> + MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestPressMoveReleaseOnCaret( + PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator, + ReleaseEventCreator aReleaseEventCreator); + + template <typename PressEventCreator, typename ReleaseEventCreator> + MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestLongTapWithSelectWordSuccessful( + PressEventCreator aPressEventCreator, + ReleaseEventCreator aReleaseEventCreator); + + template <typename PressEventCreator, typename ReleaseEventCreator> + MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestLongTapWithSelectWordFailed( + PressEventCreator aPressEventCreator, + ReleaseEventCreator aReleaseEventCreator); + + template <typename PressEventCreator, typename MoveEventCreator, + typename ReleaseEventCreator> + MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestEventDrivenAsyncPanZoomScroll( + PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator, + ReleaseEventCreator aReleaseEventCreator); + + // Member variables + RefPtr<MockAccessibleCaretEventHub> mHub{new MockAccessibleCaretEventHub()}; + +}; // class AccessibleCaretEventHubTester + +TEST_F(AccessibleCaretEventHubTester, TestMousePressReleaseOnNoCaret) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestPressReleaseOnNoCaret(CreateMousePressEvent, CreateMouseReleaseEvent); +} + +TEST_F(AccessibleCaretEventHubTester, TestTouchPressReleaseOnNoCaret) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestPressReleaseOnNoCaret(CreateTouchStartEvent, CreateTouchEndEvent); +} + +template <typename PressEventCreator, typename ReleaseEventCreator> +void AccessibleCaretEventHubTester::TestPressReleaseOnNoCaret( + PressEventCreator aPressEventCreator, + ReleaseEventCreator aReleaseEventCreator) { + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _)) + .WillOnce(Return(NS_ERROR_FAILURE)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret()).Times(0); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), TapCaret(_)).Times(0); + + HandleEventAndCheckState(aPressEventCreator(0, 0), + MockAccessibleCaretEventHub::PressNoCaretState(), + nsEventStatus_eIgnore); + + HandleEventAndCheckState(aReleaseEventCreator(0, 0), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eIgnore); +} + +TEST_F(AccessibleCaretEventHubTester, TestMousePressReleaseOnCaret) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestPressReleaseOnCaret(CreateMousePressEvent, CreateMouseReleaseEvent); +} + +TEST_F(AccessibleCaretEventHubTester, TestTouchPressReleaseOnCaret) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestPressReleaseOnCaret(CreateTouchStartEvent, CreateTouchEndEvent); +} + +template <typename PressEventCreator, typename ReleaseEventCreator> +void AccessibleCaretEventHubTester::TestPressReleaseOnCaret( + PressEventCreator aPressEventCreator, + ReleaseEventCreator aReleaseEventCreator) { + { + InSequence dummy; + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _)) + .WillOnce(Return(NS_OK)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_)) + .Times(0); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), TapCaret(_)); + } + + HandleEventAndCheckState(aPressEventCreator(0, 0), + MockAccessibleCaretEventHub::PressCaretState(), + nsEventStatus_eConsumeNoDefault); + + HandleEventAndCheckState(CreateLongTapEvent(0, 0), + MockAccessibleCaretEventHub::PressCaretState(), + nsEventStatus_eConsumeNoDefault); + + HandleEventAndCheckState(aReleaseEventCreator(0, 0), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eConsumeNoDefault); +} + +TEST_F(AccessibleCaretEventHubTester, TestMousePressMoveReleaseOnNoCaret) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestPressMoveReleaseOnNoCaret(CreateMousePressEvent, CreateMouseMoveEvent, + CreateMouseReleaseEvent); +} + +TEST_F(AccessibleCaretEventHubTester, TestTouchPressMoveReleaseOnNoCaret) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestPressMoveReleaseOnNoCaret(CreateTouchStartEvent, CreateTouchMoveEvent, + CreateTouchEndEvent); +} + +template <typename PressEventCreator, typename MoveEventCreator, + typename ReleaseEventCreator> +void AccessibleCaretEventHubTester::TestPressMoveReleaseOnNoCaret( + PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator, + ReleaseEventCreator aReleaseEventCreator) { + nscoord x0 = 0, y0 = 0; + nscoord x1 = 100, y1 = 100; + nscoord x2 = 300, y2 = 300; + nscoord x3 = 400, y3 = 400; + + { + InSequence dummy; + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _)) + .WillOnce(Return(NS_ERROR_FAILURE)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)).Times(0); + } + + HandleEventAndCheckState(aPressEventCreator(x0, y0), + MockAccessibleCaretEventHub::PressNoCaretState(), + nsEventStatus_eIgnore); + + // A small move with the distance between (x0, y0) and (x1, y1) below the + // tolerance value. + HandleEventAndCheckState(aMoveEventCreator(x1, y1), + MockAccessibleCaretEventHub::PressNoCaretState(), + nsEventStatus_eIgnore); + + // A large move to simulate a dragging to select text since the distance + // between (x0, y0) and (x2, y2) is above the tolerance value. + HandleEventAndCheckState(aMoveEventCreator(x2, y2), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eIgnore); + + HandleEventAndCheckState(aReleaseEventCreator(x3, y3), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eIgnore); +} + +TEST_F(AccessibleCaretEventHubTester, TestMousePressMoveReleaseOnCaret) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestPressMoveReleaseOnCaret(CreateMousePressEvent, CreateMouseMoveEvent, + CreateMouseReleaseEvent); +} + +TEST_F(AccessibleCaretEventHubTester, TestTouchPressMoveReleaseOnCaret) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestPressMoveReleaseOnCaret(CreateTouchStartEvent, CreateTouchMoveEvent, + CreateTouchEndEvent); +} + +template <typename PressEventCreator, typename MoveEventCreator, + typename ReleaseEventCreator> +void AccessibleCaretEventHubTester::TestPressMoveReleaseOnCaret( + PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator, + ReleaseEventCreator aReleaseEventCreator) { + nscoord x0 = 0, y0 = 0; + nscoord x1 = 100, y1 = 100; + nscoord x2 = 300, y2 = 300; + nscoord x3 = 400, y3 = 400; + + { + InSequence dummy; + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _)) + .WillOnce(Return(NS_OK)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)) + .Times(2) // two valid drag operations + .WillRepeatedly(Return(NS_OK)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret()) + .WillOnce(Return(NS_OK)); + } + + HandleEventAndCheckState(aPressEventCreator(x0, y0), + MockAccessibleCaretEventHub::PressCaretState(), + nsEventStatus_eConsumeNoDefault); + + // A small move with the distance between (x0, y0) and (x1, y1) below the + // tolerance value. + HandleEventAndCheckState(aMoveEventCreator(x1, y1), + MockAccessibleCaretEventHub::PressCaretState(), + nsEventStatus_eConsumeNoDefault); + + // A large move forms a valid drag since the distance between (x0, y0) and + // (x2, y2) is above the tolerance value. + HandleEventAndCheckState(aMoveEventCreator(x2, y2), + MockAccessibleCaretEventHub::DragCaretState(), + nsEventStatus_eConsumeNoDefault); + + // Also a valid drag since the distance between (x0, y0) and (x3, y3) above + // the tolerance value even if the distance between (x2, y2) and (x3, y3) is + // below the tolerance value. + HandleEventAndCheckState(aMoveEventCreator(x3, y3), + MockAccessibleCaretEventHub::DragCaretState(), + nsEventStatus_eConsumeNoDefault); + + HandleEventAndCheckState(aReleaseEventCreator(x3, y3), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eConsumeNoDefault); +} + +TEST_F(AccessibleCaretEventHubTester, + TestTouchStartMoveEndOnCaretWithTouchCancelIgnored) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + nscoord x0 = 0, y0 = 0; + nscoord x1 = 100, y1 = 100; + nscoord x2 = 300, y2 = 300; + nscoord x3 = 400, y3 = 400; + + { + InSequence dummy; + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _)) + .WillOnce(Return(NS_OK)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)) + .WillOnce(Return(NS_OK)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret()) + .WillOnce(Return(NS_OK)); + } + + // All the eTouchCancel events should be ignored in this test. + + HandleEventAndCheckState(CreateTouchStartEvent(x0, y0), + MockAccessibleCaretEventHub::PressCaretState(), + nsEventStatus_eConsumeNoDefault); + + HandleEventAndCheckState(CreateTouchCancelEvent(x0, y0), + MockAccessibleCaretEventHub::PressCaretState(), + nsEventStatus_eIgnore); + + // A small move with the distance between (x0, y0) and (x1, y1) below the + // tolerance value. + HandleEventAndCheckState(CreateTouchMoveEvent(x1, y1), + MockAccessibleCaretEventHub::PressCaretState(), + nsEventStatus_eConsumeNoDefault); + + HandleEventAndCheckState(CreateTouchCancelEvent(x1, y1), + MockAccessibleCaretEventHub::PressCaretState(), + nsEventStatus_eIgnore); + + // A large move forms a valid drag since the distance between (x0, y0) and + // (x2, y2) is above the tolerance value. + HandleEventAndCheckState(CreateTouchMoveEvent(x2, y2), + MockAccessibleCaretEventHub::DragCaretState(), + nsEventStatus_eConsumeNoDefault); + + HandleEventAndCheckState(CreateTouchCancelEvent(x2, y2), + MockAccessibleCaretEventHub::DragCaretState(), + nsEventStatus_eIgnore); + + HandleEventAndCheckState(CreateTouchEndEvent(x3, y3), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eConsumeNoDefault); + + HandleEventAndCheckState(CreateTouchCancelEvent(x3, y3), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eIgnore); +} + +TEST_F(AccessibleCaretEventHubTester, TestMouseLongTapWithSelectWordSuccessful) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestLongTapWithSelectWordSuccessful(CreateMousePressEvent, + CreateMouseReleaseEvent); +} + +TEST_F(AccessibleCaretEventHubTester, TestTouchLongTapWithSelectWordSuccessful) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestLongTapWithSelectWordSuccessful(CreateTouchStartEvent, + CreateTouchEndEvent); +} + +template <typename PressEventCreator, typename ReleaseEventCreator> +void AccessibleCaretEventHubTester::TestLongTapWithSelectWordSuccessful( + PressEventCreator aPressEventCreator, + ReleaseEventCreator aReleaseEventCreator) { + MockFunction<void(::std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _)) + .WillOnce(Return(NS_ERROR_FAILURE)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_)) + .WillOnce(Return(NS_OK)); + + EXPECT_CALL(check, Call("longtap with scrolling")); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _)) + .WillOnce(Return(NS_ERROR_FAILURE)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_)) + .WillOnce(Return(NS_OK)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd()); + } + + // Test long tap without scrolling. + HandleEventAndCheckState(aPressEventCreator(0, 0), + MockAccessibleCaretEventHub::PressNoCaretState(), + nsEventStatus_eIgnore); + + HandleEventAndCheckState(CreateLongTapEvent(0, 0), + MockAccessibleCaretEventHub::LongTapState(), + nsEventStatus_eIgnore); + + HandleEventAndCheckState(aReleaseEventCreator(0, 0), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eIgnore); + + // On Fennec, after long tap, the script might scroll and zoom the input field + // to the center of the screen to make typing easier before the user lifts the + // finger. + check.Call("longtap with scrolling"); + + HandleEventAndCheckState(aPressEventCreator(1, 1), + MockAccessibleCaretEventHub::PressNoCaretState(), + nsEventStatus_eIgnore); + + HandleEventAndCheckState(CreateLongTapEvent(1, 1), + MockAccessibleCaretEventHub::LongTapState(), + nsEventStatus_eIgnore); + + RefPtr<MockAccessibleCaretEventHub> hub(mHub); + hub->AsyncPanZoomStarted(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->ScrollPositionChanged(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->AsyncPanZoomStopped(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState()); + + HandleEventAndCheckState(aReleaseEventCreator(1, 1), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eIgnore); +} + +TEST_F(AccessibleCaretEventHubTester, TestMouseLongTapWithSelectWordFailed) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestLongTapWithSelectWordFailed(CreateMousePressEvent, + CreateMouseReleaseEvent); +} + +TEST_F(AccessibleCaretEventHubTester, TestTouchLongTapWithSelectWordFailed) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestLongTapWithSelectWordFailed(CreateTouchStartEvent, CreateTouchEndEvent); +} + +template <typename PressEventCreator, typename ReleaseEventCreator> +void AccessibleCaretEventHubTester::TestLongTapWithSelectWordFailed( + PressEventCreator aPressEventCreator, + ReleaseEventCreator aReleaseEventCreator) { + { + InSequence dummy; + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _)) + .WillOnce(Return(NS_ERROR_FAILURE)); + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_)) + .WillOnce(Return(NS_ERROR_FAILURE)); + } + + HandleEventAndCheckState(aPressEventCreator(0, 0), + MockAccessibleCaretEventHub::PressNoCaretState(), + nsEventStatus_eIgnore); + + HandleEventAndCheckState(CreateLongTapEvent(0, 0), + MockAccessibleCaretEventHub::LongTapState(), + nsEventStatus_eIgnore); + + HandleEventAndCheckState(aReleaseEventCreator(0, 0), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eIgnore); +} + +TEST_F(AccessibleCaretEventHubTester, TestTouchEventDrivenAsyncPanZoomScroll) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestEventDrivenAsyncPanZoomScroll(CreateTouchStartEvent, CreateTouchMoveEvent, + CreateTouchEndEvent); +} + +TEST_F(AccessibleCaretEventHubTester, TestMouseEventDrivenAsyncPanZoomScroll) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + TestEventDrivenAsyncPanZoomScroll(CreateMousePressEvent, CreateMouseMoveEvent, + CreateMouseReleaseEvent); +} + +template <typename PressEventCreator, typename MoveEventCreator, + typename ReleaseEventCreator> +void AccessibleCaretEventHubTester::TestEventDrivenAsyncPanZoomScroll( + PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator, + ReleaseEventCreator aReleaseEventCreator) { + MockFunction<void(::std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _)) + .WillOnce(Return(NS_ERROR_FAILURE)); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)).Times(0); + + EXPECT_CALL(check, Call("1")); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd()); + + EXPECT_CALL(check, Call("2")); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _)) + .WillOnce(Return(NS_ERROR_FAILURE)); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)).Times(0); + + EXPECT_CALL(check, Call("3")); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd()); + } + + // Receive press event. + HandleEventAndCheckState(aPressEventCreator(0, 0), + MockAccessibleCaretEventHub::PressNoCaretState(), + nsEventStatus_eIgnore); + + HandleEventAndCheckState(aMoveEventCreator(100, 100), + MockAccessibleCaretEventHub::PressNoCaretState(), + nsEventStatus_eIgnore); + + check.Call("1"); + + // Event driven scroll started + RefPtr<MockAccessibleCaretEventHub> hub(mHub); + hub->AsyncPanZoomStarted(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + HandleEventAndCheckState(aMoveEventCreator(160, 160), + MockAccessibleCaretEventHub::ScrollState(), + nsEventStatus_eIgnore); + + hub->ScrollPositionChanged(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + // Event driven scroll ended + hub->AsyncPanZoomStopped(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState()); + + HandleEventAndCheckState(aReleaseEventCreator(210, 210), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eIgnore); + + check.Call("2"); + + // Receive another press event. + HandleEventAndCheckState(aPressEventCreator(220, 220), + MockAccessibleCaretEventHub::PressNoCaretState(), + nsEventStatus_eIgnore); + + HandleEventAndCheckState(aMoveEventCreator(230, 230), + MockAccessibleCaretEventHub::PressNoCaretState(), + nsEventStatus_eIgnore); + + check.Call("3"); + + // Another APZ scroll started + hub->AsyncPanZoomStarted(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->ScrollPositionChanged(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + // Another APZ scroll ended + hub->AsyncPanZoomStopped(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState()); + + HandleEventAndCheckState(aReleaseEventCreator(310, 310), + MockAccessibleCaretEventHub::NoActionState(), + nsEventStatus_eIgnore); +} + +TEST_F(AccessibleCaretEventHubTester, TestAsyncPanZoomScroll) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { TestAsyncPanZoomScroll(); } + +void AccessibleCaretEventHubTester::TestAsyncPanZoomScroll() { + MockFunction<void(::std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(check, Call("1")); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), + OnScrollPositionChanged()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd()); + + EXPECT_CALL(check, Call("2")); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), + OnScrollPositionChanged()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd()); + } + + // First APZ scrolling. + check.Call("1"); + + RefPtr<MockAccessibleCaretEventHub> hub(mHub); + hub->AsyncPanZoomStarted(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->ScrollPositionChanged(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->AsyncPanZoomStopped(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState()); + + // Second APZ scrolling. + check.Call("2"); + + hub->AsyncPanZoomStarted(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->ScrollPositionChanged(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->AsyncPanZoomStopped(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState()); +} + +TEST_F(AccessibleCaretEventHubTester, TestAsyncPanZoomScrollStartedThenBlur) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + { + InSequence dummy; + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd()).Times(0); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnBlur()); + } + + RefPtr<MockAccessibleCaretEventHub> hub(mHub); + hub->AsyncPanZoomStarted(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->ScrollPositionChanged(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->NotifyBlur(true); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState()); +} + +TEST_F(AccessibleCaretEventHubTester, TestAsyncPanZoomScrollEndedThenBlur) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + { + InSequence dummy; + + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd()); + EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnBlur()); + } + + RefPtr<MockAccessibleCaretEventHub> hub(mHub); + hub->AsyncPanZoomStarted(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->ScrollPositionChanged(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState()); + + hub->AsyncPanZoomStopped(); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState()); + + hub->NotifyBlur(true); + EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState()); +} + +} // namespace mozilla diff --git a/layout/base/gtest/TestAccessibleCaretManager.cpp b/layout/base/gtest/TestAccessibleCaretManager.cpp new file mode 100644 index 0000000000..464ad68fb0 --- /dev/null +++ b/layout/base/gtest/TestAccessibleCaretManager.cpp @@ -0,0 +1,848 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include <string> + +#include "AccessibleCaret.h" +#include "AccessibleCaretManager.h" +#include "mozilla/Preferences.h" + +using ::testing::_; +using ::testing::DefaultValue; +using ::testing::Eq; +using ::testing::InSequence; +using ::testing::MockFunction; +using ::testing::Return; + +// ----------------------------------------------------------------------------- +// This file tests CaretStateChanged events and the appearance of the two +// AccessibleCarets manipulated by AccessibleCaretManager. + +namespace mozilla { +using dom::CaretChangedReason; + +class MOZ_RAII AutoRestoreBoolPref final { + public: + AutoRestoreBoolPref(const char* aPref, bool aValue) : mPref(aPref) { + Preferences::GetBool(mPref, &mOldValue); + Preferences::SetBool(mPref, aValue); + } + + ~AutoRestoreBoolPref() { Preferences::SetBool(mPref, mOldValue); } + + private: + const char* mPref = nullptr; + bool mOldValue = false; +}; + +class AccessibleCaretManagerTester : public ::testing::Test { + public: + class MockAccessibleCaret : public AccessibleCaret { + public: + MockAccessibleCaret() : AccessibleCaret(nullptr) {} + + void SetAppearance(Appearance aAppearance) override { + // A simplified version without touching CaretElement(). + mAppearance = aAppearance; + } + + MOCK_METHOD2(SetPosition, + PositionChangedResult(nsIFrame* aFrame, int32_t aOffset)); + + }; // class MockAccessibleCaret + + class MockAccessibleCaretManager : public AccessibleCaretManager { + public: + using CaretMode = AccessibleCaretManager::CaretMode; + using AccessibleCaretManager::HideCaretsAndDispatchCaretStateChangedEvent; + using AccessibleCaretManager::UpdateCarets; + + MockAccessibleCaretManager() + : AccessibleCaretManager(nullptr, + Carets{MakeUnique<MockAccessibleCaret>(), + MakeUnique<MockAccessibleCaret>()}) {} + + MockAccessibleCaret& FirstCaret() { + return static_cast<MockAccessibleCaret&>(*mCarets.GetFirst()); + } + + MockAccessibleCaret& SecondCaret() { + return static_cast<MockAccessibleCaret&>(*mCarets.GetSecond()); + } + + bool CompareTreePosition(nsIFrame* aStartFrame, + nsIFrame* aEndFrame) const override { + return true; + } + + bool IsCaretDisplayableInCursorMode( + nsIFrame** aOutFrame = nullptr, + int32_t* aOutOffset = nullptr) const override { + return true; + } + + bool UpdateCaretsForOverlappingTilt() override { return true; } + + void UpdateCaretsForAlwaysTilt(const nsIFrame* aStartFrame, + const nsIFrame* aEndFrame) override { + if (mCarets.GetFirst()->IsVisuallyVisible()) { + mCarets.GetFirst()->SetAppearance(Appearance::Left); + } + if (mCarets.GetSecond()->IsVisuallyVisible()) { + mCarets.GetSecond()->SetAppearance(Appearance::Right); + } + } + + Terminated IsTerminated() const override { return Terminated::No; } + bool IsScrollStarted() const { return mIsScrollStarted; } + + Terminated MaybeFlushLayout() override { return Terminated::No; } + + MOCK_CONST_METHOD0(GetCaretMode, CaretMode()); + MOCK_METHOD2(DispatchCaretStateChangedEvent, + void(CaretChangedReason aReason, const nsPoint* aPoint)); + MOCK_CONST_METHOD1(HasNonEmptyTextContent, bool(nsINode* aNode)); + + }; // class MockAccessibleCaretManager + + using Appearance = AccessibleCaret::Appearance; + using PositionChangedResult = AccessibleCaret::PositionChangedResult; + using CaretMode = MockAccessibleCaretManager::CaretMode; + + AccessibleCaretManagerTester() { + DefaultValue<CaretMode>::Set(CaretMode::None); + DefaultValue<PositionChangedResult>::Set(PositionChangedResult::NotChanged); + + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillRepeatedly(Return(PositionChangedResult::Position)); + + EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _)) + .WillRepeatedly(Return(PositionChangedResult::Position)); + } + + AccessibleCaret::Appearance FirstCaretAppearance() { + return mManager.FirstCaret().GetAppearance(); + } + + AccessibleCaret::Appearance SecondCaretAppearance() { + return mManager.SecondCaret().GetAppearance(); + } + + // Member variables + MockAccessibleCaretManager mManager; + +}; // class AccessibleCaretManagerTester + +TEST_F(AccessibleCaretManagerTester, TestUpdatesInSelectionMode) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + // Set default preference. + AutoRestoreBoolPref savedPref("layout.accessiblecaret.always_tilt", false); + + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Selection)); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(3); + + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal); + + mManager.OnReflow(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal); + + mManager.OnScrollPositionChanged(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal); +} + +TEST_F(AccessibleCaretManagerTester, TestSingleTapOnNonEmptyInput) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Cursor)); + + EXPECT_CALL(mManager, HasNonEmptyTextContent(_)).WillRepeatedly(Return(true)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("update")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Visibilitychange, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("mouse down")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0); + EXPECT_CALL(check, Call("reflow")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0); + EXPECT_CALL(check, Call("blur")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("mouse up")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("reflow2")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + } + + // Simulate a single tap on a non-empty input. + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("update"); + + mManager.OnSelectionChanged(nullptr, nullptr, + nsISelectionListener::DRAG_REASON | + nsISelectionListener::MOUSEDOWN_REASON); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("mouse down"); + + mManager.OnReflow(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("reflow"); + + mManager.OnBlur(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("blur"); + + mManager.OnSelectionChanged(nullptr, nullptr, + nsISelectionListener::MOUSEUP_REASON); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("mouse up"); + + mManager.OnReflow(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("reflow2"); + + mManager.OnScrollPositionChanged(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); +} + +TEST_F(AccessibleCaretManagerTester, TestSingleTapOnEmptyInput) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + // Set default preference. + AutoRestoreBoolPref savedPref( + "layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content", + false); + + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Cursor)); + + EXPECT_CALL(mManager, HasNonEmptyTextContent(_)) + .WillRepeatedly(Return(false)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("update")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Visibilitychange, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("mouse down")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0); + EXPECT_CALL(check, Call("reflow")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0); + EXPECT_CALL(check, Call("blur")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("mouse up")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("reflow2")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + } + + // Simulate a single tap on an empty input. + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("update"); + + mManager.OnSelectionChanged(nullptr, nullptr, + nsISelectionListener::DRAG_REASON | + nsISelectionListener::MOUSEDOWN_REASON); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("mouse down"); + + mManager.OnReflow(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("reflow"); + + mManager.OnBlur(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("blur"); + + mManager.OnSelectionChanged(nullptr, nullptr, + nsISelectionListener::MOUSEUP_REASON); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("mouse up"); + + mManager.OnReflow(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("reflow2"); + + mManager.OnScrollPositionChanged(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); +} + +TEST_F(AccessibleCaretManagerTester, TestTypingAtEndOfInput) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Cursor)); + + EXPECT_CALL(mManager, HasNonEmptyTextContent(_)).WillRepeatedly(Return(true)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("update")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Visibilitychange, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("keyboard")); + + // No CaretStateChanged events should be dispatched since the caret has + // being hidden in cursor mode. + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0); + } + + // Simulate typing the end of the input. + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("update"); + + mManager.OnKeyboardEvent(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("keyboard"); + + mManager.OnSelectionChanged(nullptr, nullptr, + nsISelectionListener::NO_REASON); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + + mManager.OnScrollPositionChanged(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); +} + +TEST_F(AccessibleCaretManagerTester, TestScrollInSelectionMode) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + // Set default preference. + AutoRestoreBoolPref savedPref("layout.accessiblecaret.always_tilt", false); + + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Selection)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + // Initially, first caret is out of scrollport, and second caret is visible. + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillOnce(Return(PositionChangedResult::Invisible)); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("updatecarets")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)); + EXPECT_CALL(check, Call("scrollstart1")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("reflow1")); + + // After scroll ended, first caret is visible and second caret is out of + // scroll port. + EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _)) + .WillOnce(Return(PositionChangedResult::Invisible)); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("scrollend1")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)); + EXPECT_CALL(check, Call("scrollstart2")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("reflow2")); + + // After the scroll ended, both carets are visible. + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("scrollend2")); + } + + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal); + check.Call("updatecarets"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal); + check.Call("scrollstart1"); + + mManager.OnReflow(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal); + check.Call("reflow1"); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollend1"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollstart2"); + + mManager.OnReflow(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown); + check.Call("reflow2"); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal); + check.Call("scrollend2"); +} + +TEST_F(AccessibleCaretManagerTester, + TestScrollInSelectionModeWithAlwaysTiltPref) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + // Simulate Firefox Android preference. + AutoRestoreBoolPref savedPref("layout.accessiblecaret.always_tilt", true); + + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Selection)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + // Initially, first caret is out of scrollport, and second caret is visible. + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillOnce(Return(PositionChangedResult::Invisible)); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("updatecarets")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)); + EXPECT_CALL(check, Call("scrollstart1")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0); + EXPECT_CALL(check, Call("scrollPositionChanged1")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("reflow1")); + + // After scroll ended, first caret is visible and second caret is out of + // scroll port. + EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _)) + .WillOnce(Return(PositionChangedResult::Invisible)); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("scrollend1")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)); + EXPECT_CALL(check, Call("scrollstart2")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0); + EXPECT_CALL(check, Call("scrollPositionChanged2")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("reflow2")); + + // After the scroll ended, both carets are visible. + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("scrollend2")); + } + + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Right); + check.Call("updatecarets"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Right); + check.Call("scrollstart1"); + + mManager.OnScrollPositionChanged(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Right); + check.Call("scrollPositionChanged1"); + + mManager.OnReflow(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Right); + check.Call("reflow1"); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Left); + EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollend1"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Left); + EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollstart2"); + + mManager.OnScrollPositionChanged(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Left); + EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollPositionChanged2"); + + mManager.OnReflow(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Left); + EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown); + check.Call("reflow2"); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Left); + EXPECT_EQ(SecondCaretAppearance(), Appearance::Right); + check.Call("scrollend2"); +} + +TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenLogicallyVisible) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Cursor)); + + EXPECT_CALL(mManager, HasNonEmptyTextContent(_)).WillRepeatedly(Return(true)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("updatecarets")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("scrollstart1")); + + // After scroll ended, the caret is out of scroll port. + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillRepeatedly(Return(PositionChangedResult::Invisible)); + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("scrollend1")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("scrollstart2")); + + // After scroll ended, the caret is visible again. + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillRepeatedly(Return(PositionChangedResult::Position)); + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("scrollend2")); + } + + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("updatecarets"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("scrollstart1"); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollend1"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollstart2"); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("scrollend2"); +} + +TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenHidden) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Cursor)); + + EXPECT_CALL(mManager, HasNonEmptyTextContent(_)).WillRepeatedly(Return(true)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("updatecarets")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Visibilitychange, nullptr)) + .Times(1); + EXPECT_CALL(check, Call("hidecarets")); + + // After scroll ended, the caret is out of scroll port. + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillRepeatedly(Return(PositionChangedResult::Invisible)); + EXPECT_CALL(check, Call("scrollend1")); + + // After scroll ended, the caret is visible again. + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillRepeatedly(Return(PositionChangedResult::Position)); + EXPECT_CALL(check, Call("scrollend2")); + } + + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("updatecarets"); + + mManager.HideCaretsAndDispatchCaretStateChangedEvent(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("hidecarets"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("scrollend1"); + + mManager.OnScrollStart(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("scrollend2"); +} + +TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeOnEmptyContent) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + // Set default preference. + AutoRestoreBoolPref savedPref( + "layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content", + false); + + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Cursor)); + + EXPECT_CALL(mManager, HasNonEmptyTextContent(_)) + .WillRepeatedly(Return(false)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("updatecarets")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)); + EXPECT_CALL(check, Call("scrollstart1")); + + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillOnce(Return(PositionChangedResult::Invisible)); + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("scrollend1")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)); + EXPECT_CALL(check, Call("scrollstart2")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("scrollend2")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)); + EXPECT_CALL(check, Call("scrollstart3")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("scrollend3")); + } + + // Simulate a pinch-zoom operation before tapping on an empty content. + mManager.OnScrollStart(); + mManager.OnScrollEnd(); + EXPECT_EQ(mManager.IsScrollStarted(), false); + + // Simulate a single tap on an empty content. + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("updatecarets"); + + // Scroll the caret to be out of the viewport. + mManager.OnScrollStart(); + check.Call("scrollstart1"); + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollend1"); + + // Scroll the caret into the viewport. + mManager.OnScrollStart(); + check.Call("scrollstart2"); + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollend2"); + + // Scroll the caret within the viewport. + mManager.OnScrollStart(); + check.Call("scrollstart3"); + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("scrollend3"); +} + +TEST_F(AccessibleCaretManagerTester, + TestScrollInCursorModeWithCaretShownWhenLongTappingOnEmptyContentPref) +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { + // Simulate Firefox Android preference. + AutoRestoreBoolPref savedPref( + "layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content", + true); + + EXPECT_CALL(mManager, GetCaretMode()) + .WillRepeatedly(Return(CaretMode::Cursor)); + + EXPECT_CALL(mManager, HasNonEmptyTextContent(_)) + .WillRepeatedly(Return(false)); + + MockFunction<void(std::string aCheckPointName)> check; + { + InSequence dummy; + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("singletap updatecarets")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("longtap updatecarets")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)); + EXPECT_CALL(check, Call("longtap scrollstart1")); + + EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _)) + .WillOnce(Return(PositionChangedResult::Invisible)); + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("longtap scrollend1")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)); + EXPECT_CALL(check, Call("longtap scrollstart2")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("longtap scrollend2")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Scroll, nullptr)); + EXPECT_CALL(check, Call("longtap scrollstart3")); + + EXPECT_CALL(mManager, DispatchCaretStateChangedEvent( + CaretChangedReason::Updateposition, nullptr)); + EXPECT_CALL(check, Call("longtap scrollend3")); + } + + // Simulate a single tap on an empty input. + mManager.FirstCaret().SetAppearance(Appearance::None); + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + check.Call("singletap updatecarets"); + + // Scroll the caret within the viewport. + mManager.OnScrollStart(); + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::None); + + // Simulate a long tap on an empty input. + mManager.FirstCaret().SetAppearance(Appearance::Normal); + mManager.UpdateCarets(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("longtap updatecarets"); + + // Scroll the caret to be out of the viewport. + mManager.OnScrollStart(); + check.Call("longtap scrollstart1"); + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown); + check.Call("longtap scrollend1"); + + // Scroll the caret into the viewport. + mManager.OnScrollStart(); + check.Call("longtap scrollstart2"); + mManager.OnScrollPositionChanged(); + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("longtap scrollend2"); + + // Scroll the caret within the viewport. + mManager.OnScrollStart(); + check.Call("longtap scrollstart3"); + mManager.OnScrollPositionChanged(); + mManager.OnScrollEnd(); + EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal); + check.Call("longtap scrollend3"); +} + +} // namespace mozilla diff --git a/layout/base/gtest/moz.build b/layout/base/gtest/moz.build new file mode 100644 index 0000000000..b082855295 --- /dev/null +++ b/layout/base/gtest/moz.build @@ -0,0 +1,26 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +UNIFIED_SOURCES += [ + "TestAccessibleCaretEventHub.cpp", + "TestAccessibleCaretManager.cpp", +] + +# THE MOCK_METHOD2 macro from gtest triggers this clang warning and it's hard +# to work around, so we just ignore it. +if CONFIG["CC_TYPE"] == "clang": + CXXFLAGS += ["-Wno-inconsistent-missing-override"] + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/docshell/base", + "/layout/base", + "/layout/style", +] + +FINAL_LIBRARY = "xul-gtest" |