mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-03 15:26:07 +00:00
Bug 1481923 - Fire a touchcancel event on contextmenu events only if our own context menu is opened on Android. r=botond
And don't fire the touchcancel event in the case where the content calls preventDefault() for contextmenu events. This is going to be a behavior change, but Chrome actually does fire a touchcancel event only if the Chrome's context menu is opened, so this change will make our behavior match Chrome. To tell whether our own context menu is opened or not, we use mDefaultPreventedByChrome flag. Unfortunately this approach is applicable only for Android since Android is the only one platform we call preventDefault() [1] when opening context menu. [1] https://searchfox.org/mozilla-central/rev/95c41d54c3fd65d51976d5188842a69b459a7589/mobile/android/actors/ContentDelegateChild.jsm#100 Differential Revision: https://phabricator.services.mozilla.com/D115964
This commit is contained in:
parent
aafeee2434
commit
ed2b8e554f
@ -36,7 +36,8 @@ function longPressLink() {
|
||||
var eventsFired = 0;
|
||||
function recordEvent(e) {
|
||||
let target = document.getElementById("b");
|
||||
if (getPlatform() == "windows") {
|
||||
const platform = getPlatform();
|
||||
if (platform == "windows") {
|
||||
// On Windows we get a mouselongtap event once the long-tap has been detected
|
||||
// by APZ, and that's what we use as the trigger to lift the finger. That then
|
||||
// triggers the contextmenu. This matches the platform convention.
|
||||
@ -63,10 +64,10 @@ function recordEvent(e) {
|
||||
subtestDone();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// On non-Windows platforms we get a contextmenu event once the long-tap has
|
||||
// been detected. Since we prevent-default that, we don't get a mouselongtap
|
||||
// event at all, and instead get a touchcancel.
|
||||
} else if (platform != "android") {
|
||||
// On non-Windows desktop platforms we get a contextmenu event once the
|
||||
// long-tap has been detected. Since we prevent-default that, we don't get
|
||||
// a mouselongtap event at all, and instead get a touchcancel.
|
||||
switch (eventsFired) {
|
||||
case 0: is(e.type, "touchstart", "Got a touchstart"); break;
|
||||
case 1: is(e.type, "mouseover", "Got a mouseover"); break;
|
||||
@ -88,6 +89,53 @@ function recordEvent(e) {
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// On Android we get a contextmenu event once the long-tap has been
|
||||
// detected. If contextmenu opens we get a touchcancel event, and if
|
||||
// contextmenu didn't open because of preventDefault() in the content,
|
||||
// we will not get the touchcancel event.
|
||||
switch (eventsFired) {
|
||||
case 0: is(e.type, "touchstart", "Got a touchstart"); break;
|
||||
case 1: is(e.type, "mouseover", "Got a mouseover"); break;
|
||||
case 2: is(e.type, "mouseenter", "Got a mouseenter"); break;
|
||||
case 3: is(e.type, "mousemove", "Got a mousemove"); break;
|
||||
case 4: is(e.type, "contextmenu", "Got a contextmenu");
|
||||
// Do preventDefault() in this content, thus we will not get any
|
||||
// touchcancel event.
|
||||
e.preventDefault();
|
||||
synthesizeNativeTouch(target, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
|
||||
dump("Finished synthesizing touch-end, waiting for a touchend event...\n");
|
||||
});
|
||||
break;
|
||||
case 5: is(e.type, "touchend", "Got a touchend");
|
||||
// Send another long press.
|
||||
synthesizeNativeTouch(target, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
|
||||
dump("Finished synthesizing touch-start, waiting for events...\n");
|
||||
});
|
||||
break;
|
||||
case 6: is(e.type, "touchstart", "Got another touchstart"); break;
|
||||
// NOTE: In this another event case, we don't get mouseover or moveenter
|
||||
// event either since the target element hasn't been changed.
|
||||
case 7: is(e.type, "mousemove", "Got another mousemove"); break;
|
||||
case 8: is(e.type, "contextmenu", "Got another contextmenu");
|
||||
// DON'T DO preventDefault() this time, thus we should get a touchcancel
|
||||
// event.
|
||||
break;
|
||||
case 9: is(e.type, "touchcancel", "Got a touchcancel"); break;
|
||||
default: ok(false, "Got an unexpected event of type " + e.type); break;
|
||||
}
|
||||
eventsFired++;
|
||||
|
||||
if (eventsFired == 10) {
|
||||
removeMouseEventListeners(target);
|
||||
synthesizeNativeTouch(target, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
|
||||
dump("Finished synthesizing touch-end, doing an APZ flush to see if any more unexpected events come through...\n");
|
||||
promiseOnlyApzControllerFlushed().then(function() {
|
||||
dump("Done APZ flush, ending test...\n");
|
||||
subtestDone();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -545,11 +545,11 @@ nsEventStatus APZCCallbackHelper::DispatchSynthesizedMouseEvent(
|
||||
return DispatchWidgetEvent(event);
|
||||
}
|
||||
|
||||
bool APZCCallbackHelper::DispatchMouseEvent(
|
||||
PreventDefaultResult APZCCallbackHelper::DispatchMouseEvent(
|
||||
PresShell* aPresShell, const nsString& aType, const CSSPoint& aPoint,
|
||||
int32_t aButton, int32_t aClickCount, int32_t aModifiers,
|
||||
unsigned short aInputSourceArg, uint32_t aPointerId) {
|
||||
NS_ENSURE_TRUE(aPresShell, true);
|
||||
NS_ENSURE_TRUE(aPresShell, PreventDefaultResult::ByContent);
|
||||
|
||||
PreventDefaultResult preventDefaultResult;
|
||||
nsContentUtils::SendMouseEvent(
|
||||
@ -558,7 +558,7 @@ bool APZCCallbackHelper::DispatchMouseEvent(
|
||||
/* aIgnoreRootScrollFrame = */ false, 0, aInputSourceArg, aPointerId,
|
||||
false, &preventDefaultResult, false,
|
||||
/* aIsWidgetEventSynthesized = */ false);
|
||||
return preventDefaultResult != PreventDefaultResult::No;
|
||||
return preventDefaultResult;
|
||||
}
|
||||
|
||||
void APZCCallbackHelper::FireSingleTapEvent(const LayoutDevicePoint& aPoint,
|
||||
|
@ -26,6 +26,7 @@ class nsCOMPtr;
|
||||
namespace mozilla {
|
||||
|
||||
class PresShell;
|
||||
enum class PreventDefaultResult : uint8_t;
|
||||
|
||||
namespace layers {
|
||||
|
||||
@ -115,11 +116,10 @@ class APZCCallbackHelper {
|
||||
* This is a lightweight wrapper around nsContentUtils::SendMouseEvent()
|
||||
* and as such expects |aPoint| to be in layout coordinates. */
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static bool DispatchMouseEvent(PresShell* aPresShell, const nsString& aType,
|
||||
const CSSPoint& aPoint, int32_t aButton,
|
||||
int32_t aClickCount, int32_t aModifiers,
|
||||
unsigned short aInputSourceArg,
|
||||
uint32_t aPointerId);
|
||||
static PreventDefaultResult DispatchMouseEvent(
|
||||
PresShell* aPresShell, const nsString& aType, const CSSPoint& aPoint,
|
||||
int32_t aButton, int32_t aClickCount, int32_t aModifiers,
|
||||
unsigned short aInputSourceArg, uint32_t aPointerId);
|
||||
|
||||
/* Fire a single-tap event at the given point. The event is dispatched
|
||||
* via the given widget. */
|
||||
|
@ -198,11 +198,10 @@ void APZEventState::ProcessSingleTap(const CSSPoint& aPoint,
|
||||
}
|
||||
}
|
||||
|
||||
bool APZEventState::FireContextmenuEvents(PresShell* aPresShell,
|
||||
const CSSPoint& aPoint,
|
||||
const CSSToLayoutDeviceScale& aScale,
|
||||
Modifiers aModifiers,
|
||||
const nsCOMPtr<nsIWidget>& aWidget) {
|
||||
PreventDefaultResult APZEventState::FireContextmenuEvents(
|
||||
PresShell* aPresShell, const CSSPoint& aPoint,
|
||||
const CSSToLayoutDeviceScale& aScale, Modifiers aModifiers,
|
||||
const nsCOMPtr<nsIWidget>& aWidget) {
|
||||
// Suppress retargeting for mouse events generated by a long-press
|
||||
EventRetargetSuppression suppression;
|
||||
|
||||
@ -223,14 +222,15 @@ bool APZEventState::FireContextmenuEvents(PresShell* aPresShell,
|
||||
// including in JS code, so it's not trivial to change.
|
||||
CSSPoint point = CSSPoint::FromAppUnits(
|
||||
ViewportUtils::VisualToLayout(CSSPoint::ToAppUnits(aPoint), aPresShell));
|
||||
bool eventHandled = APZCCallbackHelper::DispatchMouseEvent(
|
||||
aPresShell, u"contextmenu"_ns, point, 2, 1,
|
||||
WidgetModifiersToDOMModifiers(aModifiers),
|
||||
dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH,
|
||||
0 /* Use the default value here. */);
|
||||
PreventDefaultResult preventDefaultResult =
|
||||
APZCCallbackHelper::DispatchMouseEvent(
|
||||
aPresShell, u"contextmenu"_ns, point, 2, 1,
|
||||
WidgetModifiersToDOMModifiers(aModifiers),
|
||||
dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH,
|
||||
0 /* Use the default value here. */);
|
||||
|
||||
APZES_LOG("Contextmenu event handled: %d\n", eventHandled);
|
||||
if (eventHandled) {
|
||||
APZES_LOG("Contextmenu event %s\n", ToString(preventDefaultResult).c_str());
|
||||
if (preventDefaultResult != PreventDefaultResult::No) {
|
||||
// If the contextmenu event was handled then we're showing a contextmenu,
|
||||
// and so we should remove any activation
|
||||
mActiveElementManager->ClearActivation();
|
||||
@ -240,12 +240,18 @@ bool APZEventState::FireContextmenuEvents(PresShell* aPresShell,
|
||||
nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent(
|
||||
eMouseLongTap, /*time*/ 0, aPoint * aScale, aModifiers,
|
||||
/*clickCount*/ 1, aWidget);
|
||||
eventHandled = (status == nsEventStatus_eConsumeNoDefault);
|
||||
APZES_LOG("eMouseLongTap event handled: %d\n", eventHandled);
|
||||
if (status == nsEventStatus_eConsumeNoDefault) {
|
||||
// Assuming no JS actor listens eMouseLongTap events.
|
||||
preventDefaultResult = PreventDefaultResult::ByContent;
|
||||
} else {
|
||||
preventDefaultResult = PreventDefaultResult::No;
|
||||
}
|
||||
APZES_LOG("eMouseLongTap event %s\n",
|
||||
ToString(preventDefaultResult).c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
return eventHandled;
|
||||
return preventDefaultResult;
|
||||
}
|
||||
|
||||
void APZEventState::ProcessLongTap(PresShell* aPresShell,
|
||||
@ -272,16 +278,39 @@ void APZEventState::ProcessLongTap(PresShell* aPresShell,
|
||||
eMouseLongTap, /*time*/ 0, aPoint * aScale, aModifiers, /*clickCount*/ 1,
|
||||
widget);
|
||||
|
||||
bool eventHandled = (status == nsEventStatus_eConsumeNoDefault);
|
||||
PreventDefaultResult preventDefaultResult =
|
||||
(status == nsEventStatus_eConsumeNoDefault)
|
||||
? PreventDefaultResult::ByContent
|
||||
: PreventDefaultResult::No;
|
||||
#else
|
||||
bool eventHandled =
|
||||
PreventDefaultResult preventDefaultResult =
|
||||
FireContextmenuEvents(aPresShell, aPoint, aScale, aModifiers, widget);
|
||||
#endif
|
||||
mContentReceivedInputBlockCallback(aInputBlockId, eventHandled);
|
||||
mContentReceivedInputBlockCallback(
|
||||
aInputBlockId, preventDefaultResult != PreventDefaultResult::No);
|
||||
|
||||
const bool eventHandled =
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
// On Android, GeckoView calls preventDefault() in a JSActor
|
||||
// (ContentDelegateChild.jsm) when opening context menu so that we can
|
||||
// tell whether contextmenu opens in response to the contextmenu event by
|
||||
// checking where preventDefault() got called.
|
||||
preventDefaultResult == PreventDefaultResult::ByChrome;
|
||||
#else
|
||||
// Unfortunately on desktop platforms other than Windows we can't use
|
||||
// the same approach for Android since we no longer call preventDefault()
|
||||
// since bug 1558506. So for now, we keep the current behavior that is
|
||||
// sending a touchcancel event if the contextmenu event was
|
||||
// preventDefault-ed in an event handler in the content itself.
|
||||
preventDefaultResult == PreventDefaultResult::ByContent;
|
||||
#endif
|
||||
if (eventHandled) {
|
||||
// Also send a touchcancel to content, so that listeners that might be
|
||||
// waiting for a touchend don't trigger.
|
||||
// Also send a touchcancel to content
|
||||
// a) on Android if browser's contextmenu is open
|
||||
// b) on Windows if the long tap event was consumed
|
||||
// c) on other platforms if preventDefault() was called for the contextmenu
|
||||
// event
|
||||
// so that listeners that might be waiting for a touchend don't trigger.
|
||||
WidgetTouchEvent cancelTouchEvent(true, eTouchCancel, widget.get());
|
||||
cancelTouchEvent.mModifiers = aModifiers;
|
||||
auto ldPoint = LayoutDeviceIntPoint::Round(aPoint * aScale);
|
||||
|
@ -29,6 +29,7 @@ class nsIWidget;
|
||||
namespace mozilla {
|
||||
|
||||
class PresShell;
|
||||
enum class PreventDefaultResult : uint8_t;
|
||||
|
||||
namespace layers {
|
||||
|
||||
@ -79,10 +80,10 @@ class APZEventState final {
|
||||
~APZEventState();
|
||||
bool SendPendingTouchPreventedResponse(bool aPreventDefault);
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
bool FireContextmenuEvents(PresShell* aPresShell, const CSSPoint& aPoint,
|
||||
const CSSToLayoutDeviceScale& aScale,
|
||||
Modifiers aModifiers,
|
||||
const nsCOMPtr<nsIWidget>& aWidget);
|
||||
PreventDefaultResult FireContextmenuEvents(
|
||||
PresShell* aPresShell, const CSSPoint& aPoint,
|
||||
const CSSToLayoutDeviceScale& aScale, Modifiers aModifiers,
|
||||
const nsCOMPtr<nsIWidget>& aWidget);
|
||||
already_AddRefed<nsIWidget> GetWidget() const;
|
||||
already_AddRefed<nsIContent> GetTouchRollup() const;
|
||||
bool MainThreadAgreesEventsAreConsumableByAPZ() const;
|
||||
|
Loading…
x
Reference in New Issue
Block a user