Bug 1663000 - Return UNHANDLED even if there's no event handler which uses preventDefault() and the root content is not covered by the dynamic toolbar. r=botond,geckoview-reviewers,snorp

This change has a couple of tests but they are for scrollable/non-scrollable
contents, they are not for non-scrollable contents but covered by the dynamic
toolbar cases, e.g. 100vh body element.  Those cases will be introduced in the
subsequent change.

Differential Revision: https://phabricator.services.mozilla.com/D93915
This commit is contained in:
Hiroyuki Ikezoe 2020-11-04 03:16:07 +00:00
Родитель ea157cca7c
Коммит 9a544e618f
9 изменённых файлов: 133 добавлений и 24 удалений

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

@ -20,6 +20,18 @@ namespace layers {
class APZInputBridgeParent;
struct ScrollableLayerGuid;
enum class APZHandledResult : uint8_t {
Unhandled = 0, // we know for sure that the event will not be handled
// by either the root APZC or others
HandledByRoot = 1, // we know for sure that the event will be handled
// by the root content APZC
HandledByContent = 2, // we know for sure it will be handled by a non-root
// APZC or by an event listener using preventDefault()
// in a document
Invalid = 3,
Last = Invalid
};
/**
* Represents the outcome of APZ receiving and processing an input event.
* This is returned from APZInputBridge::ReceiveInputEvent() and related APIs.
@ -65,6 +77,7 @@ struct APZEventResult {
* by the root content APZC;
* - set to false if we know for sure it will not be;
* - left empty if we are unsure.
* FIXME: Bug 1674694 - We should use APZHandledResult here.
*/
Maybe<bool> mHandledByRootApzc;
/**
@ -145,6 +158,9 @@ class APZInputBridge {
virtual ~APZInputBridge() = default;
};
std::ostream& operator<<(std::ostream& aOut,
const APZHandledResult& aHandledResult);
} // namespace layers
} // namespace mozilla

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

@ -39,6 +39,7 @@ enum ZoomToRectBehavior : uint32_t {
};
class AsyncDragMetrics;
enum class APZHandledResult : uint8_t;
class IAPZCTreeManager {
NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(IAPZCTreeManager)
@ -147,8 +148,8 @@ class IAPZCTreeManager {
* This is only implemented when the caller is in the same process as
* the APZCTreeManager.
*/
using InputBlockCallback =
std::function<void(uint64_t aInputBlockId, bool aHandledByRootApzc)>;
using InputBlockCallback = std::function<void(
uint64_t aInputBlockId, APZHandledResult aHandledResult)>;
virtual void AddInputBlockCallback(uint64_t aInputBlockId,
InputBlockCallback&& aCallback) = 0;

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

@ -189,5 +189,27 @@ APZEventResult APZInputBridge::ReceiveInputEvent(WidgetInputEvent& aEvent) {
return result;
}
std::ostream& operator<<(std::ostream& aOut,
const APZHandledResult& aHandledResult) {
switch (aHandledResult) {
case APZHandledResult::Unhandled:
aOut << "unhandled";
break;
case APZHandledResult::HandledByRoot: {
aOut << "handled-by-root";
break;
}
case APZHandledResult::HandledByContent: {
aOut << "handled-by-content";
break;
}
case APZHandledResult::Invalid: {
aOut << "INVALID";
break;
}
}
return aOut;
}
} // namespace layers
} // namespace mozilla

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

@ -10,6 +10,7 @@
#include "GestureEventListener.h"
#include "InputBlockState.h"
#include "mozilla/layers/APZInputBridge.h"
#include "mozilla/layers/APZThreadUtils.h"
#include "mozilla/ToString.h"
#include "OverscrollHandoffState.h"
@ -854,6 +855,25 @@ void InputQueue::SetAllowedTouchBehavior(
}
}
static APZHandledResult GetHandledResultFor(
const AsyncPanZoomController* aApzc,
const InputBlockState& aCurrentInputBlock) {
if (aCurrentInputBlock.ShouldDropEvents()) {
return APZHandledResult::HandledByContent;
}
if (aApzc && aApzc->IsRootContent()) {
return aApzc->CanScrollDownwardsWithDynamicToolbar()
? APZHandledResult::HandledByRoot
: APZHandledResult::Unhandled;
}
// FIXME: We need to return `HandledByRoot` if scroll positions in all
// relevant APZCs are at the bottom edge and if there are contents covered by
// the dynamic toolbar.
return APZHandledResult::HandledByContent;
}
void InputQueue::ProcessQueue() {
APZThreadUtils::AssertOnControllerThread();
@ -875,9 +895,8 @@ void InputQueue::ProcessQueue() {
// input block, invoke it.
auto it = mInputBlockCallbacks.find(curBlock->GetBlockId());
if (it != mInputBlockCallbacks.end()) {
bool handledByRootApzc =
!curBlock->ShouldDropEvents() && target && target->IsRootContent();
it->second(curBlock->GetBlockId(), handledByRootApzc);
APZHandledResult handledResult = GetHandledResultFor(target, *curBlock);
it->second(curBlock->GetBlockId(), handledResult);
// The callback is one-shot; discard it after calling it.
mInputBlockCallbacks.erase(it);
}

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

@ -37,6 +37,7 @@ class PinchGestureBlockState;
class KeyboardBlockState;
class AsyncDragMetrics;
class QueuedInput;
enum class APZHandledResult : uint8_t;
/**
* This class stores incoming input events, associated with "input blocks",
@ -146,8 +147,8 @@ class InputQueue {
InputBlockState* GetBlockForId(uint64_t aInputBlockId);
using InputBlockCallback =
std::function<void(uint64_t aInputBlockId, bool aHandledByRootApzc)>;
using InputBlockCallback = std::function<void(
uint64_t aInputBlockId, APZHandledResult aHandledResult)>;
void AddInputBlockCallback(uint64_t aInputBlockId,
InputBlockCallback&& aCallback);

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

@ -322,7 +322,8 @@ TEST_F(APZEventRegionsTester, HandledByRootApzcFlag) {
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm,
layers);
SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID);
SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
CSSRect(0, 0, 100, 200));
ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) {
metrics.SetIsRootContent(true);
});
@ -350,11 +351,11 @@ TEST_F(APZEventRegionsTester, HandledByRootApzcFlag) {
// Register an input block callback that will tell us the
// delayed answer.
Maybe<bool> delayedAnswer;
APZHandledResult delayedAnswer = APZHandledResult::Invalid;
manager->AddInputBlockCallback(result.mInputBlockId,
[&](uint64_t id, bool answer) {
[&](uint64_t id, APZHandledResult answer) {
EXPECT_EQ(id, result.mInputBlockId);
delayedAnswer = Some(answer);
delayedAnswer = answer;
});
// Send APZ the relevant notifications to allow it to process the
@ -366,22 +367,44 @@ TEST_F(APZEventRegionsTester, HandledByRootApzcFlag) {
/*preventDefault=*/false);
// Check that we received the delayed answer and it is what we expect.
EXPECT_EQ(delayedAnswer, Some(true));
EXPECT_EQ(delayedAnswer, APZHandledResult::HandledByRoot);
// Now repeat the tap on the bottom half, but simulate a prevent-default.
// This time, we expect a delayed answer of false.
// This time, we expect a delayed answer of `HandledByContent`.
result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time());
TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time());
EXPECT_EQ(result.mHandledByRootApzc, Nothing());
manager->AddInputBlockCallback(result.mInputBlockId,
[&](uint64_t id, bool answer) {
[&](uint64_t id, APZHandledResult answer) {
EXPECT_EQ(id, result.mInputBlockId);
delayedAnswer = Some(answer);
delayedAnswer = answer;
});
manager->SetAllowedTouchBehavior(result.mInputBlockId,
{AllowedTouchBehavior::VERTICAL_PAN});
manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
manager->ContentReceivedInputBlock(result.mInputBlockId,
/*preventDefault=*/true);
EXPECT_EQ(delayedAnswer, Some(false));
EXPECT_EQ(delayedAnswer, APZHandledResult::HandledByContent);
// Shrink the scrollable area, now it's no longer scrollable.
ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) {
metrics.SetScrollableRect(CSSRect(0, 0, 100, 100));
});
UpdateHitTestingTree();
// Now repeat the tap on the bottom half with an event handler.
// This time, we expect a delayed answer of `Unhandled`.
result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time());
TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time());
EXPECT_EQ(result.mHandledByRootApzc, Nothing());
manager->AddInputBlockCallback(result.mInputBlockId,
[&](uint64_t id, APZHandledResult answer) {
EXPECT_EQ(id, result.mInputBlockId);
delayedAnswer = answer;
});
manager->SetAllowedTouchBehavior(result.mInputBlockId,
{AllowedTouchBehavior::VERTICAL_PAN});
manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
manager->ContentReceivedInputBlock(result.mInputBlockId,
/*preventDefault=*/false);
EXPECT_EQ(delayedAnswer, APZHandledResult::Unhandled);
}

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

@ -17,19 +17,19 @@ body {
#one {
background-color: red;
width: 200vw;
height: 100vh;
height: 50vh;
}
#two {
background-color: green;
width: 200vw;
height: 100vh;
height: 50vh;
}
#three {
background-color: blue;
width: 200vw;
height: 100vh;
height: 200vh;
}
</style>
</head>
@ -37,5 +37,10 @@ body {
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
<script>
document.getElementById('two').addEventListener('touchstart', e => {
console.log('not preventing default');
});
</script>
</body>
</html>

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

@ -277,11 +277,18 @@ class PanZoomControllerTest : BaseSessionTest() {
// Touch handler without preventDefault
value = sessionRule.waitForResult(sendDownEvent(50f, 75f))
assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_HANDLED))
// Nothing should have done in the event handler and the content is not scrollable,
// thus the input result should be UNHANDLED, i.e. the dynamic toolbar should NOT
// move in response to the event.
assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_UNHANDLED))
// No touch handlers, with scrolling
setupScroll()
value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
value = sessionRule.waitForResult(sendDownEvent(50f, 25f))
assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_HANDLED))
// Touch handler with scrolling
value = sessionRule.waitForResult(sendDownEvent(50f, 75f))
assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_HANDLED))
}
}

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

@ -427,6 +427,22 @@ class NPZCSupport final
return result;
}
static int32_t ConvertAPZHandledResult(APZHandledResult aHandledResult) {
switch (aHandledResult) {
case APZHandledResult::Unhandled:
return INPUT_RESULT_UNHANDLED;
case APZHandledResult::HandledByRoot:
return INPUT_RESULT_HANDLED;
case APZHandledResult::HandledByContent:
return INPUT_RESULT_HANDLED_CONTENT;
case APZHandledResult::Invalid:
MOZ_ASSERT_UNREACHABLE("The handled result should NOT be Invalid");
return INPUT_RESULT_UNHANDLED;
}
MOZ_ASSERT_UNREACHABLE("Unknown handled result");
return INPUT_RESULT_UNHANDLED;
}
public:
int32_t HandleMouseEvent(int32_t aAction, int64_t aTime, int32_t aMetaState,
float aX, float aY, int buttons) {
@ -733,10 +749,9 @@ class NPZCSupport final
controller->AddInputBlockCallback(
result.mInputBlockId,
[returnResult = java::GeckoResult::GlobalRef(returnResult)](
uint64_t aInputBlockId, bool aHandledByRootApzc) {
uint64_t aInputBlockId, APZHandledResult aHandledResult) {
returnResult->Complete(java::sdk::Integer::ValueOf(
aHandledByRootApzc ? INPUT_RESULT_HANDLED
: INPUT_RESULT_HANDLED_CONTENT));
ConvertAPZHandledResult(aHandledResult)));
});
}
};