summaryrefslogtreecommitdiffstats
path: root/chrome/renderer
diff options
context:
space:
mode:
authorkkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-16 16:34:15 +0000
committerkkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-16 16:34:15 +0000
commitf18744fd5ec25ee780633e039a9f53d2cce3b49c (patch)
tree4d81c829dbf6e1710f546277a7907f5c0a928937 /chrome/renderer
parent966ddf0958e0e3eb3a145c5a9908bc8662e174be (diff)
downloadchromium_src-f18744fd5ec25ee780633e039a9f53d2cce3b49c.zip
chromium_src-f18744fd5ec25ee780633e039a9f53d2cce3b49c.tar.gz
chromium_src-f18744fd5ec25ee780633e039a9f53d2cce3b49c.tar.bz2
Add ability to manipulate DOM elements from the automation proxy. Rework the way that javascript is packaged and parsed in the JavascriptExecutionController, and add some waiting methods.
BUG=none TEST=none Review URL: http://codereview.chromium.org/1632001 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@44778 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/renderer')
-rw-r--r--chrome/renderer/resources/dom_automation.js294
1 files changed, 239 insertions, 55 deletions
diff --git a/chrome/renderer/resources/dom_automation.js b/chrome/renderer/resources/dom_automation.js
index 16a4dd0..0af645b 100644
--- a/chrome/renderer/resources/dom_automation.js
+++ b/chrome/renderer/resources/dom_automation.js
@@ -15,8 +15,21 @@ var domAutomation = domAutomation || {};
// property value serve as the key-value pair. The key is the handle number
// and the value is the tracked object.
domAutomation.objects = {};
+
+ // The next object handle to use.
domAutomation.nextHandle = 1;
+ // The current call ID for which a response is awaited. Each asynchronous
+ // function is given a call ID. When the function has a result to return,
+ // it must supply that call ID. If a result has not yet been received for
+ // that call ID, a response containing the result will be sent to the
+ // domAutomationController.
+ domAutomation.currentCallId = 1;
+
+ // The current timeout for an asynchronous JavaScript evaluation. Can be given
+ // to window.clearTimeout.
+ domAutomation.currentTimeout = null;
+
// Returns |value| after converting it to an acceptable type for return, if
// necessary.
function getConvertedValue(value) {
@@ -51,20 +64,84 @@ var domAutomation = domAutomation || {};
return -1;
}
+ // Sends a completed response back to the domAutomationController with a
+ // return value, which can be of any type.
+ function sendCompletedResponse(returnValue) {
+ var result = [true, "", getConvertedValue(returnValue)];
+ domAutomationController.sendJSON(JSON.stringify(result));
+ }
+
+ // Sends a error response back to the domAutomationController. |exception|
+ // should be a string or an exception.
+ function sendErrorResponse(exception) {
+ var message = exception.message;
+ if (typeof message == "undefined")
+ message = exception;
+ if (typeof message != "string")
+ message = JSON.stringify(message);
+ var result = [false, message, exception];
+ domAutomationController.sendJSON(JSON.stringify(result));
+ }
+
// Safely evaluates |javascript| and sends a response back via the
// DomAutomationController. See javascript_execution_controller.cc
// for more details.
domAutomation.evaluateJavaScript = function(javascript) {
try {
- var result = [true, "", getConvertedValue(eval(javascript))];
+ sendCompletedResponse(eval(javascript));
}
catch (exception) {
- var message = exception.message;
- if (typeof message != "string")
- message = JSON.stringify(message);
- var result = [false, message, exception];
+ sendErrorResponse(exception);
}
- domAutomationController.sendJSON(JSON.stringify(result));
+ }
+
+ // Called by a function when it has completed successfully. Any value,
+ // including undefined, is acceptable for |returnValue|. This should only
+ // be used by functions with an asynchronous response.
+ function onAsyncJavaScriptComplete(callId, returnValue) {
+ if (domAutomation.currentCallId != callId) {
+ // We are not waiting for a response for this call anymore,
+ // because it already responded.
+ return;
+ }
+ domAutomation.currentCallId++;
+ window.clearTimeout(domAutomation.currentTimeout);
+ sendCompletedResponse(returnValue);
+ }
+
+ // Calld by a function when it has an error preventing its successful
+ // execution. |exception| should be an exception or a string.
+ function onAsyncJavaScriptError(callId, exception) {
+ if (domAutomation.currentCallId != callId) {
+ // We are not waiting for a response for this call anymore,
+ // because it already responded.
+ return;
+ }
+ domAutomation.currentCallId++;
+ window.clearTimeout(domAutomation.currentTimeout);
+ sendErrorResponse(exception);
+ }
+
+ // Returns whether the call with the given ID has already finished. If true,
+ // this means that the call timed out or that it already gave a response.
+ function didCallFinish(callId) {
+ return domAutomation.currentCallId != callId;
+ }
+
+ // Safely evaluates |javascript|. The JavaScript is expected to return
+ // a response via either onAsyncJavaScriptComplete or
+ // onAsyncJavaScriptError. The script should respond within the |timeoutMs|.
+ domAutomation.evaluateAsyncJavaScript = function(javascript, timeoutMs) {
+ try {
+ eval(javascript);
+ }
+ catch (exception) {
+ onAsyncJavaScriptError(domAutomation.currentCallId, exception);
+ return;
+ }
+ domAutomation.currentTimeout = window.setTimeout(
+ onAsyncJavaScriptError, timeoutMs, domAutomation.currentCallId,
+ "JavaScript timed out waiting for response.");
}
// Stops tracking the object associated with |handle|.
@@ -80,9 +157,18 @@ var domAutomation = domAutomation || {};
// Gets the object associated with this |handle|.
domAutomation.getObject = function(handle) {
+ var obj = domAutomation.objects[handle]
+ if (typeof obj == "undefined") {
+ throw "Object with handle " + handle + " does not exist."
+ }
return domAutomation.objects[handle];
}
+ // Gets the ID for this asynchronous call.
+ domAutomation.getCallId = function() {
+ return domAutomation.currentCallId;
+ }
+
// Converts an indexable list with a length property to an array.
function getArray(list) {
var arr = [];
@@ -109,24 +195,49 @@ var domAutomation = domAutomation || {};
return null;
}
- //// DOM Element automation methods
- //// See dom_element_proxy.h for method details.
-
- domAutomation.getDocumentFromFrame = function(element, frameNames) {
- // Find the window this element is in.
- var containingDocument = element.ownerDocument || element;
- var frame = findWindowForDocument(window, containingDocument);
+ // Returns |element|'s text. This includes all descendants' text.
+ // For textareas and inputs, the text is the element's value. For Text,
+ // it is the textContent.
+ function getText(element) {
+ if (element instanceof Text) {
+ return trim(element.textContent);
+ } else if (element instanceof HTMLTextAreaElement ||
+ element instanceof HTMLInputElement) {
+ return element.value || "";
+ }
+ var childrenText = "";
+ for (var i = 0; i < element.childNodes.length; i++) {
+ childrenText += getText(element.childNodes[i]);
+ }
+ return childrenText;
+ }
- for (var i = 0; i < frameNames.length; i++) {
- frame = frame.frames[frameNames[i]];
- if (typeof frame == "undefined" || !frame) {
- return null;
+ // Returns whether |element| is visible.
+ function isVisible(element) {
+ while (element.style) {
+ if (element.style.display == 'none' ||
+ element.style.visibility == 'hidden' ||
+ element.style.visibility == 'collapse') {
+ return false;
}
+ element = element.parentNode;
}
- return frame.document;
+ return true;
}
- domAutomation.findByXPath = function(context, xpath) {
+ // Returns an array of the visible elements found in the |elements| array.
+ function getVisibleElements(elements) {
+ var visibleElements = [];
+ for (var i = 0; i < elements.length; i++) {
+ if (isVisible(elements[i]))
+ visibleElements.push(elements[i]);
+ }
+ return visibleElements;
+ }
+
+ // Finds all the elements which satisfy the xpath query using the context
+ // node |context|. This function may throw an exception.
+ function findByXPath(context, xpath) {
var xpathResult =
document.evaluate(xpath, context, null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
@@ -137,39 +248,107 @@ var domAutomation = domAutomation || {};
return elements;
}
- domAutomation.find1ByXPath = function(context, xpath) {
+ // Finds the first element which satisfies the xpath query using the context
+ // node |context|. This function may throw an exception.
+ function find1ByXPath(context, xpath) {
var xpathResult =
document.evaluate(xpath, context, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return xpathResult.singleNodeValue;
}
- domAutomation.findBySelectors = function(context, selectors) {
+ // Finds all the elements which satisfy the selectors query using the context
+ // node |context|. This function may throw an exception.
+ function findBySelectors(context, selectors) {
return getArray(context.querySelectorAll(selectors));
}
- domAutomation.find1BySelectors = function(context, selectors) {
+ // Finds the first element which satisfies the selectors query using the
+ // context node |context|. This function may throw an exception.
+ function find1BySelectors(context, selectors) {
return context.querySelector(selectors);
}
- domAutomation.findByText = function(context, text) {
+ // Finds all the elements which contain |text| using the context
+ // node |context|. See getText for details about what constitutes the text
+ // of an element. This function may throw an exception.
+ function findByText(context, text) {
// Find all elements containing this text and all inputs containing
// this text.
var xpath = ".//*[contains(text(), '" + text + "')] | " +
".//input[contains(@value, '" + text + "')]";
- var elements = domAutomation.findByXPath(context, xpath);
+ var elements = findByXPath(context, xpath);
// Limit to what is visible.
- var final_list = [];
- for (var i = 0; i < elements.length; i++) {
- if (domAutomation.isVisible(elements[i]))
- final_list.push(elements[i]);
+ return getVisibleElements(elements);
+ }
+
+ // Finds the first element which contains |text| using the context
+ // node |context|. See getText for details about what constitutes the text
+ // of an element. This function may throw an exception.
+ function find1ByText(context, text) {
+ var elements = findByText(context, text);
+ if (elements.length > 0)
+ return findByText(context, text)[0];
+ return null;
+ }
+
+ //// DOM Element automation methods
+ //// See dom_element_proxy.h for method details.
+
+ domAutomation.getDocumentFromFrame = function(element, frameNames) {
+ // Find the window this element is in.
+ var containingDocument = element.ownerDocument || element;
+ var frame = findWindowForDocument(window, containingDocument);
+
+ for (var i = 0; i < frameNames.length; i++) {
+ frame = frame.frames[frameNames[i]];
+ if (typeof frame == "undefined" || !frame) {
+ return null;
+ }
+ }
+ return frame.document;
+ }
+
+ domAutomation.findElement = function(context, query) {
+ var type = query.type;
+ var queryString = query.queryString;
+ if (type == "xpath") {
+ return find1ByXPath(context, queryString);
+ } else if (type == "selectors") {
+ return find1BySelectors(context, queryString);
+ } else if (type == "text") {
+ return find1ByText(context, queryString);
}
- return final_list;
}
- domAutomation.find1ByText = function(context, text) {
- return domAutomation.findByText(context, text)[0];
+ domAutomation.findElements = function(context, query) {
+ var type = query.type;
+ var queryString = query.queryString;
+ if (type == "xpath") {
+ return findByXPath(context, queryString);
+ } else if (type == "selectors") {
+ return findBySelectors(context, queryString);
+ } else if (type == "text") {
+ return findByText(context, queryString);
+ }
+ }
+
+ domAutomation.waitForVisibleElementCount = function(context, query, count,
+ callId) {
+ (function waitHelper() {
+ try {
+ var elements = domAutomation.findElements(context, query);
+ var visibleElements = getVisibleElements(elements);
+ if (visibleElements.length == count)
+ onAsyncJavaScriptComplete(callId, visibleElements);
+ else if (!didCallFinish(callId))
+ window.setTimeout(waitHelper, 500);
+ }
+ catch (exception) {
+ onAsyncJavaScriptError(callId, exception);
+ }
+ })();
}
domAutomation.click = function(element) {
@@ -201,34 +380,39 @@ var domAutomation = domAutomation || {};
return false;
}
- domAutomation.getText = function(element) {
- if (element instanceof Text) {
- return trim(element.textContent);
- }
- else if (element instanceof HTMLTextAreaElement ||
- (element instanceof HTMLInputElement)) {
- return element.value || "";
- }
- var childrenText = "";
- for (var i = 0; i < element.childNodes.length; i++) {
- childrenText += domAutomation.getText(element.childNodes[i]);
- }
- return childrenText;
+ domAutomation.getProperty = function(element, property) {
+ return element[property];
}
- domAutomation.getInnerHTML = function(element) {
- return trim(element.innerHTML);
+ domAutomation.getAttribute = function(element, attribute) {
+ return element.getAttribute(attribute);
}
- domAutomation.isVisible = function(element) {
- while (element.style) {
- if (element.style.display == 'none' ||
- element.style.visibility == 'hidden' ||
- element.style.visibility == 'collapse') {
- return false;
- }
- element = element.parentNode;
+ domAutomation.getValue = function(element, type) {
+ if (type == "text") {
+ return getText(element);
+ } else if (type == "innerhtml") {
+ return trim(element.innerHTML);
+ } else if (type == "visibility") {
+ return isVisible(element);
+ } else if (type == "id") {
+ return element.id;
+ } else if (type == "contentdocument") {
+ return element.contentDocument;
}
- return true;
+ }
+
+ domAutomation.waitForAttribute = function(element, attribute, value, callId) {
+ (function waitForAttributeHelper() {
+ try {
+ if (element.getAttribute(attribute) == value)
+ onAsyncJavaScriptComplete(callId);
+ else if (!didCallFinish(callId))
+ window.setTimeout(waitForAttributeHelper, 200);
+ }
+ catch (exception) {
+ onAsyncJavaScriptError(callId, exception);
+ }
+ })();
}
})();