Bug 1910452: Part 3 - Fix some issues with synthesizeMockDragAndDrop r=m_kato

This fixes four issues:

1. The test didn't provide enough movement to generate a drag session on the
   source before moving to the target.  This meant that, when they were in
   different windows, Gecko wouldn't send dragleave to the source or dragenter
   to the target.  It also never sent dragenter to the source in the first
   place. This remedies that.
2. dragenter and dragleave weren't properly handled because the test was sending
   dragleaves instead of dragexits (the latter being what Gecko expects and the
   former being synthesized from that -- see e.g. nsNativeDragTarget::DragLeave).
   This now uses dragexits and sets the proper expectations.
3. expectProtectedDataTransferAccess was needlessly complicated and, after #1,
   gave the wrong answers for some events like dragenter called on the source.
4. The event handler wasn't checking for exceptions and the drop handler was
   intentionally causing one, which was causing it to miss the rest of its
   execution.

Differential Revision: https://phabricator.services.mozilla.com/D219550
This commit is contained in:
David Parks
2024-09-11 23:02:23 +00:00
parent 6bf2441cbe
commit 96fd9d11e8
4 changed files with 123 additions and 89 deletions

View File

@@ -60,11 +60,6 @@ export class DragChildContextBase {
// dataTransfer? Set as parameter to initialize. // dataTransfer? Set as parameter to initialize.
expectProtectedDataTransferAccess = false; expectProtectedDataTransferAccess = false;
// Should events dragend events have access to the dataTransfer? dragend
// access is also subject to expectProtectedDataTransferAccess. Set as
// parameter to initialize.
expectProtectedDataTransferAccessDragendOnly = false;
window = null; window = null;
dragService = null; dragService = null;
@@ -232,49 +227,56 @@ export class DragChildContextBase {
sandbox sandbox
); );
if (aEv.type == "dragstart") { try {
// Add some additional data to the DataTransfer so we can look for it if (aEv.type == "dragstart") {
// as we get later events. // Add some additional data to the DataTransfer so we can look for it
this.is( // as we get later events.
getFromDataTransfer(), this.is(
"", getFromDataTransfer(),
`[${aEv.type}]| DataTransfer didn't have kTestDataTransferType` "",
); `[${aEv.type}]| DataTransfer didn't have kTestDataTransferType`
setInDataTransfer(); );
this.is( setInDataTransfer();
getFromDataTransfer(), this.is(
kTestDataTransferData, getFromDataTransfer(),
`[${aEv.type}]| Successfully added kTestDataTransferType to DataTransfer` kTestDataTransferData,
); `[${aEv.type}]| Successfully added kTestDataTransferType to DataTransfer`
} else if (aEv.type == "drop") { );
this.is( } else if (aEv.type == "drop") {
getFromDataTransfer(), this.is(
kTestDataTransferData, getFromDataTransfer(),
`[${aEv.type}]| Successfully read from DataTransfer` kTestDataTransferData,
); `[${aEv.type}]| Successfully read from DataTransfer`
clearDataTransfer(); );
this.is( try {
getFromDataTransfer(), clearDataTransfer();
kTestDataTransferData, this.ok(false, "Writing to DataTransfer throws an exception");
`[${aEv.type}]| Properly failed to write to DataTransfer` } catch (ex) {
); this.ok(true, "Got exception: " + ex);
} else if ( }
aEv.type == "dragenter" || this.is(
aEv.type == "dragover" || getFromDataTransfer(),
aEv.type == "dragleave" || kTestDataTransferData,
aEv.type == "dragend" `[${aEv.type}]| Properly failed to write to DataTransfer`
) { );
let expectProtectedDataTransferAccess = } else if (
this.expectProtectedDataTransferAccess || aEv.type == "dragenter" ||
(aEv.type == "dragend" && aEv.type == "dragover" ||
this.expectProtectedDataTransferAccessDragendOnly); aEv.type == "dragleave" ||
this.is( aEv.type == "dragend"
getFromDataTransfer(), ) {
expectProtectedDataTransferAccess ? kTestDataTransferData : "", this.is(
`[${aEv.type}]| ${ getFromDataTransfer(),
expectProtectedDataTransferAccess ? "Successfully" : "Unsuccessfully" this.expectProtectedDataTransferAccess ? kTestDataTransferData : "",
} read from DataTransfer` `[${aEv.type}]| ${
); this.expectProtectedDataTransferAccess
? "Successfully"
: "Unsuccessfully"
} read from DataTransfer`
);
}
} catch (ex) {
this.ok(false, "Handler did not throw an uncaught exception: " + ex);
} }
if ( if (

View File

@@ -4163,21 +4163,18 @@ async function synthesizeMockDragAndDrop(aParams) {
// //
// dragstart and dragend are special because they target the drag-source, // dragstart and dragend are special because they target the drag-source,
// not the drag-target. // not the drag-target.
let expectProtectedDataTransferAccess = let expectProtectedDataTransferAccessSource = !SpecialPowers.getBoolPref(
!SpecialPowers.getBoolPref("dom.events.dataTransfer.protected.enabled") &&
browsingContextsAreRelated(targetBrowsingCxt, sourceBrowsingCxt);
// expectProtectedDataTransferAccessDragendOnly overrides
// expectProtectedDataTransferAccess when it is true
let expectProtectedDataTransferAccessDragendOnly = !SpecialPowers.getBoolPref(
"dom.events.dataTransfer.protected.enabled" "dom.events.dataTransfer.protected.enabled"
); );
let expectProtectedDataTransferAccessTarget =
expectProtectedDataTransferAccessSource &&
browsingContextsAreRelated(targetBrowsingCxt, sourceBrowsingCxt);
info( info(
`expectProtectedDataTransferAccess: ${expectProtectedDataTransferAccess}` `expectProtectedDataTransferAccessSource: ${expectProtectedDataTransferAccessSource}`
); );
info( info(
`expectProtectedDataTransferAccessDragendOnly: ${expectProtectedDataTransferAccessDragendOnly}` `expectProtectedDataTransferAccessTarget: ${expectProtectedDataTransferAccessTarget}`
); );
// Essentially the entire function is in a try block so that we can make sure // Essentially the entire function is in a try block so that we can make sure
@@ -4225,18 +4222,20 @@ async function synthesizeMockDragAndDrop(aParams) {
expectCancelDragStart, expectCancelDragStart,
expectSrcElementDisconnected, expectSrcElementDisconnected,
expectNoDragEvents, expectNoDragEvents,
expectProtectedDataTransferAccessDragendOnly, expectProtectedDataTransferAccess:
expectProtectedDataTransferAccessSource,
dragElementId: srcElement, dragElementId: srcElement,
}; };
const targetVars = { const targetVars = {
expectDragLeave, expectDragLeave,
expectNoDragTargetEvents, expectNoDragTargetEvents,
expectProtectedDataTransferAccess:
expectProtectedDataTransferAccessTarget,
dragElementId: targetElement, dragElementId: targetElement,
}; };
const bothVars = { const bothVars = {
contextLabel, contextLabel,
throwOnExtraMessage, throwOnExtraMessage,
expectProtectedDataTransferAccess,
relevantEvents: [ relevantEvents: [
"mousedown", "mousedown",
"mouseup", "mouseup",
@@ -4376,20 +4375,20 @@ async function synthesizeMockDragAndDrop(aParams) {
// Another move creates the drag session in the parent process (but we need // Another move creates the drag session in the parent process (but we need
// to wait for the src process to get there). // to wait for the src process to get there).
info(`Moving to target element.`); currentSrcScreenPos = [
let currentTargetScreenPos = [ currentSrcScreenPos[0] + step[0],
Math.ceil(targetPos.screenPos[0]), currentSrcScreenPos[1] + step[1],
Math.ceil(targetPos.screenPos[1]),
]; ];
info(
`third mousemove at ${currentSrcScreenPos[0]}, ${currentSrcScreenPos[1]}`
);
dragController.sendEvent( dragController.sendEvent(
sourceBrowsingCxt, sourceBrowsingCxt,
Ci.nsIMockDragServiceController.eMouseMove, Ci.nsIMockDragServiceController.eMouseMove,
currentTargetScreenPos[0], currentSrcScreenPos[0],
currentTargetScreenPos[1] currentSrcScreenPos[1]
); );
info(`third mousemove sent`);
await sourceCxt.checkExpected();
ok( ok(
_getDOMWindowUtils(sourceBrowsingCxt.ownerGlobal).dragSession, _getDOMWindowUtils(sourceBrowsingCxt.ownerGlobal).dragSession,
@@ -4406,26 +4405,60 @@ async function synthesizeMockDragAndDrop(aParams) {
return; return;
} }
currentTargetScreenPos = [ await sourceCxt.checkExpected();
currentTargetScreenPos[0] + step[0],
currentTargetScreenPos[1] + step[1], // Implementation detail: EventStateManager::GenerateDragDropEnterExit
// expects the source to get at least one dragover before leaving the
// widget or else it fails to send dragenter/dragleave events to the
// browsers.
info("synthesizing dragover inside source");
sourceCxt.expect("dragenter");
sourceCxt.expect("dragover");
currentSrcScreenPos = [
currentSrcScreenPos[0] + step[0],
currentSrcScreenPos[1] + step[1],
];
info(`dragover at ${currentSrcScreenPos[0]}, ${currentSrcScreenPos[1]}`);
dragController.sendEvent(
sourceBrowsingCxt,
Ci.nsIMockDragServiceController.eDragOver,
currentSrcScreenPos[0],
currentSrcScreenPos[1]
);
info(`dragover sent`);
await sourceCxt.checkExpected();
let currentTargetScreenPos = [
Math.ceil(targetPos.screenPos[0]),
Math.ceil(targetPos.screenPos[1]),
]; ];
// Send dragleave and dragenter only if we moved to another widget. // The next step is to drag to the target element.
// If we moved in the same widget then dragenter does not involve if (!expectNoDragTargetEvents) {
// the parent process. This mirrors the native behavior. Note that sourceCxt.expect("dragleave");
// these events are not forwarded to the content process -- they }
// are generated there by the EventStateManager when appropriate.
if ( if (
sourceBrowsingCxt.top.embedderElement !== sourceBrowsingCxt.top.embedderElement !==
targetBrowsingCxt.top.embedderElement targetBrowsingCxt.top.embedderElement
) { ) {
// Dragging from widget to widget // Send dragexit and dragenter only if we are dragging to another widget.
info("synthesizing dragleave and dragenter to enter new widget"); // If we are dragging in the same widget then dragenter does not involve
// the parent process. This mirrors the native behavior. In the
// widget-to-widget case, the source gets the dragexit immediately but
// the target won't get a dragenter in content until we send a dragover --
// this is because dragenters are generated by the EventStateManager and
// are not forwarded remotely.
// NB: dragleaves are synthesized by Gecko from dragexits.
info("synthesizing dragexit and dragenter to enter new widget");
if (!expectNoDragTargetEvents) {
info("This will generate dragleave on the source");
}
dragController.sendEvent( dragController.sendEvent(
sourceBrowsingCxt, sourceBrowsingCxt,
Ci.nsIMockDragServiceController.eDragLeave, Ci.nsIMockDragServiceController.eDragExit,
currentTargetScreenPos[0], currentTargetScreenPos[0],
currentTargetScreenPos[1] currentTargetScreenPos[1]
); );
@@ -4437,21 +4470,20 @@ async function synthesizeMockDragAndDrop(aParams) {
currentTargetScreenPos[1] currentTargetScreenPos[1]
); );
await sourceCxt.synchronize();
await sourceCxt.checkExpected(); await sourceCxt.checkExpected();
await targetCxt.checkExpected(); await targetCxt.checkExpected();
} }
info("synthesizing dragover to generate dragenter in DOM"); info(
"Synthesizing dragover over target. This will first generate a dragenter."
);
if (!expectNoDragTargetEvents) { if (!expectNoDragTargetEvents) {
targetCxt.expect("dragenter"); targetCxt.expect("dragenter");
targetCxt.expect("dragover"); targetCxt.expect("dragover");
} }
currentTargetScreenPos = [
currentTargetScreenPos[0] + step[0],
currentTargetScreenPos[1] + step[1],
];
dragController.sendEvent( dragController.sendEvent(
targetBrowsingCxt, targetBrowsingCxt,
Ci.nsIMockDragServiceController.eDragOver, Ci.nsIMockDragServiceController.eDragOver,

View File

@@ -102,8 +102,8 @@ static EventMessage MockEventTypeToEventMessage(
return EventMessage::eDragEnter; return EventMessage::eDragEnter;
case EventType::eDragOver: case EventType::eDragOver:
return EventMessage::eDragOver; return EventMessage::eDragOver;
case EventType::eDragLeave: case EventType::eDragExit:
return EventMessage::eDragLeave; return EventMessage::eDragExit;
case EventType::eDrop: case EventType::eDrop:
return EventMessage::eDrop; return EventMessage::eDrop;
case EventType::eMouseDown: case EventType::eMouseDown:
@@ -184,7 +184,7 @@ MockDragServiceController::SendEvent(
currentDragSession->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE); currentDragSession->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE);
widget->DispatchInputEvent(widgetEvent.get()); widget->DispatchInputEvent(widgetEvent.get());
break; break;
case EventType::eDragLeave: { case EventType::eDragExit: {
NS_ENSURE_TRUE(currentDragSession, NS_ERROR_UNEXPECTED); NS_ENSURE_TRUE(currentDragSession, NS_ERROR_UNEXPECTED);
currentDragSession->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE); currentDragSession->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE);
widget->DispatchInputEvent(widgetEvent.get()); widget->DispatchInputEvent(widgetEvent.get());

View File

@@ -21,7 +21,7 @@ interface nsIMockDragServiceController : nsISupports
cenum EventType : 8 { cenum EventType : 8 {
eDragEnter = 0, eDragEnter = 0,
eDragOver = 1, eDragOver = 1,
eDragLeave = 2, eDragExit = 2,
eDrop = 3, eDrop = 3,
eMouseDown = 4, eMouseDown = 4,
eMouseMove = 5, eMouseMove = 5,