goog.provide('bot.Keyboard');
goog.provide('bot.Keyboard.Key');
goog.provide('bot.Keyboard.Keys');
goog.require('bot.Device');
goog.require('bot.Error');
goog.require('bot.ErrorCode');
goog.require('bot.dom');
goog.require('bot.events.EventType');
goog.require('bot.userAgent');
goog.require('goog.array');
goog.require('goog.dom.TagName');
goog.require('goog.dom.selection');
goog.require('goog.structs.Map');
goog.require('goog.structs.Set');
goog.require('goog.userAgent');
bot.Keyboard = function (opt_state) {
goog.base(this);
this.editable_ = bot.dom.isEditable(this.getElement());
this.currentPos_ = 0;
this.pressed_ = new goog.structs.Set();
if (opt_state) {
goog.array.forEach(opt_state['pressed'], function (key) {
this.setKeyPressed_((key), true);
}, this);
this.currentPos_ = opt_state['currentPos'] || 0;
}
};
goog.inherits(bot.Keyboard, bot.Device);
bot.Keyboard.State;
bot.Keyboard.CHAR_TO_KEY_ = {};
bot.Keyboard.newKey_ = function (code, opt_char, opt_shiftChar) {
if (goog.isObject(code)) {
if (goog.userAgent.GECKO) {
code = code.gecko;
} else {
code = code.ieWebkit;
}
}
var key = new bot.Keyboard.Key(code, opt_char, opt_shiftChar);
if (opt_char && (!(opt_char in bot.Keyboard.CHAR_TO_KEY_) || opt_shiftChar)) {
bot.Keyboard.CHAR_TO_KEY_[opt_char] = { key: key, shift: false };
if (opt_shiftChar) {
bot.Keyboard.CHAR_TO_KEY_[opt_shiftChar] = { key: key, shift: true };
}
}
return key;
};
bot.Keyboard.Key = function (code, opt_char, opt_shiftChar) {
this.code = code;
this.character = opt_char || null;
this.shiftChar = opt_shiftChar || this.character;
};
bot.Keyboard.Keys = {
BACKSPACE: bot.Keyboard.newKey_(8),
TAB: bot.Keyboard.newKey_(9),
ENTER: bot.Keyboard.newKey_(13),
SHIFT: bot.Keyboard.newKey_(16),
CONTROL: bot.Keyboard.newKey_(17),
ALT: bot.Keyboard.newKey_(18),
PAUSE: bot.Keyboard.newKey_(19),
CAPS_LOCK: bot.Keyboard.newKey_(20),
ESC: bot.Keyboard.newKey_(27),
SPACE: bot.Keyboard.newKey_(32, ' '),
PAGE_UP: bot.Keyboard.newKey_(33),
PAGE_DOWN: bot.Keyboard.newKey_(34),
END: bot.Keyboard.newKey_(35),
HOME: bot.Keyboard.newKey_(36),
LEFT: bot.Keyboard.newKey_(37),
UP: bot.Keyboard.newKey_(38),
RIGHT: bot.Keyboard.newKey_(39),
DOWN: bot.Keyboard.newKey_(40),
PRINT_SCREEN: bot.Keyboard.newKey_(44),
INSERT: bot.Keyboard.newKey_(45),
DELETE: bot.Keyboard.newKey_(46),
ZERO: bot.Keyboard.newKey_(48, '0', ')'),
ONE: bot.Keyboard.newKey_(49, '1', '!'),
TWO: bot.Keyboard.newKey_(50, '2', '@'),
THREE: bot.Keyboard.newKey_(51, '3', '#'),
FOUR: bot.Keyboard.newKey_(52, '4', '$'),
FIVE: bot.Keyboard.newKey_(53, '5', '%'),
SIX: bot.Keyboard.newKey_(54, '6', '^'),
SEVEN: bot.Keyboard.newKey_(55, '7', '&'),
EIGHT: bot.Keyboard.newKey_(56, '8', '*'),
NINE: bot.Keyboard.newKey_(57, '9', '('),
A: bot.Keyboard.newKey_(65, 'a', 'A'),
B: bot.Keyboard.newKey_(66, 'b', 'B'),
C: bot.Keyboard.newKey_(67, 'c', 'C'),
D: bot.Keyboard.newKey_(68, 'd', 'D'),
E: bot.Keyboard.newKey_(69, 'e', 'E'),
F: bot.Keyboard.newKey_(70, 'f', 'F'),
G: bot.Keyboard.newKey_(71, 'g', 'G'),
H: bot.Keyboard.newKey_(72, 'h', 'H'),
I: bot.Keyboard.newKey_(73, 'i', 'I'),
J: bot.Keyboard.newKey_(74, 'j', 'J'),
K: bot.Keyboard.newKey_(75, 'k', 'K'),
L: bot.Keyboard.newKey_(76, 'l', 'L'),
M: bot.Keyboard.newKey_(77, 'm', 'M'),
N: bot.Keyboard.newKey_(78, 'n', 'N'),
O: bot.Keyboard.newKey_(79, 'o', 'O'),
P: bot.Keyboard.newKey_(80, 'p', 'P'),
Q: bot.Keyboard.newKey_(81, 'q', 'Q'),
R: bot.Keyboard.newKey_(82, 'r', 'R'),
S: bot.Keyboard.newKey_(83, 's', 'S'),
T: bot.Keyboard.newKey_(84, 't', 'T'),
U: bot.Keyboard.newKey_(85, 'u', 'U'),
V: bot.Keyboard.newKey_(86, 'v', 'V'),
W: bot.Keyboard.newKey_(87, 'w', 'W'),
X: bot.Keyboard.newKey_(88, 'x', 'X'),
Y: bot.Keyboard.newKey_(89, 'y', 'Y'),
Z: bot.Keyboard.newKey_(90, 'z', 'Z'),
META: bot.Keyboard.newKey_(
goog.userAgent.WINDOWS ? { gecko: 91, ieWebkit: 91 } :
(goog.userAgent.MAC ? { gecko: 224, ieWebkit: 91 } :
{ gecko: 0, ieWebkit: 91 })),
META_RIGHT: bot.Keyboard.newKey_(
goog.userAgent.WINDOWS ? { gecko: 92, ieWebkit: 92 } :
(goog.userAgent.MAC ? { gecko: 224, ieWebkit: 93 } :
{ gecko: 0, ieWebkit: 92 })),
CONTEXT_MENU: bot.Keyboard.newKey_(
goog.userAgent.WINDOWS ? { gecko: 93, ieWebkit: 93 } :
(goog.userAgent.MAC ? { gecko: 0, ieWebkit: 0 } :
{ gecko: 93, ieWebkit: null })),
NUM_ZERO: bot.Keyboard.newKey_({ gecko: 96, ieWebkit: 96 }, '0'),
NUM_ONE: bot.Keyboard.newKey_({ gecko: 97, ieWebkit: 97 }, '1'),
NUM_TWO: bot.Keyboard.newKey_({ gecko: 98, ieWebkit: 98 }, '2'),
NUM_THREE: bot.Keyboard.newKey_({ gecko: 99, ieWebkit: 99 }, '3'),
NUM_FOUR: bot.Keyboard.newKey_({ gecko: 100, ieWebkit: 100 }, '4'),
NUM_FIVE: bot.Keyboard.newKey_({ gecko: 101, ieWebkit: 101 }, '5'),
NUM_SIX: bot.Keyboard.newKey_({ gecko: 102, ieWebkit: 102 }, '6'),
NUM_SEVEN: bot.Keyboard.newKey_({ gecko: 103, ieWebkit: 103 }, '7'),
NUM_EIGHT: bot.Keyboard.newKey_({ gecko: 104, ieWebkit: 104 }, '8'),
NUM_NINE: bot.Keyboard.newKey_({ gecko: 105, ieWebkit: 105 }, '9'),
NUM_MULTIPLY: bot.Keyboard.newKey_(
{ gecko: 106, ieWebkit: 106 }, '*'),
NUM_PLUS: bot.Keyboard.newKey_(
{ gecko: 107, ieWebkit: 107 }, '+'),
NUM_MINUS: bot.Keyboard.newKey_(
{ gecko: 109, ieWebkit: 109 }, '-'),
NUM_PERIOD: bot.Keyboard.newKey_(
{ gecko: 110, ieWebkit: 110 }, '.'),
NUM_DIVISION: bot.Keyboard.newKey_(
{ gecko: 111, ieWebkit: 111 }, '/'),
NUM_LOCK: bot.Keyboard.newKey_(144),
F1: bot.Keyboard.newKey_(112),
F2: bot.Keyboard.newKey_(113),
F3: bot.Keyboard.newKey_(114),
F4: bot.Keyboard.newKey_(115),
F5: bot.Keyboard.newKey_(116),
F6: bot.Keyboard.newKey_(117),
F7: bot.Keyboard.newKey_(118),
F8: bot.Keyboard.newKey_(119),
F9: bot.Keyboard.newKey_(120),
F10: bot.Keyboard.newKey_(121),
F11: bot.Keyboard.newKey_(122),
F12: bot.Keyboard.newKey_(123),
EQUALS: bot.Keyboard.newKey_(
{ gecko: 107, ieWebkit: 187 }, '=', '+'),
SEPARATOR: bot.Keyboard.newKey_(108, ','),
HYPHEN: bot.Keyboard.newKey_(
{ gecko: 109, ieWebkit: 189 }, '-', '_'),
COMMA: bot.Keyboard.newKey_(188, ',', '<'),
PERIOD: bot.Keyboard.newKey_(190, '.', '>'),
SLASH: bot.Keyboard.newKey_(191, '/', '?'),
BACKTICK: bot.Keyboard.newKey_(192, '`', '~'),
OPEN_BRACKET: bot.Keyboard.newKey_(219, '[', '{'),
BACKSLASH: bot.Keyboard.newKey_(220, '\\', '|'),
CLOSE_BRACKET: bot.Keyboard.newKey_(221, ']', '}'),
SEMICOLON: bot.Keyboard.newKey_(
{ gecko: 59, ieWebkit: 186 }, ';', ':'),
APOSTROPHE: bot.Keyboard.newKey_(222, '\'', '"')
};
bot.Keyboard.Key.fromChar = function (ch) {
if (ch.length != 1) {
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
'Argument not a single character: ' + ch);
}
var keyShiftPair = bot.Keyboard.CHAR_TO_KEY_[ch];
if (!keyShiftPair) {
var upperCase = ch.toUpperCase();
var keyCode = upperCase.charCodeAt(0);
var key = bot.Keyboard.newKey_(keyCode, ch.toLowerCase(), upperCase);
keyShiftPair = { key: key, shift: (ch != key.character) };
}
return keyShiftPair;
};
bot.Keyboard.MODIFIERS = [
bot.Keyboard.Keys.ALT,
bot.Keyboard.Keys.CONTROL,
bot.Keyboard.Keys.META,
bot.Keyboard.Keys.SHIFT
];
bot.Keyboard.MODIFIER_TO_KEY_MAP_ = (function () {
var modifiersMap = new goog.structs.Map();
modifiersMap.set(bot.Device.Modifier.SHIFT,
bot.Keyboard.Keys.SHIFT);
modifiersMap.set(bot.Device.Modifier.CONTROL,
bot.Keyboard.Keys.CONTROL);
modifiersMap.set(bot.Device.Modifier.ALT,
bot.Keyboard.Keys.ALT);
modifiersMap.set(bot.Device.Modifier.META,
bot.Keyboard.Keys.META);
return modifiersMap;
})();
bot.Keyboard.KEY_TO_MODIFIER_ = (function (modifiersMap) {
var keyToModifierMap = new goog.structs.Map();
goog.array.forEach(modifiersMap.getKeys(), function (m) {
keyToModifierMap.set(modifiersMap.get(m).code, m);
});
return keyToModifierMap;
})(bot.Keyboard.MODIFIER_TO_KEY_MAP_);
bot.Keyboard.prototype.setKeyPressed_ = function (key, isPressed) {
if (goog.array.contains(bot.Keyboard.MODIFIERS, key)) {
var modifier = (
bot.Keyboard.KEY_TO_MODIFIER_.get(key.code));
this.modifiersState.setPressed(modifier, isPressed);
}
if (isPressed) {
this.pressed_.add(key);
} else {
this.pressed_.remove(key);
}
};
bot.Keyboard.NEW_LINE_ = goog.userAgent.IE ? '\r\n' : '\n';
bot.Keyboard.prototype.isPressed = function (key) {
return this.pressed_.contains(key);
};
bot.Keyboard.prototype.pressKey = function (key) {
if (goog.array.contains(bot.Keyboard.MODIFIERS, key) && this.isPressed(key)) {
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
'Cannot press a modifier key that is already pressed.');
}
var performDefault = !goog.isNull(key.code) &&
this.fireKeyEvent_(bot.events.EventType.KEYDOWN, key);
if (performDefault || goog.userAgent.GECKO) {
if (!this.requiresKeyPress_(key) ||
this.fireKeyEvent_(
bot.events.EventType.KEYPRESS, key, !performDefault)) {
if (performDefault) {
this.maybeSubmitForm_(key);
if (this.editable_) {
this.maybeEditText_(key);
}
}
}
}
this.setKeyPressed_(key, true);
};
bot.Keyboard.prototype.requiresKeyPress_ = function (key) {
if (key.character || key == bot.Keyboard.Keys.ENTER) {
return true;
} else if (goog.userAgent.WEBKIT || goog.userAgent.EDGE) {
return false;
} else if (goog.userAgent.IE) {
return key == bot.Keyboard.Keys.ESC;
} else {
switch (key) {
case bot.Keyboard.Keys.SHIFT:
case bot.Keyboard.Keys.CONTROL:
case bot.Keyboard.Keys.ALT:
return false;
case bot.Keyboard.Keys.META:
case bot.Keyboard.Keys.META_RIGHT:
case bot.Keyboard.Keys.CONTEXT_MENU:
return goog.userAgent.GECKO;
default:
return true;
}
}
};
bot.Keyboard.prototype.maybeSubmitForm_ = function (key) {
if (key != bot.Keyboard.Keys.ENTER) {
return;
}
if (goog.userAgent.GECKO ||
!bot.dom.isElement(this.getElement(), goog.dom.TagName.INPUT)) {
return;
}
var form = bot.Device.findAncestorForm(this.getElement());
if (form) {
var inputs = form.getElementsByTagName('input');
var hasSubmit = goog.array.some(inputs, function (e) {
return bot.Device.isFormSubmitElement(e);
});
if (hasSubmit || inputs.length == 1 ||
(goog.userAgent.WEBKIT && !bot.userAgent.isEngineVersion(534))) {
this.submitForm(form);
}
}
};
bot.Keyboard.prototype.maybeEditText_ = function (key) {
if (key.character) {
this.updateOnCharacter_(key);
} else {
switch (key) {
case bot.Keyboard.Keys.ENTER:
this.updateOnEnter_();
break;
case bot.Keyboard.Keys.BACKSPACE:
case bot.Keyboard.Keys.DELETE:
this.updateOnBackspaceOrDelete_(key);
break;
case bot.Keyboard.Keys.LEFT:
case bot.Keyboard.Keys.RIGHT:
this.updateOnLeftOrRight_(key);
break;
case bot.Keyboard.Keys.HOME:
case bot.Keyboard.Keys.END:
this.updateOnHomeOrEnd_(key);
break;
}
}
};
bot.Keyboard.prototype.releaseKey = function (key) {
if (!this.isPressed(key)) {
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
'Cannot release a key that is not pressed. (' + key.code + ')');
}
if (!goog.isNull(key.code)) {
this.fireKeyEvent_(bot.events.EventType.KEYUP, key);
}
this.setKeyPressed_(key, false);
};
bot.Keyboard.prototype.getChar_ = function (key) {
if (!key.character) {
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR, 'not a character key');
}
var shiftPressed = this.isPressed(bot.Keyboard.Keys.SHIFT);
return (shiftPressed ? key.shiftChar : key.character);
};
bot.Keyboard.KEYPRESS_EDITS_TEXT_ = goog.userAgent.GECKO &&
!bot.userAgent.isEngineVersion(12);
bot.Keyboard.prototype.updateOnCharacter_ = function (key) {
if (bot.Keyboard.KEYPRESS_EDITS_TEXT_) {
return;
}
var character = this.getChar_(key);
var newPos = goog.dom.selection.getStart(this.getElement()) + 1;
if (bot.Keyboard.supportsSelection(this.getElement())) {
goog.dom.selection.setText(this.getElement(), character);
goog.dom.selection.setStart(this.getElement(), newPos);
} else {
this.getElement().value += character;
}
if (goog.userAgent.WEBKIT) {
this.fireHtmlEvent(bot.events.EventType.TEXTINPUT);
}
if (!bot.userAgent.IE_DOC_PRE9) {
this.fireHtmlEvent(bot.events.EventType.INPUT);
}
this.updateCurrentPos_(newPos);
};
bot.Keyboard.prototype.updateOnEnter_ = function () {
if (bot.Keyboard.KEYPRESS_EDITS_TEXT_) {
return;
}
if (goog.userAgent.WEBKIT) {
this.fireHtmlEvent(bot.events.EventType.TEXTINPUT);
}
if (bot.dom.isElement(this.getElement(), goog.dom.TagName.TEXTAREA)) {
var newPos = goog.dom.selection.getStart(this.getElement()) +
bot.Keyboard.NEW_LINE_.length;
if (bot.Keyboard.supportsSelection(this.getElement())) {
goog.dom.selection.setText(this.getElement(), bot.Keyboard.NEW_LINE_);
goog.dom.selection.setStart(this.getElement(), newPos);
} else {
this.getElement().value += bot.Keyboard.NEW_LINE_;
}
if (!goog.userAgent.IE) {
this.fireHtmlEvent(bot.events.EventType.INPUT);
}
this.updateCurrentPos_(newPos);
}
};
bot.Keyboard.prototype.updateOnBackspaceOrDelete_ = function (key) {
if (bot.Keyboard.KEYPRESS_EDITS_TEXT_) {
return;
}
bot.Keyboard.checkCanUpdateSelection_(this.getElement());
var endpoints = goog.dom.selection.getEndPoints(this.getElement());
if (endpoints[0] == endpoints[1]) {
if (key == bot.Keyboard.Keys.BACKSPACE) {
goog.dom.selection.setStart(this.getElement(), endpoints[1] - 1);
goog.dom.selection.setEnd(this.getElement(), endpoints[1]);
} else {
goog.dom.selection.setEnd(this.getElement(), endpoints[1] + 1);
}
}
endpoints = goog.dom.selection.getEndPoints(this.getElement());
var textChanged = !(endpoints[0] == this.getElement().value.length ||
endpoints[1] == 0);
goog.dom.selection.setText(this.getElement(), '');
if (!goog.userAgent.IE && textChanged ||
(goog.userAgent.GECKO && key == bot.Keyboard.Keys.BACKSPACE)) {
this.fireHtmlEvent(bot.events.EventType.INPUT);
}
endpoints = goog.dom.selection.getEndPoints(this.getElement());
this.updateCurrentPos_(endpoints[1]);
};
bot.Keyboard.prototype.updateOnLeftOrRight_ = function (key) {
bot.Keyboard.checkCanUpdateSelection_(this.getElement());
var element = this.getElement();
var start = goog.dom.selection.getStart(element);
var end = goog.dom.selection.getEnd(element);
var newPos, startPos = 0, endPos = 0;
if (key == bot.Keyboard.Keys.LEFT) {
if (this.isPressed(bot.Keyboard.Keys.SHIFT)) {
if (this.currentPos_ == start) {
startPos = Math.max(start - 1, 0);
endPos = end;
newPos = startPos;
} else {
startPos = start;
endPos = end - 1;
newPos = endPos;
}
} else {
newPos = start == end ? Math.max(start - 1, 0) : start;
}
} else {
if (this.isPressed(bot.Keyboard.Keys.SHIFT)) {
if (this.currentPos_ == end) {
startPos = start;
endPos = Math.min(end + 1, element.value.length);
newPos = endPos;
} else {
startPos = start + 1;
endPos = end;
newPos = startPos;
}
} else {
newPos = start == end ? Math.min(end + 1, element.value.length) : end;
}
}
if (this.isPressed(bot.Keyboard.Keys.SHIFT)) {
goog.dom.selection.setStart(element, startPos);
goog.dom.selection.setEnd(element, endPos);
} else {
goog.dom.selection.setCursorPosition(element, newPos);
}
this.updateCurrentPos_(newPos);
};
bot.Keyboard.prototype.updateOnHomeOrEnd_ = function (key) {
bot.Keyboard.checkCanUpdateSelection_(this.getElement());
var element = this.getElement();
var start = goog.dom.selection.getStart(element);
var end = goog.dom.selection.getEnd(element);
if (key == bot.Keyboard.Keys.HOME) {
if (this.isPressed(bot.Keyboard.Keys.SHIFT)) {
goog.dom.selection.setStart(element, 0);
var endPos = this.currentPos_ == start ? end : start;
goog.dom.selection.setEnd(element, endPos);
} else {
goog.dom.selection.setCursorPosition(element, 0);
}
this.updateCurrentPos_(0);
} else {
if (this.isPressed(bot.Keyboard.Keys.SHIFT)) {
if (this.currentPos_ == start) {
goog.dom.selection.setStart(element, end);
}
goog.dom.selection.setEnd(element, element.value.length);
} else {
goog.dom.selection.setCursorPosition(element, element.value.length);
}
this.updateCurrentPos_(element.value.length);
}
};
bot.Keyboard.checkCanUpdateSelection_ = function (element) {
try {
if (typeof element.selectionStart == 'number') {
return;
}
} catch (ex) {
if (ex.message.indexOf('does not support selection.') != -1) {
throw Error(ex.message + ' (For more information, see ' +
'https://code.google.com/p/chromium/issues/detail?id=330456)');
}
throw ex;
}
throw Error('Element does not support selection');
};
bot.Keyboard.supportsSelection = function (element) {
try {
bot.Keyboard.checkCanUpdateSelection_(element);
} catch (ex) {
return false;
}
return true;
};
bot.Keyboard.prototype.updateCurrentPos_ = function (pos) {
this.currentPos_ = pos;
};
bot.Keyboard.prototype.fireKeyEvent_ = function (type, key, opt_preventDefault) {
if (goog.isNull(key.code)) {
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
'Key must have a keycode to be fired.');
}
var args = {
altKey: this.isPressed(bot.Keyboard.Keys.ALT),
ctrlKey: this.isPressed(bot.Keyboard.Keys.CONTROL),
metaKey: this.isPressed(bot.Keyboard.Keys.META),
shiftKey: this.isPressed(bot.Keyboard.Keys.SHIFT),
keyCode: key.code,
charCode: (key.character && type == bot.events.EventType.KEYPRESS) ?
this.getChar_(key).charCodeAt(0) : 0,
preventDefault: !!opt_preventDefault
};
return this.fireKeyboardEvent(type, args);
};
bot.Keyboard.prototype.moveCursor = function (element) {
this.setElement(element);
this.editable_ = bot.dom.isEditable(element);
var focusChanged = this.focusOnElement();
if (this.editable_ && focusChanged) {
goog.dom.selection.setCursorPosition(element, element.value.length);
this.updateCurrentPos_(element.value.length);
}
};
bot.Keyboard.prototype.getState = function () {
return {
'pressed': this.pressed_.getValues(),
'currentPos': this.currentPos_
};
};
bot.Keyboard.prototype.getModifiersState = function () {
return this.modifiersState;
};