Bug 1605918 - Get rid of synthesizeDragStart() in EventUtils.js r=smaug

All usage of `synthesizeDragStart()` is, starting drag, cancel `dragstart`,
and finally compares `dataTransfer` items and given expected data.  So,
we can make the users use `synthesizePlainDragAndDrop()` instead.  It's
better API because it computes position of mouse operations at runtime and
checks whether the drag start was succeeded with optional logging feature
(i.e., it's easier to debug of intermittent failures).

This patch creates `synthesizePlainDragAndCancel()` for convenience.  It
handles `dragstart` instead of the callers.

Differential Revision: https://phabricator.services.mozilla.com/D58214

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2019-12-27 16:25:24 +00:00
Родитель d695382c5e
Коммит 0a74bf0038
8 изменённых файлов: 185 добавлений и 152 удалений

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

@ -1,4 +1,4 @@
function test() {
async function test() {
waitForExplicitFinish();
let EventUtils = {};
@ -22,11 +22,13 @@ function test() {
// set the valid attribute so dropping is allowed
var oldstate = gURLBar.getAttribute("pageproxystate");
gURLBar.setPageProxyState("valid");
var dt = EventUtils.synthesizeDragStart(
document.getElementById("identity-box"),
let result = await EventUtils.synthesizePlainDragAndCancel(
{
srcElement: document.getElementById("identity-box"),
},
expected
);
is(dt, null, "drag on proxy icon");
ok(result === true, "dragging dataTransfer should be expected");
gURLBar.setPageProxyState(oldstate);
// Now, the identity information panel is opened by the proxy icon click.
// We need to close it for next tests.

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

@ -2326,11 +2326,12 @@ CustomizeMode.prototype = {
},
_isUnwantedDragDrop(aEvent) {
// The simulated events generated by synthesizeDragStart/synthesizeDrop in
// mochitests are used only for testing whether the right data is being put
// into the dataTransfer. Neither cause a real drop to occur, so they don't
// set the source node. There isn't a means of testing real drag and drops,
// so this pref skips the check but it should only be set by test code.
// The synthesized events for tests generated by synthesizePlainDragAndDrop
// and synthesizeDrop in mochitests are used only for testing whether the
// right data is being put into the dataTransfer. Neither cause a real drop
// to occur, so they don't set the source node. There isn't a means of
// testing real drag and drops, so this pref skips the check but it should
// only be set by test code.
if (this._skipSourceNodeCheck) {
return false;
}

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

@ -31,11 +31,7 @@ registerCleanupFunction(() =>
Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck")
);
var {
synthesizeDragStart,
synthesizeDrop,
synthesizeMouseAtCenter,
} = EventUtils;
var { synthesizeDrop, synthesizeMouseAtCenter } = EventUtils;
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

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

@ -60,15 +60,25 @@ function dumpTransfer(dataTransfer,expect) {
alert(dtData);
}
function runTest() {
var result = synthesizeDragStart($('link1'), dragLinkText, window);
is(result, null, "Drag -moz-user-select:none link (#link1)");
async function runTest() {
var result = await synthesizePlainDragAndCancel(
{
srcElement: $('link1').firstChild,
finalY: -10, // Avoid clicking the link
},
dragLinkText);
ok(result === true, "Drag -moz-user-select:none link (#link1)");
// if (result) dumpTransfer(result,dragLinkText);
dragLinkText[0][2].data = "link2";
dragLinkText[0][6].data = '<div id="link2"><a href="http://www.mozilla.org/">link2</a></div>'
var result = synthesizeDragStart($('link2'), dragLinkText, window);
is(result, null, "Drag link (#link2)");
var result = await synthesizePlainDragAndCancel(
{
srcElement: $('link2').firstChild,
finalY: -10, // Avoid clicking the link
},
dragLinkText);
ok(result === true, "Drag link (#link2)");
// if (result) dumpTransfer(result,dragLinkText);
SimpleTest.finish();

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

@ -6,25 +6,19 @@
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script>
function runTests()
async function runTests()
{
let dragService = SpecialPowers.Cc["@mozilla.org/widget/dragservice;1"].
getService(SpecialPowers.Ci.nsIDragService);
let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.contentDocument.body.innerHTML = '<div id="outer"/>';
let iframe = document.querySelector("iframe");
let iframeDoc = iframe.contentDocument;
let iframeWin = iframe.contentWindow;
let shadow = iframeDoc.querySelector('#outer').attachShadow({mode: 'open'});
let target = iframeDoc.createElement('a');
const TEXT = "Drag me if you can!";
let linkText = iframeDoc.createTextNode(TEXT);
target.appendChild(linkText);
target.textContent = "Drag me if you can!";
const URL = "http://www.mozilla.org/";
target.href = URL;
shadow.appendChild(target);
@ -63,8 +57,14 @@ function runTests()
data: URL,
}]];
let result = synthesizeDragStart(target, EXPECTED_DRAG_DATA, iframeWin);
is(result, null, "Should have gotten the expected drag data.");
let result = await synthesizePlainDragAndCancel(
{
srcElement: target,
srcWindow: iframeWin,
finalY: -10, // Avoid clicking the link
},
EXPECTED_DRAG_DATA);
ok(result === true, "Should have gotten the expected drag data.");
SimpleTest.finish();
}
@ -77,5 +77,6 @@ window.onload = () => {
</script>
<body>
<iframe srcdoc='<div id="outer"/>'></iframe>
</body>
</html>

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

@ -106,24 +106,6 @@
var startTime = new Date();
var result;
/* test synthesizeDragStart */
result = synthesizeDragStart($("drag1"), drag1, window);
is(result, null, "drag1 is text/uri-list");
result = synthesizeDragStart($("drag1"), drag1WrongFlavor, window);
isnot(result, null, "drag1 is not text/plain");
result = synthesizeDragStart($("drag1"), drag2items, window);
isnot(result, null, "drag1 is not 2 items");
result = synthesizeDragStart($("drag2"), drag2, window);
is(result, null, "drag2 is ordered text/plain then text/uri-list");
result = synthesizeDragStart($("drag2"), drag1, window);
isnot(result, null, "drag2 is not one flavor");
result = synthesizeDragStart($("drag2"), drag2WrongOrder, window);
isnot(result, null, "drag2 is not ordered text/uri-list then text/plain");
result = synthesizeDragStart($("dragfile"), dragfile, window);
is(result, null, "dragfile is nsIFile");
result = synthesizeDragStart($("drag1"), null, window);
is(result, regularDtForDrag1, "synthesizeDragStart accepts null expectedDragData");
/* test synthesizeDrop */
result = synthesizeDrop($("dragDrop"), $("dragDrop"), dragDrop, null, window);
ok(gEnter, "Fired dragenter");

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

@ -21,6 +21,7 @@
* synthesizeDropAfterDragOver
* synthesizeDrop
* synthesizePlainDragAndDrop
* synthesizePlainDragAndCancel
*
* When adding methods to this file, please add a performance test for it.
*/
@ -2419,80 +2420,6 @@ function synthesizeNativeOSXClick(x, y) {
CoreGraphics.close();
}
/**
* Emulate a dragstart event.
* element - element to fire the dragstart event on
* expectedDragData - the data you expect the data transfer to contain afterwards
* This data is in the format:
* [ [ {type: value, data: value, test: function}, ... ], ... ]
* can be null
* aWindow - optional; defaults to the current window object.
* x - optional; initial x coordinate
* y - optional; initial y coordinate
* Returns null if data matches.
* Returns the event.dataTransfer if data does not match
*
* eqTest is an optional function if comparison can't be done with x == y;
* function (actualData, expectedData) {return boolean}
* @param actualData from dataTransfer
* @param expectedData from expectedDragData
* see bug 462172 for example of use
*
*/
function synthesizeDragStart(element, expectedDragData, aWindow, x, y) {
if (!aWindow) aWindow = window;
x = x || 2;
y = y || 2;
const step = 9;
var result = "trapDrag was not called";
var trapDrag = function(event) {
try {
// We must wrap only in plain mochitests, not chrome
var dataTransfer = _EU_maybeWrap(event.dataTransfer);
result = null;
if (!dataTransfer) throw "no dataTransfer";
if (
expectedDragData == null ||
dataTransfer.mozItemCount != expectedDragData.length
)
throw dataTransfer;
for (var i = 0; i < dataTransfer.mozItemCount; i++) {
var dtTypes = dataTransfer.mozTypesAt(i);
if (dtTypes.length != expectedDragData[i].length) throw dataTransfer;
for (var j = 0; j < dtTypes.length; j++) {
if (dtTypes[j] != expectedDragData[i][j].type) throw dataTransfer;
var dtData = dataTransfer.mozGetDataAt(dtTypes[j], i);
if (expectedDragData[i][j].eqTest) {
if (
!expectedDragData[i][j].eqTest(
dtData,
expectedDragData[i][j].data
)
)
throw dataTransfer;
} else if (expectedDragData[i][j].data != dtData) throw dataTransfer;
}
}
} catch (ex) {
result = ex;
}
event.preventDefault();
event.stopPropagation();
};
aWindow.addEventListener("dragstart", trapDrag);
synthesizeMouse(element, x, y, { type: "mousedown" }, aWindow);
x += step;
y += step;
synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow);
x += step;
y += step;
synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow);
aWindow.removeEventListener("dragstart", trapDrag);
synthesizeMouse(element, x, y, { type: "mouseup" }, aWindow);
return result;
}
/**
* Synthesize a query text rect event.
*
@ -2881,6 +2808,19 @@ function synthesizeDrop(
}
}
function _computeSrcElementFromSrcSelection(aSrcSelection) {
let srcElement = aSrcSelection.focusNode;
while (_EU_maybeWrap(srcElement).isNativeAnonymous) {
srcElement = _EU_maybeUnwrap(
_EU_maybeWrap(srcElement).flattenedTreeParentNode
);
}
if (srcElement.nodeType !== Node.NODE_TYPE_ELEMENT) {
srcElement = srcElement.parentElement;
}
return srcElement;
}
/**
* Emulate a drag and drop by emulating a dragstart by mousedown and mousemove,
* and firing events dragenter, dragover, drop, and dragend.
@ -2945,15 +2885,7 @@ async function synthesizePlainDragAndDrop(aParams) {
}
if (srcSelection) {
srcElement = srcSelection.focusNode;
while (_EU_maybeWrap(srcElement).isNativeAnonymous) {
srcElement = _EU_maybeUnwrap(
_EU_maybeWrap(srcElement).flattenedTreeParentNode
);
}
if (srcElement.nodeType !== Node.NODE_TYPE_ELEMENT) {
srcElement = srcElement.parentElement;
}
srcElement = _computeSrcElementFromSrcSelection(srcSelection);
let srcElementRect = srcElement.getBoundingClientRect();
if (logFunc) {
logFunc(
@ -2971,21 +2903,13 @@ async function synthesizePlainDragAndDrop(aParams) {
);
}
// Click at center of last selection rect.
srcX = Math.floor(
lastSelectionRect.left + lastSelectionRect.width / 2
);
srcY = Math.floor(
lastSelectionRect.top + lastSelectionRect.height / 2
);
srcX = Math.floor(lastSelectionRect.left + lastSelectionRect.width / 2);
srcY = Math.floor(lastSelectionRect.top + lastSelectionRect.height / 2);
// Then, adjust srcX and srcY for making them offset relative to
// srcElementRect because they will be used when we call synthesizeMouse()
// with srcElement.
srcX = Math.floor(
srcX - srcElementRect.left
);
srcY = Math.floor(
srcY - srcElementRect.top
);
srcX = Math.floor(srcX - srcElementRect.left);
srcY = Math.floor(srcY - srcElementRect.top);
// Finally, recalculate finalX and finalY with new srcX and srcY if they
// are not specified by the caller.
if (aParams.finalX === undefined) {
@ -3022,7 +2946,11 @@ async function synthesizePlainDragAndDrop(aParams) {
if (logFunc) {
logFunc(`"${aEvent.type}" event is fired`);
}
if (!srcElement.contains(aEvent.target)) {
if (
!srcElement.contains(
_EU_maybeUnwrap(_EU_maybeWrap(aEvent).composedTarget)
)
) {
// If srcX and srcY does not point in one of rects in srcElement,
// "dragstart" target is not in srcElement. Such case must not
// be expected by this API users so that we should throw an exception
@ -3078,7 +3006,13 @@ async function synthesizePlainDragAndDrop(aParams) {
let session = ds.getCurrentSession();
if (!session) {
if (expectCancelDragStart) {
synthesizeMouse(srcElement, srcX, srcY, { type: "mouseup" }, srcWindow);
synthesizeMouse(
srcElement,
finalX,
finalY,
{ type: "mouseup" },
srcWindow
);
return;
}
throw new Error("drag hasn't been started by the operation");
@ -3180,12 +3114,16 @@ async function synthesizePlainDragAndDrop(aParams) {
if (logFunc) {
logFunc(`"${aEvent.type}" event is fired`);
}
if (!destElement.contains(aEvent.target)) {
if (
!destElement.contains(
_EU_maybeUnwrap(_EU_maybeWrap(aEvent).composedTarget)
)
) {
throw new Error(
'event target of "drop" is not destElement nor its descendant'
);
}
};
}
destWindow.addEventListener("drop", onDrop, { capture: true });
try {
let event = createDragEventObject(
@ -3227,9 +3165,13 @@ async function synthesizePlainDragAndDrop(aParams) {
if (logFunc) {
logFunc(`"${aEvent.type}" event is fired`);
}
if (!srcElement.contains(aEvent.target)) {
if (
!srcElement.contains(
_EU_maybeUnwrap(_EU_maybeWrap(aEvent).composedTarget)
)
) {
throw new Error(
'event target of "dragend" is not srcElement not its descendant'
'event target of "dragend" is not srcElement nor its descendant'
);
}
}
@ -3253,6 +3195,106 @@ async function synthesizePlainDragAndDrop(aParams) {
}
}
function _checkDataTransferItems(aDataTransfer, aExpectedDragData) {
try {
// We must wrap only in plain mochitests, not chrome
let dataTransfer = _EU_maybeWrap(aDataTransfer);
if (!dataTransfer) {
return null;
}
if (
aExpectedDragData == null ||
dataTransfer.mozItemCount != aExpectedDragData.length
) {
return dataTransfer;
}
for (let i = 0; i < dataTransfer.mozItemCount; i++) {
let dtTypes = dataTransfer.mozTypesAt(i);
if (dtTypes.length != aExpectedDragData[i].length) {
return dataTransfer;
}
for (let j = 0; j < dtTypes.length; j++) {
if (dtTypes[j] != aExpectedDragData[i][j].type) {
return dataTransfer;
}
let dtData = dataTransfer.mozGetDataAt(dtTypes[j], i);
if (aExpectedDragData[i][j].eqTest) {
if (
!aExpectedDragData[i][j].eqTest(
dtData,
aExpectedDragData[i][j].data
)
) {
return dataTransfer;
}
} else if (aExpectedDragData[i][j].data != dtData) {
return dataTransfer;
}
}
}
} catch (ex) {
return ex;
}
return true;
}
/**
* synthesizePlainDragAndCancel() synthesizes drag start with
* synthesizePlainDragAndDrop(), but always cancel it with preventing default
* of "dragstart". Additionally, this checks whether the dataTransfer of
* "dragstart" event has only expected items.
*
* @param aParams The params which is set to the argument of
* synthesizePlainDragAndDrop().
* @param aExpectedDataTransferItems
* All expected dataTransfer items.
* This data is in the format:
* [ [ {type: value, data: value, test: function}, ... ], ... ]
* can be null.
* eqTest is an optional function if comparison can't be
* done with x == y;
* function (actualData, expectedData) {return boolean}
* @return true if aExpectedDataTransferItems matches with
* DragEvent.dataTransfer of "dragstart" event.
* Otherwise, the dataTransfer object (may be null) or
* thrown exception, NOT false. Therefore, you shouldn't
* use
*/
async function synthesizePlainDragAndCancel(
aParams,
aExpectedDataTransferItems
) {
let srcElement = aParams.srcSelection
? _computeSrcElementFromSrcSelection(aParams.srcSelection)
: aParams.srcElement;
let result;
function onDragStart(aEvent) {
aEvent.preventDefault();
result = _checkDataTransferItems(
aEvent.dataTransfer,
aExpectedDataTransferItems
);
}
SpecialPowers.addSystemEventListener(
srcElement.ownerDocument,
"dragstart",
onDragStart,
{ capture: true }
);
try {
aParams.expectCancelDragStart = true;
await synthesizePlainDragAndDrop(aParams);
} finally {
SpecialPowers.removeSystemEventListener(
srcElement.ownerDocument,
"dragstart",
onDragStart,
{ capture: true }
);
}
return result;
}
var PluginUtils = {
withTestPlugin: function(callback) {
var ph = _EU_Cc["@mozilla.org/plugin/host;1"].getService(

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

@ -278,8 +278,7 @@ nsBaseDragService::InvokeDragSession(
//
// The best way to avoid this is to catch the dragstart event on the item
// being dragged, and then to call preventDefault() and stopPropagating() on
// it. Alternatively, use EventUtils.synthesizeDragStart, which will do this
// for you.
// it.
if (XRE_IsParentProcess()) {
MOZ_ASSERT(
!xpc::IsInAutomation(),