зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1844723 - Synthesizing `mouseup` during a drag session should end the session r=edgar,dom-core,webdriver-reviewers,whimboo
The WPT which was added by the previous patch (D187644) fails if it runs after `mousemove_prevent_default_action.tentative.html` because it synthesize `dragstart` with synthesizing multiple mouse events, however, `mouseup` does not ends the drag session and the following test starts with the session. The TestDriver finally runs `EventUtils`. Therefore, we can make it manage the drag session with XPCOM API. Note that we should synthesize `dragover` for `mousemove`, and `drop` if the drop is accepted. However, it requires more work, so we should do it in a separate bug. Differential Revision: https://phabricator.services.mozilla.com/D188934
This commit is contained in:
Родитель
b8f3f296ed
Коммит
b9f2cc10a7
|
@ -16,6 +16,9 @@ export const event = {};
|
|||
ChromeUtils.defineLazyGetter(lazy, "dblclickTimer", () => {
|
||||
return Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
});
|
||||
ChromeUtils.defineLazyGetter(event, "dragService", () => {
|
||||
return Cc["@mozilla.org/widget/dragservice;1"].getService(Ci.nsIDragService);
|
||||
});
|
||||
|
||||
const _eventUtils = new WeakMap();
|
||||
|
||||
|
|
|
@ -174,23 +174,44 @@ async function webdriverClickElement(el, a11y) {
|
|||
if (el.localName == "option") {
|
||||
interaction.selectOption(el);
|
||||
} else {
|
||||
// step 9
|
||||
let clicked = interaction.flushEventLoop(containerEl);
|
||||
|
||||
// Synthesize a pointerMove action.
|
||||
lazy.event.synthesizeMouseAtPoint(
|
||||
clickPoint.x,
|
||||
clickPoint.y,
|
||||
{
|
||||
type: "mousemove",
|
||||
allowToHandleDragDrop: true,
|
||||
},
|
||||
win
|
||||
);
|
||||
|
||||
// Synthesize a pointerDown + pointerUp action.
|
||||
lazy.event.synthesizeMouseAtPoint(clickPoint.x, clickPoint.y, {}, win);
|
||||
if (lazy.event.dragService.getCurrentSession()) {
|
||||
// Special handling is required if the mousemove started a drag session.
|
||||
// In this case, mousedown event shouldn't be fired, and the mouseup should
|
||||
// end the session. Therefore, we should synthesize only mouseup.
|
||||
lazy.event.synthesizeMouseAtPoint(
|
||||
clickPoint.x,
|
||||
clickPoint.y,
|
||||
{
|
||||
type: "mouseup",
|
||||
allowToHandleDragDrop: true,
|
||||
},
|
||||
win
|
||||
);
|
||||
} else {
|
||||
// step 9
|
||||
let clicked = interaction.flushEventLoop(containerEl);
|
||||
|
||||
await clicked;
|
||||
// Synthesize a pointerDown + pointerUp action.
|
||||
lazy.event.synthesizeMouseAtPoint(
|
||||
clickPoint.x,
|
||||
clickPoint.y,
|
||||
{ allowToHandleDragDrop: true },
|
||||
win
|
||||
);
|
||||
|
||||
await clicked;
|
||||
}
|
||||
}
|
||||
|
||||
// step 10
|
||||
|
|
|
@ -2172,6 +2172,12 @@ class MouseEventData extends PointerEventData {
|
|||
|
||||
this.button = button;
|
||||
this.buttons = 0;
|
||||
|
||||
// Some WPTs try to synthesize DnD only with mouse events. However,
|
||||
// Gecko waits DnD events directly and non-WPT-tests use Gecko specific
|
||||
// test API to synthesize DnD. Therefore, we want new path only for
|
||||
// synthesized events coming from the webdriver.
|
||||
this.allowToHandleDragDrop = true;
|
||||
}
|
||||
|
||||
update(state, inputSource) {
|
||||
|
|
|
@ -117,6 +117,16 @@ function _EU_maybeWrap(o) {
|
|||
}
|
||||
|
||||
function _EU_maybeUnwrap(o) {
|
||||
var haveWrap = false;
|
||||
try {
|
||||
haveWrap = SpecialPowers.unwrap != undefined;
|
||||
} catch (e) {
|
||||
// Just leave it false.
|
||||
}
|
||||
if (!haveWrap) {
|
||||
// Not much we can do here.
|
||||
return o;
|
||||
}
|
||||
var c = Object.getOwnPropertyDescriptor(window, "Components");
|
||||
return c && c.value && !c.writable ? o : SpecialPowers.unwrap(o);
|
||||
}
|
||||
|
@ -569,6 +579,77 @@ function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) {
|
|||
);
|
||||
}
|
||||
|
||||
function getDragService() {
|
||||
return _EU_Cc["@mozilla.org/widget/dragservice;1"].getService(
|
||||
_EU_Ci.nsIDragService
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* End drag session if there is.
|
||||
*
|
||||
* TODO: This should synthesize "drop" if necessary.
|
||||
*
|
||||
* @param left X offset in the viewport
|
||||
* @param top Y offset in the viewport
|
||||
* @param aEvent The event data, the modifiers are applied to the
|
||||
* "dragend" event.
|
||||
* @param aWindow The window.
|
||||
* @return true if handled. In this case, the caller should not
|
||||
* synthesize DOM events basically.
|
||||
*/
|
||||
function _maybeEndDragSession(left, top, aEvent, aWindow) {
|
||||
const dragService = getDragService();
|
||||
const dragSession = dragService?.getCurrentSession();
|
||||
if (!dragSession) {
|
||||
return false;
|
||||
}
|
||||
// FIXME: If dragSession.dragAction is not
|
||||
// nsIDragService.DRAGDROP_ACTION_NONE nor aEvent.type is not `keydown`, we
|
||||
// need to synthesize a "drop" event or call setDragEndPointForTests here to
|
||||
// set proper left/top to `dragend` event.
|
||||
try {
|
||||
dragService.endDragSession(false, _parseModifiers(aEvent, aWindow));
|
||||
} catch (e) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
function _maybeSynthesizeDragOver(left, top, aEvent, aWindow) {
|
||||
const dragSession = getDragService()?.getCurrentSession();
|
||||
if (!dragSession) {
|
||||
return false;
|
||||
}
|
||||
const target = aWindow.document.elementFromPoint(left, top);
|
||||
if (target) {
|
||||
sendDragEvent(
|
||||
createDragEventObject(
|
||||
"dragover",
|
||||
target,
|
||||
aWindow,
|
||||
dragSession.dataTransfer,
|
||||
{
|
||||
accelKey: aEvent.accelKey,
|
||||
altKey: aEvent.altKey,
|
||||
altGrKey: aEvent.altGrKey,
|
||||
ctrlKey: aEvent.ctrlKey,
|
||||
metaKey: aEvent.metaKey,
|
||||
shiftKey: aEvent.shiftKey,
|
||||
capsLockKey: aEvent.capsLockKey,
|
||||
fnKey: aEvent.fnKey,
|
||||
fnLockKey: aEvent.fnLockKey,
|
||||
numLockKey: aEvent.numLockKey,
|
||||
scrollLockKey: aEvent.scrollLockKey,
|
||||
symbolKey: aEvent.symbolKey,
|
||||
symbolLockKey: aEvent.symbolLockKey,
|
||||
}
|
||||
),
|
||||
target,
|
||||
aWindow
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Synthesize a mouse event at a particular point in aWindow.
|
||||
*
|
||||
|
@ -583,6 +664,18 @@ function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) {
|
|||
* aWindow is optional, and defaults to the current window object.
|
||||
*/
|
||||
function synthesizeMouseAtPoint(left, top, aEvent, aWindow = window) {
|
||||
if (aEvent.allowToHandleDragDrop) {
|
||||
if (aEvent.type == "mouseup" || !aEvent.type) {
|
||||
if (_maybeEndDragSession(left, top, aEvent, aWindow)) {
|
||||
return false;
|
||||
}
|
||||
} else if (aEvent.type == "mousemove") {
|
||||
if (_maybeSynthesizeDragOver(left, top, aEvent, aWindow)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var utils = _getDOMWindowUtils(aWindow);
|
||||
var defaultPrevented = false;
|
||||
|
||||
|
@ -1394,7 +1487,32 @@ function synthesizeAndWaitNativeMouseMove(
|
|||
*
|
||||
*/
|
||||
function synthesizeKey(aKey, aEvent = undefined, aWindow = window, aCallback) {
|
||||
var event = aEvent === undefined || aEvent === null ? {} : aEvent;
|
||||
const event = aEvent === undefined || aEvent === null ? {} : aEvent;
|
||||
let dispatchKeydown =
|
||||
!("type" in event) || event.type === "keydown" || !event.type;
|
||||
const dispatchKeyup =
|
||||
!("type" in event) || event.type === "keyup" || !event.type;
|
||||
|
||||
if (dispatchKeydown && aKey == "KEY_Escape") {
|
||||
let eventForKeydown = Object.assign({}, JSON.parse(JSON.stringify(event)));
|
||||
eventForKeydown.type = "keydown";
|
||||
if (
|
||||
_maybeEndDragSession(
|
||||
// TODO: We should set the last dragover point instead
|
||||
0,
|
||||
0,
|
||||
eventForKeydown,
|
||||
aWindow
|
||||
)
|
||||
) {
|
||||
if (!dispatchKeyup) {
|
||||
return;
|
||||
}
|
||||
// We don't need to dispatch only keydown event because it's consumed by
|
||||
// the drag session.
|
||||
dispatchKeydown = false;
|
||||
}
|
||||
}
|
||||
|
||||
var TIP = _getTIP(aWindow, aCallback);
|
||||
if (!TIP) {
|
||||
|
@ -1404,10 +1522,6 @@ function synthesizeKey(aKey, aEvent = undefined, aWindow = window, aCallback) {
|
|||
var modifiers = _emulateToActivateModifiers(TIP, event, aWindow);
|
||||
var keyEventDict = _createKeyboardEventDictionary(aKey, event, TIP, aWindow);
|
||||
var keyEvent = new KeyboardEvent("", keyEventDict.dictionary);
|
||||
var dispatchKeydown =
|
||||
!("type" in event) || event.type === "keydown" || !event.type;
|
||||
var dispatchKeyup =
|
||||
!("type" in event) || event.type === "keyup" || !event.type;
|
||||
|
||||
try {
|
||||
if (dispatchKeydown) {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
[synthetic-mouse-enter-leave-over-out-button-state-after-target-removed.tentative.html?buttonType=MIDDLE&button=1&buttons=4]
|
||||
expected:
|
||||
if os == "android": [OK, ERROR]
|
||||
[Removing an element at mousedown: mouseout and mouseleave should've been fired on the removed child]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -13,6 +15,8 @@
|
|||
|
||||
|
||||
[synthetic-mouse-enter-leave-over-out-button-state-after-target-removed.tentative.html?buttonType=LEFT&button=0&buttons=1]
|
||||
expected:
|
||||
if os == "android": [OK, ERROR]
|
||||
[Removing an element at mousedown: mouseout and mouseleave should've been fired on the removed child]
|
||||
expected: FAIL
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче