chromeos: dbus: add Bluetooth properties support
[chromium-blink-merge.git] / content / renderer / dom_automation.js
blob0af645be6a3c91ead377f2d4827c503276443ba2
1 // Copyright (c) 2010 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 "";
38     }
39     if (value instanceof Array) {
40       var result = [];
41       for (var i = 0; i < value.length; i++) {
42         result.push(getConvertedValue(value[i]));
43       }
44       return result;
45     }
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;
52       }
53       return handle;
54     }
55     return value;
56   }
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);
63       }
64       return -1;
65   }
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));
72   }
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));
84   }
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));
92     }
93     catch (exception) {
94       sendErrorResponse(exception);
95     }
96   }
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;
106     }
107     domAutomation.currentCallId++;
108     window.clearTimeout(domAutomation.currentTimeout);
109     sendCompletedResponse(returnValue);
110   }
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;
119     }
120     domAutomation.currentCallId++;
121     window.clearTimeout(domAutomation.currentTimeout);
122     sendErrorResponse(exception);
123   }
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;
129   }
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);
137     }
138     catch (exception) {
139       onAsyncJavaScriptError(domAutomation.currentCallId, exception);
140       return;
141     }
142     domAutomation.currentTimeout = window.setTimeout(
143         onAsyncJavaScriptError, timeoutMs, domAutomation.currentCallId,
144         "JavaScript timed out waiting for response.");
145   }
147   // Stops tracking the object associated with |handle|.
148   domAutomation.removeObject = function(handle) {
149     delete domAutomation.objects[handle];
150   }
152   // Stops tracking all objects.
153   domAutomation.removeAll = function() {
154     domAutomation.objects = {};
155     domAutomation.nextHandle = 1;
156   }
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."
163     }
164     return domAutomation.objects[handle];
165   }
167   // Gets the ID for this asynchronous call.
168   domAutomation.getCallId = function() {
169     return domAutomation.currentCallId;
170   }
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]);
177     }
178     return arr;
179   }
181   // Removes whitespace at the beginning and end of |text|.
182   function trim(text) {
183     return text.replace(/^\s+|\s+$/g, "");
184   }
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];
194     }
195     return null;
196   }
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 || "";
207     }
208     var childrenText = "";
209     for (var i = 0; i < element.childNodes.length; i++) {
210       childrenText += getText(element.childNodes[i]);
211     }
212     return childrenText;
213   }
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;
222       }
223       element = element.parentNode;
224     }
225     return true;
226   }
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]);
234     }
235     return visibleElements;
236   }
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));
247     }
248     return elements;
249   }
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;
258   }
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));
264   }
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);
270   }
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);
284   }
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;
294   }
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;
308       }
309     }
310     return frame.document;
311   }
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);
322     }
323   }
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);
334     }
335   }
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);
347       }
348       catch (exception) {
349         onAsyncJavaScriptError(callId, exception);
350       }
351     })();
352   }
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;
362     }
363   }
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;
370     }
371     return false;
372   }
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;
379     }
380     return false;
381   }
383   domAutomation.getProperty = function(element, property) {
384     return element[property];
385   }
387   domAutomation.getAttribute = function(element, attribute) {
388     return element.getAttribute(attribute);
389   }
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 == "visibility") {
397       return isVisible(element);
398     } else if (type == "id") {
399       return element.id;
400     } else if (type == "contentdocument") {
401       return element.contentDocument;
402     }
403   }
405   domAutomation.waitForAttribute = function(element, attribute, value, callId) {
406     (function waitForAttributeHelper() {
407       try {
408         if (element.getAttribute(attribute) == value)
409           onAsyncJavaScriptComplete(callId);
410         else if (!didCallFinish(callId))
411           window.setTimeout(waitForAttributeHelper, 200);
412       }
413       catch (exception) {
414         onAsyncJavaScriptError(callId, exception);
415       }
416     })();
417   }
418 })();