2015-04-04 09:53:59 -07:00
|
|
|
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
|
|
|
// or more contributor license agreements. See the NOTICE file
|
|
|
|
|
// distributed with this work for additional information
|
|
|
|
|
// regarding copyright ownership. The SFC licenses this file
|
|
|
|
|
// to you under the Apache License, Version 2.0 (the
|
|
|
|
|
// "License"); you may not use this file except in compliance
|
|
|
|
|
// with the License. You may obtain a copy of the License at
|
2014-11-12 19:33:37 +00:00
|
|
|
//
|
2015-04-04 09:53:59 -07:00
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2014-11-12 19:33:37 +00:00
|
|
|
//
|
2015-04-04 09:53:59 -07:00
|
|
|
// Unless required by applicable law or agreed to in writing,
|
|
|
|
|
// software distributed under the License is distributed on an
|
|
|
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
|
// KIND, either express or implied. See the License for the
|
|
|
|
|
// specific language governing permissions and limitations
|
|
|
|
|
// under the License.
|
2010-08-19 13:39:26 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @fileoverview Atoms for simulating user actions against the DOM.
|
|
|
|
|
* The bot.action namespace is required since these atoms would otherwise form a
|
|
|
|
|
* circular dependency between bot.dom and bot.events.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
goog.provide('bot.action');
|
|
|
|
|
|
2011-03-24 17:40:36 +00:00
|
|
|
goog.require('bot');
|
2011-12-06 11:44:44 +00:00
|
|
|
goog.require('bot.Device');
|
2010-08-19 13:39:26 +00:00
|
|
|
goog.require('bot.Error');
|
|
|
|
|
goog.require('bot.ErrorCode');
|
2011-07-26 17:01:24 +00:00
|
|
|
goog.require('bot.Keyboard');
|
2011-08-03 17:59:47 +00:00
|
|
|
goog.require('bot.Mouse');
|
2011-12-19 14:41:17 +00:00
|
|
|
goog.require('bot.Touchscreen');
|
2010-08-19 13:39:26 +00:00
|
|
|
goog.require('bot.dom');
|
|
|
|
|
goog.require('bot.events');
|
2011-12-06 11:44:44 +00:00
|
|
|
goog.require('bot.events.EventType');
|
2010-08-19 13:39:26 +00:00
|
|
|
goog.require('goog.array');
|
2018-11-25 13:21:40 +00:00
|
|
|
goog.require('goog.dom.TagName');
|
2011-08-03 17:59:47 +00:00
|
|
|
goog.require('goog.math.Coordinate');
|
2011-12-19 14:41:17 +00:00
|
|
|
goog.require('goog.math.Vec2');
|
2012-09-13 10:52:38 +00:00
|
|
|
goog.require('goog.style');
|
2010-08-19 13:39:26 +00:00
|
|
|
|
2011-07-26 17:01:24 +00:00
|
|
|
|
|
|
|
|
/**
|
2011-08-24 16:03:31 +00:00
|
|
|
* Throws an exception if an element is not shown to the user, ignoring its
|
|
|
|
|
* opacity.
|
|
|
|
|
|
2011-07-26 17:01:24 +00:00
|
|
|
*
|
|
|
|
|
* @param {!Element} element The element to check.
|
|
|
|
|
* @see bot.dom.isShown.
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.checkShown_ = function (element) {
|
2011-08-24 16:03:31 +00:00
|
|
|
if (!bot.dom.isShown(element, /*ignoreOpacity=*/true)) {
|
2011-07-26 17:01:24 +00:00
|
|
|
throw new bot.Error(bot.ErrorCode.ELEMENT_NOT_VISIBLE,
|
2020-11-27 15:46:30 +00:00
|
|
|
'Element is not currently visible and may not be manipulated');
|
2011-07-26 17:01:24 +00:00
|
|
|
}
|
2010-12-22 17:04:29 +00:00
|
|
|
};
|
2010-08-19 13:39:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-02-19 20:46:15 +00:00
|
|
|
* Throws an exception if the given element cannot be interacted with.
|
2011-08-24 16:03:31 +00:00
|
|
|
*
|
2010-08-19 13:39:26 +00:00
|
|
|
* @param {!Element} element The element to check.
|
2012-02-19 20:46:15 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
2011-08-24 16:03:31 +00:00
|
|
|
* @see bot.dom.isInteractable.
|
|
|
|
|
* @private
|
2010-08-19 13:39:26 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.checkInteractable_ = function (element) {
|
2011-08-24 16:03:31 +00:00
|
|
|
if (!bot.dom.isInteractable(element)) {
|
|
|
|
|
throw new bot.Error(bot.ErrorCode.INVALID_ELEMENT_STATE,
|
2020-11-27 15:46:30 +00:00
|
|
|
'Element is not currently interactable and may not be manipulated');
|
2010-08-19 13:39:26 +00:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Clears the given `element` if it is a editable text field.
|
2011-07-26 21:15:06 +00:00
|
|
|
*
|
2010-08-19 13:39:26 +00:00
|
|
|
* @param {!Element} element The element to clear.
|
2012-02-19 20:46:15 +00:00
|
|
|
* @throws {bot.Error} If the element is not an editable text field.
|
2010-08-19 13:39:26 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.clear = function (element) {
|
2011-09-08 14:00:21 +00:00
|
|
|
bot.action.checkInteractable_(element);
|
2011-07-26 21:15:06 +00:00
|
|
|
if (!bot.dom.isEditable(element)) {
|
2010-12-22 17:04:29 +00:00
|
|
|
throw new bot.Error(bot.ErrorCode.INVALID_ELEMENT_STATE,
|
2020-11-27 15:46:30 +00:00
|
|
|
'Element must be user-editable in order to clear it.');
|
2010-12-22 17:04:29 +00:00
|
|
|
}
|
2010-08-19 13:39:26 +00:00
|
|
|
|
2011-07-26 21:15:06 +00:00
|
|
|
if (element.value) {
|
2018-03-05 17:18:03 -08:00
|
|
|
bot.action.LegacyDevice_.focusOnElement(element);
|
2018-03-17 12:14:08 -07:00
|
|
|
if (goog.userAgent.IE && bot.dom.isInputType(element, 'range')) {
|
|
|
|
|
var min = element.min ? element.min : 0;
|
|
|
|
|
var max = element.max ? element.max : 100;
|
|
|
|
|
element.value = (max < min) ? min : min + (max - min) / 2;
|
|
|
|
|
} else {
|
|
|
|
|
element.value = '';
|
|
|
|
|
}
|
2011-12-06 11:44:44 +00:00
|
|
|
bot.events.fire(element, bot.events.EventType.CHANGE);
|
2019-04-14 15:54:16 -07:00
|
|
|
if (goog.userAgent.IE) {
|
|
|
|
|
bot.events.fire(element, bot.events.EventType.BLUR);
|
|
|
|
|
}
|
2018-03-07 17:04:17 +06:00
|
|
|
var body = bot.getDocument().body;
|
|
|
|
|
if (body) {
|
|
|
|
|
bot.action.LegacyDevice_.focusOnElement(body);
|
|
|
|
|
} else {
|
|
|
|
|
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
|
|
|
|
|
'Cannot unfocus element after clearing.');
|
|
|
|
|
}
|
2015-11-03 11:01:42 -08:00
|
|
|
} else if (bot.dom.isElement(element, goog.dom.TagName.INPUT) &&
|
2020-11-27 15:46:30 +00:00
|
|
|
(element.getAttribute('type') && element.getAttribute('type').toLowerCase() == "number")) {
|
2015-11-03 11:01:42 -08:00
|
|
|
// number input fields that have invalid inputs
|
|
|
|
|
// report their value as empty string with no way to tell if there is a
|
|
|
|
|
// current value or not
|
2018-03-05 17:18:03 -08:00
|
|
|
bot.action.LegacyDevice_.focusOnElement(element);
|
2015-11-03 11:01:42 -08:00
|
|
|
element.value = '';
|
2020-11-27 15:46:30 +00:00
|
|
|
} else if (bot.dom.isContentEditable(element)) {
|
2011-12-06 11:44:44 +00:00
|
|
|
// A single space is required, if you put empty string here you'll not be
|
|
|
|
|
// able to interact with this element anymore in Firefox.
|
2018-03-05 17:18:03 -08:00
|
|
|
bot.action.LegacyDevice_.focusOnElement(element);
|
2023-02-15 12:44:23 +01:00
|
|
|
if (goog.userAgent.GECKO) {
|
|
|
|
|
element.innerHTML = ' ';
|
|
|
|
|
} else {
|
|
|
|
|
element.textContent = '';
|
|
|
|
|
}
|
2019-07-15 13:44:11 -07:00
|
|
|
var body = bot.getDocument().body;
|
|
|
|
|
if (body) {
|
|
|
|
|
bot.action.LegacyDevice_.focusOnElement(body);
|
|
|
|
|
} else {
|
|
|
|
|
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
|
|
|
|
|
'Cannot unfocus element after clearing.');
|
|
|
|
|
}
|
2011-12-06 11:44:44 +00:00
|
|
|
// contentEditable does not generate onchange event.
|
2011-11-08 11:36:06 +00:00
|
|
|
}
|
2010-08-19 13:39:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2011-12-06 11:44:44 +00:00
|
|
|
/**
|
|
|
|
|
* Focuses on the given element if it is not already the active element.
|
2012-02-19 20:46:15 +00:00
|
|
|
*
|
2011-12-06 11:44:44 +00:00
|
|
|
* @param {!Element} element The element to focus on.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.focusOnElement = function (element) {
|
2011-12-06 11:44:44 +00:00
|
|
|
bot.action.checkInteractable_(element);
|
|
|
|
|
bot.action.LegacyDevice_.focusOnElement(element);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2011-07-26 17:01:24 +00:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Types keys on the given `element` with a virtual keyboard.
|
2011-07-26 17:01:24 +00:00
|
|
|
*
|
2012-04-26 17:55:18 +00:00
|
|
|
* <p>Callers can pass in a string, a key in bot.Keyboard.Key, or an array
|
|
|
|
|
* of strings or keys. If a modifier key is provided, it is pressed but not
|
|
|
|
|
* released, until it is either is listed again or the function ends.
|
2011-07-26 17:01:24 +00:00
|
|
|
*
|
2012-02-19 20:46:15 +00:00
|
|
|
* <p>Example:
|
2012-04-26 17:55:18 +00:00
|
|
|
* bot.keys.type(element, ['ab', bot.Keyboard.Key.LEFT,
|
|
|
|
|
* bot.Keyboard.Key.SHIFT, 'cd']);
|
2011-07-26 17:01:24 +00:00
|
|
|
*
|
|
|
|
|
* @param {!Element} element The element receiving the event.
|
2013-06-15 16:19:51 -07:00
|
|
|
* @param {(string|!bot.Keyboard.Key|!Array.<(string|!bot.Keyboard.Key)>)}
|
|
|
|
|
* values Value or values to type on the element.
|
2012-04-26 17:55:18 +00:00
|
|
|
* @param {bot.Keyboard=} opt_keyboard Keyboard to use; if not provided,
|
2013-06-15 16:19:51 -07:00
|
|
|
* constructs one.
|
2013-01-26 13:15:47 -08:00
|
|
|
* @param {boolean=} opt_persistModifiers Whether modifier keys should remain
|
|
|
|
|
* pressed when this function ends.
|
2012-02-19 20:46:15 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
2011-07-26 17:01:24 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.type = function (
|
|
|
|
|
element, values, opt_keyboard, opt_persistModifiers) {
|
2020-08-16 00:03:35 +05:30
|
|
|
// If the element has already been brought into focus somehow, typing is
|
2013-09-20 15:24:21 -07:00
|
|
|
// always allowed to proceed. Otherwise, we require the element be in an
|
|
|
|
|
// "interactable" state. For example, an element that is hidden by overflow
|
|
|
|
|
// can be typed on, so long as the user first tabs to it or the app calls
|
|
|
|
|
// focus() on the element first.
|
|
|
|
|
if (element != bot.dom.getActiveElement(element)) {
|
|
|
|
|
bot.action.checkInteractable_(element);
|
|
|
|
|
bot.action.scrollIntoView(element);
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-02 20:08:55 +00:00
|
|
|
var keyboard = opt_keyboard || new bot.Keyboard();
|
2011-12-06 11:44:44 +00:00
|
|
|
keyboard.moveCursor(element);
|
2011-07-26 17:01:24 +00:00
|
|
|
|
2012-04-26 17:55:18 +00:00
|
|
|
function typeValue(value) {
|
2011-07-26 17:01:24 +00:00
|
|
|
if (goog.isString(value)) {
|
2020-11-27 15:46:30 +00:00
|
|
|
goog.array.forEach(value.split(''), function (ch) {
|
2013-01-11 22:18:32 +01:00
|
|
|
var keyShiftPair = bot.Keyboard.Key.fromChar(ch);
|
2012-11-02 19:43:37 +00:00
|
|
|
var shiftIsPressed = keyboard.isPressed(bot.Keyboard.Keys.SHIFT);
|
|
|
|
|
if (keyShiftPair.shift && !shiftIsPressed) {
|
2011-07-26 17:01:24 +00:00
|
|
|
keyboard.pressKey(bot.Keyboard.Keys.SHIFT);
|
|
|
|
|
}
|
|
|
|
|
keyboard.pressKey(keyShiftPair.key);
|
|
|
|
|
keyboard.releaseKey(keyShiftPair.key);
|
2012-11-02 19:43:37 +00:00
|
|
|
if (keyShiftPair.shift && !shiftIsPressed) {
|
2011-07-26 17:01:24 +00:00
|
|
|
keyboard.releaseKey(bot.Keyboard.Keys.SHIFT);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else if (goog.array.contains(bot.Keyboard.MODIFIERS, value)) {
|
2020-11-27 15:46:30 +00:00
|
|
|
if (keyboard.isPressed(/** @type {!bot.Keyboard.Key} */(value))) {
|
2011-07-26 17:01:24 +00:00
|
|
|
keyboard.releaseKey(value);
|
|
|
|
|
} else {
|
|
|
|
|
keyboard.pressKey(value);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
keyboard.pressKey(value);
|
|
|
|
|
keyboard.releaseKey(value);
|
|
|
|
|
}
|
2012-04-26 17:55:18 +00:00
|
|
|
}
|
|
|
|
|
|
2013-03-08 10:56:33 -08:00
|
|
|
// mobile safari (iPhone / iPad). one cannot 'type' in a date field
|
2013-04-09 21:18:07 -07:00
|
|
|
// chrome implements this, but desktop Safari doesn't, what's webkit again?
|
|
|
|
|
if ((!(goog.userAgent.product.SAFARI && !goog.userAgent.MOBILE)) &&
|
2020-11-27 15:46:30 +00:00
|
|
|
goog.userAgent.WEBKIT && element.type == 'date') {
|
|
|
|
|
var val = goog.isArray(values) ? values = values.join("") : values;
|
2013-03-11 09:36:21 -07:00
|
|
|
var datePattern = /\d{4}-\d{2}-\d{2}/;
|
|
|
|
|
if (val.match(datePattern)) {
|
2013-03-08 10:56:33 -08:00
|
|
|
// The following events get fired on iOS first
|
|
|
|
|
if (goog.userAgent.MOBILE && goog.userAgent.product.SAFARI) {
|
|
|
|
|
bot.events.fire(element, bot.events.EventType.TOUCHSTART);
|
|
|
|
|
bot.events.fire(element, bot.events.EventType.TOUCHEND);
|
|
|
|
|
}
|
|
|
|
|
bot.events.fire(element, bot.events.EventType.FOCUS);
|
2013-03-11 09:36:21 -07:00
|
|
|
element.value = val.match(datePattern)[0];
|
2013-03-08 10:56:33 -08:00
|
|
|
bot.events.fire(element, bot.events.EventType.CHANGE);
|
|
|
|
|
bot.events.fire(element, bot.events.EventType.BLUR);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-26 17:55:18 +00:00
|
|
|
if (goog.isArray(values)) {
|
|
|
|
|
goog.array.forEach(values, typeValue);
|
|
|
|
|
} else {
|
|
|
|
|
typeValue(values);
|
|
|
|
|
}
|
2011-07-26 17:01:24 +00:00
|
|
|
|
2013-01-26 13:15:47 -08:00
|
|
|
if (!opt_persistModifiers) {
|
|
|
|
|
// Release all the modifier keys.
|
2020-11-27 15:46:30 +00:00
|
|
|
goog.array.forEach(bot.Keyboard.MODIFIERS, function (key) {
|
2013-01-26 13:15:47 -08:00
|
|
|
if (keyboard.isPressed(key)) {
|
|
|
|
|
keyboard.releaseKey(key);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2011-07-26 17:01:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2010-08-19 13:39:26 +00:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Submits the form containing the given `element`.
|
2012-02-19 20:46:15 +00:00
|
|
|
*
|
|
|
|
|
* <p>Note this function submits the form, but does not simulate user input
|
|
|
|
|
* (a click or key press).
|
|
|
|
|
*
|
2010-08-19 13:39:26 +00:00
|
|
|
* @param {!Element} element The element to submit.
|
2012-02-19 20:40:52 +00:00
|
|
|
* @deprecated Click on a submit button or type ENTER in a text box instead.
|
2010-08-19 13:39:26 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.submit = function (element) {
|
2012-02-19 20:40:52 +00:00
|
|
|
var form = bot.action.LegacyDevice_.findAncestorForm(element);
|
2010-08-19 13:39:26 +00:00
|
|
|
if (!form) {
|
2013-03-12 12:07:48 +04:00
|
|
|
throw new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT,
|
2020-11-27 15:46:30 +00:00
|
|
|
'Element was not in a form, so could not submit.');
|
2010-08-19 13:39:26 +00:00
|
|
|
}
|
2012-02-19 20:40:52 +00:00
|
|
|
bot.action.LegacyDevice_.submitForm(element, form);
|
2010-08-19 13:39:26 +00:00
|
|
|
};
|
2010-08-24 22:28:11 +00:00
|
|
|
|
2010-12-22 17:04:29 +00:00
|
|
|
|
2012-02-21 18:17:33 +00:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Moves the mouse over the given `element` with a virtual mouse.
|
2012-02-21 18:17:33 +00:00
|
|
|
*
|
|
|
|
|
* @param {!Element} element The element to click.
|
|
|
|
|
* @param {goog.math.Coordinate=} opt_coords Mouse position relative to the
|
|
|
|
|
* element.
|
2012-03-05 14:02:05 +00:00
|
|
|
* @param {bot.Mouse=} opt_mouse Mouse to use; if not provided, constructs one.
|
2012-02-21 18:17:33 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.moveMouse = function (element, opt_coords, opt_mouse) {
|
2012-04-26 17:55:18 +00:00
|
|
|
var coords = bot.action.prepareToInteractWith_(element, opt_coords);
|
|
|
|
|
var mouse = opt_mouse || new bot.Mouse();
|
|
|
|
|
mouse.move(element, coords);
|
2012-02-21 18:17:33 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2011-07-26 21:15:06 +00:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Clicks on the given `element` with a virtual mouse.
|
2011-08-03 17:59:47 +00:00
|
|
|
*
|
|
|
|
|
* @param {!Element} element The element to click.
|
2011-08-24 16:03:31 +00:00
|
|
|
* @param {goog.math.Coordinate=} opt_coords Mouse position relative to the
|
2012-02-19 20:46:15 +00:00
|
|
|
* element.
|
2012-03-05 14:02:05 +00:00
|
|
|
* @param {bot.Mouse=} opt_mouse Mouse to use; if not provided, constructs one.
|
2015-09-19 16:14:18 +03:00
|
|
|
* @param {boolean=} opt_force Whether the release event should be fired even if the
|
|
|
|
|
* element is not interactable.
|
2012-02-19 20:46:15 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
2011-07-26 21:15:06 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.click = function (element, opt_coords, opt_mouse, opt_force) {
|
2012-04-26 17:55:18 +00:00
|
|
|
var coords = bot.action.prepareToInteractWith_(element, opt_coords);
|
|
|
|
|
var mouse = opt_mouse || new bot.Mouse();
|
|
|
|
|
mouse.move(element, coords);
|
|
|
|
|
mouse.pressButton(bot.Mouse.Button.LEFT);
|
2015-09-19 16:14:18 +03:00
|
|
|
mouse.releaseButton(opt_force);
|
2011-07-26 21:15:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2010-08-24 22:28:11 +00:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Right-clicks on the given `element` with a virtual mouse.
|
2010-10-20 14:52:25 +00:00
|
|
|
*
|
2011-07-26 21:15:06 +00:00
|
|
|
* @param {!Element} element The element to click.
|
2011-08-24 16:03:31 +00:00
|
|
|
* @param {goog.math.Coordinate=} opt_coords Mouse position relative to the
|
2012-02-19 20:46:15 +00:00
|
|
|
* element.
|
2012-03-05 14:02:05 +00:00
|
|
|
* @param {bot.Mouse=} opt_mouse Mouse to use; if not provided, constructs one.
|
2012-02-19 20:46:15 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
2010-08-24 22:28:11 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.rightClick = function (element, opt_coords, opt_mouse) {
|
2012-04-26 17:55:18 +00:00
|
|
|
var coords = bot.action.prepareToInteractWith_(element, opt_coords);
|
|
|
|
|
var mouse = opt_mouse || new bot.Mouse();
|
|
|
|
|
mouse.move(element, coords);
|
|
|
|
|
mouse.pressButton(bot.Mouse.Button.RIGHT);
|
|
|
|
|
mouse.releaseButton();
|
2011-08-03 17:59:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Double-clicks on the given `element` with a virtual mouse.
|
2011-08-24 16:03:31 +00:00
|
|
|
*
|
|
|
|
|
* @param {!Element} element The element to click.
|
|
|
|
|
* @param {goog.math.Coordinate=} opt_coords Mouse position relative to the
|
2012-02-19 20:46:15 +00:00
|
|
|
* element.
|
2012-03-05 14:02:05 +00:00
|
|
|
* @param {bot.Mouse=} opt_mouse Mouse to use; if not provided, constructs one.
|
2012-02-19 20:46:15 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
2011-08-24 16:03:31 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.doubleClick = function (element, opt_coords, opt_mouse) {
|
2012-04-26 17:55:18 +00:00
|
|
|
var coords = bot.action.prepareToInteractWith_(element, opt_coords);
|
|
|
|
|
var mouse = opt_mouse || new bot.Mouse();
|
|
|
|
|
mouse.move(element, coords);
|
|
|
|
|
mouse.pressButton(bot.Mouse.Button.LEFT);
|
|
|
|
|
mouse.releaseButton();
|
|
|
|
|
mouse.pressButton(bot.Mouse.Button.LEFT);
|
|
|
|
|
mouse.releaseButton();
|
2012-02-21 18:17:33 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2015-09-19 23:07:49 +03:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Double-clicks on the given `element` with a virtual mouse.
|
2015-09-19 23:07:49 +03:00
|
|
|
*
|
|
|
|
|
* @param {!Element} element The element to click.
|
|
|
|
|
* @param {goog.math.Coordinate=} opt_coords Mouse position relative to the
|
|
|
|
|
* element.
|
|
|
|
|
* @param {bot.Mouse=} opt_mouse Mouse to use; if not provided, constructs one.
|
|
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.doubleClick2 = function (element, opt_coords, opt_mouse) {
|
2015-09-19 23:07:49 +03:00
|
|
|
var coords = bot.action.prepareToInteractWith_(element, opt_coords);
|
|
|
|
|
var mouse = opt_mouse || new bot.Mouse();
|
|
|
|
|
mouse.move(element, coords);
|
|
|
|
|
mouse.pressButton(bot.Mouse.Button.LEFT, 2);
|
|
|
|
|
mouse.releaseButton(true, 2);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2012-03-05 14:06:20 +00:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Scrolls the mouse wheel on the given `element` with a virtual mouse.
|
2012-03-05 14:06:20 +00:00
|
|
|
*
|
|
|
|
|
* @param {!Element} element The element to scroll the mouse wheel on.
|
2012-04-26 17:55:18 +00:00
|
|
|
* @param {number} ticks Number of ticks to scroll the mouse wheel; a positive
|
|
|
|
|
* number scrolls down and a negative scrolls up.
|
2012-03-05 14:06:20 +00:00
|
|
|
* @param {goog.math.Coordinate=} opt_coords Mouse position relative to the
|
|
|
|
|
* element.
|
|
|
|
|
* @param {bot.Mouse=} opt_mouse Mouse to use; if not provided, constructs one.
|
|
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.scrollMouse = function (element, ticks, opt_coords, opt_mouse) {
|
2012-04-26 17:55:18 +00:00
|
|
|
var coords = bot.action.prepareToInteractWith_(element, opt_coords);
|
|
|
|
|
var mouse = opt_mouse || new bot.Mouse();
|
|
|
|
|
mouse.move(element, coords);
|
2012-03-05 14:06:20 +00:00
|
|
|
mouse.scroll(ticks);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2012-02-21 18:17:33 +00:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Drags the given `element` by (dx, dy) with a virtual mouse.
|
2012-02-21 18:17:33 +00:00
|
|
|
*
|
|
|
|
|
* @param {!Element} element The element to drag.
|
|
|
|
|
* @param {number} dx Increment in x coordinate.
|
|
|
|
|
* @param {number} dy Increment in y coordinate.
|
2013-06-15 16:19:51 -07:00
|
|
|
* @param {number=} opt_steps The number of steps that should occur as part of
|
|
|
|
|
* the drag, default is 2.
|
2012-02-21 18:17:33 +00:00
|
|
|
* @param {goog.math.Coordinate=} opt_coords Drag start position relative to the
|
|
|
|
|
* element.
|
2012-03-05 14:02:05 +00:00
|
|
|
* @param {bot.Mouse=} opt_mouse Mouse to use; if not provided, constructs one.
|
2012-02-21 18:17:33 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.drag = function (element, dx, dy, opt_steps, opt_coords, opt_mouse) {
|
2012-04-26 17:55:18 +00:00
|
|
|
var coords = bot.action.prepareToInteractWith_(element, opt_coords);
|
2013-06-15 16:19:51 -07:00
|
|
|
var initRect = bot.dom.getClientRect(element);
|
2012-04-26 17:55:18 +00:00
|
|
|
var mouse = opt_mouse || new bot.Mouse();
|
|
|
|
|
mouse.move(element, coords);
|
2012-02-21 18:17:33 +00:00
|
|
|
mouse.pressButton(bot.Mouse.Button.LEFT);
|
2013-06-15 16:19:51 -07:00
|
|
|
var steps = goog.isDef(opt_steps) ? opt_steps : 2;
|
|
|
|
|
if (steps < 1) {
|
|
|
|
|
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
|
2020-11-27 15:46:30 +00:00
|
|
|
'There must be at least one step as part of a drag.');
|
2013-06-15 16:19:51 -07:00
|
|
|
}
|
|
|
|
|
for (var i = 1; i <= steps; i++) {
|
|
|
|
|
moveTo(Math.floor(i * dx / steps), Math.floor(i * dy / steps));
|
|
|
|
|
}
|
2012-02-21 18:17:33 +00:00
|
|
|
mouse.releaseButton();
|
2013-06-15 16:19:51 -07:00
|
|
|
|
|
|
|
|
function moveTo(x, y) {
|
|
|
|
|
var currRect = bot.dom.getClientRect(element);
|
|
|
|
|
var newPos = new goog.math.Coordinate(
|
2020-11-27 15:46:30 +00:00
|
|
|
coords.x + initRect.left + x - currRect.left,
|
|
|
|
|
coords.y + initRect.top + y - currRect.top);
|
2013-06-15 16:19:51 -07:00
|
|
|
mouse.move(element, newPos);
|
|
|
|
|
}
|
2011-08-24 16:03:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2011-12-19 14:41:17 +00:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Taps on the given `element` with a virtual touch screen.
|
2011-12-19 14:41:17 +00:00
|
|
|
*
|
|
|
|
|
* @param {!Element} element The element to tap.
|
|
|
|
|
* @param {goog.math.Coordinate=} opt_coords Finger position relative to the
|
|
|
|
|
* target.
|
2012-04-26 17:55:18 +00:00
|
|
|
* @param {bot.Touchscreen=} opt_touchscreen Touchscreen to use; if not
|
|
|
|
|
* provided, constructs one.
|
2012-02-19 20:46:15 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
2011-12-19 14:41:17 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.tap = function (element, opt_coords, opt_touchscreen) {
|
2012-04-26 17:55:18 +00:00
|
|
|
var coords = bot.action.prepareToInteractWith_(element, opt_coords);
|
|
|
|
|
var touchscreen = opt_touchscreen || new bot.Touchscreen();
|
|
|
|
|
touchscreen.move(element, coords);
|
|
|
|
|
touchscreen.press();
|
|
|
|
|
touchscreen.release();
|
2011-12-19 14:41:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Swipes the given `element` by (dx, dy) with a virtual touch screen.
|
2011-12-19 14:41:17 +00:00
|
|
|
*
|
|
|
|
|
* @param {!Element} element The element to swipe.
|
|
|
|
|
* @param {number} dx Increment in x coordinate.
|
|
|
|
|
* @param {number} dy Increment in y coordinate.
|
2013-06-15 16:19:51 -07:00
|
|
|
* @param {number=} opt_steps The number of steps that should occurs as part of
|
|
|
|
|
* the swipe, default is 2.
|
2012-04-26 17:55:18 +00:00
|
|
|
* @param {goog.math.Coordinate=} opt_coords Swipe start position relative to
|
2011-12-19 14:41:17 +00:00
|
|
|
* the element.
|
2012-04-26 17:55:18 +00:00
|
|
|
* @param {bot.Touchscreen=} opt_touchscreen Touchscreen to use; if not
|
|
|
|
|
* provided, constructs one.
|
2012-02-19 20:46:15 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
2011-12-19 14:41:17 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.swipe = function (element, dx, dy, opt_steps, opt_coords,
|
|
|
|
|
opt_touchscreen) {
|
2012-04-26 17:55:18 +00:00
|
|
|
var coords = bot.action.prepareToInteractWith_(element, opt_coords);
|
|
|
|
|
var touchscreen = opt_touchscreen || new bot.Touchscreen();
|
2013-06-15 16:19:51 -07:00
|
|
|
var initRect = bot.dom.getClientRect(element);
|
2012-04-26 17:55:18 +00:00
|
|
|
touchscreen.move(element, coords);
|
|
|
|
|
touchscreen.press();
|
2013-06-15 16:19:51 -07:00
|
|
|
var steps = goog.isDef(opt_steps) ? opt_steps : 2;
|
|
|
|
|
if (steps < 1) {
|
|
|
|
|
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
|
2020-11-27 15:46:30 +00:00
|
|
|
'There must be at least one step as part of a swipe.');
|
2013-06-15 16:19:51 -07:00
|
|
|
}
|
|
|
|
|
for (var i = 1; i <= steps; i++) {
|
|
|
|
|
moveTo(Math.floor(i * dx / steps), Math.floor(i * dy / steps));
|
|
|
|
|
}
|
2012-04-26 17:55:18 +00:00
|
|
|
touchscreen.release();
|
2013-06-15 16:19:51 -07:00
|
|
|
|
|
|
|
|
function moveTo(x, y) {
|
|
|
|
|
var currRect = bot.dom.getClientRect(element);
|
|
|
|
|
var newPos = new goog.math.Coordinate(
|
2020-11-27 15:46:30 +00:00
|
|
|
coords.x + initRect.left + x - currRect.left,
|
|
|
|
|
coords.y + initRect.top + y - currRect.top);
|
2013-06-15 16:19:51 -07:00
|
|
|
touchscreen.move(element, newPos);
|
|
|
|
|
}
|
2011-12-19 14:41:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Pinches the given `element` by the given distance with a virtual touch
|
2012-04-26 17:55:18 +00:00
|
|
|
* screen. A positive distance moves two fingers inward toward each and a
|
2020-08-16 00:03:35 +05:30
|
|
|
* negative distances spreads them outward. The optional coordinate is the point
|
2012-04-26 17:55:18 +00:00
|
|
|
* the fingers move towards (for positive distances) or away from (for negative
|
|
|
|
|
* distances); and if not provided, defaults to the center of the element.
|
2011-12-19 14:41:17 +00:00
|
|
|
*
|
2012-04-26 17:55:18 +00:00
|
|
|
* @param {!Element} element The element to pinch.
|
|
|
|
|
* @param {number} distance The distance by which to pinch the element.
|
|
|
|
|
* @param {goog.math.Coordinate=} opt_coords Position relative to the element
|
|
|
|
|
* at the center of the pinch.
|
|
|
|
|
* @param {bot.Touchscreen=} opt_touchscreen Touchscreen to use; if not
|
|
|
|
|
* provided, constructs one.
|
|
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
2011-12-19 14:41:17 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.pinch = function (element, distance, opt_coords, opt_touchscreen) {
|
2012-04-26 17:55:18 +00:00
|
|
|
if (distance == 0) {
|
|
|
|
|
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
|
2020-11-27 15:46:30 +00:00
|
|
|
'Cannot pinch by a distance of zero.');
|
2012-04-26 17:55:18 +00:00
|
|
|
}
|
|
|
|
|
function startSoThatEndsAtMax(offsetVec) {
|
|
|
|
|
if (distance < 0) {
|
|
|
|
|
var magnitude = offsetVec.magnitude();
|
|
|
|
|
offsetVec.scale(magnitude ? (magnitude + distance) / magnitude : 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var halfDistance = distance / 2;
|
|
|
|
|
function scaleByHalfDistance(offsetVec) {
|
|
|
|
|
var magnitude = offsetVec.magnitude();
|
|
|
|
|
offsetVec.scale(magnitude ? (magnitude - halfDistance) / magnitude : 0);
|
|
|
|
|
}
|
|
|
|
|
bot.action.multiTouchAction_(element,
|
2020-11-27 15:46:30 +00:00
|
|
|
startSoThatEndsAtMax,
|
|
|
|
|
scaleByHalfDistance,
|
|
|
|
|
opt_coords,
|
|
|
|
|
opt_touchscreen);
|
2011-12-19 14:41:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Rotates the given `element` by the given angle with a virtual touch
|
2012-04-26 17:55:18 +00:00
|
|
|
* screen. A positive angle moves two fingers clockwise and a negative angle
|
|
|
|
|
* moves them counter-clockwise. The optional coordinate is the point to
|
|
|
|
|
* rotate around; and if not provided, defaults to the center of the element.
|
2011-12-19 14:41:17 +00:00
|
|
|
*
|
2012-04-26 17:55:18 +00:00
|
|
|
* @param {!Element} element The element to rotate.
|
|
|
|
|
* @param {number} angle The angle by which to rotate the element.
|
|
|
|
|
* @param {goog.math.Coordinate=} opt_coords Position relative to the element
|
|
|
|
|
* at the center of the rotation.
|
|
|
|
|
* @param {bot.Touchscreen=} opt_touchscreen Touchscreen to use; if not
|
|
|
|
|
* provided, constructs one.
|
2012-02-19 20:46:15 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
2011-12-19 14:41:17 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.rotate = function (element, angle, opt_coords, opt_touchscreen) {
|
2012-04-26 17:55:18 +00:00
|
|
|
if (angle == 0) {
|
|
|
|
|
throw new bot.Error(bot.ErrorCode.UNKNOWN_ERROR,
|
2020-11-27 15:46:30 +00:00
|
|
|
'Cannot rotate by an angle of zero.');
|
2012-04-26 17:55:18 +00:00
|
|
|
}
|
|
|
|
|
function startHalfwayToMax(offsetVec) {
|
|
|
|
|
offsetVec.scale(0.5);
|
|
|
|
|
}
|
|
|
|
|
var halfRadians = Math.PI * (angle / 180) / 2;
|
|
|
|
|
function rotateByHalfAngle(offsetVec) {
|
|
|
|
|
offsetVec.rotate(halfRadians);
|
|
|
|
|
}
|
|
|
|
|
bot.action.multiTouchAction_(element,
|
2020-11-27 15:46:30 +00:00
|
|
|
startHalfwayToMax,
|
|
|
|
|
rotateByHalfAngle,
|
|
|
|
|
opt_coords,
|
|
|
|
|
opt_touchscreen);
|
2011-12-19 14:41:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-04-26 17:55:18 +00:00
|
|
|
* Performs a multi-touch action with two fingers on the given element. This
|
|
|
|
|
* helper function works by manipulating an "offsetVector", which is the vector
|
|
|
|
|
* away from the center of the interaction at which the fingers are positioned.
|
|
|
|
|
* It computes the maximum offset vector and passes it to transformStart to
|
|
|
|
|
* find the starting position of the fingers; it then passes it to transformHalf
|
|
|
|
|
* twice to find the midpoint and final position of the fingers.
|
2011-12-19 14:41:17 +00:00
|
|
|
*
|
2012-04-26 17:55:18 +00:00
|
|
|
* @param {!Element} element Element to interact with.
|
|
|
|
|
* @param {function(goog.math.Vec2)} transformStart Function to transform the
|
|
|
|
|
* maximum offset vector to the starting offset vector.
|
|
|
|
|
* @param {function(goog.math.Vec2)} transformHalf Function to transform the
|
|
|
|
|
* offset vector halfway to its destination.
|
|
|
|
|
* @param {goog.math.Coordinate=} opt_coords Position relative to the element
|
|
|
|
|
* at the center of the pinch.
|
|
|
|
|
* @param {bot.Touchscreen=} opt_touchscreen Touchscreen to use; if not
|
|
|
|
|
* provided, constructs one.
|
|
|
|
|
* @private
|
2011-12-19 14:41:17 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.multiTouchAction_ = function (element, transformStart, transformHalf,
|
|
|
|
|
opt_coords, opt_touchscreen) {
|
2012-04-26 17:55:18 +00:00
|
|
|
var center = bot.action.prepareToInteractWith_(element, opt_coords);
|
2012-11-08 16:22:32 +00:00
|
|
|
var size = bot.action.getInteractableSize(element);
|
2012-04-26 17:55:18 +00:00
|
|
|
var offsetVec = new goog.math.Vec2(
|
2020-11-27 15:46:30 +00:00
|
|
|
Math.min(center.x, size.width - center.x),
|
|
|
|
|
Math.min(center.y, size.height - center.y));
|
2012-04-26 17:55:18 +00:00
|
|
|
|
|
|
|
|
var touchScreen = opt_touchscreen || new bot.Touchscreen();
|
|
|
|
|
transformStart(offsetVec);
|
|
|
|
|
var start1 = goog.math.Vec2.sum(center, offsetVec);
|
|
|
|
|
var start2 = goog.math.Vec2.difference(center, offsetVec);
|
|
|
|
|
touchScreen.move(element, start1, start2);
|
|
|
|
|
touchScreen.press(/*Two Finger Press*/ true);
|
|
|
|
|
|
2013-06-15 16:19:51 -07:00
|
|
|
var initRect = bot.dom.getClientRect(element);
|
2012-04-26 17:55:18 +00:00
|
|
|
transformHalf(offsetVec);
|
|
|
|
|
var mid1 = goog.math.Vec2.sum(center, offsetVec);
|
|
|
|
|
var mid2 = goog.math.Vec2.difference(center, offsetVec);
|
|
|
|
|
touchScreen.move(element, mid1, mid2);
|
|
|
|
|
|
2013-06-15 16:19:51 -07:00
|
|
|
var midRect = bot.dom.getClientRect(element);
|
2012-04-26 17:55:18 +00:00
|
|
|
var movedVec = goog.math.Vec2.difference(
|
2020-11-27 15:46:30 +00:00
|
|
|
new goog.math.Vec2(midRect.left, midRect.top),
|
|
|
|
|
new goog.math.Vec2(initRect.left, initRect.top));
|
2012-04-26 17:55:18 +00:00
|
|
|
transformHalf(offsetVec);
|
|
|
|
|
var end1 = goog.math.Vec2.sum(center, offsetVec).subtract(movedVec);
|
|
|
|
|
var end2 = goog.math.Vec2.difference(center, offsetVec).subtract(movedVec);
|
|
|
|
|
touchScreen.move(element, end1, end2);
|
|
|
|
|
touchScreen.release();
|
2011-12-19 14:41:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Prepares to interact with the given `element`. It checks if the the
|
2012-04-26 17:55:18 +00:00
|
|
|
* element is shown, scrolls the element into view, and returns the coordinates
|
|
|
|
|
* of the interaction, which if not provided, is the center of the element.
|
2011-12-19 14:41:17 +00:00
|
|
|
*
|
2012-04-26 17:55:18 +00:00
|
|
|
* @param {!Element} element The element to be interacted with.
|
|
|
|
|
* @param {goog.math.Coordinate=} opt_coords Position relative to the target.
|
|
|
|
|
* @return {!goog.math.Vec2} Coordinates at the center of the interaction.
|
2012-02-19 20:46:15 +00:00
|
|
|
* @throws {bot.Error} If the element cannot be interacted with.
|
2012-04-26 17:55:18 +00:00
|
|
|
* @private
|
2011-12-19 14:41:17 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.prepareToInteractWith_ = function (element, opt_coords) {
|
2012-04-26 17:55:18 +00:00
|
|
|
bot.action.checkShown_(element);
|
2013-09-20 15:24:21 -07:00
|
|
|
bot.action.scrollIntoView(element, opt_coords || undefined);
|
2011-12-19 14:41:17 +00:00
|
|
|
|
2013-08-16 11:53:17 -07:00
|
|
|
// NOTE: Ideally, we would check that any provided coordinates fall
|
2012-04-26 17:55:18 +00:00
|
|
|
// within the bounds of the element, but this has proven difficult, because:
|
|
|
|
|
// (1) Browsers sometimes lie about the true size of elements, e.g. when text
|
|
|
|
|
// overflows the bounding box of an element, browsers report the size of the
|
|
|
|
|
// box even though the true area that can be interacted with is larger; and
|
|
|
|
|
// (2) Elements with children styled as position:absolute will often not have
|
|
|
|
|
// a bounding box that surrounds all of their children, but it is useful for
|
|
|
|
|
// the user to be able to interact with this parent element as if it does.
|
|
|
|
|
if (opt_coords) {
|
|
|
|
|
return goog.math.Vec2.fromCoordinate(opt_coords);
|
|
|
|
|
} else {
|
2012-11-08 16:22:32 +00:00
|
|
|
var size = bot.action.getInteractableSize(element);
|
2012-04-26 17:55:18 +00:00
|
|
|
return new goog.math.Vec2(size.width / 2, size.height / 2);
|
|
|
|
|
}
|
|
|
|
|
};
|
2011-12-19 14:41:17 +00:00
|
|
|
|
|
|
|
|
|
2012-04-26 17:55:18 +00:00
|
|
|
/**
|
|
|
|
|
* Returns the interactable size of an element.
|
|
|
|
|
*
|
|
|
|
|
* @param {!Element} elem Element.
|
|
|
|
|
* @return {!goog.math.Size} size Size of the element.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.getInteractableSize = function (elem) {
|
2012-04-26 17:55:18 +00:00
|
|
|
var size = goog.style.getSize(elem);
|
|
|
|
|
return ((size.width > 0 && size.height > 0) || !elem.offsetParent) ? size :
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.getInteractableSize(elem.offsetParent);
|
2011-12-19 14:41:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2011-12-06 11:44:44 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A Device that is intended to allows access to protected members of the
|
|
|
|
|
* Device superclass. A singleton.
|
|
|
|
|
*
|
|
|
|
|
* @constructor
|
|
|
|
|
* @extends {bot.Device}
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.LegacyDevice_ = function () {
|
2011-12-06 11:44:44 +00:00
|
|
|
goog.base(this);
|
|
|
|
|
};
|
|
|
|
|
goog.inherits(bot.action.LegacyDevice_, bot.Device);
|
|
|
|
|
goog.addSingletonGetter(bot.action.LegacyDevice_);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Focuses on the given element. See {@link bot.device.focusOnElement}.
|
|
|
|
|
* @param {!Element} element The element to focus on.
|
|
|
|
|
* @return {boolean} True if element.focus() was called on the element.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.LegacyDevice_.focusOnElement = function (element) {
|
2011-12-06 11:44:44 +00:00
|
|
|
var instance = bot.action.LegacyDevice_.getInstance();
|
|
|
|
|
instance.setElement(element);
|
|
|
|
|
return instance.focusOnElement();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2012-02-19 20:40:52 +00:00
|
|
|
/**
|
|
|
|
|
* Submit the form for the element. See {@link bot.device.submit}.
|
|
|
|
|
* @param {!Element} element The element to submit a form on.
|
|
|
|
|
* @param {!Element} form The form to submit.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.LegacyDevice_.submitForm = function (element, form) {
|
2012-02-19 20:40:52 +00:00
|
|
|
var instance = bot.action.LegacyDevice_.getInstance();
|
|
|
|
|
instance.setElement(element);
|
|
|
|
|
instance.submitForm(form);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Find FORM element that is an ancestor of the passed in element. See
|
|
|
|
|
* {@link bot.device.findAncestorForm}.
|
|
|
|
|
* @param {!Element} element The element to find an ancestor form.
|
|
|
|
|
* @return {Element} form The ancestor form, or null if none.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.LegacyDevice_.findAncestorForm = function (element) {
|
2012-02-19 20:40:52 +00:00
|
|
|
return bot.Device.findAncestorForm(element);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2011-11-29 13:50:53 +00:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Scrolls the given `element` in to the current viewport. Aims to do the
|
2012-02-19 20:46:15 +00:00
|
|
|
* minimum scrolling necessary, but prefers too much scrolling to too little.
|
|
|
|
|
*
|
2013-09-20 15:24:21 -07:00
|
|
|
* If an optional coordinate or rectangle region is provided, scrolls that
|
|
|
|
|
* region relative to the element into view. A coordinate is treated as a 1x1
|
|
|
|
|
* region whose top-left corner is positioned at that coordinate.
|
|
|
|
|
*
|
2011-11-29 13:50:53 +00:00
|
|
|
* @param {!Element} element The element to scroll in to view.
|
2013-09-20 15:24:21 -07:00
|
|
|
* @param {!(goog.math.Coordinate|goog.math.Rect)=} opt_region
|
|
|
|
|
* Region relative to the top-left corner of the element.
|
2011-11-29 13:50:53 +00:00
|
|
|
* @return {boolean} Whether the element is in view after scrolling.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.action.scrollIntoView = function (element, opt_region) {
|
2013-08-16 11:53:17 -07:00
|
|
|
// If the element is already in view, return true; if hidden, return false.
|
2013-09-20 15:24:21 -07:00
|
|
|
var overflow = bot.dom.getOverflowState(element, opt_region);
|
2013-08-16 11:53:17 -07:00
|
|
|
if (overflow != bot.dom.OverflowState.SCROLL) {
|
|
|
|
|
return overflow == bot.dom.OverflowState.NONE;
|
2011-11-29 13:50:53 +00:00
|
|
|
}
|
2013-08-16 11:53:17 -07:00
|
|
|
|
|
|
|
|
// Some elements may not have a scrollIntoView function - for example,
|
|
|
|
|
// elements under an SVG element. Call those only if they exist.
|
|
|
|
|
if (element.scrollIntoView) {
|
|
|
|
|
element.scrollIntoView();
|
|
|
|
|
if (bot.dom.OverflowState.NONE ==
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.dom.getOverflowState(element, opt_region)) {
|
2013-08-16 11:53:17 -07:00
|
|
|
return true;
|
2011-12-03 13:36:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-16 11:53:17 -07:00
|
|
|
// There may have not been a scrollIntoView function, or the specified
|
|
|
|
|
// coordinate may not be in view, so scroll "manually".
|
2013-09-20 15:24:21 -07:00
|
|
|
var region = bot.dom.getClientRegion(element, opt_region);
|
|
|
|
|
for (var container = bot.dom.getParentElement(element);
|
2020-11-27 15:46:30 +00:00
|
|
|
container;
|
|
|
|
|
container = bot.dom.getParentElement(container)) {
|
2013-09-20 15:24:21 -07:00
|
|
|
scrollClientRegionIntoContainerView(container);
|
|
|
|
|
}
|
2013-08-16 11:53:17 -07:00
|
|
|
return bot.dom.OverflowState.NONE ==
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.dom.getOverflowState(element, opt_region);
|
2013-09-20 15:24:21 -07:00
|
|
|
|
|
|
|
|
function scrollClientRegionIntoContainerView(container) {
|
|
|
|
|
// Based largely from goog.style.scrollIntoContainerView.
|
|
|
|
|
var containerRect = bot.dom.getClientRect(container);
|
|
|
|
|
var containerBorder = goog.style.getBorderBox(container);
|
|
|
|
|
|
|
|
|
|
// Relative position of the region to the container's content box.
|
|
|
|
|
var relX = region.left - containerRect.left - containerBorder.left;
|
|
|
|
|
var relY = region.top - containerRect.top - containerBorder.top;
|
|
|
|
|
|
|
|
|
|
// How much the region can move in the container. Use the container's
|
|
|
|
|
// clientWidth/Height, not containerRect, to account for the scrollbar.
|
|
|
|
|
var spaceX = container.clientWidth + region.left - region.right;
|
|
|
|
|
var spaceY = container.clientHeight + region.top - region.bottom;
|
|
|
|
|
|
|
|
|
|
// Scroll the element into view of the container.
|
|
|
|
|
container.scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
|
|
|
|
|
container.scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
|
|
|
|
|
}
|
2011-11-29 13:50:53 +00:00
|
|
|
};
|