michael@0: /* michael@0: * Copyright 2007-2009 WebDriver committers michael@0: * Copyright 2007-2009 Google Inc. michael@0: * Portions copyright 2012 Software Freedom Conservancy michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: michael@0: var type = function(doc, element, text, releaseModifiers, michael@0: opt_keysState) { michael@0: michael@0: var currentTextLength = element.value ? element.value.length : 0; michael@0: element.selectionStart = currentTextLength; michael@0: element.selectionEnd = currentTextLength; michael@0: michael@0: // For consistency between native and synthesized events, convert common michael@0: // escape sequences to their Key enum aliases. michael@0: text = text.replace(new RegExp('\b', 'g'), '\uE003'). // DOM_VK_BACK_SPACE michael@0: replace(/\t/g, '\uE004'). // DOM_VK_TAB michael@0: replace(/(\r\n|\n|\r)/g, '\uE006'); // DOM_VK_RETURN michael@0: michael@0: var controlKey = false; michael@0: var shiftKey = false; michael@0: var altKey = false; michael@0: var metaKey = false; michael@0: if (opt_keysState) { michael@0: controlKey = opt_keysState.control; michael@0: shiftKey = opt_keysState.shiftKey; michael@0: altKey = opt_keysState.alt; michael@0: metaKey = opt_keysState.meta; michael@0: } michael@0: michael@0: shiftCount = 0; michael@0: michael@0: var upper = text.toUpperCase(); michael@0: michael@0: for (var i = 0; i < text.length; i++) { michael@0: var c = text.charAt(i); michael@0: michael@0: // NULL key: reset modifier key states, and continue michael@0: michael@0: if (c == '\uE000') { michael@0: if (controlKey) { michael@0: var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL; michael@0: keyEvent(doc, element, "keyup", kCode, 0, michael@0: controlKey = false, shiftKey, altKey, metaKey, false); michael@0: } michael@0: michael@0: if (shiftKey) { michael@0: var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT; michael@0: keyEvent(doc, element, "keyup", kCode, 0, michael@0: controlKey, shiftKey = false, altKey, metaKey, false); michael@0: } michael@0: michael@0: if (altKey) { michael@0: var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT; michael@0: keyEvent(doc, element, "keyup", kCode, 0, michael@0: controlKey, shiftKey, altKey = false, metaKey, false); michael@0: } michael@0: michael@0: if (metaKey) { michael@0: var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META; michael@0: keyEvent(doc, element, "keyup", kCode, 0, michael@0: controlKey, shiftKey, altKey, metaKey = false, false); michael@0: } michael@0: michael@0: continue; michael@0: } michael@0: michael@0: // otherwise decode keyCode, charCode, modifiers ... michael@0: michael@0: var modifierEvent = ""; michael@0: var charCode = 0; michael@0: var keyCode = 0; michael@0: michael@0: if (c == '\uE001') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CANCEL; michael@0: } else if (c == '\uE002') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_HELP; michael@0: } else if (c == '\uE003') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_BACK_SPACE; michael@0: } else if (c == '\uE004') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_TAB; michael@0: } else if (c == '\uE005') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CLEAR; michael@0: } else if (c == '\uE006' || c == '\uE007') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_RETURN; michael@0: } else if (c == '\uE008') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT; michael@0: shiftKey = !shiftKey; michael@0: modifierEvent = shiftKey ? "keydown" : "keyup"; michael@0: } else if (c == '\uE009') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL; michael@0: controlKey = !controlKey; michael@0: modifierEvent = controlKey ? "keydown" : "keyup"; michael@0: } else if (c == '\uE00A') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT; michael@0: altKey = !altKey; michael@0: modifierEvent = altKey ? "keydown" : "keyup"; michael@0: } else if (c == '\uE03D') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META; michael@0: metaKey = !metaKey; michael@0: modifierEvent = metaKey ? "keydown" : "keyup"; michael@0: } else if (c == '\uE00B') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PAUSE; michael@0: } else if (c == '\uE00C') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ESCAPE; michael@0: } else if (c == '\uE00D') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SPACE; michael@0: keyCode = charCode = ' '.charCodeAt(0); // printable michael@0: } else if (c == '\uE00E') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PAGE_UP; michael@0: } else if (c == '\uE00F') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN; michael@0: } else if (c == '\uE010') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_END; michael@0: } else if (c == '\uE011') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_HOME; michael@0: } else if (c == '\uE012') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_LEFT; michael@0: } else if (c == '\uE013') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_UP; michael@0: } else if (c == '\uE014') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_RIGHT; michael@0: } else if (c == '\uE015') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DOWN; michael@0: } else if (c == '\uE016') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_INSERT; michael@0: } else if (c == '\uE017') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DELETE; michael@0: } else if (c == '\uE018') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SEMICOLON; michael@0: charCode = ';'.charCodeAt(0); michael@0: } else if (c == '\uE019') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_EQUALS; michael@0: charCode = '='.charCodeAt(0); michael@0: } else if (c == '\uE01A') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD0; michael@0: charCode = '0'.charCodeAt(0); michael@0: } else if (c == '\uE01B') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD1; michael@0: charCode = '1'.charCodeAt(0); michael@0: } else if (c == '\uE01C') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD2; michael@0: charCode = '2'.charCodeAt(0); michael@0: } else if (c == '\uE01D') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD3; michael@0: charCode = '3'.charCodeAt(0); michael@0: } else if (c == '\uE01E') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD4; michael@0: charCode = '4'.charCodeAt(0); michael@0: } else if (c == '\uE01F') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD5; michael@0: charCode = '5'.charCodeAt(0); michael@0: } else if (c == '\uE020') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD6; michael@0: charCode = '6'.charCodeAt(0); michael@0: } else if (c == '\uE021') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD7; michael@0: charCode = '7'.charCodeAt(0); michael@0: } else if (c == '\uE022') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD8; michael@0: charCode = '8'.charCodeAt(0); michael@0: } else if (c == '\uE023') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD9; michael@0: charCode = '9'.charCodeAt(0); michael@0: } else if (c == '\uE024') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_MULTIPLY; michael@0: charCode = '*'.charCodeAt(0); michael@0: } else if (c == '\uE025') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ADD; michael@0: charCode = '+'.charCodeAt(0); michael@0: } else if (c == '\uE026') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SEPARATOR; michael@0: charCode = ','.charCodeAt(0); michael@0: } else if (c == '\uE027') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SUBTRACT; michael@0: charCode = '-'.charCodeAt(0); michael@0: } else if (c == '\uE028') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DECIMAL; michael@0: charCode = '.'.charCodeAt(0); michael@0: } else if (c == '\uE029') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DIVIDE; michael@0: charCode = '/'.charCodeAt(0); michael@0: } else if (c == '\uE031') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F1; michael@0: } else if (c == '\uE032') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F2; michael@0: } else if (c == '\uE033') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F3; michael@0: } else if (c == '\uE034') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F4; michael@0: } else if (c == '\uE035') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F5; michael@0: } else if (c == '\uE036') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F6; michael@0: } else if (c == '\uE037') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F7; michael@0: } else if (c == '\uE038') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F8; michael@0: } else if (c == '\uE039') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F9; michael@0: } else if (c == '\uE03A') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F10; michael@0: } else if (c == '\uE03B') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F11; michael@0: } else if (c == '\uE03C') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F12; michael@0: } else if (c == ',' || c == '<') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_COMMA; michael@0: charCode = c.charCodeAt(0); michael@0: } else if (c == '.' || c == '>') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PERIOD; michael@0: charCode = c.charCodeAt(0); michael@0: } else if (c == '/' || c == '?') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SLASH; michael@0: charCode = text.charCodeAt(i); michael@0: } else if (c == '`' || c == '~') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_BACK_QUOTE; michael@0: charCode = c.charCodeAt(0); michael@0: } else if (c == '{' || c == '[') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET; michael@0: charCode = c.charCodeAt(0); michael@0: } else if (c == '\\' || c == '|') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_BACK_SLASH; michael@0: charCode = c.charCodeAt(0); michael@0: } else if (c == '}' || c == ']') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET; michael@0: charCode = c.charCodeAt(0); michael@0: } else if (c == '\'' || c == '"') { michael@0: keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_QUOTE; michael@0: charCode = c.charCodeAt(0); michael@0: } else { michael@0: keyCode = upper.charCodeAt(i); michael@0: charCode = text.charCodeAt(i); michael@0: } michael@0: michael@0: // generate modifier key event if needed, and continue michael@0: michael@0: if (modifierEvent) { michael@0: keyEvent(doc, element, modifierEvent, keyCode, 0, michael@0: controlKey, shiftKey, altKey, metaKey, false); michael@0: continue; michael@0: } michael@0: michael@0: // otherwise, shift down if needed michael@0: michael@0: var needsShift = false; michael@0: if (charCode) { michael@0: needsShift = /[A-Z\!\$\^\*\(\)\+\{\}\:\?\|~@#%&_"<>]/.test(c); michael@0: } michael@0: michael@0: if (needsShift && !shiftKey) { michael@0: var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT; michael@0: keyEvent(doc, element, "keydown", kCode, 0, michael@0: controlKey, true, altKey, metaKey, false); michael@0: shiftCount += 1; michael@0: } michael@0: michael@0: // generate key[down/press/up] for key michael@0: michael@0: var pressCode = keyCode; michael@0: if (charCode >= 32 && charCode < 127) { michael@0: pressCode = 0; michael@0: if (!needsShift && shiftKey && charCode > 32) { michael@0: // If typing a lowercase character key and the shiftKey is down, the michael@0: // charCode should be mapped to the shifted key value. This assumes michael@0: // a default 104 international keyboard layout. michael@0: if (charCode >= 97 && charCode <= 122) { michael@0: charCode = charCode + 65 - 97; // [a-z] -> [A-Z] michael@0: } else { michael@0: var mapFrom = '`1234567890-=[]\\;\',./'; michael@0: var mapTo = '~!@#$%^&*()_+{}|:"<>?'; michael@0: michael@0: var value = String.fromCharCode(charCode). michael@0: replace(/([\[\\\.])/g, '\\$1'); michael@0: var index = mapFrom.search(value); michael@0: if (index >= 0) { michael@0: charCode = mapTo.charCodeAt(index); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: var accepted = michael@0: keyEvent(doc, element, "keydown", keyCode, 0, michael@0: controlKey, needsShift || shiftKey, altKey, metaKey, false); michael@0: michael@0: keyEvent(doc, element, "keypress", pressCode, charCode, michael@0: controlKey, needsShift || shiftKey, altKey, metaKey, !accepted); michael@0: michael@0: keyEvent(doc, element, "keyup", keyCode, 0, michael@0: controlKey, needsShift || shiftKey, altKey, metaKey, false); michael@0: michael@0: // shift up if needed michael@0: michael@0: if (needsShift && !shiftKey) { michael@0: var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT; michael@0: keyEvent(doc, element, "keyup", kCode, 0, michael@0: controlKey, false, altKey, metaKey, false); michael@0: } michael@0: } michael@0: michael@0: // exit cleanup: keyup active modifier keys michael@0: michael@0: if (controlKey && releaseModifiers) { michael@0: var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL; michael@0: keyEvent(doc, element, "keyup", kCode, 0, michael@0: controlKey = false, shiftKey, altKey, metaKey, false); michael@0: } michael@0: michael@0: if (shiftKey && releaseModifiers) { michael@0: var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT; michael@0: keyEvent(doc, element, "keyup", kCode, 0, michael@0: controlKey, shiftKey = false, altKey, metaKey, false); michael@0: } michael@0: michael@0: if (altKey && releaseModifiers) { michael@0: var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT; michael@0: keyEvent(doc, element, "keyup", kCode, 0, michael@0: controlKey, shiftKey, altKey = false, metaKey, false); michael@0: } michael@0: michael@0: if (metaKey && releaseModifiers) { michael@0: var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META; michael@0: keyEvent(doc, element, "keyup", kCode, 0, michael@0: controlKey, shiftKey, altKey, metaKey = false, false); michael@0: } michael@0: michael@0: return { michael@0: shiftKey: shiftKey, michael@0: alt: altKey, michael@0: meta: metaKey, michael@0: control: controlKey michael@0: }; michael@0: }; michael@0: michael@0: michael@0: var keyEvent = function(doc, element, type, keyCode, charCode, michael@0: controlState, shiftState, altState, metaState, michael@0: shouldPreventDefault) { michael@0: var preventDefault = shouldPreventDefault == undefined ? false michael@0: : shouldPreventDefault; michael@0: michael@0: var keyboardEvent = doc.createEvent("KeyEvents"); michael@0: var currentView = doc.defaultView; michael@0: michael@0: keyboardEvent.initKeyEvent( michael@0: type, // in DOMString typeArg, michael@0: true, // in boolean canBubbleArg michael@0: true, // in boolean cancelableArg michael@0: currentView, // in nsIDOMAbstractView viewArg michael@0: controlState, // in boolean ctrlKeyArg michael@0: altState, // in boolean altKeyArg michael@0: shiftState, // in boolean shiftKeyArg michael@0: metaState, // in boolean metaKeyArg michael@0: keyCode, // in unsigned long keyCodeArg michael@0: charCode); // in unsigned long charCodeArg michael@0: michael@0: if (preventDefault) { michael@0: keyboardEvent.preventDefault(); michael@0: } michael@0: michael@0: var win = doc.defaultView; michael@0: var domUtil = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) michael@0: .getInterface(Components.interfaces.nsIDOMWindowUtils); michael@0: return domUtil.dispatchDOMEventViaPresShell(element, keyboardEvent, true); michael@0: }; michael@0: