[content shell] implement testRunner.overridePreference
[chromium-blink-merge.git] / content / renderer / dom_automation.js
blob676d6228ee913688dac83e08442ac5263cef4f69
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.
5 // dom_automation.js
6 // Methods for performing common DOM operations. Used in Chrome testing
7 // involving the DomAutomationController.
9 var domAutomation = domAutomation || {};
11 (function() {
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
34 // necessary.
35 function getConvertedValue(value) {
36 if (typeof value == "undefined" || !value) {
37 return "";
39 if (value instanceof Array) {
40 var result = [];
41 for (var i = 0; i < value.length; i++) {
42 result.push(getConvertedValue(value[i]));
44 return result;
46 if (typeof(value) == "object") {
47 var handle = getHandleForObject(value);
48 if (handle == -1) {
49 // Track this object.
50 var handle = domAutomation.nextHandle++;
51 domAutomation.objects[handle] = value;
53 return handle;
55 return 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);
64 return -1;
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")
79 message = exception;
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
88 // for more details.
89 domAutomation.evaluateJavaScript = function(javascript) {
90 try {
91 sendCompletedResponse(eval(javascript));
93 catch (exception) {
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.
105 return;
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.
118 return;
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) {
135 try {
136 eval(javascript);
138 catch (exception) {
139 onAsyncJavaScriptError(domAutomation.currentCallId, exception);
140 return;
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) {
174 var arr = [];
175 for (var i = 0; i < list.length; i++) {
176 arr.push(list[i]);
178 return arr;
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)
190 return win;
191 for (var i = 0; i < win.frames.length; i++) {
192 if (findWindowForDocument(win.frames[i], doc))
193 return win.frames[i];
195 return null;
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]);
212 return childrenText;
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') {
221 return false;
223 element = element.parentNode;
225 return true;
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) {
241 var xpathResult =
242 document.evaluate(xpath, context, null,
243 XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
244 var elements = [];
245 for (var i = 0; i < xpathResult.snapshotLength; i++) {
246 elements.push(xpathResult.snapshotItem(i));
248 return elements;
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) {
254 var xpathResult =
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
277 // this text.
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];
293 return null;
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) {
307 return null;
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,
338 callId) {
339 (function waitHelper() {
340 try {
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);
348 catch (exception) {
349 onAsyncJavaScriptError(callId, exception);
351 })();
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);
359 while (element) {
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;
369 return true;
371 return false;
374 domAutomation.setText = function(element, text) {
375 if (element instanceof HTMLTextAreaElement ||
376 (element instanceof HTMLInputElement && element.type == "text")) {
377 element.value = text;
378 return true;
380 return false;
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") {
401 return element.id;
402 } else if (type == "contentdocument") {
403 return element.contentDocument;
407 domAutomation.waitForAttribute = function(element, attribute, value, callId) {
408 (function waitForAttributeHelper() {
409 try {
410 if (element.getAttribute(attribute) == value)
411 onAsyncJavaScriptComplete(callId);
412 else if (!didCallFinish(callId))
413 window.setTimeout(waitForAttributeHelper, 200);
415 catch (exception) {
416 onAsyncJavaScriptError(callId, exception);
418 })();
420 })();