siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
1,104 lines (882 loc) • 46.8 kB
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>The source code</title>
<link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="../resources/prettify/prettify.js"></script>
<style type="text/css">
.highlight { display: block; background-color: #ddd; }
</style>
<script type="text/javascript">
function highlight() {
document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
}
</script>
</head>
<body onload="prettyPrint(); highlight();">
<pre class="prettyprint lang-js">/*
Siesta 5.6.1
Copyright(c) 2009-2022 Bryntum AB
https://bryntum.com/contact
https://bryntum.com/products/siesta/license
*/
Role('Siesta.Test.Simulate.Mouse', {
requires : [
'simulateEvent', 'getSimulateEventsWith', '$'
],
does : [
Siesta.Util.Role.Dom,
Siesta.Util.Role.CanCalculatePageScroll,
Siesta.Test.Browser.Role.CanGetElementFromPoint
],
has: {
// Current viewport coordinates of the cursor
// this will be a shared array instance between all subtests
// it should not be overwritten, instead modify individual elements:
// NO: this.currentPosition = [ 1, 2 ]
// YES: this.currentPosition[ 0 ] = 1
// YES: this.currentPosition[ 1 ] = 2
currentPosition : {
init : function () { return [ 0, 0 ]; }
},
<span id='global-cfg-dragDelay'> /**
</span> * @cfg {Int} dragDelay The delay between individual drag events (mousemove)
*/
dragDelay : 25,
pathBatchSize : bowser.msie ? 10 : 5,
mouseMovePrecision : 1,
mouseDragPrecision : 1,
enableUnreachableClickWarning : true,
overEls : Joose.I.Array,
lastMouseOverEl : null,
mouseState : 'up',
// The last element we fired 'mousedown' upon
lastMouseDownEl : null,
pointerEventNamesMap : {
lazy : function () {
if (window.PointerEvent)
return {
pointerdown : 'pointerdown',
pointerup : 'pointerup',
pointerover : 'pointerover',
pointerout : 'pointerout',
pointerenter : 'pointerenter',
pointerleave : 'pointerleave',
pointermove : 'pointermove'
}
else
if (window.MSPointerEvent)
return {
pointerdown : 'MSPointerDown',
pointerup : 'MSPointerUp',
pointerover : 'MSPointerOver',
pointerout : 'MSPointerOut',
pointerenter : 'MSPointerEnter',
pointerleave : 'MSPointerLeave',
pointermove : 'MSPointerMove'
}
else
return {}
}
}
},
after : {
cleanup : function () {
this.overEls = null
this.lastMouseDownEl = null
this.lastMouseOverEl = null
}
},
override : {
normalizeEventName : function (eventName) {
var eventMap = this.getPointerEventNamesMap()
return eventMap[ eventName ] || this.SUPERARG(arguments);
},
simulateEvent : function (el, eventName) {
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
if (supportsPointerEvents ? /pointerdown$/i.test(eventName) : eventName == 'mousedown') {
this.mouseState = 'down'
this.lastMouseDownEl = el;
}
if (supportsPointerEvents ? /pointerup$/i.test(eventName) : eventName == 'mouseup') {
this.mouseState = 'up'
this.lastMouseUpEl = el;
}
var event = this.SUPERARG(arguments)
if (/pointerdown$/i.test(eventName)) {
this.lastPointerDownPrevented = this.isEventPrevented(event);
}
// in FF for 'textInput' events the returning value can be `undefined`
if (this.test && this.test.mouseVisualizer && event) this.test.mouseVisualizer.onEventSimulated(event, this.currentPosition)
return event
}
},
methods: {
// private
createMouseEvent: function (type, options, el) {
var global = this.global
var doc = el.ownerDocument
var event
var isPointer = type.match(/^(ms)?pointer/i)
options = this.prepareMouseEventOptions(type, options, el)
if (!bowser.msie && global.MouseEvent) {
if (type === 'wheel') {
event = new global.WheelEvent(type, options);
} else if (isPointer) {
// this is non IE branch, so no mess with MS prefix
// Chrome sets button to -1 for pointermove
if (type === 'pointermove') {
options.button = -1;
}
event = new global.PointerEvent(type, options);
} else {
event = new global.MouseEvent(type, options);
}
}
// use W3C standard when available and allowed by "simulateEventsWith" option
else if (doc.createEvent && this.getSimulateEventsWith() == 'dispatchEvent') {
if (type === 'wheel') {
// https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ff975847(v=vs.85)
/*
* A space-separated list of any of the following values:
Alt
The left or right Alt key is pressed.
AltGraph
The Ctrl and Alt keys are pressed.
CapsLock
The Caps Lock toggle is enabled.
Control
The left or right Ctrl key is pressed.
Meta
The Meta/Control key is pressed.
NumLock
The Num Lock toggle is enabled.
Scroll
The Scroll Lock toggle is enabled.
Shift
The left or right Shift key is pressed.
Win
The left or right Windows logo key is pressed.
**/
var modifiersArg = '';
if (options.ctrlKey) modifiersArg = 'Control';
if (options.altKey) modifiersArg += ' Alt';
if (options.shiftKey) modifiersArg += ' Shift';
event = doc.createEvent('WheelEvent');
event.initWheelEvent(
type,
options.bubbles,
options.cancelable,
options.view,
options.detail,
options.screenX,
options.screenY,
options.clientX,
options.clientY,
options.button,
options.relatedTarget || doc.documentElement,
modifiersArg,
options.deltaX || 0,
options.deltaY || 0,
options.deltaZ || 0,
options.deltaMode || 0);
} else {
event = doc.createEvent(isPointer ? (isPointer[ 1 ] ? 'MS' : '') + 'PointerEvent' : 'MouseEvents');
event[ isPointer ? 'initPointerEvent' : 'initMouseEvent' ](
type, options.bubbles, options.cancelable, options.view, options.detail,
options.screenX, options.screenY, options.clientX, options.clientY,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
options.button, options.relatedTarget || doc.documentElement,
// the following extra args are used in the "initPointerEvent"
// offsetX, offsetY
null, null,
// width, height
null, null,
// pressure, rotation
null, null,
// tiltX, tiltY
null, null,
// pointerId
options.pointerId,
// pointerType
// NOTE: this has to be set to "mouse" (IE11) or 4 (IE10, 11) because otherwise
// ExtJS5 blocks the event
// need to investigate what happens in SenchaTouch
options.pointerType,
// timestamp
null,
// isPrimary
null
);
}
} else if (doc.createEventObject) {
event = doc.createEventObject();
$.extend(event, options);
event.button = { 0: 1, 1: 4, 2: 2 }[ event.button ] || event.button;
}
// in Edge, the "pageX/pageY" properties of the event object are calculated by browser completely
// wrong - need to override those
if (this.bowser.msedge) {
global.Object.defineProperty(event, 'pageX', { value : options.pageX })
global.Object.defineProperty(event, 'pageY', { value : options.pageY })
}
// Mouse over is used in some certain edge cases which interfer with this tracking
if (!/(mouse|pointer)over$/.test(type) && !/(mouse|pointer)out$/.test(type)) {
var elWindow = doc.defaultView || doc.parentWindow;
var cursorX = options.clientX;
var cursorY = options.clientY;
// Potentially we're interacting with an element inside a nested frame, which means the coordinates are local to that frame
if (elWindow !== global) {
var offsets = this.$(elWindow.frameElement).offset();
cursorX += offsets.left;
cursorY += offsets.top;
}
if (!options.doNotUpdateCurrentPosition) {
// TODO should be moved to `simulateEvent` (and set right before the `dispatchEvent` call)
this.currentPosition[ 0 ] = cursorX;
this.currentPosition[ 1 ] = cursorY;
}
}
return event;
},
prepareMouseEventOptions : function (type, options, el) {
var global = this.global
options = $.extend({
bubbles : !/(ms)?(mouse|pointer)enter/i.test(type) && !/(ms)?(mouse|pointer)leave/i.test(type),
cancelable : !/(ms)?(mouse|pointer)move/i.test(type),
view : global,
detail : 0,
screenX : 0,
screenY : 0,
// https://developer.mozilla.org/en-US/docs/Web/API/Event/composed
// The read-only composed property of the Event interface returns a Boolean which indicates whether or not
// the event will propagate across the shadow DOM boundary into the standard DOM.
composed : true,
ctrlKey : false,
altKey : false,
shiftKey : false,
metaKey : false,
/*
* https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
*
* A number representing a given button:
0: Main button pressed, usually the left button or the un-initialized state
1: Auxiliary button pressed, usually the wheel button or the middle button (if present)
2: Secondary button pressed, usually the right button
3: Fourth button, typically the Browser Back button
4: Fifth button, typically the Browser Forward button
* */
button : 0,
/*
* https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
*
* 0 : No button or un-initialized
* 1 : Left button
* 2 : Right button
* 4 : Wheel button or middle button
* 8 : 4th button (typically the "Browser Back" button)
* 16 : 5th button (typically the "Browser Forward" button)
*
* */
buttons : 0,
relatedTarget : undefined,
// pointerType
// NOTE: this has to be set to "mouse" (IE11) or 4 (IE10, 11) because otherwise
// ExtJS5 blocks the event
// need to investigate what happens in SenchaTouch
pointerType : bowser.msie ? 4 : 'mouse'
}, options);
if (!("clientX" in options) || !("clientY" in options)) {
var center = this.test.findCenter(el);
options.clientX = center[ 0 ]
options.clientY = center[ 1 ]
}
options.clientX = Math.round(options.clientX)
options.clientY = Math.round(options.clientY)
// edge seems to incorrectly calculate pageX/pageY, providing explicitly
if (this.bowser.msedge && (!("pageX" in options) || !("pageY" in options))) {
options.pageX = this.viewportXtoPageX(options.clientX)
options.pageY = this.viewportYtoPageY(options.clientY)
}
// Not supported in IE
if ("screenX" in window) {
options.screenX = Math.round(global.screenX + options.clientX)
options.screenY = Math.round(global.screenY + options.clientY)
}
return options
},
simulateMouseMove : function (x, y, options, params) {
var me = this
params = params || {}
var pathBatchSize = params.pathBatchSize
var async = params.async
var mouseMovePrecision = params.mouseMovePrecision
if (params.moveKind == 'instant') {
pathBatchSize = 10000
mouseMovePrecision = 10000
}
return this.movePointerTemplate({
xy : this.currentPosition,
xy2 : [ x, y ],
options : options,
overEls : this.overEls,
interval : async !== false ? this.dragDelay : 0,
callbackDelay : async !== false ? 50 : 0,
pathBatchSize : pathBatchSize || me.pathBatchSize,
mouseMovePrecision : mouseMovePrecision || me.mouseMovePrecision,
onVoidOverEls : function () {
return me.overEls = []
},
onPointerEnter : function (el, options) {
me.onPointerEnter(el, options)
},
onPointerLeave : function (el, options) {
me.onPointerLeave(el, options)
},
onPointerOver : function (el, options) {
me.onPointerOver(el, options)
},
onPointerOut : function (el, options) {
me.onPointerOut(el, options)
},
onPointerMove : function (el, options) {
me.onPointerMove(el, options)
}
})
},
// xy, xy2, overEls, pathBatchSize, interval, callbackDelay, options,
// onPointerEnter, onPointerLeave, onPointerOver, onPointerOut, onPointerMove
movePointerTemplate: function (args) {
var document = this.global.document,
me = this,
overEls = args.overEls,
// Remember last visited element, since a previous action may have changed the DOM
// which possibly should trigger a mouseout event
lastOverEl = overEls[ overEls.length - 1 ];
if (lastOverEl && this.nodeIsUnloaded(lastOverEl)) {
lastOverEl = null
overEls = args.onVoidOverEls()
}
// this method works as follows:
// `path` - contains in array of points
// we split that array into batches with size - `pathBatchSize`, but, each batch can't be less than `mouseMovePrecision`
// every batch is processed by one queue step (see below), so for every batch there's one call to `processor`
// inside the processor, there's a loop, which iterates the batch with the delta, equal to `mouseMovePrecision`,
// but no less than 1st and last point
// always simulate drag with 1px precision
var mouseMovePrecision = me.mouseState == 'down' ? me.mouseDragPrecision : args.mouseMovePrecision || me.mouseMovePrecision
var pathBatchSize = Math.max(args.pathBatchSize, mouseMovePrecision)
var options = args.options || {}
var supports = Siesta.Project.Browser.FeatureSupport().supports
var path = this.getPathBetweenPoints(args.xy, args.xy2);
var queue = new Siesta.Util.Queue({
deferer : this.test.originalSetTimeout,
deferClearer : this.test.originalClearTimeout,
interval : args.interval,
callbackDelay : args.callbackDelay,
observeTest : this.test,
processor : function (data, index) {
var fromIndex = data.sourceIndex,
toIndex = data.targetIndex,
info,
x,
y,
visitPoint = function (point) {
info = me.elementFromPoint(point[ 0 ], point[ 1 ], false, null, true);
// targetEl will possibly be from the nested iframe
// and `localXY in `info` will contain local viewport point for `x, y` in that iframe
var targetEl = info.el
// Might get null if moving over a non-initialized frame (seen in Chrome)
if (!targetEl) return
x = info.localXY[ 0 ]
y = info.localXY[ 1 ]
if (targetEl !== lastOverEl) {
me.onElementAtCursorChanged(targetEl, lastOverEl, x, y, options, args);
lastOverEl = targetEl;
}
args.onPointerMove(targetEl, $.extend({ clientX : x, clientY : y }, options), j < toIndex)
};
// replace 0 with 1 to avoid infinite loop
var delta = Math.min(toIndex - fromIndex, mouseMovePrecision) || 1
for (var j = fromIndex; j <= toIndex; j += delta) {
var point = path[ j ];
visitPoint(point);
}
// Absolutely vital that we visit the final point of the path,
// which can be missed above with different combinations pathBatchSize and mouseMovePrecision
if (j >= path.length && point !== path[path.length - 1]) {
point = visitPoint(path[path.length - 1]);
}
// check again if the PointerMove simulation triggered a change of the element at the cursor
// and process it if needed
if (point) {
info = me.elementFromPoint(point[ 0 ], point[ 1 ], false, null, true);
if (info.el && info.el !== lastOverEl) {
me.onElementAtCursorChanged(info.el, lastOverEl, x, y, options, args);
}
}
// eof for
}
});
var pathLength = path.length
if (pathLength <= pathBatchSize && mouseMovePrecision >= pathBatchSize) {
// special case, when only the 1st and last points of the path will simulate mouse events
// in this case, we want to simulate the events for *two* initial and *two* final points
// so that in the begining and at the end of the path simulation is more accurate
queue.addStep({
sourceIndex : 0,
targetIndex : Math.min(1, pathLength - 1)
});
if (pathLength >= 3)
queue.addStep({
sourceIndex : pathLength - 2,
targetIndex : pathLength - 1
})
} else
for (var i = 0, l = pathLength; i < l; i += pathBatchSize) {
queue.addStep({
sourceIndex : i,
targetIndex : Math.min(i + pathBatchSize - 1, pathLength - 1)
});
}
queue.addStep({
processor : function () {
me.afterMouseInteraction()
}
});
return new Promise(function (resolve, reject) {
queue.run(resolve)
})
},
onPointerEnter : function (el, options) {
var me = this;
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
if (supportsPointerEvents) me.simulateEvent(el, "pointerenter", options)
me.simulateEvent(el, "mouseenter", options)
},
onPointerLeave : function (el, options) {
var me = this;
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
if (supportsPointerEvents) me.simulateEvent(el, "pointerleave", options)
me.simulateEvent(el, "mouseleave", options)
},
onPointerOver : function (el, options) {
var me = this;
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
if (supportsPointerEvents) me.simulateEvent(el, "pointerover", options)
me.simulateEvent(el, "mouseover", options)
},
onPointerOut : function (el, options) {
var me = this;
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
if (supportsPointerEvents) me.simulateEvent(el, "pointerout", options)
me.simulateEvent(el, "mouseout", options)
},
onPointerMove : function (el, options) {
var me = this;
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
options.buttons = me.mouseState == 'up' ? 0 : 1
if (supportsPointerEvents) me.simulateEvent(el, "pointermove", options)
me.simulateEvent(el, "mousemove", options)
},
onElementAtCursorChanged : function (targetEl, lastOverEl, clientX, clientY, options, movePointerInterface) {
var me = this,
supports = Siesta.Project.Browser.FeatureSupport().supports,
overEls = me.overEls;
if (lastOverEl && !me.nodeIsOrphan(lastOverEl) && !me.nodeIsUnloaded(lastOverEl)) {
movePointerInterface.onPointerOut(lastOverEl, $.extend({
clientX : clientX,
clientY : clientY,
relatedTarget : targetEl
}, options))
}
for (var i = overEls.length - 1; i >= 0; i--) {
var el = overEls[ i ];
if (me.nodeIsUnloaded(el) || me.nodeIsOrphan(el))
overEls.splice(i, 1);
else if (el !== targetEl && me.$(el).has(targetEl).length === 0) {
if (supports.mouseEnterLeave) {
movePointerInterface.onPointerLeave(el, $.extend({
clientX : clientX,
clientY : clientY,
relatedTarget : targetEl
}, options))
}
overEls.splice(i, 1);
}
}
// "mouseover" should be simulated before "mouseleave"
movePointerInterface.onPointerOver(targetEl, $.extend({
clientX : clientX,
clientY : clientY,
relatedTarget : lastOverEl
}, options))
if (supports.mouseEnterLeave && jQuery.inArray(targetEl, overEls) === -1) {
var els = []
var docEl = this.getRootElement(targetEl)
var mouseEnterEl = targetEl
// collecting all the els for which to fire the "mouseenter" event, strictly speaking these can be any elements
// (because of absolute positioning) but in most cases it will be just parent elements
while (mouseEnterEl && mouseEnterEl !== docEl) {
els.unshift(mouseEnterEl)
mouseEnterEl = mouseEnterEl.parentNode
}
for (var i = 0; i < els.length; i++) {
if (jQuery.inArray(els[ i ], overEls) === -1) {
movePointerInterface.onPointerEnter(els[ i ], $.extend({
clientX : clientX,
clientY : clientY,
relatedTarget : lastOverEl
}, options))
overEls.push(els[ i ]);
}
}
}
},
// Check if the mouse interaction triggered a DOM update causing the last interacted element to be removed from the DOM
// In this case we should simulate a new 'mouseover' event on whatever appeared under the cursor.
afterMouseInteraction : function() {
// var overEls = this.overEls,
// lastOverEl = overEls[ overEls.length - 1 ]
//
// //URL might have changed, then ignore
// if (!this.global.document.body) return;
//
// if (lastOverEl&&this.nodeIsUnloaded(lastOverEl)) {
// lastOverEl = null
// this.overEls = []
//
// //after page reload we want to simulate the `mouseover`
// //for the element appeared at the current cursor position
// this.mouseOver(this.currentPosition);
// }
},
simulateMouseDown: function (clickInfo, options) {
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
return this.processMouseActionSteps(
clickInfo,
options,
[
supportsPointerEvents ?
{ event : "pointerdown", interval : 0 }
:
null,
{ event : "mousedown", focus : true }
]
);
},
simulateMouseUp: function (clickInfo, options) {
var el = clickInfo.el;
// Should only happen if parent el of the mousedown/up events are the same
var targetChanged = this.lastMouseDownEl && el !== this.lastMouseDownEl && !($.contains(el, this.lastMouseDownEl) || $.contains(this.lastMouseDownEl, el));
var shouldFireClick = !targetChanged || !(this.bowser.safari || this.bowser.gecko);
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
return this.processMouseActionSteps(
clickInfo,
options,
[
supportsPointerEvents ?
{ event : "pointerup", interval : 0 }
:
null,
{ event : "mouseup" }
].concat(shouldFireClick ?
[
{ event : "click" }
] :
[]
)
);
},
// private, should not be used in tests
mouseOver: function (el, options) {
var info = this.test.getNormalizedTopElementInfo(el, true);
if (!info) return;
options = options || {}
options.clientX = options.clientX != null ? options.clientX : info.localXY[0];
options.clientY = options.clientY != null ? options.clientY : info.localXY[1];
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
if (supportsPointerEvents) this.simulateEvent(el, 'pointerover', options);
this.simulateEvent(el, 'mouseover', options);
},
// private, should not be used in tests
mouseOut: function (el, options) {
var info = this.test.getNormalizedTopElementInfo(el, true);
if (!info) return;
options = options || {}
options.clientX = options.clientX != null ? options.clientX : info.localXY[0];
options.clientY = options.clientY != null ? options.clientY : info.localXY[1];
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
if (supportsPointerEvents) this.simulateEvent(el, 'pointerout', options);
this.simulateEvent(el, 'mouseout', options);
},
processMouseActionSteps : function (clickInfo, options, steps) {
// trying to get the top element again, enabling the warning if needed
// do it here and not in the `genericMouseAction` method to allow scenario
// when target element appears when mouse moves to the click point
if (clickInfo.originalEl && this.enableUnreachableClickWarning) {
this.test.getNormalizedTopElementInfo(clickInfo.originalEl, false, clickInfo.method, clickInfo.offset)
}
var me = this
var x = clickInfo.globalXY[ 0 ]
var y = clickInfo.globalXY[ 1 ]
var isOption = clickInfo.el.nodeName.toLowerCase() === 'option';
var doc = me.global.document
var prevScrollTop = this.getPageScrollY()
// re-evaluate the target el - it might have changed while we were syncing the cursor position
var target = isOption ? clickInfo.el : me.elementFromPoint(x, y, false, clickInfo.el)
var targetParent = target.parentNode;
var targetHasChanged = false
var queue = new Siesta.Util.Queue({
deferer : this.test.originalSetTimeout,
deferClearer : this.test.originalClearTimeout,
interval : 10,
callbackDelay : me.afterActionDelay,
observeTest : this.test,
processor : function (data) {
if (me.lastPointerDownPrevented && /mouse/i.test(data.event)) return
// XXX this has to be investigated more deeply (notably the <body> vs <html> scrolling, etc)
// - When simulating events browser performs weird scrolls on the document.
// Seems it tries to make the point of simulated event visible on the screen.
// This is native browser behavior out of our control.
// Thing is, when the document is scrolled, `elementFromPoint` returns different
// element for the same point. Because of that the logic for clicks is vulnerable.
// Scenario is - "mousedown" (or may be "mouseup") is simulated, scroll position changes
// further "click" event happens on different element
// body can be absent if the doubleclick happens on the anchor and page is reloaded in the middle
// of double click
var delta = doc.body ? me.getPageScrollY() - prevScrollTop : 0
var elAtCursor = isOption ? target : me.elementFromPoint(x, y - delta, false, target)
var fireEl = elAtCursor;
if (!isOption && data.recaptureTarget) { target = elAtCursor; targetHasChanged = false }
// The "click" event should be canceled if "mousedown/up" happened on different elements,
// _unless_ these elements has parent/child relationship
if (!isOption && elAtCursor !== target && !($.contains(elAtCursor, target) || $.contains(target, elAtCursor))) targetHasChanged = true
// Special treatment of click event firing:
// * Don't fire click if a node was moved in the DOM tree, or if was orphaned
// * Chrome + IE fires click on the common ancestor of mousedown target + mouseup target after drag drop
if (!isOption && data.event === 'click') {
var nodeMovedInDomTree = elAtCursor === target && elAtCursor.parentNode !== targetParent;
// Don't fire click if a node was moved in the DOM tree, or if target or mouseDown target was orphaned
// or if mouseDownElement !== mouseUpElement
if (nodeMovedInDomTree || me.nodeIsOrphan(target) || (me.lastMouseDownEl && me.nodeIsOrphan(me.lastMouseDownEl))) return;
var mouseDownUpTargetsChanged;
// Check if mousedownElement differs from what is at cursor
if (
me.lastMouseDownEl && elAtCursor !== me.lastMouseDownEl
&& !($.contains(elAtCursor, me.lastMouseDownEl) || $.contains(me.lastMouseDownEl, elAtCursor))
) {
mouseDownUpTargetsChanged = true;
fireEl = me.getCommonAncestor(elAtCursor, me.lastMouseDownEl);
// Also check if mouseupElement differs from what is at cursor
} else
if (
me.lastMouseUpEl && me.lastMouseUpEl !== elAtCursor && !($.contains(elAtCursor, me.lastMouseUpEl))
) {
mouseDownUpTargetsChanged = true;
fireEl = me.getCommonAncestor(elAtCursor, me.lastMouseUpEl);
}
// When target changed, Chrome + IE fires click on the common ancestor after drag drop
if (mouseDownUpTargetsChanged) {
if (me.bowser.gecko || me.bowser.safari) {
// Safari + FF does not fire click on the common ancestor after drag drop
return;
} else {
// mouseDown/mouseUp happened in 2 different frames?
if (!fireEl) return;
}
}
}
if (targetHasChanged && data.cancelIfTargetChanged) {
return;
}
var event = me.simulateEvent(fireEl, data.event, options);
if (!me.lastPointerDownPrevented && data.focus) {
me.mimicFocusOnMouseDown(elAtCursor, event);
}
if (!isOption) {
// Check if this event triggered another element to be visible at cursor, if so handle pointerleave/mouseleave.
var elementAtPoint = me.elementFromPoint(x, y - delta, false, target);
if (elementAtPoint !== elAtCursor) {
me.onElementAtCursorChanged(elementAtPoint, elAtCursor, x, y - delta, options, me);
}
}
}
})
Joose.A.each(steps, function (step) {
step && queue.addStep(step)
})
return new Promise(function (resolve, reject) {
queue.run(function () {
me.afterMouseInteraction();
resolve()
})
})
},
// private
simulateMouseClick: function (clickInfo, options) {
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
return this.processMouseActionSteps(
clickInfo,
options,
[
supportsPointerEvents ?
{ event : "pointerdown", interval : 0 }
:
null,
{ event : "mousedown", focus : true },
supportsPointerEvents ?
{ event : "pointerup", interval : 0 }
:
null,
{ event : "mouseup", interval : 0 },
{ event : "click", cancelIfTargetChanged : true }
]
)
},
// private
simulateRightClick: function (clickInfo, options) {
// Mac doesn't fire mouseup when right clicking
var isMac = navigator.platform.indexOf('Mac') > -1;
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
options = options || {};
options.button = options.buttons = 2;
return this.processMouseActionSteps(
clickInfo,
options,
[
supportsPointerEvents ?
{ event : "pointerdown", interval : 0 }
:
null,
{ event : "mousedown", focus : true }
].concat(isMac ? [] :
[
supportsPointerEvents ?
{ event : "pointerup", interval : 0 }
:
null,
{ event : "mouseup", interval : 0 }
]
).concat(
[ { event : "contextmenu" } ]
)
)
},
// private
simulateDoubleClick: function (clickInfo, options) {
var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric
return this.processMouseActionSteps(
clickInfo,
options,
[
supportsPointerEvents ?
{ event : "pointerdown", interval : 0 }
:
null,
{ event : "mousedown", focus : true },
supportsPointerEvents ?
{ event : "pointerup", interval : 0 }
:
null,
{ event : "mouseup", interval : 0 },
{ event : "click", cancelIfTargetChanged : true },
supportsPointerEvents ?
{ event : "pointerdown", interval : 0 }
:
null,
{ event : "mousedown", recaptureTarget : true, focus : true },
supportsPointerEvents ?
{ event : "pointerup", interval : 0 }
:
null,
{ event : "mouseup", interval : 0 },
{ event : "click" , cancelIfTargetChanged : true, interval : 0 },
{ event : "dblclick" , cancelIfTargetChanged : true }
]
)
},
// private
mimicFocusOnMouseDown : function (el, mouseDownEvent) {
// only do focus if `mousedown` event is not prevented by outside world
if (this.isEventPrevented(mouseDownEvent)) return;
el = this.test.findClosestFocusableElement(el);
// focus body as the last resort to trigger the "blur" event on the currently focused element
if (el) {
this.test.focus(el, true)
}
},
simulateMouseWheel : function (targetInfo, options) {
var eventName = 'wheel';
var doc = targetInfo.el.ownerDocument;
// For legacy browsers where we don't use dispatchEvent, fallback to 'mousewheel' event ('wheel' cannot be simulated with doc.createEventObject in <= IE9)
if (!doc.createEvent || this.getSimulateEventsWith() !== 'dispatchEvent') {
eventName = 'mousewheel';
}
return this.processMouseActionSteps(
targetInfo,
options,
[
{ event : eventName }
]
);
},
// private
getPathBetweenPoints: function (from, to) {
if (
typeof from[0] !== 'number' ||
typeof from[1] !== 'number' ||
typeof to[0] !== 'number' ||
typeof to[1] !== 'number' ||
isNaN(from[0]) ||
isNaN(from[1]) ||
isNaN(to[0]) ||
isNaN(to[1])
) {
throw new Error('Incorrect arguments passed to getPathBetweenPoints: ' + from + ', ' + to);
}
var stops = [],
x0 = Math.floor(from[0]),
x1 = Math.floor(to[0]),
y0 = Math.floor(from[1]),
y1 = Math.floor(to[1]),
dx = Math.abs(x1 - x0),
dy = Math.abs(y1 - y0),
sx, sy, err, e2;
if (x0 < x1) {
sx = 1;
} else {
sx = -1;
}
if (y0 < y1) {
sy = 1;
} else {
sy = -1;
}
err = dx - dy;
while (x0 !== x1 || y0 !== y1) {
e2 = 2 * err;
if (e2 > -dy) {
err = err - dy;
x0 = x0 + sx;
}
if (e2 < dx) {
err = err + dx;
y0 = y0 + sy;
}
stops.push([x0, y0]);
}
var last = stops[stops.length-1];
if (stops.length > 0 && (last[0] !== to[0] || last[1] !== to[1])) {
// the points of the path can be modified in the move mouse method - thus pushing a copy
// of the original target
stops.push(to.slice());
}
return stops;
}
}
});
</pre>
</body>
</html>