| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // dom_automation.js |
| // Methods for performing common DOM operations. Used in Chrome testing |
| // involving the DomAutomationController. |
| |
| var domAutomation = domAutomation || {}; |
| |
| (function() { |
| // |objects| is used to track objects which are sent back to the |
| // DomAutomationController. Since JavaScript does not have a map type, |
| // |objects| is simply an object in which the property name and |
| // 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) { |
| if (typeof value == "undefined" || !value) { |
| return ""; |
| } |
| if (value instanceof Array) { |
| var result = []; |
| for (var i = 0; i < value.length; i++) { |
| result.push(getConvertedValue(value[i])); |
| } |
| return result; |
| } |
| if (typeof(value) == "object") { |
| var handle = getHandleForObject(value); |
| if (handle == -1) { |
| // Track this object. |
| var handle = domAutomation.nextHandle++; |
| domAutomation.objects[handle] = value; |
| } |
| return handle; |
| } |
| return value; |
| } |
| |
| // Returns the handle for |obj|, or -1 if no handle exists. |
| function getHandleForObject(obj) { |
| for (var property in domAutomation.objects) { |
| if (domAutomation.objects[property] == obj) |
| return parseInt(property); |
| } |
| 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 { |
| sendCompletedResponse(eval(javascript)); |
| } |
| catch (exception) { |
| sendErrorResponse(exception); |
| } |
| } |
| |
| // 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|. |
| domAutomation.removeObject = function(handle) { |
| delete domAutomation.objects[handle]; |
| } |
| |
| // Stops tracking all objects. |
| domAutomation.removeAll = function() { |
| domAutomation.objects = {}; |
| domAutomation.nextHandle = 1; |
| } |
| |
| // 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 = []; |
| for (var i = 0; i < list.length; i++) { |
| arr.push(list[i]); |
| } |
| return arr; |
| } |
| |
| // Removes whitespace at the beginning and end of |text|. |
| function trim(text) { |
| return text.replace(/^\s+|\s+$/g, ""); |
| } |
| |
| // Returns the window (which is a sub window of |win|) which |
| // directly contains |doc|. May return null. |
| function findWindowForDocument(win, doc) { |
| if (win.document == doc) |
| return win; |
| for (var i = 0; i < win.frames.length; i++) { |
| if (findWindowForDocument(win.frames[i], doc)) |
| return win.frames[i]; |
| } |
| return null; |
| } |
| |
| // 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; |
| } |
| |
| // 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 true; |
| } |
| |
| // 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); |
| var elements = []; |
| for (var i = 0; i < xpathResult.snapshotLength; i++) { |
| elements.push(xpathResult.snapshotItem(i)); |
| } |
| return elements; |
| } |
| |
| // 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; |
| } |
| |
| // 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)); |
| } |
| |
| // 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); |
| } |
| |
| // 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 = findByXPath(context, xpath); |
| |
| // Limit to what is visible. |
| 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); |
| } |
| } |
| |
| 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) { |
| var evt = document.createEvent('MouseEvents'); |
| evt.initMouseEvent('click', true, true, window, |
| 0, 0, 0, 0, 0, false, false, |
| false, false, 0, null); |
| while (element) { |
| element.dispatchEvent(evt); |
| element = element.parentNode; |
| } |
| } |
| |
| domAutomation.type = function(element, text) { |
| if (element instanceof HTMLTextAreaElement || |
| (element instanceof HTMLInputElement && element.type == "text")) { |
| element.value += text; |
| return true; |
| } |
| return false; |
| } |
| |
| domAutomation.setText = function(element, text) { |
| if (element instanceof HTMLTextAreaElement || |
| (element instanceof HTMLInputElement && element.type == "text")) { |
| element.value = text; |
| return true; |
| } |
| return false; |
| } |
| |
| domAutomation.getProperty = function(element, property) { |
| return element[property]; |
| } |
| |
| domAutomation.getAttribute = function(element, attribute) { |
| return element.getAttribute(attribute); |
| } |
| |
| domAutomation.getValue = function(element, type) { |
| if (type == "text") { |
| return getText(element); |
| } else if (type == "innerhtml") { |
| return trim(element.innerHTML); |
| } else if (type == "innertext") { |
| return trim(element.innerText); |
| } else if (type == "visibility") { |
| return isVisible(element); |
| } else if (type == "id") { |
| return element.id; |
| } else if (type == "contentdocument") { |
| return element.contentDocument; |
| } |
| } |
| |
| 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); |
| } |
| })(); |
| } |
| })(); |