UNPKG

dom-event-simulate

Version:
1,528 lines (1,396 loc) 47.8 kB
;(function(root, factory) { if (typeof define === 'function' && define.amd) { return define(['exports'], factory); } else if (typeof exports !== 'undefined') { return factory(exports); } else { factory(root['_macaca_simulate'] || (root['_macaca_simulate'] = {})); } })(this, function(exports) { /** * @refer https://github.com/yui/yui3/tree/master/src/event-simulate */ var parseUA = function(subUA) { var numberify = function(s) { var c = 0; return parseFloat(s.replace(/\./g, function() { return (c++ === 1) ? '' : '.'; })); }; var nav = win && win.navigator; var o = { /** * Internet Explorer version number or 0. Example: 6 * @property ie * @type float * @static */ ie: 0, /** * Opera version number or 0. Example: 9.2 * @property opera * @type float * @static */ opera: 0, /** * Gecko engine revision number. Will evaluate to 1 if Gecko * is detected but the revision could not be found. Other browsers * will be 0. Example: 1.8 * <pre> * Firefox 1.0.0.4: 1.7.8 <-- Reports 1.7 * Firefox 1.5.0.9: 1.8.0.9 <-- 1.8 * Firefox 2.0.0.3: 1.8.1.3 <-- 1.81 * Firefox 3.0 <-- 1.9 * Firefox 3.5 <-- 1.91 * </pre> * @property gecko * @type float * @static */ gecko: 0, /** * AppleWebKit version. KHTML browsers that are not WebKit browsers * will evaluate to 1, other browsers 0. Example: 418.9 * <pre> * Safari 1.3.2 (312.6): 312.8.1 <-- Reports 312.8 -- currently the * latest available for Mac OSX 10.3. * Safari 2.0.2: 416 <-- hasOwnProperty introduced * Safari 2.0.4: 418 <-- preventDefault fixed * Safari 2.0.4 (419.3): 418.9.1 <-- One version of Safari may run * different versions of webkit * Safari 2.0.4 (419.3): 419 <-- Tiger installations that have been * updated, but not updated * to the latest patch. * Webkit 212 nightly: 522+ <-- Safari 3.0 precursor (with native * SVG and many major issues fixed). * Safari 3.0.4 (523.12) 523.12 <-- First Tiger release - automatic * update from 2.x via the 10.4.11 OS patch. * Webkit nightly 1/2008:525+ <-- Supports DOMContentLoaded event. * yahoo.com user agent hack removed. * </pre> * http://en.wikipedia.org/wiki/Safari_version_history * @property webkit * @type float * @static */ webkit: 0, /** * Safari will be detected as webkit, but this property will also * be populated with the Safari version number * @property safari * @type float * @static */ safari: 0, /** * Chrome will be detected as webkit, but this property will also * be populated with the Chrome version number * @property chrome * @type float * @static */ chrome: 0, /** * The mobile property will be set to a string containing any relevant * user agent information when a modern mobile browser is detected. * Currently limited to Safari on the iPhone/iPod Touch, Nokia N-series * devices with the WebKit-based browser, and Opera Mini. * @property mobile * @type string * @default null * @static */ mobile: null, /** * Adobe AIR version number or 0. Only populated if webkit is detected. * Example: 1.0 * @property air * @type float */ air: 0, /** * PhantomJS version number or 0. Only populated if webkit is detected. * Example: 1.0 * @property phantomjs * @type float */ phantomjs: 0, /** * Detects Apple iPad's OS version * @property ipad * @type float * @static */ ipad: 0, /** * Detects Apple iPhone's OS version * @property iphone * @type float * @static */ iphone: 0, /** * Detects Apples iPod's OS version * @property ipod * @type float * @static */ ipod: 0, /** * General truthy check for iPad, iPhone or iPod * @property ios * @type Boolean * @default null * @static */ ios: null, /** * Detects Googles Android OS version * @property android * @type float * @static */ android: 0, /** * Detects Kindle Silk * @property silk * @type float * @static */ silk: 0, /** * Detects Ubuntu version * @property ubuntu * @type float * @static */ ubuntu: 0, /** * Detects Kindle Silk Acceleration * @property accel * @type Boolean * @static */ accel: false, /** * Detects Palms WebOS version * @property webos * @type float * @static */ webos: 0, /** * Google Caja version number or 0. * @property caja * @type float */ caja: nav && nav.cajaVersion, /** * Set to true if the page appears to be in SSL * @property secure * @type boolean * @static */ secure: false, /** * The operating system. * * Possible values are `windows`, `macintosh`, `android`, `symbos`, `linux`, `rhino` and `ios`. * * @property os * @type string * @default null * @static */ os: null, /** * The Nodejs Version * @property nodejs * @type float * @default 0 * @static */ nodejs: 0, /** * Window8/IE10 Application host environment * @property winjs * @type Boolean * @static */ winjs: !!((typeof Windows !== 'undefined') && Windows.System), /** * Are touch/msPointer events available on this device * @property touchEnabled * @type Boolean * @static */ touchEnabled: false }; var ua = subUA || nav && nav.userAgent; var loc = win && win.location; var href = loc && loc.href; var m; /** * The User Agent string that was parsed * @property userAgent * @type String * @static */ o.userAgent = ua; o.secure = href && (href.toLowerCase().indexOf('https') === 0); if (ua) { if ((/windows|win32/i).test(ua)) { o.os = 'windows'; } else if ((/macintosh|mac_powerpc/i).test(ua)) { o.os = 'macintosh'; } else if ((/android/i).test(ua)) { o.os = 'android'; } else if ((/symbos/i).test(ua)) { o.os = 'symbos'; } else if ((/linux/i).test(ua)) { o.os = 'linux'; } else if ((/rhino/i).test(ua)) { o.os = 'rhino'; } // Modern KHTML browsers should qualify as Safari X-Grade if ((/KHTML/).test(ua)) { o.webkit = 1; } if ((/IEMobile|XBLWP7/).test(ua)) { o.mobile = 'windows'; } if ((/Fennec/).test(ua)) { o.mobile = 'gecko'; } // Modern WebKit browsers are at least X-Grade m = ua.match(/AppleWebKit\/([^\s]*)/); if (m && m[1]) { o.webkit = numberify(m[1]); o.safari = o.webkit; if (/PhantomJS/.test(ua)) { m = ua.match(/PhantomJS\/([^\s]*)/); if (m && m[1]) { o.phantomjs = numberify(m[1]); } } // Mobile browser check if (/ Mobile\//.test(ua) || (/iPad|iPod|iPhone/).test(ua)) { o.mobile = 'Apple'; // iPhone or iPod Touch m = ua.match(/OS ([^\s]*)/); if (m && m[1]) { m = numberify(m[1].replace('_', '.')); } o.ios = m; o.os = 'ios'; o.ipad = o.ipod = o.iphone = 0; m = ua.match(/iPad|iPod|iPhone/); if (m && m[0]) { o[m[0].toLowerCase()] = o.ios; } } else { m = ua.match(/NokiaN[^\/]*|webOS\/\d\.\d/); if (m) { // Nokia N-series, webOS, ex: NokiaN95 o.mobile = m[0]; } if (/webOS/.test(ua)) { o.mobile = 'WebOS'; m = ua.match(/webOS\/([^\s]*);/); if (m && m[1]) { o.webos = numberify(m[1]); } } if (/ Android/.test(ua)) { o.mobile = 'Android'; m = ua.match(/Android ([^\s]*);/); if (m && m[1]) { o.android = numberify(m[1]); } } if (/Silk/.test(ua)) { m = ua.match(/Silk\/([^\s]*)/); if (m && m[1]) { o.silk = numberify(m[1]); } if (!o.android) { o.android = 2.34; // Hack for desktop mode in Kindle o.os = 'Android'; } if (/Accelerated=true/.test(ua)) { o.accel = true; } } } m = ua.match(/OPR\/(\d+\.\d+)/); if (m && m[1]) { // Opera 15+ with Blink (pretends to be both Chrome and Safari) o.opera = numberify(m[1]); } else { m = ua.match(/(Chrome|CrMo|CriOS)\/([^\s]*)/); if (m && m[1] && m[2]) { o.chrome = numberify(m[2]); // Chrome o.safari = 0; // Reset safari back to 0 if (m[1] === 'CrMo') { o.mobile = 'chrome'; } } else { m = ua.match(/AdobeAIR\/([^\s]*)/); if (m) { o.air = m[0]; // Adobe AIR 1.0 or better } } } } m = ua.match(/Ubuntu\ (\d+\.\d+)/); if (m && m[1]) { o.os = 'linux'; o.ubuntu = numberify(m[1]); m = ua.match(/\ WebKit\/([^\s]*)/); if (m && m[1]) { o.webkit = numberify(m[1]); } m = ua.match(/\ Chromium\/([^\s]*)/); if (m && m[1]) { o.chrome = numberify(m[1]); } if (/ Mobile$/.test(ua)) { o.mobile = 'Ubuntu'; } } if (!o.webkit) { // not webkit // @todo check Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4509/1316; fi; U; ssr) if (/Opera/.test(ua)) { m = ua.match(/Opera[\s\/]([^\s]*)/); if (m && m[1]) { o.opera = numberify(m[1]); } m = ua.match(/Version\/([^\s]*)/); if (m && m[1]) { o.opera = numberify(m[1]); // opera 10+ } if (/Opera Mobi/.test(ua)) { o.mobile = 'opera'; m = ua.replace('Opera Mobi', '').match(/Opera ([^\s]*)/); if (m && m[1]) { o.opera = numberify(m[1]); } } m = ua.match(/Opera Mini[^;]*/); if (m) { o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316 } } else { // not opera or webkit m = ua.match(/MSIE ([^;]*)|Trident.*; rv:([0-9.]+)/); if (m && (m[1] || m[2])) { o.ie = numberify(m[1] || m[2]); } else { // not opera, webkit, or ie m = ua.match(/Gecko\/([^\s]*)/); if (m) { o.gecko = 1; // Gecko detected, look for revision m = ua.match(/rv:([^\s\)]*)/); if (m && m[1]) { o.gecko = numberify(m[1]); if (/Mobile|Tablet/.test(ua)) { o.mobile = 'ffos'; } } } } } } } // Check for known properties to tell if touch events are enabled on this device or if // the number of MSPointer touchpoints on this device is greater than 0. if (win && nav && !(o.chrome && o.chrome < 6)) { o.touchEnabled = (('ontouchstart' in win) || (('msMaxTouchPoints' in nav) && (nav.msMaxTouchPoints > 0))); } // It was a parsed UA, do not assign the global value. if (!subUA) { if (typeof process === 'object') { if (process.versions && process.versions.node) { // NodeJS o.os = process.platform; o.nodejs = numberify(process.versions.node); } } UA = o; } return o; }; var UA = parseUA(); // shortcuts var toString = Object.prototype.toString; var isFunction = function (o) { return toString.call(o) === '[object Function]'; }; var isString = function (o) { return toString.call(o) === '[object String]'; }; var isBoolean = function (o) { return toString.call(o) === '[object Boolean]'; }; var isObject = function (o) { return o === Object(o); }; var isNumber = function (o) { return toString.call(o) === '[object Number]'; }; var doc = document; var win = window; var mix = function (r, s) { for (var p in s) { r[p] = s[p]; } }; // mouse events supported var mouseEvents = { click: 1, dblclick: 1, mouseover: 1, mouseout: 1, mouseenter: 1, mouseleave: 1, mousedown: 1, mouseup: 1, mousemove: 1, contextmenu: 1, dragstart: 1, dragenter: 1, dragover: 1, dragleave: 1, drag: 1, drop: 1, dragend: 1, wheel: 1 }; var msPointerEvents = { MSPointerOver: 1, MSPointerOut: 1, MSPointerDown: 1, MSPointerUp: 1, MSPointerMove: 1 }; // key events supported var keyEvents = { keydown: 1, keyup: 1, keypress: 1 }; // HTML events supported var uiEvents = { submit: 1, blur: 1, change: 1, focus: 1, resize: 1, scroll: 1, select: 1 }; // events that bubble by default var bubbleEvents = { scroll: 1, resize: 1, reset: 1, submit: 1, change: 1, select: 1, error: 1, abort: 1 }; // touch events supported var touchEvents = { touchstart: 1, touchmove: 1, touchend: 1, touchcancel: 1 }; var gestureEvents = { gesturestart: 1, gesturechange: 1, gestureend: 1 }; // all key and mouse events bubble mix(bubbleEvents, mouseEvents); mix(bubbleEvents, keyEvents); mix(bubbleEvents, touchEvents); /* * Simulates a key event using the given event information to populate * the generated event object. This method does browser-equalizing * calculations to account for differences in the DOM and IE event models * as well as different browser quirks. Note: keydown causes Safari 2.x to * crash. * @method simulateKeyEvent * @private * @static * @param {HTMLElement} target The target of the given event. * @param {String} type The type of event to fire. This can be any one of * the following: keyup, keydown, and keypress. * @param {Boolean} bubbles (Optional) Indicates if the event can be * bubbled up. DOM Level 3 specifies that all key events bubble by * default. The default is true. * @param {Boolean} cancelable (Optional) Indicates if the event can be * canceled using preventDefault(). DOM Level 3 specifies that all * key events can be cancelled. The default * is true. * @param {Window} view (Optional) The view containing the target. This is * typically the window object. The default is window. * @param {Boolean} ctrlKey (Optional) Indicates if one of the CTRL keys * is pressed while the event is firing. The default is false. * @param {Boolean} altKey (Optional) Indicates if one of the ALT keys * is pressed while the event is firing. The default is false. * @param {Boolean} shiftKey (Optional) Indicates if one of the SHIFT keys * is pressed while the event is firing. The default is false. * @param {Boolean} metaKey (Optional) Indicates if one of the META keys * is pressed while the event is firing. The default is false. * @param {int} keyCode (Optional) The code for the key that is in use. * The default is 0. * @param {int} charCode (Optional) The Unicode code for the character * associated with the key being used. The default is 0. */ function simulateKeyEvent( target /* :HTMLElement*/, type /* :String*/, bubbles /* :Boolean*/, cancelable /* :Boolean*/, view /* :Window*/, ctrlKey /* :Boolean*/, altKey /* :Boolean*/, shiftKey /* :Boolean*/, metaKey /* :Boolean*/, keyCode /* :int*/, charCode /* :int*/, which /* :int*/, key ) /* :Void*/ { // check target if (!target) { throw 'simulateKeyEvent(): Invalid target.'; } // check event type if (isString(type)) { type = type.toLowerCase(); switch (type) { case 'textevent': // DOM Level 3 type = 'keypress'; break; case 'keyup': case 'keydown': case 'keypress': break; default: throw 'simulateKeyEvent(): Event type ' + type + ' not supported.'; } } else { throw 'simulateKeyEvent(): Event type must be a string.'; } // setup default values if (!isBoolean(bubbles)) { bubbles = true; // all key events bubble } if (!isBoolean(cancelable)) { cancelable = true; // all key events can be cancelled } if (!isObject(view)) { view = window; // view is typically window } if (!isBoolean(ctrlKey)) { ctrlKey = false; } if (!isBoolean(altKey)) { altKey = false; } if (!isBoolean(shiftKey)) { shiftKey = false; } if (!isBoolean(metaKey)) { metaKey = false; } if (!isNumber(keyCode)) { keyCode = 0; } if (!isNumber(charCode)) { charCode = 0; } // try to create a mouse event var customEvent /* :MouseEvent*/ = null; // check for DOM-compliant browsers first if (isFunction(doc.createEvent)) { try { // try to create key event customEvent = doc.createEvent('KeyboardEvent'); if (isFunction(customEvent.initKeyEvent)) { /* * Interesting problem: Firefox implemented a non-standard * version of initKeyEvent() based on DOM Level 2 specs. * Key event was removed from DOM Level 2 and re-introduced * in DOM Level 3 with a different interface. Firefox is the * only browser with any implementation of Key Events, so for * now, assume it's Firefox if the above line doesn't error. */ // @TODO: Decipher between Firefox's implementation and a correct one. customEvent.initKeyEvent( type, bubbles, cancelable, view, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode ); } else { // 通过KeyboardEvent构建 customEvent = new KeyboardEvent(type, { key: key || String.fromCharCode(keyCode), bubbles, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode, which }); } } catch (ex /* :Error*/) { /* * If it got here, that means key events aren't officially supported. * Safari/WebKit is a real problem now. WebKit 522 won't let you * set keyCode, charCode, or other properties if you use a * UIEvent, so we first must try to create a generic event. The * fun part is that this will throw an error on Safari 2.x. The * end result is that we need another try...catch statement just to * deal with this mess. */ try { // try to create generic event - will fail in Safari 2.x customEvent = doc.createEvent('Events'); } catch (uierror /* :Error*/) { // the above failed, so create a UIEvent for Safari 2.x customEvent = doc.createEvent('UIEvents'); } finally { customEvent.initEvent(type, bubbles, cancelable, window, 0); // initialize customEvent.which = which; customEvent.view = view; customEvent.altKey = altKey; customEvent.ctrlKey = ctrlKey; customEvent.shiftKey = shiftKey; customEvent.metaKey = metaKey; customEvent.keyCode = keyCode; customEvent.charCode = charCode; customEvent.key = key || String.fromCharCode(keyCode); } } // fire the event target.dispatchEvent(customEvent); } else if (isObject(doc.createEventObject)) { // IE // create an IE event object customEvent = doc.createEventObject(); // assign available properties customEvent.bubbles = bubbles; customEvent.cancelable = cancelable; customEvent.view = view; customEvent.ctrlKey = ctrlKey; customEvent.altKey = altKey; customEvent.shiftKey = shiftKey; customEvent.metaKey = metaKey; /* * IE doesn't support charCode explicitly. CharCode should * take precedence over any keyCode value for accurate * representation. */ customEvent.keyCode = (charCode > 0) ? charCode : keyCode; // fire the event target.fireEvent('on' + type, customEvent); } else { throw 'simulateKeyEvent(): No event simulation framework present.'; } } /* * Simulates a mouse event using the given event information to populate * the generated event object. This method does browser-equalizing * calculations to account for differences in the DOM and IE event models * as well as different browser quirks. * @method simulateMouseEvent * @private * @static * @param {HTMLElement} target The target of the given event. * @param {String} type The type of event to fire. This can be any one of * the following: click, dblclick, mousedown, mouseup, mouseout, * mouseover, and mousemove. * @param {Boolean} bubbles (Optional) Indicates if the event can be * bubbled up. DOM Level 2 specifies that all mouse events bubble by * default. The default is true. * @param {Boolean} cancelable (Optional) Indicates if the event can be * canceled using preventDefault(). DOM Level 2 specifies that all * mouse events except mousemove can be cancelled. The default * is true for all events except mousemove, for which the default * is false. * @param {Window} view (Optional) The view containing the target. This is * typically the window object. The default is window. * @param {int} detail (Optional) The number of times the mouse button has * been used. The default value is 1. * @param {int} screenX (Optional) The x-coordinate on the screen at which * point the event occured. The default is 0. * @param {int} screenY (Optional) The y-coordinate on the screen at which * point the event occured. The default is 0. * @param {int} clientX (Optional) The x-coordinate on the client at which * point the event occured. The default is 0. * @param {int} clientY (Optional) The y-coordinate on the client at which * point the event occured. The default is 0. * @param {Boolean} ctrlKey (Optional) Indicates if one of the CTRL keys * is pressed while the event is firing. The default is false. * @param {Boolean} altKey (Optional) Indicates if one of the ALT keys * is pressed while the event is firing. The default is false. * @param {Boolean} shiftKey (Optional) Indicates if one of the SHIFT keys * is pressed while the event is firing. The default is false. * @param {Boolean} metaKey (Optional) Indicates if one of the META keys * is pressed while the event is firing. The default is false. * @param {int} button (Optional) The button being pressed while the event * is executing. The value should be 0 for the primary mouse button * (typically the left button), 1 for the terciary mouse button * (typically the middle button), and 2 for the secondary mouse button * (typically the right button). The default is 0. * @param {HTMLElement} relatedTarget (Optional) For mouseout events, * this is the element that the mouse has moved to. For mouseover * events, this is the element that the mouse has moved from. This * argument is ignored for all other events. The default is null. */ function simulateMouseEvent(target, type, options) { // check target if (!target) { throw 'simulateMouseEvent(): Invalid target.'; } var bubbles = options.bubbles; var cancelable = options.cancelable; var view = options.view; var detail = options.detail; var screenX = options.screenX; var screenY = options.screenY; var clientX = options.clientX; var clientY = options.clientY; var ctrlKey = options.ctrlKey; var altKey = options.altKey; var shiftKey = options.shiftKey; var metaKey = options.metaKey; var dataTransfer = options.dataTransfer || { setDragImage() {} }; var button = options.button; var relatedTarget = options.relatedTarget; if (isString(type)) { // make sure it's a supported mouse event or an msPointerEvent. if (!mouseEvents[type.toLowerCase()] && !msPointerEvents[type]) { throw 'simulateMouseEvent(): Event type ' + type + ' not supported.'; } } else { throw 'simulateMouseEvent(): Event type must be a string.'; } // setup default values if (!isBoolean(bubbles)) { bubbles = true; // all mouse events bubble } if (!isBoolean(cancelable)) { cancelable = (type !== 'mousemove'); // mousemove is the only one that can't be cancelled } if (!isObject(view)) { view = window; // view is typically window } if (!isNumber(detail)) { detail = 1; // number of mouse clicks must be at least one } if (!isNumber(screenX)) { screenX = 0; } if (!isNumber(screenY)) { screenY = 0; } if (!isNumber(clientX)) { clientX = 0; } if (!isNumber(clientY)) { clientY = 0; } if (!isBoolean(ctrlKey)) { ctrlKey = false; } if (!isBoolean(altKey)) { altKey = false; } if (!isBoolean(shiftKey)) { shiftKey = false; } if (!isBoolean(metaKey)) { metaKey = false; } if (!isNumber(button)) { button = 0; } relatedTarget = relatedTarget || null; // try to create a mouse event var customEvent /* :MouseEvent*/ = null; if (type === 'wheel') { // this used to work in Chrome until version 49. Now it's just an anecdote. customEvent = doc.createEvent('MouseEvents'); customEvent.initEvent('wheel', true, true); mix(customEvent, options); target.dispatchEvent(customEvent); if (options.elementScroll) { // support wheelEvent(standard) & MouseWheelEvent var deltaX = 0; var deltaY = 0; if (!isNaN(options.deltaX)) { deltaX = options.deltaX; } else if (!isNaN(options.wheelDeltaX)) { deltaX = options.wheelDeltaX; } if (!isNaN(options.deltaY)) { deltaY = options.deltaY; } else if (!isNaN(options.wheelDeltaY)) { deltaY = options.wheelDeltaY; } target.scrollTo(deltaX, deltaY); } return; } // check for DOM-compliant browsers first if (isFunction(doc.createEvent)) { customEvent = doc.createEvent('MouseEvents'); customEvent.dataTransfer = dataTransfer; // Safari 2.x (WebKit 418) still doesn't implement initMouseEvent() if (customEvent.initMouseEvent) { customEvent.initMouseEvent( type, bubbles, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget ); } else { // Safari // the closest thing available in Safari 2.x is UIEvents customEvent = doc.createEvent('UIEvents'); customEvent.initEvent(type, bubbles, cancelable); customEvent.view = view; customEvent.detail = detail; customEvent.screenX = screenX; customEvent.screenY = screenY; customEvent.clientX = clientX; customEvent.clientY = clientY; customEvent.ctrlKey = ctrlKey; customEvent.altKey = altKey; customEvent.metaKey = metaKey; customEvent.shiftKey = shiftKey; customEvent.button = button; customEvent.relatedTarget = relatedTarget; } /* * Check to see if relatedTarget has been assigned. Firefox * versions less than 2.0 don't allow it to be assigned via * initMouseEvent() and the property is readonly after event * creation, so in order to keep .relatedTarget * working, assign to the IE proprietary toElement property * for mouseout event and fromElement property for mouseover * event. */ if (relatedTarget && !customEvent.relatedTarget) { if (type === 'mouseout') { customEvent.toElement = relatedTarget; } else if (type === 'mouseover') { customEvent.fromElement = relatedTarget; } } // fire the event target.dispatchEvent(customEvent); } else if (isObject(doc.createEventObject)) { // IE // create an IE event object customEvent = doc.createEventObject(); // assign available properties customEvent.bubbles = bubbles; customEvent.cancelable = cancelable; customEvent.view = view; customEvent.detail = detail; customEvent.screenX = screenX; customEvent.screenY = screenY; customEvent.clientX = clientX; customEvent.clientY = clientY; customEvent.ctrlKey = ctrlKey; customEvent.altKey = altKey; customEvent.metaKey = metaKey; customEvent.shiftKey = shiftKey; // fix button property for IE's wacky implementation switch (button) { case 0: customEvent.button = 1; break; case 1: customEvent.button = 4; break; case 2: // leave as is break; default: customEvent.button = 0; } /* * Have to use relatedTarget because IE won't allow assignment * to toElement or fromElement on generic events. This keeps * .relatedTarget. */ customEvent.relatedTarget = relatedTarget; // fire the event target.fireEvent('on' + type, customEvent); } else { throw 'simulateMouseEvent(): No event simulation framework present.'; } } /* * Simulates a UI event using the given event information to populate * the generated event object. This method does browser-equalizing * calculations to account for differences in the DOM and IE event models * as well as different browser quirks. * @method simulateHTMLEvent * @private * @static * @param {HTMLElement} target The target of the given event. * @param {String} type The type of event to fire. This can be any one of * the following: click, dblclick, mousedown, mouseup, mouseout, * mouseover, and mousemove. * @param {Boolean} bubbles (Optional) Indicates if the event can be * bubbled up. DOM Level 2 specifies that all mouse events bubble by * default. The default is true. * @param {Boolean} cancelable (Optional) Indicates if the event can be * canceled using preventDefault(). DOM Level 2 specifies that all * mouse events except mousemove can be cancelled. The default * is true for all events except mousemove, for which the default * is false. * @param {Window} view (Optional) The view containing the target. This is * typically the window object. The default is window. * @param {int} detail (Optional) The number of times the mouse button has * been used. The default value is 1. */ function simulateUIEvent( target /* :HTMLElement*/, type /* :String*/, bubbles /* :Boolean*/, cancelable /* :Boolean*/, view /* :Window*/, detail /* :int*/, data /* extra data */ ) /* :Void*/ { // check target if (!target) { throw 'simulateUIEvent(): Invalid target.'; } // check event type if (isString(type)) { type = type.toLowerCase(); // make sure it's a supported mouse event if (!uiEvents[type]) { throw "simulateUIEvent(): Event type '" + type + "' not supported."; } } else { throw 'simulateUIEvent(): Event type must be a string.'; } // try to create a mouse event var customEvent = null; // setup default values if (!isBoolean(bubbles)) { bubbles = (type in bubbleEvents); // not all events bubble } if (!isBoolean(cancelable)) { cancelable = (type === 'submit'); // submit is the only one that can be cancelled } if (!isObject(view)) { view = window; // view is typically window } if (!isNumber(detail)) { detail = 1; // usually not used but defaulted to this } // check for DOM-compliant browsers first if (isFunction(doc.createEvent)) { // just a generic UI Event object is needed customEvent = doc.createEvent('UIEvents'); customEvent.initUIEvent(type, bubbles, cancelable, view, detail); if ( target.constructor.name === 'HTMLInputElement' || target.constructor.name === 'HTMLTextAreaElement' ) { for (var dataKey in data) { Object.defineProperty(customEvent, dataKey, { get() { return data[dataKey]; } }); } } // fire the event target.dispatchEvent(customEvent); } else if (isObject(doc.createEventObject)) { // IE // create an IE event object customEvent = doc.createEventObject(); // assign available properties customEvent.bubbles = bubbles; customEvent.cancelable = cancelable; customEvent.view = view; customEvent.detail = detail; // fire the event target.fireEvent('on' + type, customEvent); } else { throw 'simulateUIEvent(): No event simulation framework present.'; } } /* * (iOS only) This is for creating native DOM gesture events which only iOS * v2.0+ is supporting. * * @method simulateGestureEvent * @private * @param {HTMLElement} target The target of the given event. * @param {String} type The type of event to fire. This can be any one of * the following: touchstart, touchmove, touchend, touchcancel. * @param {Boolean} bubbles (Optional) Indicates if the event can be * bubbled up. DOM Level 2 specifies that all mouse events bubble by * default. The default is true. * @param {Boolean} cancelable (Optional) Indicates if the event can be * canceled using preventDefault(). DOM Level 2 specifies that all * touch events except touchcancel can be cancelled. The default * is true for all events except touchcancel, for which the default * is false. * @param {Window} view (Optional) The view containing the target. This is * typically the window object. The default is window. * @param {int} detail (Optional) Specifies some detail information about * the event depending on the type of event. * @param {int} screenX (Optional) The x-coordinate on the screen at which * point the event occured. The default is 0. * @param {int} screenY (Optional) The y-coordinate on the screen at which * point the event occured. The default is 0. * @param {int} clientX (Optional) The x-coordinate on the client at which * point the event occured. The default is 0. * @param {int} clientY (Optional) The y-coordinate on the client at which * point the event occured. The default is 0. * @param {Boolean} ctrlKey (Optional) Indicates if one of the CTRL keys * is pressed while the event is firing. The default is false. * @param {Boolean} altKey (Optional) Indicates if one of the ALT keys * is pressed while the event is firing. The default is false. * @param {Boolean} shiftKey (Optional) Indicates if one of the SHIFT keys * is pressed while the event is firing. The default is false. * @param {Boolean} metaKey (Optional) Indicates if one of the META keys * is pressed while the event is firing. The default is false. * @param {float} scale (iOS v2+ only) The distance between two fingers * since the start of an event as a multiplier of the initial distance. * The default value is 1.0. * @param {float} rotation (iOS v2+ only) The delta rotation since the start * of an event, in degrees, where clockwise is positive and * counter-clockwise is negative. The default value is 0.0. */ function simulateGestureEvent( target, type, bubbles, // boolean cancelable, // boolean view, // DOMWindow detail, // long screenX, screenY, // long clientX, clientY, // long ctrlKey, altKey, shiftKey, metaKey, // boolean scale, // float rotation // float ) { var customEvent; if (!UA.ios || UA.ios < 2.0) { throw 'simulateGestureEvent(): Native gesture DOM eventframe is not available in this platform.'; } // check taget if (!target) { throw 'simulateGestureEvent(): Invalid target.'; } // check event type if (isString(type)) { type = type.toLowerCase(); // make sure it's a supported touch event if (!gestureEvents[type]) { throw "simulateTouchEvent(): Event type '" + type + "' not supported."; } } else { throw 'simulateGestureEvent(): Event type must be a string.'; } // setup default values if (isBoolean(bubbles)) { bubbles = true; } // bubble by default if (isBoolean(cancelable)) { cancelable = true; } if (isObject(view)) { view = window; } if (isNumber(detail)) { detail = 2; } // usually not used. if (isNumber(screenX)) { screenX = 0; } if (isNumber(screenY)) { screenY = 0; } if (isNumber(clientX)) { clientX = 0; } if (isNumber(clientY)) { clientY = 0; } if (isBoolean(ctrlKey)) { ctrlKey = false; } if (isBoolean(altKey)) { altKey = false; } if (isBoolean(shiftKey)) { shiftKey = false; } if (isBoolean(metaKey)) { metaKey = false; } if (isNumber(scale)) { scale = 1.0; } if (isNumber(rotation)) { rotation = 0.0; } customEvent = doc.createEvent('GestureEvent'); customEvent.initGestureEvent( type, bubbles, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, target, scale, rotation ); target.dispatchEvent(customEvent); } /* * @method simulateTouchEvent * @private * @param {HTMLElement} target The target of the given event. * @param {String} type The type of event to fire. This can be any one of * the following: touchstart, touchmove, touchend, touchcancel. * @param {Boolean} bubbles (Optional) Indicates if the event can be * bubbled up. DOM Level 2 specifies that all mouse events bubble by * default. The default is true. * @param {Boolean} cancelable (Optional) Indicates if the event can be * canceled using preventDefault(). DOM Level 2 specifies that all * touch events except touchcancel can be cancelled. The default * is true for all events except touchcancel, for which the default * is false. * @param {Window} view (Optional) The view containing the target. This is * typically the window object. The default is window. * @param {int} detail (Optional) Specifies some detail information about * the event depending on the type of event. * @param {int} screenX (Optional) The x-coordinate on the screen at which * point the event occured. The default is 0. * @param {int} screenY (Optional) The y-coordinate on the screen at which * point the event occured. The default is 0. * @param {int} clientX (Optional) The x-coordinate on the client at which * point the event occured. The default is 0. * @param {int} clientY (Optional) The y-coordinate on the client at which * point the event occured. The default is 0. * @param {TouchList} touches A collection of Touch objects representing * all touches associated with this event. * @param {TouchList} targetTouches A collection of Touch objects * representing all touches associated with this target. * @param {TouchList} changedTouches A collection of Touch objects * representing all touches that changed in this event. * @param {float} scale (iOS v2+ only) The distance between two fingers * since the start of an event as a multiplier of the initial distance. * The default value is 1.0. * @param {float} rotation (iOS v2+ only) The delta rotation since the start * of an event, in degrees, where clockwise is positive and * counter-clockwise is negative. The default value is 0.0. */ function simulateTouchEvent(target, type, bubbles, // boolean cancelable, // boolean view, // DOMWindow detail, // long screenX, screenY, // long clientX, clientY, // long touches, // TouchList targetTouches, // TouchList changedTouches, // TouchList scale, // float rotation // float ) { // check taget if (!target) { throw 'simulateTouchEvent(): Invalid target.'; } // check event type if (isString(type)) { type = type.toLowerCase(); // make sure it's a supported touch event if (!touchEvents[type]) { throw "simulateTouchEvent(): Event type '" + type + "' not supported."; } } else { throw 'simulateTouchEvent(): Event type must be a string.'; } // setup default values if (!isBoolean(bubbles)) { bubbles = true; } // bubble by default. if (!isBoolean(cancelable)) { cancelable = (type !== 'touchcancel'); // touchcancel is not cancelled } if (!isObject(view)) { view = window; } if (!isNumber(detail)) { detail = 1; } // usually not used. defaulted to # of touch objects. if (!isNumber(screenX)) { screenX = 0; } if (!isNumber(screenY)) { screenY = 0; } if (!isNumber(clientX)) { clientX = 0; } if (!isNumber(clientY)) { clientY = 0; } var touch = new Touch({ target: target, identifier: 1, screenX: screenX, screenY: screenY, clientX: clientX, clientY: clientY }); var customEvent = new TouchEvent(type, { bubbles: bubbles, cancelable: cancelable, view: view, detail: detail, touches: touches || [touch], targetTouches: targetTouches || [touch], changedTouches: changedTouches || [touch] }); target.dispatchEvent(customEvent); } /** * Simulates the event or gesture with the given name on a target. * @param {HTMLElement} target The DOM element that's the target of the event. * @param {String} type The type of event or name of the supported gesture to simulate * (i.e., "click", "doubletap", "flick"). * @param {Object} options (Optional) Extra options to copy onto the event object. * For gestures, options are used to refine the gesture behavior. * @return {void} * @for Event * @method domEvent * @static */ exports.domEvent = function(target, type, options) { if (target[0]) { target = target[0]; } options = options || {}; if (mouseEvents[type] || msPointerEvents[type]) { simulateMouseEvent(target, type, options); } else if (keyEvents[type]) { simulateKeyEvent( target, type, options.bubbles, options.cancelable, options.view, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode, options.which, options.key ); } else if (uiEvents[type]) { simulateUIEvent( target, type, options.bubbles, options.cancelable, options.view, options.detail, options.data || {} ); // touch low-level event simulation } else if (touchEvents[type]) { if (window && window.TouchEvent) { simulateTouchEvent( target, 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.touches, options.targetTouches, options.changedTouches, options.scale, options.rotation ); } else { throw 'simulate(): Event ' + type + ' can\'t be simulated. Use gesture-simulate module instead.'; } // ios gesture low-level event simulation (iOS v2+ only) } else if (UA.ios && UA.ios >= 2.0 && gestureEvents[type]) { simulateGestureEvent( target, 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.scale, options.rotation ); // anything else } else { throw "simulate(): Event '" + type + "' can't be simulated."; } }; exports.helper = { // Type content to input element. formInput: function(element, value) { if (!element) { throw Error('element not found'); } const setValue = Object.getOwnPropertyDescriptor( window[element.constructor.name].prototype, 'value' ).set; setValue.call(element, value); const event = document.createEvent('Event'); event.initEvent('input', true, true); element.dispatchEvent(event); }, // Type content to a contentEditable element. elementInput: function(element, value) { if (!element) { throw Error('element not found'); } const setValue = Object.getOwnPropertyDescriptor( window.Element.prototype, 'innerHTML' ).set; setValue.call(element, value); const event = document.createEvent('Event'); event.initEvent('input', true, true); element.dispatchEvent(event); } }; });