1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 // Methods for performing common DOM operations. Used in Chrome testing
7 // involving the DomAutomationController.
9 var domAutomation
= domAutomation
|| {};
12 // |objects| is used to track objects which are sent back to the
13 // DomAutomationController. Since JavaScript does not have a map type,
14 // |objects| is simply an object in which the property name and
15 // property value serve as the key-value pair. The key is the handle number
16 // and the value is the tracked object.
17 domAutomation
.objects
= {};
19 // The next object handle to use.
20 domAutomation
.nextHandle
= 1;
22 // The current call ID for which a response is awaited. Each asynchronous
23 // function is given a call ID. When the function has a result to return,
24 // it must supply that call ID. If a result has not yet been received for
25 // that call ID, a response containing the result will be sent to the
26 // domAutomationController.
27 domAutomation
.currentCallId
= 1;
29 // The current timeout for an asynchronous JavaScript evaluation. Can be given
30 // to window.clearTimeout.
31 domAutomation
.currentTimeout
= null;
33 // Returns |value| after converting it to an acceptable type for return, if
35 function getConvertedValue(value
) {
36 if (typeof value
== "undefined" || !value
) {
39 if (value
instanceof Array
) {
41 for (var i
= 0; i
< value
.length
; i
++) {
42 result
.push(getConvertedValue(value
[i
]));
46 if (typeof(value
) == "object") {
47 var handle
= getHandleForObject(value
);
50 var handle
= domAutomation
.nextHandle
++;
51 domAutomation
.objects
[handle
] = value
;
58 // Returns the handle for |obj|, or -1 if no handle exists.
59 function getHandleForObject(obj
) {
60 for (var property
in domAutomation
.objects
) {
61 if (domAutomation
.objects
[property
] == obj
)
62 return parseInt(property
);
67 // Sends a completed response back to the domAutomationController with a
68 // return value, which can be of any type.
69 function sendCompletedResponse(returnValue
) {
70 var result
= [true, "", getConvertedValue(returnValue
)];
71 domAutomationController
.sendJSON(JSON
.stringify(result
));
74 // Sends a error response back to the domAutomationController. |exception|
75 // should be a string or an exception.
76 function sendErrorResponse(exception
) {
77 var message
= exception
.message
;
78 if (typeof message
== "undefined")
80 if (typeof message
!= "string")
81 message
= JSON
.stringify(message
);
82 var result
= [false, message
, exception
];
83 domAutomationController
.sendJSON(JSON
.stringify(result
));
86 // Safely evaluates |javascript| and sends a response back via the
87 // DomAutomationController. See javascript_execution_controller.cc
89 domAutomation
.evaluateJavaScript = function(javascript
) {
91 sendCompletedResponse(eval(javascript
));
94 sendErrorResponse(exception
);
98 // Called by a function when it has completed successfully. Any value,
99 // including undefined, is acceptable for |returnValue|. This should only
100 // be used by functions with an asynchronous response.
101 function onAsyncJavaScriptComplete(callId
, returnValue
) {
102 if (domAutomation
.currentCallId
!= callId
) {
103 // We are not waiting for a response for this call anymore,
104 // because it already responded.
107 domAutomation
.currentCallId
++;
108 window
.clearTimeout(domAutomation
.currentTimeout
);
109 sendCompletedResponse(returnValue
);
112 // Calld by a function when it has an error preventing its successful
113 // execution. |exception| should be an exception or a string.
114 function onAsyncJavaScriptError(callId
, exception
) {
115 if (domAutomation
.currentCallId
!= callId
) {
116 // We are not waiting for a response for this call anymore,
117 // because it already responded.
120 domAutomation
.currentCallId
++;
121 window
.clearTimeout(domAutomation
.currentTimeout
);
122 sendErrorResponse(exception
);
125 // Returns whether the call with the given ID has already finished. If true,
126 // this means that the call timed out or that it already gave a response.
127 function didCallFinish(callId
) {
128 return domAutomation
.currentCallId
!= callId
;
131 // Safely evaluates |javascript|. The JavaScript is expected to return
132 // a response via either onAsyncJavaScriptComplete or
133 // onAsyncJavaScriptError. The script should respond within the |timeoutMs|.
134 domAutomation
.evaluateAsyncJavaScript = function(javascript
, timeoutMs
) {
139 onAsyncJavaScriptError(domAutomation
.currentCallId
, exception
);
142 domAutomation
.currentTimeout
= window
.setTimeout(
143 onAsyncJavaScriptError
, timeoutMs
, domAutomation
.currentCallId
,
144 "JavaScript timed out waiting for response.");
147 // Stops tracking the object associated with |handle|.
148 domAutomation
.removeObject = function(handle
) {
149 delete domAutomation
.objects
[handle
];
152 // Stops tracking all objects.
153 domAutomation
.removeAll = function() {
154 domAutomation
.objects
= {};
155 domAutomation
.nextHandle
= 1;
158 // Gets the object associated with this |handle|.
159 domAutomation
.getObject = function(handle
) {
160 var obj
= domAutomation
.objects
[handle
]
161 if (typeof obj
== "undefined") {
162 throw "Object with handle " + handle
+ " does not exist."
164 return domAutomation
.objects
[handle
];
167 // Gets the ID for this asynchronous call.
168 domAutomation
.getCallId = function() {
169 return domAutomation
.currentCallId
;
172 // Converts an indexable list with a length property to an array.
173 function getArray(list
) {
175 for (var i
= 0; i
< list
.length
; i
++) {
181 // Removes whitespace at the beginning and end of |text|.
182 function trim(text
) {
183 return text
.replace(/^\s+|\s+$/g, "");
186 // Returns the window (which is a sub window of |win|) which
187 // directly contains |doc|. May return null.
188 function findWindowForDocument(win
, doc
) {
189 if (win
.document
== doc
)
191 for (var i
= 0; i
< win
.frames
.length
; i
++) {
192 if (findWindowForDocument(win
.frames
[i
], doc
))
193 return win
.frames
[i
];
198 // Returns |element|'s text. This includes all descendants' text.
199 // For textareas and inputs, the text is the element's value. For Text,
200 // it is the textContent.
201 function getText(element
) {
202 if (element
instanceof Text
) {
203 return trim(element
.textContent
);
204 } else if (element
instanceof HTMLTextAreaElement
||
205 element
instanceof HTMLInputElement
) {
206 return element
.value
|| "";
208 var childrenText
= "";
209 for (var i
= 0; i
< element
.childNodes
.length
; i
++) {
210 childrenText
+= getText(element
.childNodes
[i
]);
215 // Returns whether |element| is visible.
216 function isVisible(element
) {
217 while (element
.style
) {
218 if (element
.style
.display
== 'none' ||
219 element
.style
.visibility
== 'hidden' ||
220 element
.style
.visibility
== 'collapse') {
223 element
= element
.parentNode
;
228 // Returns an array of the visible elements found in the |elements| array.
229 function getVisibleElements(elements
) {
230 var visibleElements
= [];
231 for (var i
= 0; i
< elements
.length
; i
++) {
232 if (isVisible(elements
[i
]))
233 visibleElements
.push(elements
[i
]);
235 return visibleElements
;
238 // Finds all the elements which satisfy the xpath query using the context
239 // node |context|. This function may throw an exception.
240 function findByXPath(context
, xpath
) {
242 document
.evaluate(xpath
, context
, null,
243 XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
, null);
245 for (var i
= 0; i
< xpathResult
.snapshotLength
; i
++) {
246 elements
.push(xpathResult
.snapshotItem(i
));
251 // Finds the first element which satisfies the xpath query using the context
252 // node |context|. This function may throw an exception.
253 function find1ByXPath(context
, xpath
) {
255 document
.evaluate(xpath
, context
, null,
256 XPathResult
.FIRST_ORDERED_NODE_TYPE
, null);
257 return xpathResult
.singleNodeValue
;
260 // Finds all the elements which satisfy the selectors query using the context
261 // node |context|. This function may throw an exception.
262 function findBySelectors(context
, selectors
) {
263 return getArray(context
.querySelectorAll(selectors
));
266 // Finds the first element which satisfies the selectors query using the
267 // context node |context|. This function may throw an exception.
268 function find1BySelectors(context
, selectors
) {
269 return context
.querySelector(selectors
);
272 // Finds all the elements which contain |text| using the context
273 // node |context|. See getText for details about what constitutes the text
274 // of an element. This function may throw an exception.
275 function findByText(context
, text
) {
276 // Find all elements containing this text and all inputs containing
278 var xpath
= ".//*[contains(text(), '" + text
+ "')] | " +
279 ".//input[contains(@value, '" + text
+ "')]";
280 var elements
= findByXPath(context
, xpath
);
282 // Limit to what is visible.
283 return getVisibleElements(elements
);
286 // Finds the first element which contains |text| using the context
287 // node |context|. See getText for details about what constitutes the text
288 // of an element. This function may throw an exception.
289 function find1ByText(context
, text
) {
290 var elements
= findByText(context
, text
);
291 if (elements
.length
> 0)
292 return findByText(context
, text
)[0];
296 //// DOM Element automation methods
297 //// See dom_element_proxy.h for method details.
299 domAutomation
.getDocumentFromFrame = function(element
, frameNames
) {
300 // Find the window this element is in.
301 var containingDocument
= element
.ownerDocument
|| element
;
302 var frame
= findWindowForDocument(window
, containingDocument
);
304 for (var i
= 0; i
< frameNames
.length
; i
++) {
305 frame
= frame
.frames
[frameNames
[i
]];
306 if (typeof frame
== "undefined" || !frame
) {
310 return frame
.document
;
313 domAutomation
.findElement = function(context
, query
) {
314 var type
= query
.type
;
315 var queryString
= query
.queryString
;
316 if (type
== "xpath") {
317 return find1ByXPath(context
, queryString
);
318 } else if (type
== "selectors") {
319 return find1BySelectors(context
, queryString
);
320 } else if (type
== "text") {
321 return find1ByText(context
, queryString
);
325 domAutomation
.findElements = function(context
, query
) {
326 var type
= query
.type
;
327 var queryString
= query
.queryString
;
328 if (type
== "xpath") {
329 return findByXPath(context
, queryString
);
330 } else if (type
== "selectors") {
331 return findBySelectors(context
, queryString
);
332 } else if (type
== "text") {
333 return findByText(context
, queryString
);
337 domAutomation
.waitForVisibleElementCount = function(context
, query
, count
,
339 (function waitHelper() {
341 var elements
= domAutomation
.findElements(context
, query
);
342 var visibleElements
= getVisibleElements(elements
);
343 if (visibleElements
.length
== count
)
344 onAsyncJavaScriptComplete(callId
, visibleElements
);
345 else if (!didCallFinish(callId
))
346 window
.setTimeout(waitHelper
, 500);
349 onAsyncJavaScriptError(callId
, exception
);
354 domAutomation
.click = function(element
) {
355 var evt
= document
.createEvent('MouseEvents');
356 evt
.initMouseEvent('click', true, true, window
,
357 0, 0, 0, 0, 0, false, false,
358 false, false, 0, null);
360 element
.dispatchEvent(evt
);
361 element
= element
.parentNode
;
365 domAutomation
.type = function(element
, text
) {
366 if (element
instanceof HTMLTextAreaElement
||
367 (element
instanceof HTMLInputElement
&& element
.type
== "text")) {
368 element
.value
+= text
;
374 domAutomation
.setText = function(element
, text
) {
375 if (element
instanceof HTMLTextAreaElement
||
376 (element
instanceof HTMLInputElement
&& element
.type
== "text")) {
377 element
.value
= text
;
383 domAutomation
.getProperty = function(element
, property
) {
384 return element
[property
];
387 domAutomation
.getAttribute = function(element
, attribute
) {
388 return element
.getAttribute(attribute
);
391 domAutomation
.getValue = function(element
, type
) {
392 if (type
== "text") {
393 return getText(element
);
394 } else if (type
== "innerhtml") {
395 return trim(element
.innerHTML
);
396 } else if (type
== "innertext") {
397 return trim(element
.innerText
);
398 } else if (type
== "visibility") {
399 return isVisible(element
);
400 } else if (type
== "id") {
402 } else if (type
== "contentdocument") {
403 return element
.contentDocument
;
407 domAutomation
.waitForAttribute = function(element
, attribute
, value
, callId
) {
408 (function waitForAttributeHelper() {
410 if (element
.getAttribute(attribute
) == value
)
411 onAsyncJavaScriptComplete(callId
);
412 else if (!didCallFinish(callId
))
413 window
.setTimeout(waitForAttributeHelper
, 200);
416 onAsyncJavaScriptError(callId
, exception
);