UNPKG

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
<!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(&#39;Siesta.Test.Simulate.Mouse&#39;, { requires : [ &#39;simulateEvent&#39;, &#39;getSimulateEventsWith&#39;, &#39;$&#39; ], 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 : &#39;up&#39;, // The last element we fired &#39;mousedown&#39; upon lastMouseDownEl : null, pointerEventNamesMap : { lazy : function () { if (window.PointerEvent) return { pointerdown : &#39;pointerdown&#39;, pointerup : &#39;pointerup&#39;, pointerover : &#39;pointerover&#39;, pointerout : &#39;pointerout&#39;, pointerenter : &#39;pointerenter&#39;, pointerleave : &#39;pointerleave&#39;, pointermove : &#39;pointermove&#39; } else if (window.MSPointerEvent) return { pointerdown : &#39;MSPointerDown&#39;, pointerup : &#39;MSPointerUp&#39;, pointerover : &#39;MSPointerOver&#39;, pointerout : &#39;MSPointerOut&#39;, pointerenter : &#39;MSPointerEnter&#39;, pointerleave : &#39;MSPointerLeave&#39;, pointermove : &#39;MSPointerMove&#39; } 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 == &#39;mousedown&#39;) { this.mouseState = &#39;down&#39; this.lastMouseDownEl = el; } if (supportsPointerEvents ? /pointerup$/i.test(eventName) : eventName == &#39;mouseup&#39;) { this.mouseState = &#39;up&#39; this.lastMouseUpEl = el; } var event = this.SUPERARG(arguments) if (/pointerdown$/i.test(eventName)) { this.lastPointerDownPrevented = this.isEventPrevented(event); } // in FF for &#39;textInput&#39; events the returning value can be `undefined` if (this.test &amp;&amp; this.test.mouseVisualizer &amp;&amp; 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 &amp;&amp; global.MouseEvent) { if (type === &#39;wheel&#39;) { 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 === &#39;pointermove&#39;) { options.button = -1; } event = new global.PointerEvent(type, options); } else { event = new global.MouseEvent(type, options); } } // use W3C standard when available and allowed by &quot;simulateEventsWith&quot; option else if (doc.createEvent &amp;&amp; this.getSimulateEventsWith() == &#39;dispatchEvent&#39;) { if (type === &#39;wheel&#39;) { // 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 = &#39;&#39;; if (options.ctrlKey) modifiersArg = &#39;Control&#39;; if (options.altKey) modifiersArg += &#39; Alt&#39;; if (options.shiftKey) modifiersArg += &#39; Shift&#39;; event = doc.createEvent(&#39;WheelEvent&#39;); 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 ] ? &#39;MS&#39; : &#39;&#39;) + &#39;PointerEvent&#39; : &#39;MouseEvents&#39;); event[ isPointer ? &#39;initPointerEvent&#39; : &#39;initMouseEvent&#39; ]( 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 &quot;initPointerEvent&quot; // 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 &quot;mouse&quot; (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 &quot;pageX/pageY&quot; properties of the event object are calculated by browser completely // wrong - need to override those if (this.bowser.msedge) { global.Object.defineProperty(event, &#39;pageX&#39;, { value : options.pageX }) global.Object.defineProperty(event, &#39;pageY&#39;, { value : options.pageY }) } // Mouse over is used in some certain edge cases which interfer with this tracking if (!/(mouse|pointer)over$/.test(type) &amp;&amp; !/(mouse|pointer)out$/.test(type)) { var elWindow = doc.defaultView || doc.parentWindow; var cursorX = options.clientX; var cursorY = options.clientY; // Potentially we&#39;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) &amp;&amp; !/(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 &quot;Browser Back&quot; button) * 16 : 5th button (typically the &quot;Browser Forward&quot; button) * * */ buttons : 0, relatedTarget : undefined, // pointerType // NOTE: this has to be set to &quot;mouse&quot; (IE11) or 4 (IE10, 11) because otherwise // ExtJS5 blocks the event // need to investigate what happens in SenchaTouch pointerType : bowser.msie ? 4 : &#39;mouse&#39; }, options); if (!(&quot;clientX&quot; in options) || !(&quot;clientY&quot; 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 &amp;&amp; (!(&quot;pageX&quot; in options) || !(&quot;pageY&quot; in options))) { options.pageX = this.viewportXtoPageX(options.clientX) options.pageY = this.viewportYtoPageY(options.clientY) } // Not supported in IE if (&quot;screenX&quot; 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 == &#39;instant&#39;) { 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 &amp;&amp; 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&#39;t be less than `mouseMovePrecision` // every batch is processed by one queue step (see below), so for every batch there&#39;s one call to `processor` // inside the processor, there&#39;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 == &#39;down&#39; ? 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 &lt; toIndex) }; // replace 0 with 1 to avoid infinite loop var delta = Math.min(toIndex - fromIndex, mouseMovePrecision) || 1 for (var j = fromIndex; j &lt;= 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 &gt;= path.length &amp;&amp; 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 &amp;&amp; info.el !== lastOverEl) { me.onElementAtCursorChanged(info.el, lastOverEl, x, y, options, args); } } // eof for } }); var pathLength = path.length if (pathLength &lt;= pathBatchSize &amp;&amp; mouseMovePrecision &gt;= 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 &gt;= 3) queue.addStep({ sourceIndex : pathLength - 2, targetIndex : pathLength - 1 }) } else for (var i = 0, l = pathLength; i &lt; 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, &quot;pointerenter&quot;, options) me.simulateEvent(el, &quot;mouseenter&quot;, options) }, onPointerLeave : function (el, options) { var me = this; var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric if (supportsPointerEvents) me.simulateEvent(el, &quot;pointerleave&quot;, options) me.simulateEvent(el, &quot;mouseleave&quot;, options) }, onPointerOver : function (el, options) { var me = this; var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric if (supportsPointerEvents) me.simulateEvent(el, &quot;pointerover&quot;, options) me.simulateEvent(el, &quot;mouseover&quot;, options) }, onPointerOut : function (el, options) { var me = this; var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric if (supportsPointerEvents) me.simulateEvent(el, &quot;pointerout&quot;, options) me.simulateEvent(el, &quot;mouseout&quot;, options) }, onPointerMove : function (el, options) { var me = this; var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric options.buttons = me.mouseState == &#39;up&#39; ? 0 : 1 if (supportsPointerEvents) me.simulateEvent(el, &quot;pointermove&quot;, options) me.simulateEvent(el, &quot;mousemove&quot;, options) }, onElementAtCursorChanged : function (targetEl, lastOverEl, clientX, clientY, options, movePointerInterface) { var me = this, supports = Siesta.Project.Browser.FeatureSupport().supports, overEls = me.overEls; if (lastOverEl &amp;&amp; !me.nodeIsOrphan(lastOverEl) &amp;&amp; !me.nodeIsUnloaded(lastOverEl)) { movePointerInterface.onPointerOut(lastOverEl, $.extend({ clientX : clientX, clientY : clientY, relatedTarget : targetEl }, options)) } for (var i = overEls.length - 1; i &gt;= 0; i--) { var el = overEls[ i ]; if (me.nodeIsUnloaded(el) || me.nodeIsOrphan(el)) overEls.splice(i, 1); else if (el !== targetEl &amp;&amp; me.$(el).has(targetEl).length === 0) { if (supports.mouseEnterLeave) { movePointerInterface.onPointerLeave(el, $.extend({ clientX : clientX, clientY : clientY, relatedTarget : targetEl }, options)) } overEls.splice(i, 1); } } // &quot;mouseover&quot; should be simulated before &quot;mouseleave&quot; movePointerInterface.onPointerOver(targetEl, $.extend({ clientX : clientX, clientY : clientY, relatedTarget : lastOverEl }, options)) if (supports.mouseEnterLeave &amp;&amp; jQuery.inArray(targetEl, overEls) === -1) { var els = [] var docEl = this.getRootElement(targetEl) var mouseEnterEl = targetEl // collecting all the els for which to fire the &quot;mouseenter&quot; event, strictly speaking these can be any elements // (because of absolute positioning) but in most cases it will be just parent elements while (mouseEnterEl &amp;&amp; mouseEnterEl !== docEl) { els.unshift(mouseEnterEl) mouseEnterEl = mouseEnterEl.parentNode } for (var i = 0; i &lt; 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 &#39;mouseover&#39; 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&amp;&amp;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 : &quot;pointerdown&quot;, interval : 0 } : null, { event : &quot;mousedown&quot;, 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 &amp;&amp; el !== this.lastMouseDownEl &amp;&amp; !($.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 : &quot;pointerup&quot;, interval : 0 } : null, { event : &quot;mouseup&quot; } ].concat(shouldFireClick ? [ { event : &quot;click&quot; } ] : [] ) ); }, // 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, &#39;pointerover&#39;, options); this.simulateEvent(el, &#39;mouseover&#39;, 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, &#39;pointerout&#39;, options); this.simulateEvent(el, &#39;mouseout&#39;, 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 &amp;&amp; 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() === &#39;option&#39;; 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 &amp;&amp; /mouse/i.test(data.event)) return // XXX this has to be investigated more deeply (notably the &lt;body&gt; vs &lt;html&gt; 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 - &quot;mousedown&quot; (or may be &quot;mouseup&quot;) is simulated, scroll position changes // further &quot;click&quot; 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 &amp;&amp; data.recaptureTarget) { target = elAtCursor; targetHasChanged = false } // The &quot;click&quot; event should be canceled if &quot;mousedown/up&quot; happened on different elements, // _unless_ these elements has parent/child relationship if (!isOption &amp;&amp; elAtCursor !== target &amp;&amp; !($.contains(elAtCursor, target) || $.contains(target, elAtCursor))) targetHasChanged = true // Special treatment of click event firing: // * Don&#39;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 &amp;&amp; data.event === &#39;click&#39;) { var nodeMovedInDomTree = elAtCursor === target &amp;&amp; elAtCursor.parentNode !== targetParent; // Don&#39;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 &amp;&amp; me.nodeIsOrphan(me.lastMouseDownEl))) return; var mouseDownUpTargetsChanged; // Check if mousedownElement differs from what is at cursor if ( me.lastMouseDownEl &amp;&amp; elAtCursor !== me.lastMouseDownEl &amp;&amp; !($.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 &amp;&amp; me.lastMouseUpEl !== elAtCursor &amp;&amp; !($.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 &amp;&amp; data.cancelIfTargetChanged) { return; } var event = me.simulateEvent(fireEl, data.event, options); if (!me.lastPointerDownPrevented &amp;&amp; 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 &amp;&amp; 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 : &quot;pointerdown&quot;, interval : 0 } : null, { event : &quot;mousedown&quot;, focus : true }, supportsPointerEvents ? { event : &quot;pointerup&quot;, interval : 0 } : null, { event : &quot;mouseup&quot;, interval : 0 }, { event : &quot;click&quot;, cancelIfTargetChanged : true } ] ) }, // private simulateRightClick: function (clickInfo, options) { // Mac doesn&#39;t fire mouseup when right clicking var isMac = navigator.platform.indexOf(&#39;Mac&#39;) &gt; -1; var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric options = options || {}; options.button = options.buttons = 2; return this.processMouseActionSteps( clickInfo, options, [ supportsPointerEvents ? { event : &quot;pointerdown&quot;, interval : 0 } : null, { event : &quot;mousedown&quot;, focus : true } ].concat(isMac ? [] : [ supportsPointerEvents ? { event : &quot;pointerup&quot;, interval : 0 } : null, { event : &quot;mouseup&quot;, interval : 0 } ] ).concat( [ { event : &quot;contextmenu&quot; } ] ) ) }, // private simulateDoubleClick: function (clickInfo, options) { var supportsPointerEvents = Siesta.Project.Browser.FeatureSupport().supports.PointerEventsGeneric return this.processMouseActionSteps( clickInfo, options, [ supportsPointerEvents ? { event : &quot;pointerdown&quot;, interval : 0 } : null, { event : &quot;mousedown&quot;, focus : true }, supportsPointerEvents ? { event : &quot;pointerup&quot;, interval : 0 } : null, { event : &quot;mouseup&quot;, interval : 0 }, { event : &quot;click&quot;, cancelIfTargetChanged : true }, supportsPointerEvents ? { event : &quot;pointerdown&quot;, interval : 0 } : null, { event : &quot;mousedown&quot;, recaptureTarget : true, focus : true }, supportsPointerEvents ? { event : &quot;pointerup&quot;, interval : 0 } : null, { event : &quot;mouseup&quot;, interval : 0 }, { event : &quot;click&quot; , cancelIfTargetChanged : true, interval : 0 }, { event : &quot;dblclick&quot; , 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 &quot;blur&quot; event on the currently focused element if (el) { this.test.focus(el, true) } }, simulateMouseWheel : function (targetInfo, options) { var eventName = &#39;wheel&#39;; var doc = targetInfo.el.ownerDocument; // For legacy browsers where we don&#39;t use dispatchEvent, fallback to &#39;mousewheel&#39; event (&#39;wheel&#39; cannot be simulated with doc.createEventObject in &lt;= IE9) if (!doc.createEvent || this.getSimulateEventsWith() !== &#39;dispatchEvent&#39;) { eventName = &#39;mousewheel&#39;; } return this.processMouseActionSteps( targetInfo, options, [ { event : eventName } ] ); }, // private getPathBetweenPoints: function (from, to) { if ( typeof from[0] !== &#39;number&#39; || typeof from[1] !== &#39;number&#39; || typeof to[0] !== &#39;number&#39; || typeof to[1] !== &#39;number&#39; || isNaN(from[0]) || isNaN(from[1]) || isNaN(to[0]) || isNaN(to[1]) ) { throw new Error(&#39;Incorrect arguments passed to getPathBetweenPoints: &#39; + from + &#39;, &#39; + 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 &lt; x1) { sx = 1; } else { sx = -1; } if (y0 &lt; y1) { sy = 1; } else { sy = -1; } err = dx - dy; while (x0 !== x1 || y0 !== y1) { e2 = 2 * err; if (e2 &gt; -dy) { err = err - dy; x0 = x0 + sx; } if (e2 &lt; dx) { err = err + dx; y0 = y0 + sy; } stops.push([x0, y0]); } var last = stops[stops.length-1]; if (stops.length &gt; 0 &amp;&amp; (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>