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-07-25 21:06:09 +00:00
|
|
|
|
2010-07-19 11:24:30 +00:00
|
|
|
/**
|
|
|
|
|
* @fileoverview Browser atom for injecting JavaScript into the page under
|
|
|
|
|
* test. There is no point in using this atom directly from JavaScript.
|
|
|
|
|
* Instead, it is intended to be used in its compiled form when injecting
|
|
|
|
|
* script from another language (e.g. C++).
|
|
|
|
|
*
|
2013-08-16 11:53:17 -07:00
|
|
|
* TODO: Add an example
|
2010-07-19 11:24:30 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
goog.provide('bot.inject');
|
|
|
|
|
goog.provide('bot.inject.cache');
|
|
|
|
|
|
2011-06-21 22:56:38 +00:00
|
|
|
goog.require('bot');
|
2010-07-19 11:24:30 +00:00
|
|
|
goog.require('bot.Error');
|
|
|
|
|
goog.require('bot.ErrorCode');
|
2012-04-26 17:55:18 +00:00
|
|
|
goog.require('bot.json');
|
2012-09-13 10:52:38 +00:00
|
|
|
/**
|
|
|
|
|
* @suppress {extraRequire} Used as a forward declaration which causes
|
|
|
|
|
* compilation errors if missing.
|
|
|
|
|
*/
|
2012-05-24 15:48:45 +00:00
|
|
|
goog.require('bot.response.ResponseObject');
|
2010-07-19 11:24:30 +00:00
|
|
|
goog.require('goog.array');
|
|
|
|
|
goog.require('goog.dom.NodeType');
|
|
|
|
|
goog.require('goog.object');
|
2014-01-08 11:18:48 -08:00
|
|
|
goog.require('goog.userAgent');
|
2025-12-20 14:10:39 +00:00
|
|
|
goog.require('goog.utils');
|
2010-07-19 11:24:30 +00:00
|
|
|
|
|
|
|
|
|
2014-01-15 13:34:43 -08:00
|
|
|
/**
|
|
|
|
|
* Type definition for the WebDriver's JSON wire protocol representation
|
|
|
|
|
* of a DOM element.
|
|
|
|
|
* @typedef {{ELEMENT: string}}
|
|
|
|
|
* @see bot.inject.ELEMENT_KEY
|
2015-03-17 19:15:20 -04:00
|
|
|
* @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
|
2014-01-15 13:34:43 -08:00
|
|
|
*/
|
|
|
|
|
bot.inject.JsonElement;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Type definition for a cached Window object that can be referenced in
|
|
|
|
|
* WebDriver's JSON wire protocol. Note, this is a non-standard
|
|
|
|
|
* representation.
|
|
|
|
|
* @typedef {{WINDOW: string}}
|
|
|
|
|
* @see bot.inject.WINDOW_KEY
|
|
|
|
|
*/
|
|
|
|
|
bot.inject.JsonWindow;
|
|
|
|
|
|
|
|
|
|
|
2010-07-19 11:24:30 +00:00
|
|
|
/**
|
|
|
|
|
* Key used to identify DOM elements in the WebDriver wire protocol.
|
|
|
|
|
* @type {string}
|
|
|
|
|
* @const
|
2015-03-17 19:15:20 -04:00
|
|
|
* @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
|
2010-07-19 11:24:30 +00:00
|
|
|
*/
|
|
|
|
|
bot.inject.ELEMENT_KEY = 'ELEMENT';
|
|
|
|
|
|
2011-08-15 04:24:29 +00:00
|
|
|
|
2011-06-21 22:56:38 +00:00
|
|
|
/**
|
|
|
|
|
* Key used to identify Window objects in the WebDriver wire protocol.
|
2011-08-15 04:24:29 +00:00
|
|
|
* @type {string}
|
2011-06-21 22:56:38 +00:00
|
|
|
* @const
|
|
|
|
|
*/
|
|
|
|
|
bot.inject.WINDOW_KEY = 'WINDOW';
|
|
|
|
|
|
2010-07-19 11:24:30 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts an element to a JSON friendly value so that it can be
|
|
|
|
|
* stringified for transmission to the injector. Values are modified as
|
|
|
|
|
* follows:
|
|
|
|
|
* <ul>
|
|
|
|
|
* <li>booleans, numbers, strings, and null are returned as is</li>
|
|
|
|
|
* <li>undefined values are returned as null</li>
|
|
|
|
|
* <li>functions are returned as a string</li>
|
|
|
|
|
* <li>each element in an array is recursively processed</li>
|
|
|
|
|
* <li>DOM Elements are wrapped in object-literals as dictated by the
|
|
|
|
|
* WebDriver wire protocol</li>
|
|
|
|
|
* <li>all other objects will be treated as hash-maps, and will be
|
|
|
|
|
* recursively processed for any string and number key types (all
|
|
|
|
|
* other key types are discarded as they cannot be converted to JSON).
|
|
|
|
|
* </ul>
|
|
|
|
|
*
|
|
|
|
|
* @param {*} value The value to make JSON friendly.
|
|
|
|
|
* @return {*} The JSON friendly value.
|
2015-03-17 19:15:20 -04:00
|
|
|
* @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
|
2010-07-19 11:24:30 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.wrapValue = function (value) {
|
|
|
|
|
var _wrap = function (value, seen) {
|
2025-12-20 14:10:39 +00:00
|
|
|
switch (goog.utils.typeOf(value)) {
|
2016-12-14 23:56:03 +03:00
|
|
|
case 'string':
|
|
|
|
|
case 'number':
|
|
|
|
|
case 'boolean':
|
|
|
|
|
return value;
|
2010-07-19 11:24:30 +00:00
|
|
|
|
2016-12-14 23:56:03 +03:00
|
|
|
case 'function':
|
|
|
|
|
return value.toString();
|
2011-06-21 22:56:38 +00:00
|
|
|
|
2016-12-14 23:56:03 +03:00
|
|
|
case 'array':
|
2020-11-27 15:46:30 +00:00
|
|
|
return goog.array.map(/**@type {IArrayLike}*/(value),
|
|
|
|
|
function (v) { return _wrap(v, seen); });
|
2016-12-14 23:56:03 +03:00
|
|
|
|
|
|
|
|
case 'object':
|
|
|
|
|
// Since {*} expands to {Object|boolean|number|string|undefined}, the
|
|
|
|
|
// JSCompiler complains that it is too broad a type for the remainder of
|
|
|
|
|
// this block where {!Object} is expected. Downcast to prevent generating
|
|
|
|
|
// a ton of compiler warnings.
|
|
|
|
|
value = /**@type {!Object}*/ (value);
|
|
|
|
|
if (seen.indexOf(value) >= 0) {
|
2017-12-08 08:54:23 +03:00
|
|
|
throw new bot.Error(bot.ErrorCode.JAVASCRIPT_ERROR,
|
2016-12-14 23:56:03 +03:00
|
|
|
'Recursive object cannot be transferred');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sniff out DOM elements. We're using duck-typing instead of an
|
|
|
|
|
// instanceof check since the instanceof might not always work
|
|
|
|
|
// (e.g. if the value originated from another Firefox component)
|
|
|
|
|
if (goog.object.containsKey(value, 'nodeType') &&
|
2020-11-27 15:46:30 +00:00
|
|
|
(value['nodeType'] == goog.dom.NodeType.ELEMENT ||
|
|
|
|
|
value['nodeType'] == goog.dom.NodeType.DOCUMENT)) {
|
2016-12-14 23:56:03 +03:00
|
|
|
var ret = {};
|
|
|
|
|
ret[bot.inject.ELEMENT_KEY] =
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.cache.addElement(/**@type {!Element}*/(value));
|
2016-12-14 23:56:03 +03:00
|
|
|
return ret;
|
|
|
|
|
}
|
2010-07-19 11:24:30 +00:00
|
|
|
|
2016-12-14 23:56:03 +03:00
|
|
|
// Check if this is a Window
|
|
|
|
|
if (goog.object.containsKey(value, 'document')) {
|
|
|
|
|
var ret = {};
|
|
|
|
|
ret[bot.inject.WINDOW_KEY] =
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.cache.addElement(/**@type{!Window}*/(value));
|
2016-12-14 23:56:03 +03:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seen.push(value);
|
2025-12-20 14:10:39 +00:00
|
|
|
if (goog.utils.isArrayLike(value)) {
|
2020-11-27 15:46:30 +00:00
|
|
|
return goog.array.map(/**@type {IArrayLike}*/(value),
|
|
|
|
|
function (v) { return _wrap(v, seen); });
|
2016-12-14 23:56:03 +03:00
|
|
|
}
|
|
|
|
|
|
2020-11-27 15:46:30 +00:00
|
|
|
var filtered = goog.object.filter(value, function (val, key) {
|
2025-12-20 14:10:39 +00:00
|
|
|
return typeof key === 'number' || typeof key === 'string';
|
2016-12-14 23:56:03 +03:00
|
|
|
});
|
2020-11-27 15:46:30 +00:00
|
|
|
return goog.object.map(filtered, function (v) { return _wrap(v, seen); });
|
2016-12-14 23:56:03 +03:00
|
|
|
|
|
|
|
|
default: // goog.typeOf(value) == 'undefined' || 'null'
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return _wrap(value, []);
|
2010-07-19 11:24:30 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Unwraps any DOM element's encoded in the given `value`.
|
2010-07-19 11:24:30 +00:00
|
|
|
* @param {*} value The value to unwrap.
|
2011-09-06 21:27:45 +00:00
|
|
|
* @param {Document=} opt_doc The document whose cache to retrieve wrapped
|
2010-07-19 11:24:30 +00:00
|
|
|
* elements from. Defaults to the current document.
|
|
|
|
|
* @return {*} The unwrapped value.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.unwrapValue = function (value, opt_doc) {
|
2025-12-20 14:10:39 +00:00
|
|
|
if (Array.isArray(value)) {
|
2020-11-27 15:46:30 +00:00
|
|
|
return goog.array.map(/**@type {IArrayLike}*/(value),
|
|
|
|
|
function (v) { return bot.inject.unwrapValue(v, opt_doc); });
|
2025-12-20 14:10:39 +00:00
|
|
|
} else if (goog.utils.isObject(value)) {
|
2011-09-06 21:27:45 +00:00
|
|
|
if (typeof value == 'function') {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-20 14:10:39 +00:00
|
|
|
var obj = /** @type {!Object} */ (value);
|
|
|
|
|
if (goog.object.containsKey(obj, bot.inject.ELEMENT_KEY)) {
|
|
|
|
|
return bot.inject.cache.getElement(obj[bot.inject.ELEMENT_KEY],
|
2020-11-27 15:46:30 +00:00
|
|
|
opt_doc);
|
2011-06-21 22:56:38 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-20 14:10:39 +00:00
|
|
|
if (goog.object.containsKey(obj, bot.inject.WINDOW_KEY)) {
|
|
|
|
|
return bot.inject.cache.getElement(obj[bot.inject.WINDOW_KEY],
|
2020-11-27 15:46:30 +00:00
|
|
|
opt_doc);
|
2011-06-21 22:56:38 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-20 14:10:39 +00:00
|
|
|
return goog.object.map(obj, function (val) {
|
2013-09-30 13:44:39 -04:00
|
|
|
return bot.inject.unwrapValue(val, opt_doc);
|
2011-06-21 22:56:38 +00:00
|
|
|
});
|
2010-07-19 11:24:30 +00:00
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2011-05-04 21:01:40 +00:00
|
|
|
/**
|
2018-11-25 13:21:40 +00:00
|
|
|
* Recompiles `fn` in the context of another window so that the
|
2011-05-04 21:01:40 +00:00
|
|
|
* correct symbol table is used when the function is executed. This
|
2018-11-25 13:21:40 +00:00
|
|
|
* function assumes the `fn` can be decompiled to its source using
|
|
|
|
|
* `Function.prototype.toString` and that it only refers to symbols
|
2011-05-04 21:01:40 +00:00
|
|
|
* defined in the target window's context.
|
|
|
|
|
*
|
2020-08-16 00:03:35 +05:30
|
|
|
* @param {!(Function|string)} fn Either the function that should be
|
2011-05-04 21:01:40 +00:00
|
|
|
* recompiled, or a string defining the body of an anonymous function
|
|
|
|
|
* that should be compiled in the target window's context.
|
|
|
|
|
* @param {!Window} theWindow The window to recompile the function in.
|
|
|
|
|
* @return {!Function} The recompiled function.
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.recompileFunction_ = function (fn, theWindow) {
|
2025-12-20 14:10:39 +00:00
|
|
|
if (typeof fn === 'string') {
|
2014-01-07 19:20:29 -05:00
|
|
|
try {
|
|
|
|
|
return new theWindow['Function'](fn);
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
// Try to recover if in IE5-quirks mode
|
|
|
|
|
// Need to initialize the script engine on the passed-in window
|
|
|
|
|
if (goog.userAgent.IE && theWindow.execScript) {
|
|
|
|
|
theWindow.execScript(';');
|
|
|
|
|
return new theWindow['Function'](fn);
|
|
|
|
|
}
|
|
|
|
|
throw ex;
|
|
|
|
|
}
|
2011-05-04 21:01:40 +00:00
|
|
|
}
|
2011-05-09 23:21:31 +00:00
|
|
|
return theWindow == window ? fn : new theWindow['Function'](
|
2020-11-27 15:46:30 +00:00
|
|
|
'return (' + fn + ').apply(null,arguments);');
|
2011-05-04 21:01:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2011-06-22 00:07:21 +00:00
|
|
|
/**
|
2010-07-19 11:24:30 +00:00
|
|
|
* Executes an injected script. This function should never be called from
|
|
|
|
|
* within JavaScript itself. Instead, it is used from an external source that
|
|
|
|
|
* is injecting a script for execution.
|
|
|
|
|
*
|
|
|
|
|
* <p/>For example, in a WebDriver Java test, one might have:
|
|
|
|
|
* <pre><code>
|
|
|
|
|
* Object result = ((JavascriptExecutor) driver).executeScript(
|
|
|
|
|
* "return arguments[0] + arguments[1];", 1, 2);
|
|
|
|
|
* </code></pre>
|
|
|
|
|
*
|
|
|
|
|
* <p/>Once transmitted to the driver, this command would be injected into the
|
|
|
|
|
* page for evaluation as:
|
|
|
|
|
* <pre><code>
|
|
|
|
|
* bot.inject.executeScript(
|
|
|
|
|
* function() {return arguments[0] + arguments[1];},
|
|
|
|
|
* [1, 2]);
|
|
|
|
|
* </code></pre>
|
|
|
|
|
*
|
|
|
|
|
* <p/>The details of how this actually gets injected for evaluation is left
|
|
|
|
|
* as an implementation detail for clients of this library.
|
|
|
|
|
*
|
2011-05-04 21:01:40 +00:00
|
|
|
* @param {!(Function|string)} fn Either the function to execute, or a string
|
|
|
|
|
* defining the body of an anonymous function that should be executed. This
|
|
|
|
|
* function should only contain references to symbols defined in the context
|
2018-11-25 13:21:40 +00:00
|
|
|
* of the target window (`opt_window`). Any references to symbols
|
2013-09-27 13:45:59 -07:00
|
|
|
* defined in this context will likely generate a ReferenceError.
|
2010-07-19 11:24:30 +00:00
|
|
|
* @param {Array.<*>} args An array of wrapped script arguments, as defined by
|
|
|
|
|
* the WebDriver wire protocol.
|
2010-12-14 02:04:44 +00:00
|
|
|
* @param {boolean=} opt_stringify Whether the result should be returned as a
|
|
|
|
|
* serialized JSON string.
|
2011-05-04 21:01:40 +00:00
|
|
|
* @param {!Window=} opt_window The window in whose context the function should
|
|
|
|
|
* be invoked; defaults to the current window.
|
2012-04-29 04:33:52 +00:00
|
|
|
* @return {!(string|bot.response.ResponseObject)} The response object. If
|
2011-02-15 19:22:39 +00:00
|
|
|
* opt_stringify is true, the result will be serialized and returned in
|
|
|
|
|
* string format.
|
2010-07-19 11:24:30 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.executeScript = function (fn, args, opt_stringify, opt_window) {
|
2011-06-21 22:56:38 +00:00
|
|
|
var win = opt_window || bot.getWindow();
|
2011-02-14 06:32:00 +00:00
|
|
|
var ret;
|
2010-07-19 11:24:30 +00:00
|
|
|
try {
|
2011-05-05 22:01:03 +00:00
|
|
|
fn = bot.inject.recompileFunction_(fn, win);
|
2013-09-30 13:44:39 -04:00
|
|
|
var unwrappedArgs = /**@type {Object}*/ (bot.inject.unwrapValue(args,
|
2020-11-27 15:46:30 +00:00
|
|
|
win.document));
|
2012-04-26 17:55:18 +00:00
|
|
|
ret = bot.inject.wrapResponse(fn.apply(null, unwrappedArgs));
|
2010-07-19 11:24:30 +00:00
|
|
|
} catch (ex) {
|
2012-04-26 17:55:18 +00:00
|
|
|
ret = bot.inject.wrapError(ex);
|
2010-07-19 11:24:30 +00:00
|
|
|
}
|
2012-04-26 17:55:18 +00:00
|
|
|
return opt_stringify ? bot.json.stringify(ret) : ret;
|
2010-07-19 11:24:30 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2011-04-28 03:13:45 +00:00
|
|
|
/**
|
|
|
|
|
* Executes an injected script, which is expected to finish asynchronously
|
2018-11-25 13:21:40 +00:00
|
|
|
* before the given `timeout`. When the script finishes or an error
|
|
|
|
|
* occurs, the given `onDone` callback will be invoked. This callback
|
2012-04-29 04:33:52 +00:00
|
|
|
* will have a single argument, a {@link bot.response.ResponseObject} object.
|
2011-04-28 03:13:45 +00:00
|
|
|
*
|
|
|
|
|
* The script signals its completion by invoking a supplied callback given
|
|
|
|
|
* as its last argument. The callback may be invoked with a single value.
|
|
|
|
|
*
|
2011-05-04 19:37:37 +00:00
|
|
|
* The script timeout event will be scheduled with the provided window,
|
|
|
|
|
* ensuring the timeout is synchronized with that window's event queue.
|
|
|
|
|
* Furthermore, asynchronous scripts do not work across new page loads; if an
|
|
|
|
|
* "unload" event is fired on the window while an asynchronous script is
|
|
|
|
|
* pending, the script will be aborted and an error will be returned.
|
|
|
|
|
*
|
2018-11-25 13:21:40 +00:00
|
|
|
* Like `bot.inject.executeScript`, this function should only be called
|
2011-04-28 03:13:45 +00:00
|
|
|
* from an external source. It handles wrapping and unwrapping of input/output
|
|
|
|
|
* values.
|
|
|
|
|
*
|
2013-09-27 13:45:59 -07:00
|
|
|
* @param {(!Function|string)} fn Either the function to execute, or a string
|
|
|
|
|
* defining the body of an anonymous function that should be executed. This
|
|
|
|
|
* function should only contain references to symbols defined in the context
|
2018-11-25 13:21:40 +00:00
|
|
|
* of the target window (`opt_window`). Any references to symbols
|
2013-09-27 13:45:59 -07:00
|
|
|
* defined in this context will likely generate a ReferenceError.
|
2011-04-28 03:13:45 +00:00
|
|
|
* @param {Array.<*>} args An array of wrapped script arguments, as defined by
|
|
|
|
|
* the WebDriver wire protocol.
|
|
|
|
|
* @param {number} timeout The amount of time, in milliseconds, the script
|
2011-05-04 19:37:37 +00:00
|
|
|
* should be permitted to run; must be non-negative.
|
2012-04-29 04:33:52 +00:00
|
|
|
* @param {function(string)|function(!bot.response.ResponseObject)} onDone
|
2018-11-25 13:21:40 +00:00
|
|
|
* The function to call when the given `fn` invokes its callback,
|
2011-04-28 03:13:45 +00:00
|
|
|
* or when an exception or timeout occurs. This will always be called.
|
|
|
|
|
* @param {boolean=} opt_stringify Whether the result should be returned as a
|
|
|
|
|
* serialized JSON string.
|
2011-05-04 21:01:40 +00:00
|
|
|
* @param {!Window=} opt_window The window to synchronize the script with;
|
2011-08-15 04:24:29 +00:00
|
|
|
* defaults to the current window.
|
2011-04-28 03:13:45 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.executeAsyncScript = function (fn, args, timeout, onDone,
|
|
|
|
|
opt_stringify, opt_window) {
|
2011-05-04 19:37:37 +00:00
|
|
|
var win = opt_window || window;
|
2012-04-26 17:55:18 +00:00
|
|
|
var timeoutId;
|
2011-05-04 19:37:37 +00:00
|
|
|
var responseSent = false;
|
|
|
|
|
|
|
|
|
|
function sendResponse(status, value) {
|
|
|
|
|
if (!responseSent) {
|
2012-04-26 17:55:18 +00:00
|
|
|
if (win.removeEventListener) {
|
|
|
|
|
win.removeEventListener('unload', onunload, true);
|
|
|
|
|
} else {
|
|
|
|
|
win.detachEvent('onunload', onunload);
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-04 19:37:37 +00:00
|
|
|
win.clearTimeout(timeoutId);
|
|
|
|
|
if (status != bot.ErrorCode.SUCCESS) {
|
|
|
|
|
var err = new bot.Error(status, value.message || value + '');
|
|
|
|
|
err.stack = value.stack;
|
2012-04-26 17:55:18 +00:00
|
|
|
value = bot.inject.wrapError(err);
|
2011-05-04 19:37:37 +00:00
|
|
|
} else {
|
2012-04-26 17:55:18 +00:00
|
|
|
value = bot.inject.wrapResponse(value);
|
2011-05-04 19:37:37 +00:00
|
|
|
}
|
2012-04-26 17:55:18 +00:00
|
|
|
onDone(opt_stringify ? bot.json.stringify(value) : value);
|
2011-05-04 21:01:40 +00:00
|
|
|
responseSent = true;
|
2011-05-04 19:37:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-20 14:10:39 +00:00
|
|
|
var sendError = goog.utils.partial(sendResponse, bot.ErrorCode.UNKNOWN_ERROR);
|
2011-05-04 19:37:37 +00:00
|
|
|
|
|
|
|
|
if (win.closed) {
|
2012-09-13 10:52:38 +00:00
|
|
|
sendError('Unable to execute script; the target window is closed.');
|
|
|
|
|
return;
|
2011-04-28 03:13:45 +00:00
|
|
|
}
|
|
|
|
|
|
2011-05-04 21:01:40 +00:00
|
|
|
fn = bot.inject.recompileFunction_(fn, win);
|
2011-04-28 03:13:45 +00:00
|
|
|
|
2013-09-30 13:44:39 -04:00
|
|
|
args = /** @type {Array.<*>} */ (bot.inject.unwrapValue(args, win.document));
|
2025-12-20 14:10:39 +00:00
|
|
|
args.push(goog.utils.partial(sendResponse, bot.ErrorCode.SUCCESS));
|
2011-05-05 22:01:03 +00:00
|
|
|
|
2012-04-26 17:55:18 +00:00
|
|
|
if (win.addEventListener) {
|
|
|
|
|
win.addEventListener('unload', onunload, true);
|
|
|
|
|
} else {
|
|
|
|
|
win.attachEvent('onunload', onunload);
|
|
|
|
|
}
|
2011-05-04 19:37:37 +00:00
|
|
|
|
2025-12-20 14:10:39 +00:00
|
|
|
var startTime = goog.utils.now();
|
2011-04-28 03:13:45 +00:00
|
|
|
try {
|
2011-05-04 19:37:37 +00:00
|
|
|
fn.apply(win, args);
|
|
|
|
|
|
|
|
|
|
// Register our timeout *after* the function has been invoked. This will
|
|
|
|
|
// ensure we don't timeout on a function that invokes its callback after
|
|
|
|
|
// a 0-based timeout.
|
2020-11-27 15:46:30 +00:00
|
|
|
timeoutId = win.setTimeout(function () {
|
2011-05-04 19:37:37 +00:00
|
|
|
sendResponse(bot.ErrorCode.SCRIPT_TIMEOUT,
|
2020-11-27 15:46:30 +00:00
|
|
|
Error('Timed out waiting for asynchronous script result ' +
|
2025-12-20 14:10:39 +00:00
|
|
|
'after ' + (goog.utils.now() - startTime) + ' ms'));
|
2011-05-04 19:37:37 +00:00
|
|
|
}, Math.max(0, timeout));
|
2011-04-28 03:13:45 +00:00
|
|
|
} catch (ex) {
|
2011-05-04 19:37:37 +00:00
|
|
|
sendResponse(ex.code || bot.ErrorCode.UNKNOWN_ERROR, ex);
|
2011-04-28 03:13:45 +00:00
|
|
|
}
|
2012-04-26 17:55:18 +00:00
|
|
|
|
|
|
|
|
function onunload() {
|
|
|
|
|
sendResponse(bot.ErrorCode.UNKNOWN_ERROR,
|
2020-11-27 15:46:30 +00:00
|
|
|
Error('Detected a page unload event; asynchronous script ' +
|
|
|
|
|
'execution does not work across page loads.'));
|
2012-04-26 17:55:18 +00:00
|
|
|
}
|
2011-04-28 03:13:45 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Wraps the response to an injected script that executed successfully so it
|
|
|
|
|
* can be JSON-ified for transmission to the process that injected this
|
|
|
|
|
* script.
|
|
|
|
|
* @param {*} value The script result.
|
|
|
|
|
* @return {{status:bot.ErrorCode,value:*}} The wrapped value.
|
2015-03-17 19:15:20 -04:00
|
|
|
* @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#responses
|
2011-04-28 03:13:45 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.wrapResponse = function (value) {
|
2011-04-28 03:13:45 +00:00
|
|
|
return {
|
|
|
|
|
'status': bot.ErrorCode.SUCCESS,
|
|
|
|
|
'value': bot.inject.wrapValue(value)
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2010-07-19 11:24:30 +00:00
|
|
|
/**
|
|
|
|
|
* Wraps a JavaScript error in an object-literal so that it can be JSON-ified
|
|
|
|
|
* for transmission to the process that injected this script.
|
|
|
|
|
* @param {Error} err The error to wrap.
|
|
|
|
|
* @return {{status:bot.ErrorCode,value:*}} The wrapped error object.
|
2015-03-17 19:15:20 -04:00
|
|
|
* @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#failed-commands
|
2010-07-19 11:24:30 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.wrapError = function (err) {
|
2013-08-16 11:53:17 -07:00
|
|
|
// TODO: Parse stackTrace
|
2010-07-19 11:24:30 +00:00
|
|
|
return {
|
|
|
|
|
'status': goog.object.containsKey(err, 'code') ?
|
2020-11-27 15:46:30 +00:00
|
|
|
err['code'] : bot.ErrorCode.UNKNOWN_ERROR,
|
2013-08-16 11:53:17 -07:00
|
|
|
// TODO: Parse stackTrace
|
2010-07-19 11:24:30 +00:00
|
|
|
'value': {
|
2014-01-07 19:59:59 -05:00
|
|
|
'message': err.message
|
2010-07-19 11:24:30 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The property key used to store the element cache on the DOCUMENT node
|
|
|
|
|
* when it is injected into the page. Since compiling each browser atom results
|
|
|
|
|
* in a different symbol table, we must use this known key to access the cache.
|
|
|
|
|
* This ensures the same object is used between injections of different atoms.
|
2013-03-10 13:56:37 -07:00
|
|
|
* @private {string}
|
2013-06-15 16:19:51 -07:00
|
|
|
* @const
|
2010-07-19 11:24:30 +00:00
|
|
|
*/
|
|
|
|
|
bot.inject.cache.CACHE_KEY_ = '$wdc_';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The prefix for each key stored in an cache.
|
|
|
|
|
* @type {string}
|
|
|
|
|
* @const
|
|
|
|
|
*/
|
|
|
|
|
bot.inject.cache.ELEMENT_KEY_PREFIX = ':wdc:';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Retrieves the cache object for the given window. Will initialize the cache
|
|
|
|
|
* if it does not yet exist.
|
2011-09-06 21:27:45 +00:00
|
|
|
* @param {Document=} opt_doc The document whose cache to retrieve. Defaults to
|
2010-07-19 11:24:30 +00:00
|
|
|
* the current document.
|
2011-08-15 04:24:29 +00:00
|
|
|
* @return {Object.<string, (Element|Window)>} The cache object.
|
2010-07-19 11:24:30 +00:00
|
|
|
* @private
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.cache.getCache_ = function (opt_doc) {
|
2010-07-19 11:24:30 +00:00
|
|
|
var doc = opt_doc || document;
|
|
|
|
|
var cache = doc[bot.inject.cache.CACHE_KEY_];
|
|
|
|
|
if (!cache) {
|
|
|
|
|
cache = doc[bot.inject.cache.CACHE_KEY_] = {};
|
|
|
|
|
// Store the counter used for generated IDs in the cache so that it gets
|
|
|
|
|
// reset whenever the cache does.
|
2025-12-20 14:10:39 +00:00
|
|
|
cache.nextId = goog.utils.now();
|
2010-07-19 11:24:30 +00:00
|
|
|
}
|
2011-04-28 17:07:04 +00:00
|
|
|
// Sometimes the nextId does not get initialized and returns NaN
|
|
|
|
|
// TODO: Generate UID on the fly instead.
|
|
|
|
|
if (!cache.nextId) {
|
2025-12-20 14:10:39 +00:00
|
|
|
cache.nextId = goog.utils.now();
|
2011-04-28 17:07:04 +00:00
|
|
|
}
|
2010-07-19 11:24:30 +00:00
|
|
|
return cache;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds an element to its ownerDocument's cache.
|
2011-06-21 22:56:38 +00:00
|
|
|
* @param {(Element|Window)} el The element or Window object to add.
|
2010-07-19 11:24:30 +00:00
|
|
|
* @return {string} The key generated for the cached element.
|
|
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.cache.addElement = function (el) {
|
2010-07-19 11:24:30 +00:00
|
|
|
// Check if the element already exists in the cache.
|
|
|
|
|
var cache = bot.inject.cache.getCache_(el.ownerDocument);
|
2020-11-27 15:46:30 +00:00
|
|
|
var id = goog.object.findKey(cache, function (value) {
|
2010-07-19 11:24:30 +00:00
|
|
|
return value == el;
|
|
|
|
|
});
|
|
|
|
|
if (!id) {
|
|
|
|
|
id = bot.inject.cache.ELEMENT_KEY_PREFIX + cache.nextId++;
|
|
|
|
|
cache[id] = el;
|
|
|
|
|
}
|
|
|
|
|
return id;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Retrieves an element from the cache. Will verify that the element is
|
|
|
|
|
* still attached to the DOM before returning.
|
|
|
|
|
* @param {string} key The element's key in the cache.
|
2011-09-06 21:27:45 +00:00
|
|
|
* @param {Document=} opt_doc The document whose cache to retrieve the element
|
2010-07-19 11:24:30 +00:00
|
|
|
* from. Defaults to the current document.
|
2011-08-15 04:24:29 +00:00
|
|
|
* @return {Element|Window} The cached element.
|
2010-07-19 11:24:30 +00:00
|
|
|
*/
|
2020-11-27 15:46:30 +00:00
|
|
|
bot.inject.cache.getElement = function (key, opt_doc) {
|
2010-12-22 19:54:10 +00:00
|
|
|
key = decodeURIComponent(key);
|
2010-07-19 11:24:30 +00:00
|
|
|
var doc = opt_doc || document;
|
|
|
|
|
var cache = bot.inject.cache.getCache_(doc);
|
|
|
|
|
if (!goog.object.containsKey(cache, key)) {
|
|
|
|
|
// Throw STALE_ELEMENT_REFERENCE instead of NO_SUCH_ELEMENT since the
|
|
|
|
|
// key may have been defined by a prior document's cache.
|
|
|
|
|
throw new bot.Error(bot.ErrorCode.STALE_ELEMENT_REFERENCE,
|
2020-11-27 15:46:30 +00:00
|
|
|
'Element does not exist in cache');
|
2010-07-19 11:24:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var el = cache[key];
|
|
|
|
|
|
2012-01-03 12:30:32 +00:00
|
|
|
// If this is a Window check if it's closed
|
|
|
|
|
if (goog.object.containsKey(el, 'setInterval')) {
|
2011-08-15 04:24:29 +00:00
|
|
|
if (el.closed) {
|
2011-06-21 22:56:38 +00:00
|
|
|
delete cache[key];
|
|
|
|
|
throw new bot.Error(bot.ErrorCode.NO_SUCH_WINDOW,
|
2020-11-27 15:46:30 +00:00
|
|
|
'Window has been closed.');
|
2011-06-21 22:56:38 +00:00
|
|
|
}
|
|
|
|
|
return el;
|
|
|
|
|
}
|
|
|
|
|
|
2010-07-19 11:24:30 +00:00
|
|
|
// Make sure the element is still attached to the DOM before returning.
|
|
|
|
|
var node = el;
|
|
|
|
|
while (node) {
|
|
|
|
|
if (node == doc.documentElement) {
|
|
|
|
|
return el;
|
|
|
|
|
}
|
2020-04-07 22:44:46 +02:00
|
|
|
if (node.host && node.nodeType === 11) {
|
|
|
|
|
node = node.host;
|
|
|
|
|
}
|
2010-07-19 11:24:30 +00:00
|
|
|
node = node.parentNode;
|
|
|
|
|
}
|
|
|
|
|
delete cache[key];
|
|
|
|
|
throw new bot.Error(bot.ErrorCode.STALE_ELEMENT_REFERENCE,
|
2020-11-27 15:46:30 +00:00
|
|
|
'Element is no longer attached to the DOM');
|
2010-07-19 11:24:30 +00:00
|
|
|
};
|