1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- // Provides functionality for creating and sending DOM events.
- "use strict";
- const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
- Cu.import("resource://gre/modules/Log.jsm");
- const logger = Log.repository.getLogger("Marionette");
- Cu.import("chrome://marionette/content/element.js");
- Cu.import("chrome://marionette/content/error.js");
- this.EXPORTED_SYMBOLS = ["event"];
- // must be synchronised with nsIDOMWindowUtils
- const COMPOSITION_ATTR_RAWINPUT = 0x02;
- const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
- const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
- const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
- // TODO(ato): Document!
- let seenEvent = false;
- function getDOMWindowUtils(win) {
- if (!win) {
- win = window;
- }
- // this assumes we are operating in chrome space
- return win.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- }
- this.event = {};
- event.MouseEvents = {
- click: 0,
- dblclick: 1,
- mousedown: 2,
- mouseup: 3,
- mouseover: 4,
- mouseout: 5,
- };
- event.Modifiers = {
- shiftKey: 0,
- ctrlKey: 1,
- altKey: 2,
- metaKey: 3,
- };
- /**
- * Sends a mouse event to given target.
- *
- * @param {nsIDOMMouseEvent} mouseEvent
- * Event to send.
- * @param {(DOMElement|string)} target
- * Target of event. Can either be an element or the ID of an element.
- * @param {Window=} window
- * Window object. Defaults to the current window.
- *
- * @throws {TypeError}
- * If the event is unsupported.
- */
- event.sendMouseEvent = function (mouseEvent, target, window = undefined) {
- if (!event.MouseEvents.hasOwnProperty(mouseEvent.type)) {
- throw new TypeError("Unsupported event type: " + mouseEvent.type);
- }
- if (!target.nodeType && typeof target != "string") {
- throw new TypeError("Target can only be a DOM element or a string: " + target);
- }
- if (!target.nodeType) {
- target = window.document.getElementById(target);
- } else {
- window = window || target.ownerDocument.defaultView;
- }
- let ev = window.document.createEvent("MouseEvent");
- let type = mouseEvent.type;
- let view = window;
- let detail = mouseEvent.detail;
- if (!detail) {
- if (mouseEvent.type in ["click", "mousedown", "mouseup"]) {
- detail = 1;
- } else if (mouseEvent.type == "dblclick") {
- detail = 2;
- } else {
- detail = 0;
- }
- }
- let screenX = mouseEvent.screenX || 0;
- let screenY = mouseEvent.screenY || 0;
- let clientX = mouseEvent.clientX || 0;
- let clientY = mouseEvent.clientY || 0;
- let ctrlKey = mouseEvent.ctrlKey || false;
- let altKey = mouseEvent.altKey || false;
- let shiftKey = mouseEvent.shiftKey || false;
- let metaKey = mouseEvent.metaKey || false;
- let button = mouseEvent.button || 0;
- let relatedTarget = mouseEvent.relatedTarget || null;
- ev.initMouseEvent(
- mouseEvent.type,
- /* canBubble */ true,
- /* cancelable */ true,
- view,
- detail,
- screenX,
- screenY,
- clientX,
- clientY,
- ctrlKey,
- altKey,
- shiftKey,
- metaKey,
- button,
- relatedTarget);
- };
- /**
- * Send character to the currently focused element.
- *
- * This function handles casing of characters (sends the right charcode,
- * and sends a shift key for uppercase chars). No other modifiers are
- * handled at this point.
- *
- * For now this method only works for English letters (lower and upper
- * case) and the digits 0-9.
- */
- event.sendChar = function (char, window = undefined) {
- // DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9
- let hasShift = (char == char.toUpperCase());
- event.synthesizeKey(char, {shiftKey: hasShift}, window);
- };
- /**
- * Send string to the focused element.
- *
- * For now this method only works for English letters (lower and upper
- * case) and the digits 0-9.
- */
- event.sendString = function (string, window = undefined) {
- for (let i = 0; i < string.length; ++i) {
- event.sendChar(string.charAt(i), window);
- }
- };
- /**
- * Send the non-character key to the focused element.
- *
- * The name of the key should be the part that comes after "DOM_VK_"
- * in the nsIDOMKeyEvent constant name for this key. No modifiers are
- * handled at this point.
- */
- event.sendKey = function (key, window = undefined) {
- let keyName = "VK_" + key.toUpperCase();
- event.synthesizeKey(keyName, {shiftKey: false}, window);
- };
- // TODO(ato): Unexpose this when action.Chain#emitMouseEvent
- // no longer emits its own events
- event.parseModifiers_ = function (modifiers) {
- let mval = 0;
- if (modifiers.shiftKey) {
- mval |= Ci.nsIDOMNSEvent.SHIFT_MASK;
- }
- if (modifiers.ctrlKey) {
- mval |= Ci.nsIDOMNSEvent.CONTROL_MASK;
- }
- if (modifiers.altKey) {
- mval |= Ci.nsIDOMNSEvent.ALT_MASK;
- }
- if (modifiers.metaKey) {
- mval |= Ci.nsIDOMNSEvent.META_MASK;
- }
- if (modifiers.accelKey) {
- if (navigator.platform.indexOf("Mac") >= 0) {
- mval |= Ci.nsIDOMNSEvent.META_MASK;
- } else {
- mval |= Ci.nsIDOMNSEvent.CONTROL_MASK;
- }
- }
- return mval;
- };
- /**
- * Synthesise a mouse event on a target.
- *
- * The actual client point is determined by taking the aTarget's client
- * box and offseting it by offsetX and offsetY. This allows mouse clicks
- * to be simulated by calling this method.
- *
- * If the type is specified, an mouse event of that type is
- * fired. Otherwise, a mousedown followed by a mouse up is performed.
- *
- * @param {Element} element
- * Element to click.
- * @param {number} offsetX
- * Horizontal offset to click from the target's bounding box.
- * @param {number} offsetY
- * Vertical offset to click from the target's bounding box.
- * @param {Object.<string, ?>} opts
- * Object which may contain the properties "shiftKey", "ctrlKey",
- * "altKey", "metaKey", "accessKey", "clickCount", "button", and
- * "type".
- * @param {Window=} window
- * Window object. Defaults to the current window.
- */
- event.synthesizeMouse = function (
- element, offsetX, offsetY, opts, window = undefined) {
- let rect = element.getBoundingClientRect();
- event.synthesizeMouseAtPoint(
- rect.left + offsetX, rect.top + offsetY, opts, window);
- };
- /*
- * Synthesize a mouse event at a particular point in a window.
- *
- * If the type of the event is specified, a mouse event of that type is
- * fired. Otherwise, a mousedown followed by a mouse up is performed.
- *
- * @param {number} left
- * CSS pixels from the left document margin.
- * @param {number} top
- * CSS pixels from the top document margin.
- * @param {Object.<string, ?>} opts
- * Object which may contain the properties "shiftKey", "ctrlKey",
- * "altKey", "metaKey", "accessKey", "clickCount", "button", and
- * "type".
- * @param {Window=} window
- * Window object. Defaults to the current window.
- */
- event.synthesizeMouseAtPoint = function (
- left, top, opts, window = undefined) {
- let domutils = getDOMWindowUtils(window);
- let button = opts.button || 0;
- let clickCount = opts.clickCount || 1;
- let modifiers = event.parseModifiers_(opts);
- let pressure = ("pressure" in opts) ? opts.pressure : 0;
- let inputSource = ("inputSource" in opts) ? opts.inputSource :
- Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE;
- let isDOMEventSynthesized =
- ("isSynthesized" in opts) ? opts.isSynthesized : true;
- let isWidgetEventSynthesized =
- ("isWidgetEventSynthesized" in opts) ? opts.isWidgetEventSynthesized : false;
- let buttons = ("buttons" in opts) ? opts.buttons : domutils.MOUSE_BUTTONS_NOT_SPECIFIED;
- if (("type" in opts) && opts.type) {
- domutils.sendMouseEvent(
- opts.type, left, top, button, clickCount, modifiers, false, pressure, inputSource,
- isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
- } else {
- domutils.sendMouseEvent(
- "mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource,
- isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
- domutils.sendMouseEvent(
- "mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource,
- isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
- }
- };
- /**
- * Call event.synthesizeMouse with coordinates at the centre of the
- * target.
- */
- event.synthesizeMouseAtCenter = function (element, event, window) {
- let rect = element.getBoundingClientRect();
- event.synthesizeMouse(
- element,
- rect.width / 2,
- rect.height / 2,
- event,
- window);
- };
- /**
- * Synthesise a mouse scroll event on a target.
- *
- * The actual client point is determined by taking the target's client
- * box and offseting it by |offsetX| and |offsetY|.
- *
- * If the |type| property is specified for the |event| argument, a mouse
- * scroll event of that type is fired. Otherwise, DOMMouseScroll is used.
- *
- * If the |axis| is specified, it must be one of "horizontal" or
- * "vertical". If not specified, "vertical" is used.
- *
- * |delta| is the amount to scroll by (can be positive or negative).
- * It must be specified.
- *
- * |hasPixels| specifies whether kHasPixels should be set in the
- * |scrollFlags|.
- *
- * |isMomentum| specifies whether kIsMomentum should be set in the
- * |scrollFlags|.
- *
- * @param {Element} target
- * @param {number} offsetY
- * @param {number} offsetY
- * @param {Object.<string, ?>} event
- * Object which may contain the properties shiftKey, ctrlKey, altKey,
- * metaKey, accessKey, button, type, axis, delta, and hasPixels.
- * @param {Window=} window
- * Window object. Defaults to the current window.
- */
- event.synthesizeMouseScroll = function (
- target, offsetX, offsetY, ev, window = undefined) {
- let domutils = getDOMWindowUtils(window);
- // see nsMouseScrollFlags in nsGUIEvent.h
- const kIsVertical = 0x02;
- const kIsHorizontal = 0x04;
- const kHasPixels = 0x08;
- const kIsMomentum = 0x40;
- let button = ev.button || 0;
- let modifiers = event.parseModifiers_(ev);
- let rect = target.getBoundingClientRect();
- let left = rect.left;
- let top = rect.top;
- let type = (("type" in ev) && ev.type) || "DOMMouseScroll";
- let axis = ev.axis || "vertical";
- let scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical;
- if (ev.hasPixels) {
- scrollFlags |= kHasPixels;
- }
- if (ev.isMomentum) {
- scrollFlags |= kIsMomentum;
- }
- domutils.sendMouseScrollEvent(
- type,
- left + offsetX,
- top + offsetY,
- button,
- scrollFlags,
- ev.delta,
- modifiers);
- };
- function computeKeyCodeFromChar_(char) {
- if (char.length != 1) {
- return 0;
- }
- if (char >= "a" && char <= "z") {
- return Ci.nsIDOMKeyEvent.DOM_VK_A + char.charCodeAt(0) - "a".charCodeAt(0);
- }
- if (char >= "A" && char <= "Z") {
- return Ci.nsIDOMKeyEvent.DOM_VK_A + char.charCodeAt(0) - "A".charCodeAt(0);
- }
- if (char >= "0" && char <= "9") {
- return Ci.nsIDOMKeyEvent.DOM_VK_0 + char.charCodeAt(0) - "0".charCodeAt(0);
- }
- // returns US keyboard layout's keycode
- switch (char) {
- case "~":
- case "`":
- return Ci.nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
- case "!":
- return Ci.nsIDOMKeyEvent.DOM_VK_1;
- case "@":
- return Ci.nsIDOMKeyEvent.DOM_VK_2;
- case "#":
- return Ci.nsIDOMKeyEvent.DOM_VK_3;
- case "$":
- return Ci.nsIDOMKeyEvent.DOM_VK_4;
- case "%":
- return Ci.nsIDOMKeyEvent.DOM_VK_5;
- case "^":
- return Ci.nsIDOMKeyEvent.DOM_VK_6;
- case "&":
- return Ci.nsIDOMKeyEvent.DOM_VK_7;
- case "*":
- return Ci.nsIDOMKeyEvent.DOM_VK_8;
- case "(":
- return Ci.nsIDOMKeyEvent.DOM_VK_9;
- case ")":
- return Ci.nsIDOMKeyEvent.DOM_VK_0;
- case "-":
- case "_":
- return Ci.nsIDOMKeyEvent.DOM_VK_SUBTRACT;
- case "+":
- case "=":
- return Ci.nsIDOMKeyEvent.DOM_VK_EQUALS;
- case "{":
- case "[":
- return Ci.nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
- case "}":
- case "]":
- return Ci.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
- case "|":
- case "\\":
- return Ci.nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
- case ":":
- case ";":
- return Ci.nsIDOMKeyEvent.DOM_VK_SEMICOLON;
- case "'":
- case "\"":
- return Ci.nsIDOMKeyEvent.DOM_VK_QUOTE;
- case "<":
- case ",":
- return Ci.nsIDOMKeyEvent.DOM_VK_COMMA;
- case ">":
- case ".":
- return Ci.nsIDOMKeyEvent.DOM_VK_PERIOD;
- case "?":
- case "/":
- return Ci.nsIDOMKeyEvent.DOM_VK_SLASH;
- case "\n":
- return Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
- default:
- return 0;
- }
- }
- /**
- * Returns true if the given key should cause keypress event when widget
- * handles the native key event. Otherwise, false.
- *
- * The key code should be one of consts of nsIDOMKeyEvent.DOM_VK_*,
- * or a key name begins with "VK_", or a character.
- */
- event.isKeypressFiredKey = function (key) {
- if (typeof key == "string") {
- if (key.indexOf("VK_") === 0) {
- key = Ci.nsIDOMKeyEvent["DOM_" + key];
- if (!key) {
- throw new TypeError("Unknown key: " + key);
- }
- // if key generates a character, it must cause a keypress event
- } else {
- return true;
- }
- }
- switch (key) {
- case Ci.nsIDOMKeyEvent.DOM_VK_SHIFT:
- case Ci.nsIDOMKeyEvent.DOM_VK_CONTROL:
- case Ci.nsIDOMKeyEvent.DOM_VK_ALT:
- case Ci.nsIDOMKeyEvent.DOM_VK_CAPS_LOCK:
- case Ci.nsIDOMKeyEvent.DOM_VK_NUM_LOCK:
- case Ci.nsIDOMKeyEvent.DOM_VK_SCROLL_LOCK:
- case Ci.nsIDOMKeyEvent.DOM_VK_META:
- return false;
- default:
- return true;
- }
- };
- /**
- * Synthesise a key event.
- *
- * It is targeted at whatever would be targeted by an actual keypress
- * by the user, typically the focused element.
- *
- * @param {string} key
- * Key to synthesise. Should either be a character or a key code
- * starting with "VK_" such as VK_RETURN, or a normalized key value.
- * @param {Object.<string, ?>} event
- * Object which may contain the properties shiftKey, ctrlKey, altKey,
- * metaKey, accessKey, type. If the type is specified (keydown or keyup),
- * a key event of that type is fired. Otherwise, a keydown, a keypress,
- * and then a keyup event are fired in sequence.
- * @param {Window=} window
- * Window object. Defaults to the current window.
- *
- * @throws {TypeError}
- * If unknown key.
- */
- event.synthesizeKey = function (key, event, win = undefined)
- {
- var TIP = getTIP_(win);
- if (!TIP) {
- return;
- }
- var KeyboardEvent = getKeyboardEvent_(win);
- var modifiers = emulateToActivateModifiers_(TIP, event, win);
- var keyEventDict = createKeyboardEventDictionary_(key, event, win);
- var keyEvent = new KeyboardEvent("", keyEventDict.dictionary);
- var dispatchKeydown =
- !("type" in event) || event.type === "keydown" || !event.type;
- var dispatchKeyup =
- !("type" in event) || event.type === "keyup" || !event.type;
- try {
- if (dispatchKeydown) {
- TIP.keydown(keyEvent, keyEventDict.flags);
- if ("repeat" in event && event.repeat > 1) {
- keyEventDict.dictionary.repeat = true;
- var repeatedKeyEvent = new KeyboardEvent("", keyEventDict.dictionary);
- for (var i = 1; i < event.repeat; i++) {
- TIP.keydown(repeatedKeyEvent, keyEventDict.flags);
- }
- }
- }
- if (dispatchKeyup) {
- TIP.keyup(keyEvent, keyEventDict.flags);
- }
- } finally {
- emulateToInactivateModifiers_(TIP, modifiers, win);
- }
- };
- var TIPMap = new WeakMap();
- function getTIP_(win, callback)
- {
- if (!win) {
- win = window;
- }
- var tip;
- if (TIPMap.has(win)) {
- tip = TIPMap.get(win);
- } else {
- tip =
- Cc["@mozilla.org/text-input-processor;1"].
- createInstance(Ci.nsITextInputProcessor);
- TIPMap.set(win, tip);
- }
- if (!tip.beginInputTransactionForTests(win, callback)) {
- tip = null;
- TIPMap.delete(win);
- }
- return tip;
- }
- function getKeyboardEvent_(win = window)
- {
- if (typeof KeyboardEvent != "undefined") {
- try {
- // See if the object can be instantiated; sometimes this yields
- // 'TypeError: can't access dead object' or 'KeyboardEvent is not a constructor'.
- new KeyboardEvent("", {});
- return KeyboardEvent;
- } catch (ex) {}
- }
- if (typeof content != "undefined" && ("KeyboardEvent" in content)) {
- return content.KeyboardEvent;
- }
- return win.KeyboardEvent;
- }
- function createKeyboardEventDictionary_(key, keyEvent, win = window) {
- var result = { dictionary: null, flags: 0 };
- var keyCodeIsDefined = "keyCode" in keyEvent;
- var keyCode =
- (keyCodeIsDefined && keyEvent.keyCode >= 0 && keyEvent.keyCode <= 255) ?
- keyEvent.keyCode : 0;
- var keyName = "Unidentified";
- if (key.indexOf("KEY_") == 0) {
- keyName = key.substr("KEY_".length);
- result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
- } else if (key.indexOf("VK_") == 0) {
- keyCode = Ci.nsIDOMKeyEvent["DOM_" + key];
- if (!keyCode) {
- throw "Unknown key: " + key;
- }
- keyName = guessKeyNameFromKeyCode_(keyCode, win);
- result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
- } else if (key != "") {
- keyName = key;
- if (!keyCodeIsDefined) {
- keyCode = computeKeyCodeFromChar_(key.charAt(0));
- }
- if (!keyCode) {
- result.flags |= Ci.nsITextInputProcessor.KEY_KEEP_KEYCODE_ZERO;
- }
- // keyName was already determined in keyEvent so no fall-back needed
- if (!("key" in keyEvent && keyName == keyEvent.key)) {
- result.flags |= Ci.nsITextInputProcessor.KEY_FORCE_PRINTABLE_KEY;
- }
- }
- var locationIsDefined = "location" in keyEvent;
- if (locationIsDefined && keyEvent.location === 0) {
- result.flags |= Ci.nsITextInputProcessor.KEY_KEEP_KEY_LOCATION_STANDARD;
- }
- result.dictionary = {
- key: keyName,
- code: "code" in keyEvent ? keyEvent.code : "",
- location: locationIsDefined ? keyEvent.location : 0,
- repeat: "repeat" in keyEvent ? keyEvent.repeat === true : false,
- keyCode: keyCode,
- };
- return result;
- }
- function emulateToActivateModifiers_(TIP, keyEvent, win = window)
- {
- if (!keyEvent) {
- return null;
- }
- var KeyboardEvent = getKeyboardEvent_(win);
- var navigator = getNavigator_(win);
- var modifiers = {
- normal: [
- { key: "Alt", attr: "altKey" },
- { key: "AltGraph", attr: "altGraphKey" },
- { key: "Control", attr: "ctrlKey" },
- { key: "Fn", attr: "fnKey" },
- { key: "Meta", attr: "metaKey" },
- { key: "OS", attr: "osKey" },
- { key: "Shift", attr: "shiftKey" },
- { key: "Symbol", attr: "symbolKey" },
- { key: isMac_(win) ? "Meta" : "Control",
- attr: "accelKey" },
- ],
- lockable: [
- { key: "CapsLock", attr: "capsLockKey" },
- { key: "FnLock", attr: "fnLockKey" },
- { key: "NumLock", attr: "numLockKey" },
- { key: "ScrollLock", attr: "scrollLockKey" },
- { key: "SymbolLock", attr: "symbolLockKey" },
- ]
- }
- for (var i = 0; i < modifiers.normal.length; i++) {
- if (!keyEvent[modifiers.normal[i].attr]) {
- continue;
- }
- if (TIP.getModifierState(modifiers.normal[i].key)) {
- continue; // already activated.
- }
- var event = new KeyboardEvent("", { key: modifiers.normal[i].key });
- TIP.keydown(event,
- TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
- modifiers.normal[i].activated = true;
- }
- for (var i = 0; i < modifiers.lockable.length; i++) {
- if (!keyEvent[modifiers.lockable[i].attr]) {
- continue;
- }
- if (TIP.getModifierState(modifiers.lockable[i].key)) {
- continue; // already activated.
- }
- var event = new KeyboardEvent("", { key: modifiers.lockable[i].key });
- TIP.keydown(event,
- TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
- TIP.keyup(event,
- TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
- modifiers.lockable[i].activated = true;
- }
- return modifiers;
- }
- function emulateToInactivateModifiers_(TIP, modifiers, win = window)
- {
- if (!modifiers) {
- return;
- }
- var KeyboardEvent = getKeyboardEvent_(win);
- for (var i = 0; i < modifiers.normal.length; i++) {
- if (!modifiers.normal[i].activated) {
- continue;
- }
- var event = new KeyboardEvent("", { key: modifiers.normal[i].key });
- TIP.keyup(event,
- TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
- }
- for (var i = 0; i < modifiers.lockable.length; i++) {
- if (!modifiers.lockable[i].activated) {
- continue;
- }
- if (!TIP.getModifierState(modifiers.lockable[i].key)) {
- continue; // who already inactivated this?
- }
- var event = new KeyboardEvent("", { key: modifiers.lockable[i].key });
- TIP.keydown(event,
- TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
- TIP.keyup(event,
- TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
- }
- }
- function getNavigator_(win = window)
- {
- if (typeof navigator != "undefined") {
- return navigator;
- }
- return win.navigator;
- }
- function isMac_(win = window) {
- if (win) {
- try {
- return win.navigator.platform.indexOf("Mac") > -1;
- } catch (ex) {}
- }
- return navigator.platform.indexOf("Mac") > -1;
- }
- function guessKeyNameFromKeyCode_(aKeyCode, win = window)
- {
- var KeyboardEvent = getKeyboardEvent_(win);
- switch (aKeyCode) {
- case KeyboardEvent.DOM_VK_CANCEL:
- return "Cancel";
- case KeyboardEvent.DOM_VK_HELP:
- return "Help";
- case KeyboardEvent.DOM_VK_BACK_SPACE:
- return "Backspace";
- case KeyboardEvent.DOM_VK_TAB:
- return "Tab";
- case KeyboardEvent.DOM_VK_CLEAR:
- return "Clear";
- case KeyboardEvent.DOM_VK_RETURN:
- return "Enter";
- case KeyboardEvent.DOM_VK_SHIFT:
- return "Shift";
- case KeyboardEvent.DOM_VK_CONTROL:
- return "Control";
- case KeyboardEvent.DOM_VK_ALT:
- return "Alt";
- case KeyboardEvent.DOM_VK_PAUSE:
- return "Pause";
- case KeyboardEvent.DOM_VK_EISU:
- return "Eisu";
- case KeyboardEvent.DOM_VK_ESCAPE:
- return "Escape";
- case KeyboardEvent.DOM_VK_CONVERT:
- return "Convert";
- case KeyboardEvent.DOM_VK_NONCONVERT:
- return "NonConvert";
- case KeyboardEvent.DOM_VK_ACCEPT:
- return "Accept";
- case KeyboardEvent.DOM_VK_MODECHANGE:
- return "ModeChange";
- case KeyboardEvent.DOM_VK_PAGE_UP:
- return "PageUp";
- case KeyboardEvent.DOM_VK_PAGE_DOWN:
- return "PageDown";
- case KeyboardEvent.DOM_VK_END:
- return "End";
- case KeyboardEvent.DOM_VK_HOME:
- return "Home";
- case KeyboardEvent.DOM_VK_LEFT:
- return "ArrowLeft";
- case KeyboardEvent.DOM_VK_UP:
- return "ArrowUp";
- case KeyboardEvent.DOM_VK_RIGHT:
- return "ArrowRight";
- case KeyboardEvent.DOM_VK_DOWN:
- return "ArrowDown";
- case KeyboardEvent.DOM_VK_SELECT:
- return "Select";
- case KeyboardEvent.DOM_VK_PRINT:
- return "Print";
- case KeyboardEvent.DOM_VK_EXECUTE:
- return "Execute";
- case KeyboardEvent.DOM_VK_PRINTSCREEN:
- return "PrintScreen";
- case KeyboardEvent.DOM_VK_INSERT:
- return "Insert";
- case KeyboardEvent.DOM_VK_DELETE:
- return "Delete";
- case KeyboardEvent.DOM_VK_WIN:
- return "OS";
- case KeyboardEvent.DOM_VK_CONTEXT_MENU:
- return "ContextMenu";
- case KeyboardEvent.DOM_VK_SLEEP:
- return "Standby";
- case KeyboardEvent.DOM_VK_F1:
- return "F1";
- case KeyboardEvent.DOM_VK_F2:
- return "F2";
- case KeyboardEvent.DOM_VK_F3:
- return "F3";
- case KeyboardEvent.DOM_VK_F4:
- return "F4";
- case KeyboardEvent.DOM_VK_F5:
- return "F5";
- case KeyboardEvent.DOM_VK_F6:
- return "F6";
- case KeyboardEvent.DOM_VK_F7:
- return "F7";
- case KeyboardEvent.DOM_VK_F8:
- return "F8";
- case KeyboardEvent.DOM_VK_F9:
- return "F9";
- case KeyboardEvent.DOM_VK_F10:
- return "F10";
- case KeyboardEvent.DOM_VK_F11:
- return "F11";
- case KeyboardEvent.DOM_VK_F12:
- return "F12";
- case KeyboardEvent.DOM_VK_F13:
- return "F13";
- case KeyboardEvent.DOM_VK_F14:
- return "F14";
- case KeyboardEvent.DOM_VK_F15:
- return "F15";
- case KeyboardEvent.DOM_VK_F16:
- return "F16";
- case KeyboardEvent.DOM_VK_F17:
- return "F17";
- case KeyboardEvent.DOM_VK_F18:
- return "F18";
- case KeyboardEvent.DOM_VK_F19:
- return "F19";
- case KeyboardEvent.DOM_VK_F20:
- return "F20";
- case KeyboardEvent.DOM_VK_F21:
- return "F21";
- case KeyboardEvent.DOM_VK_F22:
- return "F22";
- case KeyboardEvent.DOM_VK_F23:
- return "F23";
- case KeyboardEvent.DOM_VK_F24:
- return "F24";
- case KeyboardEvent.DOM_VK_NUM_LOCK:
- return "NumLock";
- case KeyboardEvent.DOM_VK_SCROLL_LOCK:
- return "ScrollLock";
- case KeyboardEvent.DOM_VK_VOLUME_MUTE:
- return "AudioVolumeMute";
- case KeyboardEvent.DOM_VK_VOLUME_DOWN:
- return "AudioVolumeDown";
- case KeyboardEvent.DOM_VK_VOLUME_UP:
- return "AudioVolumeUp";
- case KeyboardEvent.DOM_VK_META:
- return "Meta";
- case KeyboardEvent.DOM_VK_ALTGR:
- return "AltGraph";
- case KeyboardEvent.DOM_VK_ATTN:
- return "Attn";
- case KeyboardEvent.DOM_VK_CRSEL:
- return "CrSel";
- case KeyboardEvent.DOM_VK_EXSEL:
- return "ExSel";
- case KeyboardEvent.DOM_VK_EREOF:
- return "EraseEof";
- case KeyboardEvent.DOM_VK_PLAY:
- return "Play";
- default:
- return "Unidentified";
- }
- }
- /**
- * Indicate that an event with an original target and type is expected
- * to be fired, or not expected to be fired.
- */
- function expectEvent_(expectedTarget, expectedEvent, testName) {
- if (!expectedTarget || !expectedEvent) {
- return null;
- }
- seenEvent = false;
- let type;
- if (expectedEvent.charAt(0) == "!") {
- type = expectedEvent.substring(1);
- } else {
- type = expectedEvent;
- }
- let handler = ev => {
- let pass = (!seenEvent && ev.originalTarget == expectedTarget && ev.type == type);
- is(pass, true, `${testName} ${type} event target ${seenEvent ? "twice" : ""}`);
- seenEvent = true;
- };
- expectedTarget.addEventListener(type, handler, false);
- return handler;
- }
- /**
- * Check if the event was fired or not. The provided event handler will
- * be removed.
- */
- function checkExpectedEvent_(
- expectedTarget, expectedEvent, eventHandler, testName) {
- if (eventHandler) {
- let expectEvent = (expectedEvent.charAt(0) != "!");
- let type = expectEvent;
- if (!type) {
- type = expectedEvent.substring(1);
- }
- expectedTarget.removeEventListener(type, eventHandler, false);
- let desc = `${type} event`;
- if (!expectEvent) {
- desc += " not";
- }
- is(seenEvent, expectEvent, `${testName} ${desc} fired`);
- }
- seenEvent = false;
- }
- /**
- * Similar to event.synthesizeMouse except that a test is performed to
- * see if an event is fired at the right target as a result.
- *
- * To test that an event is not fired, use an expected type preceded by
- * an exclamation mark, such as "!select". This might be used to test that
- * a click on a disabled element doesn't fire certain events for instance.
- *
- * @param {Element} target
- * Synthesise the mouse event on this target.
- * @param {number} offsetX
- * Horizontal offset from the target's bounding box.
- * @param {number} offsetY
- * Vertical offset from the target's bounding box.
- * @param {Object.<string, ?>} ev
- * Object which may contain the properties shiftKey, ctrlKey, altKey,
- * metaKey, accessKey, type.
- * @param {Element} expectedTarget
- * Expected originalTarget of the event.
- * @param {DOMEvent} expectedEvent
- * Expected type of the event, such as "select".
- * @param {string} testName
- * Test name when outputing results.
- * @param {Window=} window
- * Window object. Defaults to the current window.
- */
- event.synthesizeMouseExpectEvent = function (
- target, offsetX, offsetY, ev, expectedTarget, expectedEvent,
- testName, window = undefined) {
- let eventHandler = expectEvent_(
- expectedTarget,
- expectedEvent,
- testName);
- event.synthesizeMouse(target, offsetX, offsetY, ev, window);
- checkExpectedEvent_(
- expectedTarget,
- expectedEvent,
- eventHandler,
- testName);
- };
- /**
- * Similar to synthesizeKey except that a test is performed to see if
- * an event is fired at the right target as a result.
- *
- * @param {string} key
- * Key to synthesise.
- * @param {Object.<string, ?>} ev
- * Object which may contain the properties shiftKey, ctrlKey, altKey,
- * metaKey, accessKey, type.
- * @param {Element} expectedTarget
- * Expected originalTarget of the event.
- * @param {DOMEvent} expectedEvent
- * Expected type of the event, such as "select".
- * @param {string} testName
- * Test name when outputing results
- * @param {Window=} window
- * Window object. Defaults to the current window.
- *
- * To test that an event is not fired, use an expected type preceded by an
- * exclamation mark, such as "!select".
- *
- * aWindow is optional, and defaults to the current window object.
- */
- event.synthesizeKeyExpectEvent = function (
- key, ev, expectedTarget, expectedEvent, testName,
- window = undefined) {
- let eventHandler = expectEvent_(
- expectedTarget,
- expectedEvent,
- testName);
- event.synthesizeKey(key, ev, window);
- checkExpectedEvent_(
- expectedTarget,
- expectedEvent,
- eventHandler,
- testName);
- };
- /**
- * Synthesize a composition event.
- *
- * @param {DOMEvent} ev
- * The composition event information. This must have |type|
- * member. The value must be "compositionstart", "compositionend" or
- * "compositionupdate". And also this may have |data| and |locale|
- * which would be used for the value of each property of the
- * composition event. Note that the data would be ignored if the
- * event type were "compositionstart".
- * @param {Window=} window
- * Window object. Defaults to the current window.
- */
- event.synthesizeComposition = function (ev, window = undefined) {
- let domutils = getDOMWindowUtils(window);
- domutils.sendCompositionEvent(ev.type, ev.data || "", ev.locale || "");
- };
- /**
- * Synthesize a text event.
- *
- * The text event's information, this has |composition| and |caret|
- * members. |composition| has |string| and |clauses| members. |clauses|
- * must be array object. Each object has |length| and |attr|.
- * And |caret| has |start| and |length|. See the following tree image.
- *
- * ev
- * +-- composition
- * | +-- string
- * | +-- clauses[]
- * | +-- length
- * | +-- attr
- * +-- caret
- * +-- start
- * +-- length
- *
- * Set the composition string to |composition.string|. Set its clauses
- * information to the |clauses| array.
- *
- * When it's composing, set the each clauses' length
- * to the |composition.clauses[n].length|. The sum
- * of the all length values must be same as the length of
- * |composition.string|. Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
- * |composition.clauses[n].attr|.
- *
- * When it's not composing, set 0 to the |composition.clauses[0].length|
- * and |composition.clauses[0].attr|.
- *
- * Set caret position to the |caret.start|. Its offset from the start of
- * the composition string. Set caret length to |caret.length|. If it's
- * larger than 0, it should be wide caret. However, current nsEditor
- * doesn't support wide caret, therefore, you should always set 0 now.
- *
- * @param {Object.<string, ?>} ev
- * The text event's information,
- * @param {Window=} window
- * Window object. Defaults to the current window.
- */
- event.synthesizeText = function (ev, window = undefined) {
- let domutils = getDOMWindowUtils(window);
- if (!ev.composition ||
- !ev.composition.clauses ||
- !ev.composition.clauses[0]) {
- return;
- }
- let firstClauseLength = ev.composition.clauses[0].length;
- let firstClauseAttr = ev.composition.clauses[0].attr;
- let secondClauseLength = 0;
- let secondClauseAttr = 0;
- let thirdClauseLength = 0;
- let thirdClauseAttr = 0;
- if (ev.composition.clauses[1]) {
- secondClauseLength = ev.composition.clauses[1].length;
- secondClauseAttr = ev.composition.clauses[1].attr;
- if (event.composition.clauses[2]) {
- thirdClauseLength = ev.composition.clauses[2].length;
- thirdClauseAttr = ev.composition.clauses[2].attr;
- }
- }
- let caretStart = -1;
- let caretLength = 0;
- if (event.caret) {
- caretStart = ev.caret.start;
- caretLength = ev.caret.length;
- }
- domutils.sendTextEvent(
- ev.composition.string,
- firstClauseLength,
- firstClauseAttr,
- secondClauseLength,
- secondClauseAttr,
- thirdClauseLength,
- thirdClauseAttr,
- caretStart,
- caretLength);
- };
- /**
- * Synthesize a query selected text event.
- *
- * @param {Window=}
- * Window object. Defaults to the current window.
- *
- * @return {(nsIQueryContentEventResult|null)}
- * Event's result, or null if it failed.
- */
- event.synthesizeQuerySelectedText = function (window = undefined) {
- let domutils = getDOMWindowUtils(window);
- return domutils.sendQueryContentEvent(
- domutils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
- };
- /**
- * Synthesize a selection set event.
- *
- * @param {number} offset
- * Character offset. 0 means the first character in the selection
- * root.
- * @param {number} length
- * Length of the text. If the length is too long, the extra length
- * is ignored.
- * @param {boolean} reverse
- * If true, the selection is from |aOffset + aLength| to |aOffset|.
- * Otherwise, from |aOffset| to |aOffset + aLength|.
- * @param {Window=} window
- * Window object. Defaults to the current window.
- *
- * @return True, if succeeded. Otherwise false.
- */
- event.synthesizeSelectionSet = function (
- offset, length, reverse, window = undefined) {
- let domutils = getDOMWindowUtils(window);
- return domutils.sendSelectionSetEvent(offset, length, reverse);
- };
- const KEYCODES_LOOKUP = {
- "VK_SHIFT": "shiftKey",
- "VK_CONTROL": "ctrlKey",
- "VK_ALT": "altKey",
- "VK_META": "metaKey",
- };
- const VIRTUAL_KEYCODE_LOOKUP = {
- "\uE001": "VK_CANCEL",
- "\uE002": "VK_HELP",
- "\uE003": "VK_BACK_SPACE",
- "\uE004": "VK_TAB",
- "\uE005": "VK_CLEAR",
- "\uE006": "VK_RETURN",
- "\uE007": "VK_RETURN",
- "\uE008": "VK_SHIFT",
- "\uE009": "VK_CONTROL",
- "\uE00A": "VK_ALT",
- "\uE03D": "VK_META",
- "\uE00B": "VK_PAUSE",
- "\uE00C": "VK_ESCAPE",
- "\uE00D": "VK_SPACE", // printable
- "\uE00E": "VK_PAGE_UP",
- "\uE00F": "VK_PAGE_DOWN",
- "\uE010": "VK_END",
- "\uE011": "VK_HOME",
- "\uE012": "VK_LEFT",
- "\uE013": "VK_UP",
- "\uE014": "VK_RIGHT",
- "\uE015": "VK_DOWN",
- "\uE016": "VK_INSERT",
- "\uE017": "VK_DELETE",
- "\uE018": "VK_SEMICOLON",
- "\uE019": "VK_EQUALS",
- "\uE01A": "VK_NUMPAD0",
- "\uE01B": "VK_NUMPAD1",
- "\uE01C": "VK_NUMPAD2",
- "\uE01D": "VK_NUMPAD3",
- "\uE01E": "VK_NUMPAD4",
- "\uE01F": "VK_NUMPAD5",
- "\uE020": "VK_NUMPAD6",
- "\uE021": "VK_NUMPAD7",
- "\uE022": "VK_NUMPAD8",
- "\uE023": "VK_NUMPAD9",
- "\uE024": "VK_MULTIPLY",
- "\uE025": "VK_ADD",
- "\uE026": "VK_SEPARATOR",
- "\uE027": "VK_SUBTRACT",
- "\uE028": "VK_DECIMAL",
- "\uE029": "VK_DIVIDE",
- "\uE031": "VK_F1",
- "\uE032": "VK_F2",
- "\uE033": "VK_F3",
- "\uE034": "VK_F4",
- "\uE035": "VK_F5",
- "\uE036": "VK_F6",
- "\uE037": "VK_F7",
- "\uE038": "VK_F8",
- "\uE039": "VK_F9",
- "\uE03A": "VK_F10",
- "\uE03B": "VK_F11",
- "\uE03C": "VK_F12",
- };
- function getKeyCode(c) {
- if (c in VIRTUAL_KEYCODE_LOOKUP) {
- return VIRTUAL_KEYCODE_LOOKUP[c];
- }
- return c;
- }
- event.sendKeyDown = function (keyToSend, modifiers, document) {
- modifiers.type = "keydown";
- event.sendSingleKey(keyToSend, modifiers, document);
- // TODO This doesn't do anything since |synthesizeKeyEvent| ignores explicit
- // keypress request, and instead figures out itself when to send keypress
- if (["VK_SHIFT", "VK_CONTROL", "VK_ALT", "VK_META"].indexOf(getKeyCode(keyToSend)) < 0) {
- modifiers.type = "keypress";
- event.sendSingleKey(keyToSend, modifiers, document);
- }
- delete modifiers.type;
- };
- event.sendKeyUp = function (keyToSend, modifiers, window = undefined) {
- modifiers.type = "keyup";
- event.sendSingleKey(keyToSend, modifiers, window);
- delete modifiers.type;
- };
- /**
- * Synthesize a key event for a single key.
- *
- * @param {string} keyToSend
- * Code point or normalized key value
- * @param {?} modifiers
- * Object with properties used in KeyboardEvent (shiftkey, repeat, ...)
- * as well as, the event |type| such as keydown. All properties are optional.
- * @param {Window=} window
- * Window object. If |window| is undefined, the event is synthesized in
- * current window.
- */
- event.sendSingleKey = function (keyToSend, modifiers, window = undefined) {
- let keyCode = getKeyCode(keyToSend);
- if (keyCode in KEYCODES_LOOKUP) {
- // We assume that if |keyToSend| is a raw code point (like "\uE009") then
- // |modifiers| does not already have correct value for corresponding
- // |modName| attribute (like ctrlKey), so that value needs to be flipped
- let modName = KEYCODES_LOOKUP[keyCode];
- modifiers[modName] = !modifiers[modName];
- } else if (modifiers.shiftKey && keyCode != "Shift") {
- keyCode = keyCode.toUpperCase();
- }
- event.synthesizeKey(keyCode, modifiers, window);
- };
- /**
- * Focus element and, if a textual input field and no previous selection
- * state exists, move the caret to the end of the input field.
- *
- * @param {Element} element
- * Element to focus.
- */
- function focusElement(element) {
- let t = element.type;
- if (t && (t == "text" || t == "textarea")) {
- if (element.selectionEnd == 0) {
- let len = element.value.length;
- element.setSelectionRange(len, len);
- }
- }
- element.focus();
- }
- /**
- * @param {Array.<string>} keySequence
- * @param {Element} element
- * @param {Object.<string, boolean>=} opts
- * @param {Window=} window
- */
- event.sendKeysToElement = function (
- keySequence, el, opts = {}, window = undefined) {
- if (opts.ignoreVisibility || element.isVisible(el)) {
- focusElement(el);
- // make Object.<modifier, false> map
- let modifiers = Object.create(event.Modifiers);
- for (let modifier in event.Modifiers) {
- modifiers[modifier] = false;
- }
- let value = keySequence.join("");
- for (let i = 0; i < value.length; i++) {
- let c = value.charAt(i);
- event.sendSingleKey(c, modifiers, window);
- }
- } else {
- throw new ElementNotInteractableError("Element is not visible");
- }
- };
- event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
- opts.canBubble = opts.canBubble || true;
- let doc = el.ownerDocument || el.document;
- let ev = doc.createEvent("Event");
- ev.shiftKey = modifiers["shift"];
- ev.metaKey = modifiers["meta"];
- ev.altKey = modifiers["alt"];
- ev.ctrlKey = modifiers["ctrl"];
- ev.initEvent(eventType, opts.canBubble, true);
- el.dispatchEvent(ev);
- };
- event.focus = function (el, opts = {}) {
- opts.canBubble = opts.canBubble || true;
- let doc = el.ownerDocument || el.document;
- let win = doc.defaultView;
- let ev = new win.FocusEvent(el);
- ev.initEvent("focus", opts.canBubble, true);
- el.dispatchEvent(ev);
- };
- event.mouseover = function (el, modifiers = {}, opts = {}) {
- return event.sendEvent("mouseover", el, modifiers, opts);
- };
- event.mousemove = function (el, modifiers = {}, opts = {}) {
- return event.sendEvent("mousemove", el, modifiers, opts);
- };
- event.mousedown = function (el, modifiers = {}, opts = {}) {
- return event.sendEvent("mousedown", el, modifiers, opts);
- };
- event.mouseup = function (el, modifiers = {}, opts = {}) {
- return event.sendEvent("mouseup", el, modifiers, opts);
- };
- event.click = function (el, modifiers = {}, opts = {}) {
- return event.sendEvent("click", el, modifiers, opts);
- };
- event.change = function (el, modifiers = {}, opts = {}) {
- return event.sendEvent("change", el, modifiers, opts);
- };
- event.input = function (el, modifiers = {}, opts = {}) {
- return event.sendEvent("input", el, modifiers, opts);
- };
|