event.js 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. // Provides functionality for creating and sending DOM events.
  5. "use strict";
  6. const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
  7. Cu.import("resource://gre/modules/Log.jsm");
  8. const logger = Log.repository.getLogger("Marionette");
  9. Cu.import("chrome://marionette/content/element.js");
  10. Cu.import("chrome://marionette/content/error.js");
  11. this.EXPORTED_SYMBOLS = ["event"];
  12. // must be synchronised with nsIDOMWindowUtils
  13. const COMPOSITION_ATTR_RAWINPUT = 0x02;
  14. const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
  15. const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
  16. const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
  17. // TODO(ato): Document!
  18. let seenEvent = false;
  19. function getDOMWindowUtils(win) {
  20. if (!win) {
  21. win = window;
  22. }
  23. // this assumes we are operating in chrome space
  24. return win.QueryInterface(Ci.nsIInterfaceRequestor)
  25. .getInterface(Ci.nsIDOMWindowUtils);
  26. }
  27. this.event = {};
  28. event.MouseEvents = {
  29. click: 0,
  30. dblclick: 1,
  31. mousedown: 2,
  32. mouseup: 3,
  33. mouseover: 4,
  34. mouseout: 5,
  35. };
  36. event.Modifiers = {
  37. shiftKey: 0,
  38. ctrlKey: 1,
  39. altKey: 2,
  40. metaKey: 3,
  41. };
  42. /**
  43. * Sends a mouse event to given target.
  44. *
  45. * @param {nsIDOMMouseEvent} mouseEvent
  46. * Event to send.
  47. * @param {(DOMElement|string)} target
  48. * Target of event. Can either be an element or the ID of an element.
  49. * @param {Window=} window
  50. * Window object. Defaults to the current window.
  51. *
  52. * @throws {TypeError}
  53. * If the event is unsupported.
  54. */
  55. event.sendMouseEvent = function (mouseEvent, target, window = undefined) {
  56. if (!event.MouseEvents.hasOwnProperty(mouseEvent.type)) {
  57. throw new TypeError("Unsupported event type: " + mouseEvent.type);
  58. }
  59. if (!target.nodeType && typeof target != "string") {
  60. throw new TypeError("Target can only be a DOM element or a string: " + target);
  61. }
  62. if (!target.nodeType) {
  63. target = window.document.getElementById(target);
  64. } else {
  65. window = window || target.ownerDocument.defaultView;
  66. }
  67. let ev = window.document.createEvent("MouseEvent");
  68. let type = mouseEvent.type;
  69. let view = window;
  70. let detail = mouseEvent.detail;
  71. if (!detail) {
  72. if (mouseEvent.type in ["click", "mousedown", "mouseup"]) {
  73. detail = 1;
  74. } else if (mouseEvent.type == "dblclick") {
  75. detail = 2;
  76. } else {
  77. detail = 0;
  78. }
  79. }
  80. let screenX = mouseEvent.screenX || 0;
  81. let screenY = mouseEvent.screenY || 0;
  82. let clientX = mouseEvent.clientX || 0;
  83. let clientY = mouseEvent.clientY || 0;
  84. let ctrlKey = mouseEvent.ctrlKey || false;
  85. let altKey = mouseEvent.altKey || false;
  86. let shiftKey = mouseEvent.shiftKey || false;
  87. let metaKey = mouseEvent.metaKey || false;
  88. let button = mouseEvent.button || 0;
  89. let relatedTarget = mouseEvent.relatedTarget || null;
  90. ev.initMouseEvent(
  91. mouseEvent.type,
  92. /* canBubble */ true,
  93. /* cancelable */ true,
  94. view,
  95. detail,
  96. screenX,
  97. screenY,
  98. clientX,
  99. clientY,
  100. ctrlKey,
  101. altKey,
  102. shiftKey,
  103. metaKey,
  104. button,
  105. relatedTarget);
  106. };
  107. /**
  108. * Send character to the currently focused element.
  109. *
  110. * This function handles casing of characters (sends the right charcode,
  111. * and sends a shift key for uppercase chars). No other modifiers are
  112. * handled at this point.
  113. *
  114. * For now this method only works for English letters (lower and upper
  115. * case) and the digits 0-9.
  116. */
  117. event.sendChar = function (char, window = undefined) {
  118. // DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9
  119. let hasShift = (char == char.toUpperCase());
  120. event.synthesizeKey(char, {shiftKey: hasShift}, window);
  121. };
  122. /**
  123. * Send string to the focused element.
  124. *
  125. * For now this method only works for English letters (lower and upper
  126. * case) and the digits 0-9.
  127. */
  128. event.sendString = function (string, window = undefined) {
  129. for (let i = 0; i < string.length; ++i) {
  130. event.sendChar(string.charAt(i), window);
  131. }
  132. };
  133. /**
  134. * Send the non-character key to the focused element.
  135. *
  136. * The name of the key should be the part that comes after "DOM_VK_"
  137. * in the nsIDOMKeyEvent constant name for this key. No modifiers are
  138. * handled at this point.
  139. */
  140. event.sendKey = function (key, window = undefined) {
  141. let keyName = "VK_" + key.toUpperCase();
  142. event.synthesizeKey(keyName, {shiftKey: false}, window);
  143. };
  144. // TODO(ato): Unexpose this when action.Chain#emitMouseEvent
  145. // no longer emits its own events
  146. event.parseModifiers_ = function (modifiers) {
  147. let mval = 0;
  148. if (modifiers.shiftKey) {
  149. mval |= Ci.nsIDOMNSEvent.SHIFT_MASK;
  150. }
  151. if (modifiers.ctrlKey) {
  152. mval |= Ci.nsIDOMNSEvent.CONTROL_MASK;
  153. }
  154. if (modifiers.altKey) {
  155. mval |= Ci.nsIDOMNSEvent.ALT_MASK;
  156. }
  157. if (modifiers.metaKey) {
  158. mval |= Ci.nsIDOMNSEvent.META_MASK;
  159. }
  160. if (modifiers.accelKey) {
  161. if (navigator.platform.indexOf("Mac") >= 0) {
  162. mval |= Ci.nsIDOMNSEvent.META_MASK;
  163. } else {
  164. mval |= Ci.nsIDOMNSEvent.CONTROL_MASK;
  165. }
  166. }
  167. return mval;
  168. };
  169. /**
  170. * Synthesise a mouse event on a target.
  171. *
  172. * The actual client point is determined by taking the aTarget's client
  173. * box and offseting it by offsetX and offsetY. This allows mouse clicks
  174. * to be simulated by calling this method.
  175. *
  176. * If the type is specified, an mouse event of that type is
  177. * fired. Otherwise, a mousedown followed by a mouse up is performed.
  178. *
  179. * @param {Element} element
  180. * Element to click.
  181. * @param {number} offsetX
  182. * Horizontal offset to click from the target's bounding box.
  183. * @param {number} offsetY
  184. * Vertical offset to click from the target's bounding box.
  185. * @param {Object.<string, ?>} opts
  186. * Object which may contain the properties "shiftKey", "ctrlKey",
  187. * "altKey", "metaKey", "accessKey", "clickCount", "button", and
  188. * "type".
  189. * @param {Window=} window
  190. * Window object. Defaults to the current window.
  191. */
  192. event.synthesizeMouse = function (
  193. element, offsetX, offsetY, opts, window = undefined) {
  194. let rect = element.getBoundingClientRect();
  195. event.synthesizeMouseAtPoint(
  196. rect.left + offsetX, rect.top + offsetY, opts, window);
  197. };
  198. /*
  199. * Synthesize a mouse event at a particular point in a window.
  200. *
  201. * If the type of the event is specified, a mouse event of that type is
  202. * fired. Otherwise, a mousedown followed by a mouse up is performed.
  203. *
  204. * @param {number} left
  205. * CSS pixels from the left document margin.
  206. * @param {number} top
  207. * CSS pixels from the top document margin.
  208. * @param {Object.<string, ?>} opts
  209. * Object which may contain the properties "shiftKey", "ctrlKey",
  210. * "altKey", "metaKey", "accessKey", "clickCount", "button", and
  211. * "type".
  212. * @param {Window=} window
  213. * Window object. Defaults to the current window.
  214. */
  215. event.synthesizeMouseAtPoint = function (
  216. left, top, opts, window = undefined) {
  217. let domutils = getDOMWindowUtils(window);
  218. let button = opts.button || 0;
  219. let clickCount = opts.clickCount || 1;
  220. let modifiers = event.parseModifiers_(opts);
  221. let pressure = ("pressure" in opts) ? opts.pressure : 0;
  222. let inputSource = ("inputSource" in opts) ? opts.inputSource :
  223. Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE;
  224. let isDOMEventSynthesized =
  225. ("isSynthesized" in opts) ? opts.isSynthesized : true;
  226. let isWidgetEventSynthesized =
  227. ("isWidgetEventSynthesized" in opts) ? opts.isWidgetEventSynthesized : false;
  228. let buttons = ("buttons" in opts) ? opts.buttons : domutils.MOUSE_BUTTONS_NOT_SPECIFIED;
  229. if (("type" in opts) && opts.type) {
  230. domutils.sendMouseEvent(
  231. opts.type, left, top, button, clickCount, modifiers, false, pressure, inputSource,
  232. isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
  233. } else {
  234. domutils.sendMouseEvent(
  235. "mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource,
  236. isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
  237. domutils.sendMouseEvent(
  238. "mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource,
  239. isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
  240. }
  241. };
  242. /**
  243. * Call event.synthesizeMouse with coordinates at the centre of the
  244. * target.
  245. */
  246. event.synthesizeMouseAtCenter = function (element, event, window) {
  247. let rect = element.getBoundingClientRect();
  248. event.synthesizeMouse(
  249. element,
  250. rect.width / 2,
  251. rect.height / 2,
  252. event,
  253. window);
  254. };
  255. /**
  256. * Synthesise a mouse scroll event on a target.
  257. *
  258. * The actual client point is determined by taking the target's client
  259. * box and offseting it by |offsetX| and |offsetY|.
  260. *
  261. * If the |type| property is specified for the |event| argument, a mouse
  262. * scroll event of that type is fired. Otherwise, DOMMouseScroll is used.
  263. *
  264. * If the |axis| is specified, it must be one of "horizontal" or
  265. * "vertical". If not specified, "vertical" is used.
  266. *
  267. * |delta| is the amount to scroll by (can be positive or negative).
  268. * It must be specified.
  269. *
  270. * |hasPixels| specifies whether kHasPixels should be set in the
  271. * |scrollFlags|.
  272. *
  273. * |isMomentum| specifies whether kIsMomentum should be set in the
  274. * |scrollFlags|.
  275. *
  276. * @param {Element} target
  277. * @param {number} offsetY
  278. * @param {number} offsetY
  279. * @param {Object.<string, ?>} event
  280. * Object which may contain the properties shiftKey, ctrlKey, altKey,
  281. * metaKey, accessKey, button, type, axis, delta, and hasPixels.
  282. * @param {Window=} window
  283. * Window object. Defaults to the current window.
  284. */
  285. event.synthesizeMouseScroll = function (
  286. target, offsetX, offsetY, ev, window = undefined) {
  287. let domutils = getDOMWindowUtils(window);
  288. // see nsMouseScrollFlags in nsGUIEvent.h
  289. const kIsVertical = 0x02;
  290. const kIsHorizontal = 0x04;
  291. const kHasPixels = 0x08;
  292. const kIsMomentum = 0x40;
  293. let button = ev.button || 0;
  294. let modifiers = event.parseModifiers_(ev);
  295. let rect = target.getBoundingClientRect();
  296. let left = rect.left;
  297. let top = rect.top;
  298. let type = (("type" in ev) && ev.type) || "DOMMouseScroll";
  299. let axis = ev.axis || "vertical";
  300. let scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical;
  301. if (ev.hasPixels) {
  302. scrollFlags |= kHasPixels;
  303. }
  304. if (ev.isMomentum) {
  305. scrollFlags |= kIsMomentum;
  306. }
  307. domutils.sendMouseScrollEvent(
  308. type,
  309. left + offsetX,
  310. top + offsetY,
  311. button,
  312. scrollFlags,
  313. ev.delta,
  314. modifiers);
  315. };
  316. function computeKeyCodeFromChar_(char) {
  317. if (char.length != 1) {
  318. return 0;
  319. }
  320. if (char >= "a" && char <= "z") {
  321. return Ci.nsIDOMKeyEvent.DOM_VK_A + char.charCodeAt(0) - "a".charCodeAt(0);
  322. }
  323. if (char >= "A" && char <= "Z") {
  324. return Ci.nsIDOMKeyEvent.DOM_VK_A + char.charCodeAt(0) - "A".charCodeAt(0);
  325. }
  326. if (char >= "0" && char <= "9") {
  327. return Ci.nsIDOMKeyEvent.DOM_VK_0 + char.charCodeAt(0) - "0".charCodeAt(0);
  328. }
  329. // returns US keyboard layout's keycode
  330. switch (char) {
  331. case "~":
  332. case "`":
  333. return Ci.nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
  334. case "!":
  335. return Ci.nsIDOMKeyEvent.DOM_VK_1;
  336. case "@":
  337. return Ci.nsIDOMKeyEvent.DOM_VK_2;
  338. case "#":
  339. return Ci.nsIDOMKeyEvent.DOM_VK_3;
  340. case "$":
  341. return Ci.nsIDOMKeyEvent.DOM_VK_4;
  342. case "%":
  343. return Ci.nsIDOMKeyEvent.DOM_VK_5;
  344. case "^":
  345. return Ci.nsIDOMKeyEvent.DOM_VK_6;
  346. case "&":
  347. return Ci.nsIDOMKeyEvent.DOM_VK_7;
  348. case "*":
  349. return Ci.nsIDOMKeyEvent.DOM_VK_8;
  350. case "(":
  351. return Ci.nsIDOMKeyEvent.DOM_VK_9;
  352. case ")":
  353. return Ci.nsIDOMKeyEvent.DOM_VK_0;
  354. case "-":
  355. case "_":
  356. return Ci.nsIDOMKeyEvent.DOM_VK_SUBTRACT;
  357. case "+":
  358. case "=":
  359. return Ci.nsIDOMKeyEvent.DOM_VK_EQUALS;
  360. case "{":
  361. case "[":
  362. return Ci.nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
  363. case "}":
  364. case "]":
  365. return Ci.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
  366. case "|":
  367. case "\\":
  368. return Ci.nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
  369. case ":":
  370. case ";":
  371. return Ci.nsIDOMKeyEvent.DOM_VK_SEMICOLON;
  372. case "'":
  373. case "\"":
  374. return Ci.nsIDOMKeyEvent.DOM_VK_QUOTE;
  375. case "<":
  376. case ",":
  377. return Ci.nsIDOMKeyEvent.DOM_VK_COMMA;
  378. case ">":
  379. case ".":
  380. return Ci.nsIDOMKeyEvent.DOM_VK_PERIOD;
  381. case "?":
  382. case "/":
  383. return Ci.nsIDOMKeyEvent.DOM_VK_SLASH;
  384. case "\n":
  385. return Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
  386. default:
  387. return 0;
  388. }
  389. }
  390. /**
  391. * Returns true if the given key should cause keypress event when widget
  392. * handles the native key event. Otherwise, false.
  393. *
  394. * The key code should be one of consts of nsIDOMKeyEvent.DOM_VK_*,
  395. * or a key name begins with "VK_", or a character.
  396. */
  397. event.isKeypressFiredKey = function (key) {
  398. if (typeof key == "string") {
  399. if (key.indexOf("VK_") === 0) {
  400. key = Ci.nsIDOMKeyEvent["DOM_" + key];
  401. if (!key) {
  402. throw new TypeError("Unknown key: " + key);
  403. }
  404. // if key generates a character, it must cause a keypress event
  405. } else {
  406. return true;
  407. }
  408. }
  409. switch (key) {
  410. case Ci.nsIDOMKeyEvent.DOM_VK_SHIFT:
  411. case Ci.nsIDOMKeyEvent.DOM_VK_CONTROL:
  412. case Ci.nsIDOMKeyEvent.DOM_VK_ALT:
  413. case Ci.nsIDOMKeyEvent.DOM_VK_CAPS_LOCK:
  414. case Ci.nsIDOMKeyEvent.DOM_VK_NUM_LOCK:
  415. case Ci.nsIDOMKeyEvent.DOM_VK_SCROLL_LOCK:
  416. case Ci.nsIDOMKeyEvent.DOM_VK_META:
  417. return false;
  418. default:
  419. return true;
  420. }
  421. };
  422. /**
  423. * Synthesise a key event.
  424. *
  425. * It is targeted at whatever would be targeted by an actual keypress
  426. * by the user, typically the focused element.
  427. *
  428. * @param {string} key
  429. * Key to synthesise. Should either be a character or a key code
  430. * starting with "VK_" such as VK_RETURN, or a normalized key value.
  431. * @param {Object.<string, ?>} event
  432. * Object which may contain the properties shiftKey, ctrlKey, altKey,
  433. * metaKey, accessKey, type. If the type is specified (keydown or keyup),
  434. * a key event of that type is fired. Otherwise, a keydown, a keypress,
  435. * and then a keyup event are fired in sequence.
  436. * @param {Window=} window
  437. * Window object. Defaults to the current window.
  438. *
  439. * @throws {TypeError}
  440. * If unknown key.
  441. */
  442. event.synthesizeKey = function (key, event, win = undefined)
  443. {
  444. var TIP = getTIP_(win);
  445. if (!TIP) {
  446. return;
  447. }
  448. var KeyboardEvent = getKeyboardEvent_(win);
  449. var modifiers = emulateToActivateModifiers_(TIP, event, win);
  450. var keyEventDict = createKeyboardEventDictionary_(key, event, win);
  451. var keyEvent = new KeyboardEvent("", keyEventDict.dictionary);
  452. var dispatchKeydown =
  453. !("type" in event) || event.type === "keydown" || !event.type;
  454. var dispatchKeyup =
  455. !("type" in event) || event.type === "keyup" || !event.type;
  456. try {
  457. if (dispatchKeydown) {
  458. TIP.keydown(keyEvent, keyEventDict.flags);
  459. if ("repeat" in event && event.repeat > 1) {
  460. keyEventDict.dictionary.repeat = true;
  461. var repeatedKeyEvent = new KeyboardEvent("", keyEventDict.dictionary);
  462. for (var i = 1; i < event.repeat; i++) {
  463. TIP.keydown(repeatedKeyEvent, keyEventDict.flags);
  464. }
  465. }
  466. }
  467. if (dispatchKeyup) {
  468. TIP.keyup(keyEvent, keyEventDict.flags);
  469. }
  470. } finally {
  471. emulateToInactivateModifiers_(TIP, modifiers, win);
  472. }
  473. };
  474. var TIPMap = new WeakMap();
  475. function getTIP_(win, callback)
  476. {
  477. if (!win) {
  478. win = window;
  479. }
  480. var tip;
  481. if (TIPMap.has(win)) {
  482. tip = TIPMap.get(win);
  483. } else {
  484. tip =
  485. Cc["@mozilla.org/text-input-processor;1"].
  486. createInstance(Ci.nsITextInputProcessor);
  487. TIPMap.set(win, tip);
  488. }
  489. if (!tip.beginInputTransactionForTests(win, callback)) {
  490. tip = null;
  491. TIPMap.delete(win);
  492. }
  493. return tip;
  494. }
  495. function getKeyboardEvent_(win = window)
  496. {
  497. if (typeof KeyboardEvent != "undefined") {
  498. try {
  499. // See if the object can be instantiated; sometimes this yields
  500. // 'TypeError: can't access dead object' or 'KeyboardEvent is not a constructor'.
  501. new KeyboardEvent("", {});
  502. return KeyboardEvent;
  503. } catch (ex) {}
  504. }
  505. if (typeof content != "undefined" && ("KeyboardEvent" in content)) {
  506. return content.KeyboardEvent;
  507. }
  508. return win.KeyboardEvent;
  509. }
  510. function createKeyboardEventDictionary_(key, keyEvent, win = window) {
  511. var result = { dictionary: null, flags: 0 };
  512. var keyCodeIsDefined = "keyCode" in keyEvent;
  513. var keyCode =
  514. (keyCodeIsDefined && keyEvent.keyCode >= 0 && keyEvent.keyCode <= 255) ?
  515. keyEvent.keyCode : 0;
  516. var keyName = "Unidentified";
  517. if (key.indexOf("KEY_") == 0) {
  518. keyName = key.substr("KEY_".length);
  519. result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
  520. } else if (key.indexOf("VK_") == 0) {
  521. keyCode = Ci.nsIDOMKeyEvent["DOM_" + key];
  522. if (!keyCode) {
  523. throw "Unknown key: " + key;
  524. }
  525. keyName = guessKeyNameFromKeyCode_(keyCode, win);
  526. result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
  527. } else if (key != "") {
  528. keyName = key;
  529. if (!keyCodeIsDefined) {
  530. keyCode = computeKeyCodeFromChar_(key.charAt(0));
  531. }
  532. if (!keyCode) {
  533. result.flags |= Ci.nsITextInputProcessor.KEY_KEEP_KEYCODE_ZERO;
  534. }
  535. // keyName was already determined in keyEvent so no fall-back needed
  536. if (!("key" in keyEvent && keyName == keyEvent.key)) {
  537. result.flags |= Ci.nsITextInputProcessor.KEY_FORCE_PRINTABLE_KEY;
  538. }
  539. }
  540. var locationIsDefined = "location" in keyEvent;
  541. if (locationIsDefined && keyEvent.location === 0) {
  542. result.flags |= Ci.nsITextInputProcessor.KEY_KEEP_KEY_LOCATION_STANDARD;
  543. }
  544. result.dictionary = {
  545. key: keyName,
  546. code: "code" in keyEvent ? keyEvent.code : "",
  547. location: locationIsDefined ? keyEvent.location : 0,
  548. repeat: "repeat" in keyEvent ? keyEvent.repeat === true : false,
  549. keyCode: keyCode,
  550. };
  551. return result;
  552. }
  553. function emulateToActivateModifiers_(TIP, keyEvent, win = window)
  554. {
  555. if (!keyEvent) {
  556. return null;
  557. }
  558. var KeyboardEvent = getKeyboardEvent_(win);
  559. var navigator = getNavigator_(win);
  560. var modifiers = {
  561. normal: [
  562. { key: "Alt", attr: "altKey" },
  563. { key: "AltGraph", attr: "altGraphKey" },
  564. { key: "Control", attr: "ctrlKey" },
  565. { key: "Fn", attr: "fnKey" },
  566. { key: "Meta", attr: "metaKey" },
  567. { key: "OS", attr: "osKey" },
  568. { key: "Shift", attr: "shiftKey" },
  569. { key: "Symbol", attr: "symbolKey" },
  570. { key: isMac_(win) ? "Meta" : "Control",
  571. attr: "accelKey" },
  572. ],
  573. lockable: [
  574. { key: "CapsLock", attr: "capsLockKey" },
  575. { key: "FnLock", attr: "fnLockKey" },
  576. { key: "NumLock", attr: "numLockKey" },
  577. { key: "ScrollLock", attr: "scrollLockKey" },
  578. { key: "SymbolLock", attr: "symbolLockKey" },
  579. ]
  580. }
  581. for (var i = 0; i < modifiers.normal.length; i++) {
  582. if (!keyEvent[modifiers.normal[i].attr]) {
  583. continue;
  584. }
  585. if (TIP.getModifierState(modifiers.normal[i].key)) {
  586. continue; // already activated.
  587. }
  588. var event = new KeyboardEvent("", { key: modifiers.normal[i].key });
  589. TIP.keydown(event,
  590. TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
  591. modifiers.normal[i].activated = true;
  592. }
  593. for (var i = 0; i < modifiers.lockable.length; i++) {
  594. if (!keyEvent[modifiers.lockable[i].attr]) {
  595. continue;
  596. }
  597. if (TIP.getModifierState(modifiers.lockable[i].key)) {
  598. continue; // already activated.
  599. }
  600. var event = new KeyboardEvent("", { key: modifiers.lockable[i].key });
  601. TIP.keydown(event,
  602. TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
  603. TIP.keyup(event,
  604. TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
  605. modifiers.lockable[i].activated = true;
  606. }
  607. return modifiers;
  608. }
  609. function emulateToInactivateModifiers_(TIP, modifiers, win = window)
  610. {
  611. if (!modifiers) {
  612. return;
  613. }
  614. var KeyboardEvent = getKeyboardEvent_(win);
  615. for (var i = 0; i < modifiers.normal.length; i++) {
  616. if (!modifiers.normal[i].activated) {
  617. continue;
  618. }
  619. var event = new KeyboardEvent("", { key: modifiers.normal[i].key });
  620. TIP.keyup(event,
  621. TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
  622. }
  623. for (var i = 0; i < modifiers.lockable.length; i++) {
  624. if (!modifiers.lockable[i].activated) {
  625. continue;
  626. }
  627. if (!TIP.getModifierState(modifiers.lockable[i].key)) {
  628. continue; // who already inactivated this?
  629. }
  630. var event = new KeyboardEvent("", { key: modifiers.lockable[i].key });
  631. TIP.keydown(event,
  632. TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
  633. TIP.keyup(event,
  634. TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
  635. }
  636. }
  637. function getNavigator_(win = window)
  638. {
  639. if (typeof navigator != "undefined") {
  640. return navigator;
  641. }
  642. return win.navigator;
  643. }
  644. function isMac_(win = window) {
  645. if (win) {
  646. try {
  647. return win.navigator.platform.indexOf("Mac") > -1;
  648. } catch (ex) {}
  649. }
  650. return navigator.platform.indexOf("Mac") > -1;
  651. }
  652. function guessKeyNameFromKeyCode_(aKeyCode, win = window)
  653. {
  654. var KeyboardEvent = getKeyboardEvent_(win);
  655. switch (aKeyCode) {
  656. case KeyboardEvent.DOM_VK_CANCEL:
  657. return "Cancel";
  658. case KeyboardEvent.DOM_VK_HELP:
  659. return "Help";
  660. case KeyboardEvent.DOM_VK_BACK_SPACE:
  661. return "Backspace";
  662. case KeyboardEvent.DOM_VK_TAB:
  663. return "Tab";
  664. case KeyboardEvent.DOM_VK_CLEAR:
  665. return "Clear";
  666. case KeyboardEvent.DOM_VK_RETURN:
  667. return "Enter";
  668. case KeyboardEvent.DOM_VK_SHIFT:
  669. return "Shift";
  670. case KeyboardEvent.DOM_VK_CONTROL:
  671. return "Control";
  672. case KeyboardEvent.DOM_VK_ALT:
  673. return "Alt";
  674. case KeyboardEvent.DOM_VK_PAUSE:
  675. return "Pause";
  676. case KeyboardEvent.DOM_VK_EISU:
  677. return "Eisu";
  678. case KeyboardEvent.DOM_VK_ESCAPE:
  679. return "Escape";
  680. case KeyboardEvent.DOM_VK_CONVERT:
  681. return "Convert";
  682. case KeyboardEvent.DOM_VK_NONCONVERT:
  683. return "NonConvert";
  684. case KeyboardEvent.DOM_VK_ACCEPT:
  685. return "Accept";
  686. case KeyboardEvent.DOM_VK_MODECHANGE:
  687. return "ModeChange";
  688. case KeyboardEvent.DOM_VK_PAGE_UP:
  689. return "PageUp";
  690. case KeyboardEvent.DOM_VK_PAGE_DOWN:
  691. return "PageDown";
  692. case KeyboardEvent.DOM_VK_END:
  693. return "End";
  694. case KeyboardEvent.DOM_VK_HOME:
  695. return "Home";
  696. case KeyboardEvent.DOM_VK_LEFT:
  697. return "ArrowLeft";
  698. case KeyboardEvent.DOM_VK_UP:
  699. return "ArrowUp";
  700. case KeyboardEvent.DOM_VK_RIGHT:
  701. return "ArrowRight";
  702. case KeyboardEvent.DOM_VK_DOWN:
  703. return "ArrowDown";
  704. case KeyboardEvent.DOM_VK_SELECT:
  705. return "Select";
  706. case KeyboardEvent.DOM_VK_PRINT:
  707. return "Print";
  708. case KeyboardEvent.DOM_VK_EXECUTE:
  709. return "Execute";
  710. case KeyboardEvent.DOM_VK_PRINTSCREEN:
  711. return "PrintScreen";
  712. case KeyboardEvent.DOM_VK_INSERT:
  713. return "Insert";
  714. case KeyboardEvent.DOM_VK_DELETE:
  715. return "Delete";
  716. case KeyboardEvent.DOM_VK_WIN:
  717. return "OS";
  718. case KeyboardEvent.DOM_VK_CONTEXT_MENU:
  719. return "ContextMenu";
  720. case KeyboardEvent.DOM_VK_SLEEP:
  721. return "Standby";
  722. case KeyboardEvent.DOM_VK_F1:
  723. return "F1";
  724. case KeyboardEvent.DOM_VK_F2:
  725. return "F2";
  726. case KeyboardEvent.DOM_VK_F3:
  727. return "F3";
  728. case KeyboardEvent.DOM_VK_F4:
  729. return "F4";
  730. case KeyboardEvent.DOM_VK_F5:
  731. return "F5";
  732. case KeyboardEvent.DOM_VK_F6:
  733. return "F6";
  734. case KeyboardEvent.DOM_VK_F7:
  735. return "F7";
  736. case KeyboardEvent.DOM_VK_F8:
  737. return "F8";
  738. case KeyboardEvent.DOM_VK_F9:
  739. return "F9";
  740. case KeyboardEvent.DOM_VK_F10:
  741. return "F10";
  742. case KeyboardEvent.DOM_VK_F11:
  743. return "F11";
  744. case KeyboardEvent.DOM_VK_F12:
  745. return "F12";
  746. case KeyboardEvent.DOM_VK_F13:
  747. return "F13";
  748. case KeyboardEvent.DOM_VK_F14:
  749. return "F14";
  750. case KeyboardEvent.DOM_VK_F15:
  751. return "F15";
  752. case KeyboardEvent.DOM_VK_F16:
  753. return "F16";
  754. case KeyboardEvent.DOM_VK_F17:
  755. return "F17";
  756. case KeyboardEvent.DOM_VK_F18:
  757. return "F18";
  758. case KeyboardEvent.DOM_VK_F19:
  759. return "F19";
  760. case KeyboardEvent.DOM_VK_F20:
  761. return "F20";
  762. case KeyboardEvent.DOM_VK_F21:
  763. return "F21";
  764. case KeyboardEvent.DOM_VK_F22:
  765. return "F22";
  766. case KeyboardEvent.DOM_VK_F23:
  767. return "F23";
  768. case KeyboardEvent.DOM_VK_F24:
  769. return "F24";
  770. case KeyboardEvent.DOM_VK_NUM_LOCK:
  771. return "NumLock";
  772. case KeyboardEvent.DOM_VK_SCROLL_LOCK:
  773. return "ScrollLock";
  774. case KeyboardEvent.DOM_VK_VOLUME_MUTE:
  775. return "AudioVolumeMute";
  776. case KeyboardEvent.DOM_VK_VOLUME_DOWN:
  777. return "AudioVolumeDown";
  778. case KeyboardEvent.DOM_VK_VOLUME_UP:
  779. return "AudioVolumeUp";
  780. case KeyboardEvent.DOM_VK_META:
  781. return "Meta";
  782. case KeyboardEvent.DOM_VK_ALTGR:
  783. return "AltGraph";
  784. case KeyboardEvent.DOM_VK_ATTN:
  785. return "Attn";
  786. case KeyboardEvent.DOM_VK_CRSEL:
  787. return "CrSel";
  788. case KeyboardEvent.DOM_VK_EXSEL:
  789. return "ExSel";
  790. case KeyboardEvent.DOM_VK_EREOF:
  791. return "EraseEof";
  792. case KeyboardEvent.DOM_VK_PLAY:
  793. return "Play";
  794. default:
  795. return "Unidentified";
  796. }
  797. }
  798. /**
  799. * Indicate that an event with an original target and type is expected
  800. * to be fired, or not expected to be fired.
  801. */
  802. function expectEvent_(expectedTarget, expectedEvent, testName) {
  803. if (!expectedTarget || !expectedEvent) {
  804. return null;
  805. }
  806. seenEvent = false;
  807. let type;
  808. if (expectedEvent.charAt(0) == "!") {
  809. type = expectedEvent.substring(1);
  810. } else {
  811. type = expectedEvent;
  812. }
  813. let handler = ev => {
  814. let pass = (!seenEvent && ev.originalTarget == expectedTarget && ev.type == type);
  815. is(pass, true, `${testName} ${type} event target ${seenEvent ? "twice" : ""}`);
  816. seenEvent = true;
  817. };
  818. expectedTarget.addEventListener(type, handler, false);
  819. return handler;
  820. }
  821. /**
  822. * Check if the event was fired or not. The provided event handler will
  823. * be removed.
  824. */
  825. function checkExpectedEvent_(
  826. expectedTarget, expectedEvent, eventHandler, testName) {
  827. if (eventHandler) {
  828. let expectEvent = (expectedEvent.charAt(0) != "!");
  829. let type = expectEvent;
  830. if (!type) {
  831. type = expectedEvent.substring(1);
  832. }
  833. expectedTarget.removeEventListener(type, eventHandler, false);
  834. let desc = `${type} event`;
  835. if (!expectEvent) {
  836. desc += " not";
  837. }
  838. is(seenEvent, expectEvent, `${testName} ${desc} fired`);
  839. }
  840. seenEvent = false;
  841. }
  842. /**
  843. * Similar to event.synthesizeMouse except that a test is performed to
  844. * see if an event is fired at the right target as a result.
  845. *
  846. * To test that an event is not fired, use an expected type preceded by
  847. * an exclamation mark, such as "!select". This might be used to test that
  848. * a click on a disabled element doesn't fire certain events for instance.
  849. *
  850. * @param {Element} target
  851. * Synthesise the mouse event on this target.
  852. * @param {number} offsetX
  853. * Horizontal offset from the target's bounding box.
  854. * @param {number} offsetY
  855. * Vertical offset from the target's bounding box.
  856. * @param {Object.<string, ?>} ev
  857. * Object which may contain the properties shiftKey, ctrlKey, altKey,
  858. * metaKey, accessKey, type.
  859. * @param {Element} expectedTarget
  860. * Expected originalTarget of the event.
  861. * @param {DOMEvent} expectedEvent
  862. * Expected type of the event, such as "select".
  863. * @param {string} testName
  864. * Test name when outputing results.
  865. * @param {Window=} window
  866. * Window object. Defaults to the current window.
  867. */
  868. event.synthesizeMouseExpectEvent = function (
  869. target, offsetX, offsetY, ev, expectedTarget, expectedEvent,
  870. testName, window = undefined) {
  871. let eventHandler = expectEvent_(
  872. expectedTarget,
  873. expectedEvent,
  874. testName);
  875. event.synthesizeMouse(target, offsetX, offsetY, ev, window);
  876. checkExpectedEvent_(
  877. expectedTarget,
  878. expectedEvent,
  879. eventHandler,
  880. testName);
  881. };
  882. /**
  883. * Similar to synthesizeKey except that a test is performed to see if
  884. * an event is fired at the right target as a result.
  885. *
  886. * @param {string} key
  887. * Key to synthesise.
  888. * @param {Object.<string, ?>} ev
  889. * Object which may contain the properties shiftKey, ctrlKey, altKey,
  890. * metaKey, accessKey, type.
  891. * @param {Element} expectedTarget
  892. * Expected originalTarget of the event.
  893. * @param {DOMEvent} expectedEvent
  894. * Expected type of the event, such as "select".
  895. * @param {string} testName
  896. * Test name when outputing results
  897. * @param {Window=} window
  898. * Window object. Defaults to the current window.
  899. *
  900. * To test that an event is not fired, use an expected type preceded by an
  901. * exclamation mark, such as "!select".
  902. *
  903. * aWindow is optional, and defaults to the current window object.
  904. */
  905. event.synthesizeKeyExpectEvent = function (
  906. key, ev, expectedTarget, expectedEvent, testName,
  907. window = undefined) {
  908. let eventHandler = expectEvent_(
  909. expectedTarget,
  910. expectedEvent,
  911. testName);
  912. event.synthesizeKey(key, ev, window);
  913. checkExpectedEvent_(
  914. expectedTarget,
  915. expectedEvent,
  916. eventHandler,
  917. testName);
  918. };
  919. /**
  920. * Synthesize a composition event.
  921. *
  922. * @param {DOMEvent} ev
  923. * The composition event information. This must have |type|
  924. * member. The value must be "compositionstart", "compositionend" or
  925. * "compositionupdate". And also this may have |data| and |locale|
  926. * which would be used for the value of each property of the
  927. * composition event. Note that the data would be ignored if the
  928. * event type were "compositionstart".
  929. * @param {Window=} window
  930. * Window object. Defaults to the current window.
  931. */
  932. event.synthesizeComposition = function (ev, window = undefined) {
  933. let domutils = getDOMWindowUtils(window);
  934. domutils.sendCompositionEvent(ev.type, ev.data || "", ev.locale || "");
  935. };
  936. /**
  937. * Synthesize a text event.
  938. *
  939. * The text event's information, this has |composition| and |caret|
  940. * members. |composition| has |string| and |clauses| members. |clauses|
  941. * must be array object. Each object has |length| and |attr|.
  942. * And |caret| has |start| and |length|. See the following tree image.
  943. *
  944. * ev
  945. * +-- composition
  946. * | +-- string
  947. * | +-- clauses[]
  948. * | +-- length
  949. * | +-- attr
  950. * +-- caret
  951. * +-- start
  952. * +-- length
  953. *
  954. * Set the composition string to |composition.string|. Set its clauses
  955. * information to the |clauses| array.
  956. *
  957. * When it's composing, set the each clauses' length
  958. * to the |composition.clauses[n].length|. The sum
  959. * of the all length values must be same as the length of
  960. * |composition.string|. Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
  961. * |composition.clauses[n].attr|.
  962. *
  963. * When it's not composing, set 0 to the |composition.clauses[0].length|
  964. * and |composition.clauses[0].attr|.
  965. *
  966. * Set caret position to the |caret.start|. Its offset from the start of
  967. * the composition string. Set caret length to |caret.length|. If it's
  968. * larger than 0, it should be wide caret. However, current nsEditor
  969. * doesn't support wide caret, therefore, you should always set 0 now.
  970. *
  971. * @param {Object.<string, ?>} ev
  972. * The text event's information,
  973. * @param {Window=} window
  974. * Window object. Defaults to the current window.
  975. */
  976. event.synthesizeText = function (ev, window = undefined) {
  977. let domutils = getDOMWindowUtils(window);
  978. if (!ev.composition ||
  979. !ev.composition.clauses ||
  980. !ev.composition.clauses[0]) {
  981. return;
  982. }
  983. let firstClauseLength = ev.composition.clauses[0].length;
  984. let firstClauseAttr = ev.composition.clauses[0].attr;
  985. let secondClauseLength = 0;
  986. let secondClauseAttr = 0;
  987. let thirdClauseLength = 0;
  988. let thirdClauseAttr = 0;
  989. if (ev.composition.clauses[1]) {
  990. secondClauseLength = ev.composition.clauses[1].length;
  991. secondClauseAttr = ev.composition.clauses[1].attr;
  992. if (event.composition.clauses[2]) {
  993. thirdClauseLength = ev.composition.clauses[2].length;
  994. thirdClauseAttr = ev.composition.clauses[2].attr;
  995. }
  996. }
  997. let caretStart = -1;
  998. let caretLength = 0;
  999. if (event.caret) {
  1000. caretStart = ev.caret.start;
  1001. caretLength = ev.caret.length;
  1002. }
  1003. domutils.sendTextEvent(
  1004. ev.composition.string,
  1005. firstClauseLength,
  1006. firstClauseAttr,
  1007. secondClauseLength,
  1008. secondClauseAttr,
  1009. thirdClauseLength,
  1010. thirdClauseAttr,
  1011. caretStart,
  1012. caretLength);
  1013. };
  1014. /**
  1015. * Synthesize a query selected text event.
  1016. *
  1017. * @param {Window=}
  1018. * Window object. Defaults to the current window.
  1019. *
  1020. * @return {(nsIQueryContentEventResult|null)}
  1021. * Event's result, or null if it failed.
  1022. */
  1023. event.synthesizeQuerySelectedText = function (window = undefined) {
  1024. let domutils = getDOMWindowUtils(window);
  1025. return domutils.sendQueryContentEvent(
  1026. domutils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
  1027. };
  1028. /**
  1029. * Synthesize a selection set event.
  1030. *
  1031. * @param {number} offset
  1032. * Character offset. 0 means the first character in the selection
  1033. * root.
  1034. * @param {number} length
  1035. * Length of the text. If the length is too long, the extra length
  1036. * is ignored.
  1037. * @param {boolean} reverse
  1038. * If true, the selection is from |aOffset + aLength| to |aOffset|.
  1039. * Otherwise, from |aOffset| to |aOffset + aLength|.
  1040. * @param {Window=} window
  1041. * Window object. Defaults to the current window.
  1042. *
  1043. * @return True, if succeeded. Otherwise false.
  1044. */
  1045. event.synthesizeSelectionSet = function (
  1046. offset, length, reverse, window = undefined) {
  1047. let domutils = getDOMWindowUtils(window);
  1048. return domutils.sendSelectionSetEvent(offset, length, reverse);
  1049. };
  1050. const KEYCODES_LOOKUP = {
  1051. "VK_SHIFT": "shiftKey",
  1052. "VK_CONTROL": "ctrlKey",
  1053. "VK_ALT": "altKey",
  1054. "VK_META": "metaKey",
  1055. };
  1056. const VIRTUAL_KEYCODE_LOOKUP = {
  1057. "\uE001": "VK_CANCEL",
  1058. "\uE002": "VK_HELP",
  1059. "\uE003": "VK_BACK_SPACE",
  1060. "\uE004": "VK_TAB",
  1061. "\uE005": "VK_CLEAR",
  1062. "\uE006": "VK_RETURN",
  1063. "\uE007": "VK_RETURN",
  1064. "\uE008": "VK_SHIFT",
  1065. "\uE009": "VK_CONTROL",
  1066. "\uE00A": "VK_ALT",
  1067. "\uE03D": "VK_META",
  1068. "\uE00B": "VK_PAUSE",
  1069. "\uE00C": "VK_ESCAPE",
  1070. "\uE00D": "VK_SPACE", // printable
  1071. "\uE00E": "VK_PAGE_UP",
  1072. "\uE00F": "VK_PAGE_DOWN",
  1073. "\uE010": "VK_END",
  1074. "\uE011": "VK_HOME",
  1075. "\uE012": "VK_LEFT",
  1076. "\uE013": "VK_UP",
  1077. "\uE014": "VK_RIGHT",
  1078. "\uE015": "VK_DOWN",
  1079. "\uE016": "VK_INSERT",
  1080. "\uE017": "VK_DELETE",
  1081. "\uE018": "VK_SEMICOLON",
  1082. "\uE019": "VK_EQUALS",
  1083. "\uE01A": "VK_NUMPAD0",
  1084. "\uE01B": "VK_NUMPAD1",
  1085. "\uE01C": "VK_NUMPAD2",
  1086. "\uE01D": "VK_NUMPAD3",
  1087. "\uE01E": "VK_NUMPAD4",
  1088. "\uE01F": "VK_NUMPAD5",
  1089. "\uE020": "VK_NUMPAD6",
  1090. "\uE021": "VK_NUMPAD7",
  1091. "\uE022": "VK_NUMPAD8",
  1092. "\uE023": "VK_NUMPAD9",
  1093. "\uE024": "VK_MULTIPLY",
  1094. "\uE025": "VK_ADD",
  1095. "\uE026": "VK_SEPARATOR",
  1096. "\uE027": "VK_SUBTRACT",
  1097. "\uE028": "VK_DECIMAL",
  1098. "\uE029": "VK_DIVIDE",
  1099. "\uE031": "VK_F1",
  1100. "\uE032": "VK_F2",
  1101. "\uE033": "VK_F3",
  1102. "\uE034": "VK_F4",
  1103. "\uE035": "VK_F5",
  1104. "\uE036": "VK_F6",
  1105. "\uE037": "VK_F7",
  1106. "\uE038": "VK_F8",
  1107. "\uE039": "VK_F9",
  1108. "\uE03A": "VK_F10",
  1109. "\uE03B": "VK_F11",
  1110. "\uE03C": "VK_F12",
  1111. };
  1112. function getKeyCode(c) {
  1113. if (c in VIRTUAL_KEYCODE_LOOKUP) {
  1114. return VIRTUAL_KEYCODE_LOOKUP[c];
  1115. }
  1116. return c;
  1117. }
  1118. event.sendKeyDown = function (keyToSend, modifiers, document) {
  1119. modifiers.type = "keydown";
  1120. event.sendSingleKey(keyToSend, modifiers, document);
  1121. // TODO This doesn't do anything since |synthesizeKeyEvent| ignores explicit
  1122. // keypress request, and instead figures out itself when to send keypress
  1123. if (["VK_SHIFT", "VK_CONTROL", "VK_ALT", "VK_META"].indexOf(getKeyCode(keyToSend)) < 0) {
  1124. modifiers.type = "keypress";
  1125. event.sendSingleKey(keyToSend, modifiers, document);
  1126. }
  1127. delete modifiers.type;
  1128. };
  1129. event.sendKeyUp = function (keyToSend, modifiers, window = undefined) {
  1130. modifiers.type = "keyup";
  1131. event.sendSingleKey(keyToSend, modifiers, window);
  1132. delete modifiers.type;
  1133. };
  1134. /**
  1135. * Synthesize a key event for a single key.
  1136. *
  1137. * @param {string} keyToSend
  1138. * Code point or normalized key value
  1139. * @param {?} modifiers
  1140. * Object with properties used in KeyboardEvent (shiftkey, repeat, ...)
  1141. * as well as, the event |type| such as keydown. All properties are optional.
  1142. * @param {Window=} window
  1143. * Window object. If |window| is undefined, the event is synthesized in
  1144. * current window.
  1145. */
  1146. event.sendSingleKey = function (keyToSend, modifiers, window = undefined) {
  1147. let keyCode = getKeyCode(keyToSend);
  1148. if (keyCode in KEYCODES_LOOKUP) {
  1149. // We assume that if |keyToSend| is a raw code point (like "\uE009") then
  1150. // |modifiers| does not already have correct value for corresponding
  1151. // |modName| attribute (like ctrlKey), so that value needs to be flipped
  1152. let modName = KEYCODES_LOOKUP[keyCode];
  1153. modifiers[modName] = !modifiers[modName];
  1154. } else if (modifiers.shiftKey && keyCode != "Shift") {
  1155. keyCode = keyCode.toUpperCase();
  1156. }
  1157. event.synthesizeKey(keyCode, modifiers, window);
  1158. };
  1159. /**
  1160. * Focus element and, if a textual input field and no previous selection
  1161. * state exists, move the caret to the end of the input field.
  1162. *
  1163. * @param {Element} element
  1164. * Element to focus.
  1165. */
  1166. function focusElement(element) {
  1167. let t = element.type;
  1168. if (t && (t == "text" || t == "textarea")) {
  1169. if (element.selectionEnd == 0) {
  1170. let len = element.value.length;
  1171. element.setSelectionRange(len, len);
  1172. }
  1173. }
  1174. element.focus();
  1175. }
  1176. /**
  1177. * @param {Array.<string>} keySequence
  1178. * @param {Element} element
  1179. * @param {Object.<string, boolean>=} opts
  1180. * @param {Window=} window
  1181. */
  1182. event.sendKeysToElement = function (
  1183. keySequence, el, opts = {}, window = undefined) {
  1184. if (opts.ignoreVisibility || element.isVisible(el)) {
  1185. focusElement(el);
  1186. // make Object.<modifier, false> map
  1187. let modifiers = Object.create(event.Modifiers);
  1188. for (let modifier in event.Modifiers) {
  1189. modifiers[modifier] = false;
  1190. }
  1191. let value = keySequence.join("");
  1192. for (let i = 0; i < value.length; i++) {
  1193. let c = value.charAt(i);
  1194. event.sendSingleKey(c, modifiers, window);
  1195. }
  1196. } else {
  1197. throw new ElementNotInteractableError("Element is not visible");
  1198. }
  1199. };
  1200. event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
  1201. opts.canBubble = opts.canBubble || true;
  1202. let doc = el.ownerDocument || el.document;
  1203. let ev = doc.createEvent("Event");
  1204. ev.shiftKey = modifiers["shift"];
  1205. ev.metaKey = modifiers["meta"];
  1206. ev.altKey = modifiers["alt"];
  1207. ev.ctrlKey = modifiers["ctrl"];
  1208. ev.initEvent(eventType, opts.canBubble, true);
  1209. el.dispatchEvent(ev);
  1210. };
  1211. event.focus = function (el, opts = {}) {
  1212. opts.canBubble = opts.canBubble || true;
  1213. let doc = el.ownerDocument || el.document;
  1214. let win = doc.defaultView;
  1215. let ev = new win.FocusEvent(el);
  1216. ev.initEvent("focus", opts.canBubble, true);
  1217. el.dispatchEvent(ev);
  1218. };
  1219. event.mouseover = function (el, modifiers = {}, opts = {}) {
  1220. return event.sendEvent("mouseover", el, modifiers, opts);
  1221. };
  1222. event.mousemove = function (el, modifiers = {}, opts = {}) {
  1223. return event.sendEvent("mousemove", el, modifiers, opts);
  1224. };
  1225. event.mousedown = function (el, modifiers = {}, opts = {}) {
  1226. return event.sendEvent("mousedown", el, modifiers, opts);
  1227. };
  1228. event.mouseup = function (el, modifiers = {}, opts = {}) {
  1229. return event.sendEvent("mouseup", el, modifiers, opts);
  1230. };
  1231. event.click = function (el, modifiers = {}, opts = {}) {
  1232. return event.sendEvent("click", el, modifiers, opts);
  1233. };
  1234. event.change = function (el, modifiers = {}, opts = {}) {
  1235. return event.sendEvent("change", el, modifiers, opts);
  1236. };
  1237. event.input = function (el, modifiers = {}, opts = {}) {
  1238. return event.sendEvent("input", el, modifiers, opts);
  1239. };