Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / renderer / resources / extensions / automation / automation_node.js
blob5e96405fd1cb408f2cc7fccc639c229a7b995ff0
1 // Copyright 2014 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 var AutomationEvent = require('automationEvent').AutomationEvent;
6 var automationInternal =
7     require('binding').Binding.create('automationInternal').generate();
8 var IsInteractPermitted =
9     requireNative('automationInternal').IsInteractPermitted;
11 /**
12  * @param {number} axTreeID The id of the accessibility tree.
13  * @return {?number} The id of the root node.
14  */
15 var GetRootID = requireNative('automationInternal').GetRootID;
17 /**
18  * @param {number} axTreeID The id of the accessibility tree.
19  * @param {number} nodeID The id of a node.
20  * @return {?number} The id of the node's parent, or undefined if it's the
21  *    root of its tree or if the tree or node wasn't found.
22  */
23 var GetParentID = requireNative('automationInternal').GetParentID;
25 /**
26  * @param {number} axTreeID The id of the accessibility tree.
27  * @param {number} nodeID The id of a node.
28  * @return {?number} The number of children of the node, or undefined if
29  *     the tree or node wasn't found.
30  */
31 var GetChildCount = requireNative('automationInternal').GetChildCount;
33 /**
34  * @param {number} axTreeID The id of the accessibility tree.
35  * @param {number} nodeID The id of a node.
36  * @param {number} childIndex An index of a child of this node.
37  * @return {?number} The id of the child at the given index, or undefined
38  *     if the tree or node or child at that index wasn't found.
39  */
40 var GetChildIDAtIndex = requireNative('automationInternal').GetChildIDAtIndex;
42 /**
43  * @param {number} axTreeID The id of the accessibility tree.
44  * @param {number} nodeID The id of a node.
45  * @return {?number} The index of this node in its parent, or undefined if
46  *     the tree or node or node parent wasn't found.
47  */
48 var GetIndexInParent = requireNative('automationInternal').GetIndexInParent;
50 /**
51  * @param {number} axTreeID The id of the accessibility tree.
52  * @param {number} nodeID The id of a node.
53  * @return {?Object} An object with a string key for every state flag set,
54  *     or undefined if the tree or node or node parent wasn't found.
55  */
56 var GetState = requireNative('automationInternal').GetState;
58 /**
59  * @param {number} axTreeID The id of the accessibility tree.
60  * @param {number} nodeID The id of a node.
61  * @return {string} The role of the node, or undefined if the tree or
62  *     node wasn't found.
63  */
64 var GetRole = requireNative('automationInternal').GetRole;
66 /**
67  * @param {number} axTreeID The id of the accessibility tree.
68  * @param {number} nodeID The id of a node.
69  * @return {?automation.Rect} The location of the node, or undefined if
70  *     the tree or node wasn't found.
71  */
72 var GetLocation = requireNative('automationInternal').GetLocation;
74 /**
75  * @param {number} axTreeID The id of the accessibility tree.
76  * @param {number} nodeID The id of a node.
77  * @param {string} attr The name of a string attribute.
78  * @return {?string} The value of this attribute, or undefined if the tree,
79  *     node, or attribute wasn't found.
80  */
81 var GetStringAttribute = requireNative('automationInternal').GetStringAttribute;
83 /**
84  * @param {number} axTreeID The id of the accessibility tree.
85  * @param {number} nodeID The id of a node.
86  * @param {string} attr The name of an attribute.
87  * @return {?boolean} The value of this attribute, or undefined if the tree,
88  *     node, or attribute wasn't found.
89  */
90 var GetBoolAttribute = requireNative('automationInternal').GetBoolAttribute;
92 /**
93  * @param {number} axTreeID The id of the accessibility tree.
94  * @param {number} nodeID The id of a node.
95  * @param {string} attr The name of an attribute.
96  * @return {?number} The value of this attribute, or undefined if the tree,
97  *     node, or attribute wasn't found.
98  */
99 var GetIntAttribute = requireNative('automationInternal').GetIntAttribute;
102  * @param {number} axTreeID The id of the accessibility tree.
103  * @param {number} nodeID The id of a node.
104  * @param {string} attr The name of an attribute.
105  * @return {?number} The value of this attribute, or undefined if the tree,
106  *     node, or attribute wasn't found.
107  */
108 var GetFloatAttribute = requireNative('automationInternal').GetFloatAttribute;
111  * @param {number} axTreeID The id of the accessibility tree.
112  * @param {number} nodeID The id of a node.
113  * @param {string} attr The name of an attribute.
114  * @return {?Array.<number>} The value of this attribute, or undefined
115  *     if the tree, node, or attribute wasn't found.
116  */
117 var GetIntListAttribute =
118     requireNative('automationInternal').GetIntListAttribute;
121  * @param {number} axTreeID The id of the accessibility tree.
122  * @param {number} nodeID The id of a node.
123  * @param {string} attr The name of an HTML attribute.
124  * @return {?string} The value of this attribute, or undefined if the tree,
125  *     node, or attribute wasn't found.
126  */
127 var GetHtmlAttribute = requireNative('automationInternal').GetHtmlAttribute;
129 var lastError = require('lastError');
130 var logging = requireNative('logging');
131 var schema = requireNative('automationInternal').GetSchemaAdditions();
132 var utils = require('utils');
135  * A single node in the Automation tree.
136  * @param {AutomationRootNodeImpl} root The root of the tree.
137  * @constructor
138  */
139 function AutomationNodeImpl(root) {
140   this.rootImpl = root;
141   // Public attributes. No actual data gets set on this object.
142   this.listeners = {};
145 AutomationNodeImpl.prototype = {
146   treeID: -1,
147   id: -1,
148   role: '',
149   state: { busy: true },
150   isRootNode: false,
152   get root() {
153     return this.rootImpl.wrapper;
154   },
156   get parent() {
157     if (this.hostNode_)
158       return this.hostNode_;
159     var parentID = GetParentID(this.treeID, this.id);
160     return this.rootImpl.get(parentID);
161   },
163   get state() {
164     return GetState(this.treeID, this.id);
165   },
167   get role() {
168     return GetRole(this.treeID, this.id);
169   },
171   get location() {
172     return GetLocation(this.treeID, this.id);
173   },
175   get indexInParent() {
176     return GetIndexInParent(this.treeID, this.id);
177   },
179   get childTree() {
180     var childTreeID = GetIntAttribute(this.treeID, this.id, 'childTreeId');
181     if (childTreeID)
182       return AutomationRootNodeImpl.get(childTreeID);
183   },
185   get firstChild() {
186     if (this.childTree)
187       return this.childTree;
188     if (!GetChildCount(this.treeID, this.id))
189       return undefined;
190     var firstChildID = GetChildIDAtIndex(this.treeID, this.id, 0);
191     return this.rootImpl.get(firstChildID);
192   },
194   get lastChild() {
195     if (this.childTree)
196       return this.childTree;
197     var count = GetChildCount(this.treeID, this.id);
198     if (!count)
199       return undefined;
200     var lastChildID = GetChildIDAtIndex(this.treeID, this.id, count - 1);
201     return this.rootImpl.get(lastChildID);
202   },
204   get children() {
205     if (this.childTree)
206       return [this.childTree];
208     var children = [];
209     var count = GetChildCount(this.treeID, this.id);
210     for (var i = 0; i < count; ++i) {
211       var childID = GetChildIDAtIndex(this.treeID, this.id, i);
212       var child = this.rootImpl.get(childID);
213       children.push(child);
214     }
215     return children;
216   },
218   get previousSibling() {
219     var parent = this.parent;
220     var indexInParent = GetIndexInParent(this.treeID, this.id);
221     if (parent && indexInParent > 0)
222       return parent.children[indexInParent - 1];
223     return undefined;
224   },
226   get nextSibling() {
227     var parent = this.parent;
228     var indexInParent = GetIndexInParent(this.treeID, this.id);
229     if (parent && indexInParent < parent.children.length)
230       return parent.children[indexInParent + 1];
231     return undefined;
232   },
234   doDefault: function() {
235     this.performAction_('doDefault');
236   },
238   focus: function() {
239     this.performAction_('focus');
240   },
242   makeVisible: function() {
243     this.performAction_('makeVisible');
244   },
246   setSelection: function(startIndex, endIndex) {
247     this.performAction_('setSelection',
248                         { startIndex: startIndex,
249                           endIndex: endIndex });
250   },
252   showContextMenu: function() {
253     this.performAction_('showContextMenu');
254   },
256   domQuerySelector: function(selector, callback) {
257     automationInternal.querySelector(
258       { treeID: this.rootImpl.treeID,
259         automationNodeID: this.id,
260         selector: selector },
261       this.domQuerySelectorCallback_.bind(this, callback));
262   },
264   find: function(params) {
265     return this.findInternal_(params);
266   },
268   findAll: function(params) {
269     return this.findInternal_(params, []);
270   },
272   matches: function(params) {
273     return this.matchInternal_(params);
274   },
276   addEventListener: function(eventType, callback, capture) {
277     this.removeEventListener(eventType, callback);
278     if (!this.listeners[eventType])
279       this.listeners[eventType] = [];
280     this.listeners[eventType].push({callback: callback, capture: !!capture});
281   },
283   // TODO(dtseng/aboxhall): Check this impl against spec.
284   removeEventListener: function(eventType, callback) {
285     if (this.listeners[eventType]) {
286       var listeners = this.listeners[eventType];
287       for (var i = 0; i < listeners.length; i++) {
288         if (callback === listeners[i].callback)
289           listeners.splice(i, 1);
290       }
291     }
292   },
294   toJSON: function() {
295     return { treeID: this.treeID,
296              id: this.id,
297              role: this.role,
298              attributes: this.attributes };
299   },
301   dispatchEvent: function(eventType) {
302     var path = [];
303     var parent = this.parent;
304     while (parent) {
305       path.push(parent);
306       parent = parent.parent;
307     }
308     var event = new AutomationEvent(eventType, this.wrapper);
310     // Dispatch the event through the propagation path in three phases:
311     // - capturing: starting from the root and going down to the target's parent
312     // - targeting: dispatching the event on the target itself
313     // - bubbling: starting from the target's parent, going back up to the root.
314     // At any stage, a listener may call stopPropagation() on the event, which
315     // will immediately stop event propagation through this path.
316     if (this.dispatchEventAtCapturing_(event, path)) {
317       if (this.dispatchEventAtTargeting_(event, path))
318         this.dispatchEventAtBubbling_(event, path);
319     }
320   },
322   toString: function() {
323     var impl = privates(this).impl;
324     if (!impl)
325       impl = this;
327     var parentID = GetParentID(this.treeID, this.id);
328     var count = GetChildCount(this.treeID, this.id);
329     var childIDs = [];
330     for (var i = 0; i < count; ++i) {
331       var childID = GetChildIDAtIndex(this.treeID, this.id, i);
332       childIDs.push(childID);
333     }
335     return 'node id=' + impl.id +
336         ' role=' + this.role +
337         ' state=' + $JSON.stringify(this.state) +
338         ' parentID=' + parentID +
339         ' childIds=' + $JSON.stringify(childIDs);
340   },
342   dispatchEventAtCapturing_: function(event, path) {
343     privates(event).impl.eventPhase = Event.CAPTURING_PHASE;
344     for (var i = path.length - 1; i >= 0; i--) {
345       this.fireEventListeners_(path[i], event);
346       if (privates(event).impl.propagationStopped)
347         return false;
348     }
349     return true;
350   },
352   dispatchEventAtTargeting_: function(event) {
353     privates(event).impl.eventPhase = Event.AT_TARGET;
354     this.fireEventListeners_(this.wrapper, event);
355     return !privates(event).impl.propagationStopped;
356   },
358   dispatchEventAtBubbling_: function(event, path) {
359     privates(event).impl.eventPhase = Event.BUBBLING_PHASE;
360     for (var i = 0; i < path.length; i++) {
361       this.fireEventListeners_(path[i], event);
362       if (privates(event).impl.propagationStopped)
363         return false;
364     }
365     return true;
366   },
368   fireEventListeners_: function(node, event) {
369     var nodeImpl = privates(node).impl;
370     var listeners = nodeImpl.listeners[event.type];
371     if (!listeners)
372       return;
373     var eventPhase = event.eventPhase;
374     for (var i = 0; i < listeners.length; i++) {
375       if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture)
376         continue;
377       if (eventPhase == Event.BUBBLING_PHASE && listeners[i].capture)
378         continue;
380       try {
381         listeners[i].callback(event);
382       } catch (e) {
383         logging.WARNING('Error in event handler for ' + event.type +
384                         ' during phase ' + eventPhase + ': ' +
385                         e.message + '\nStack trace: ' + e.stack);
386       }
387     }
388   },
390   performAction_: function(actionType, opt_args) {
391     // Not yet initialized.
392     if (this.rootImpl.treeID === undefined ||
393         this.id === undefined) {
394       return;
395     }
397     // Check permissions.
398     if (!IsInteractPermitted()) {
399       throw new Error(actionType + ' requires {"desktop": true} or' +
400           ' {"interact": true} in the "automation" manifest key.');
401     }
403     automationInternal.performAction({ treeID: this.rootImpl.treeID,
404                                        automationNodeID: this.id,
405                                        actionType: actionType },
406                                      opt_args || {});
407   },
409   domQuerySelectorCallback_: function(userCallback, resultAutomationNodeID) {
410     // resultAutomationNodeID could be zero or undefined or (unlikely) null;
411     // they all amount to the same thing here, which is that no node was
412     // returned.
413     if (!resultAutomationNodeID) {
414       userCallback(null);
415       return;
416     }
417     var resultNode = this.rootImpl.get(resultAutomationNodeID);
418     if (!resultNode) {
419       logging.WARNING('Query selector result not in tree: ' +
420                       resultAutomationNodeID);
421       userCallback(null);
422     }
423     userCallback(resultNode);
424   },
426   findInternal_: function(params, opt_results) {
427     var result = null;
428     this.forAllDescendants_(function(node) {
429       if (privates(node).impl.matchInternal_(params)) {
430         if (opt_results)
431           opt_results.push(node);
432         else
433           result = node;
434         return !opt_results;
435       }
436     });
437     if (opt_results)
438       return opt_results;
439     return result;
440   },
442   /**
443    * Executes a closure for all of this node's descendants, in pre-order.
444    * Early-outs if the closure returns true.
445    * @param {Function(AutomationNode):boolean} closure Closure to be executed
446    *     for each node. Return true to early-out the traversal.
447    */
448   forAllDescendants_: function(closure) {
449     var stack = this.wrapper.children.reverse();
450     while (stack.length > 0) {
451       var node = stack.pop();
452       if (closure(node))
453         return;
455       var children = node.children;
456       for (var i = children.length - 1; i >= 0; i--)
457         stack.push(children[i]);
458     }
459   },
461   matchInternal_: function(params) {
462     if (Object.keys(params).length == 0)
463       return false;
465     if ('role' in params && this.role != params.role)
466         return false;
468     if ('state' in params) {
469       for (var state in params.state) {
470         if (params.state[state] != (state in this.state))
471           return false;
472       }
473     }
474     if ('attributes' in params) {
475       for (var attribute in params.attributes) {
476         var attrValue = params.attributes[attribute];
477         if (typeof attrValue != 'object') {
478           if (this[attribute] !== attrValue)
479             return false;
480         } else if (attrValue instanceof RegExp) {
481           if (typeof this[attribute] != 'string')
482             return false;
483           if (!attrValue.test(this[attribute]))
484             return false;
485         } else {
486           // TODO(aboxhall): handle intlist case.
487           return false;
488         }
489       }
490     }
491     return true;
492   }
495 var stringAttributes = [
496     'accessKey',
497     'action',
498     'ariaInvalidValue',
499     'autoComplete',
500     'containerLiveRelevant',
501     'containerLiveStatus',
502     'description',
503     'display',
504     'docDoctype',
505     'docMimetype',
506     'docTitle',
507     'docUrl',
508     'dropeffect',
509     'help',
510     'htmlTag',
511     'liveRelevant',
512     'liveStatus',
513     'name',
514     'placeholder',
515     'shortcut',
516     'textInputType',
517     'url',
518     'value'];
520 var boolAttributes = [
521     'ariaReadonly',
522     'buttonMixed',
523     'canSetValue',
524     'canvasHasFallback',
525     'containerLiveAtomic',
526     'containerLiveBusy',
527     'docLoaded',
528     'grabbed',
529     'isAxTreeHost',
530     'liveAtomic',
531     'liveBusy',
532     'updateLocationOnly'];
534 var intAttributes = [
535     'anchorOffset',
536     'backgroundColor',
537     'color',
538     'colorValue',
539     'focusOffset',
540     'hierarchicalLevel',
541     'invalidState',
542     'posInSet',
543     'scrollX',
544     'scrollXMax',
545     'scrollXMin',
546     'scrollY',
547     'scrollYMax',
548     'scrollYMin',
549     'setSize',
550     'sortDirection',
551     'tableCellColumnIndex',
552     'tableCellColumnSpan',
553     'tableCellRowIndex',
554     'tableCellRowSpan',
555     'tableColumnCount',
556     'tableColumnIndex',
557     'tableRowCount',
558     'tableRowIndex',
559     'textDirection',
560     'textSelEnd',
561     'textSelStart',
562     'textStyle'];
564 var nodeRefAttributes = [
565     ['activedescendantId', 'activedescendant'],
566     ['anchorObjectId', 'anchorObject'],
567     ['focusObjectId', 'focusObject'],
568     ['tableColumnHeaderId', 'tableColumnHeader'],
569     ['tableHeaderId', 'tableHeader'],
570     ['tableRowHeaderId', 'tableRowHeader'],
571     ['titleUiElement', 'titleUIElement']];
573 var intListAttributes = [
574     'characterOffsets',
575     'lineBreaks',
576     'wordEnds',
577     'wordStarts'];
579 var nodeRefListAttributes = [
580     ['cellIds', 'cells'],
581     ['controlsIds', 'controls'],
582     ['describedbyIds', 'describedBy'],
583     ['flowtoIds', 'flowTo'],
584     ['labelledbyIds', 'labelledBy'],
585     ['uniqueCellIds', 'uniqueCells']];
587 var floatAttributes = [
588     'docLoadingProgress',
589     'valueForRange',
590     'minValueForRange',
591     'maxValueForRange',
592     'fontSize'];
594 var htmlAttributes = [
595     ['type', 'inputType']];
597 var publicAttributes = [];
599 stringAttributes.forEach(function (attributeName) {
600   publicAttributes.push(attributeName);
601   Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
602     get: function() {
603       return GetStringAttribute(this.treeID, this.id, attributeName);
604     }
605   });
608 boolAttributes.forEach(function (attributeName) {
609   publicAttributes.push(attributeName);
610   Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
611     get: function() {
612       return GetBoolAttribute(this.treeID, this.id, attributeName);
613     }
614   });
617 intAttributes.forEach(function (attributeName) {
618   publicAttributes.push(attributeName);
619   Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
620     get: function() {
621       return GetIntAttribute(this.treeID, this.id, attributeName);
622     }
623   });
626 nodeRefAttributes.forEach(function (params) {
627   var srcAttributeName = params[0];
628   var dstAttributeName = params[1];
629   publicAttributes.push(dstAttributeName);
630   Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
631     get: function() {
632       var id = GetIntAttribute(this.treeID, this.id, srcAttributeName);
633       if (id)
634         return this.rootImpl.get(id);
635       else
636         return undefined;
637     }
638   });
641 intListAttributes.forEach(function (attributeName) {
642   publicAttributes.push(attributeName);
643   Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
644     get: function() {
645       return GetIntListAttribute(this.treeID, this.id, attributeName);
646     }
647   });
650 nodeRefListAttributes.forEach(function (params) {
651   var srcAttributeName = params[0];
652   var dstAttributeName = params[1];
653   publicAttributes.push(dstAttributeName);
654   Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
655     get: function() {
656       var ids = GetIntListAttribute(this.treeID, this.id, srcAttributeName);
657       if (!ids)
658         return undefined;
659       var result = [];
660       for (var i = 0; i < ids.length; ++i) {
661         var node = this.rootImpl.get(ids[i]);
662         if (node)
663           result.push(node);
664       }
665       return result;
666     }
667   });
670 floatAttributes.forEach(function (attributeName) {
671   publicAttributes.push(attributeName);
672   Object.defineProperty(AutomationNodeImpl.prototype, attributeName, {
673     get: function() {
674       return GetFloatAttribute(this.treeID, this.id, attributeName);
675     }
676   });
679 htmlAttributes.forEach(function (params) {
680   var srcAttributeName = params[0];
681   var dstAttributeName = params[1];
682   publicAttributes.push(dstAttributeName);
683   Object.defineProperty(AutomationNodeImpl.prototype, dstAttributeName, {
684     get: function() {
685       return GetHtmlAttribute(this.treeID, this.id, srcAttributeName);
686     }
687   });
691  * AutomationRootNode.
693  * An AutomationRootNode is the javascript end of an AXTree living in the
694  * browser. AutomationRootNode handles unserializing incremental updates from
695  * the source AXTree. Each update contains node data that form a complete tree
696  * after applying the update.
698  * A brief note about ids used through this class. The source AXTree assigns
699  * unique ids per node and we use these ids to build a hash to the actual
700  * AutomationNode object.
701  * Thus, tree traversals amount to a lookup in our hash.
703  * The tree itself is identified by the accessibility tree id of the
704  * renderer widget host.
705  * @constructor
706  */
707 function AutomationRootNodeImpl(treeID) {
708   AutomationNodeImpl.call(this, this);
709   this.treeID = treeID;
710   this.axNodeDataCache_ = {};
713 AutomationRootNodeImpl.idToAutomationRootNode_ = {};
715 AutomationRootNodeImpl.get = function(treeID) {
716   var result = AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
717   return result || undefined;
720 AutomationRootNodeImpl.getOrCreate = function(treeID) {
721   if (AutomationRootNodeImpl.idToAutomationRootNode_[treeID])
722     return AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
723   var result = new AutomationRootNode(treeID);
724   AutomationRootNodeImpl.idToAutomationRootNode_[treeID] = result;
725   return result;
728 AutomationRootNodeImpl.destroy = function(treeID) {
729   delete AutomationRootNodeImpl.idToAutomationRootNode_[treeID];
732 AutomationRootNodeImpl.prototype = {
733   __proto__: AutomationNodeImpl.prototype,
735   /**
736    * @type {boolean}
737    */
738   isRootNode: true,
740   /**
741    * @type {number}
742    */
743   treeID: -1,
745   /**
746    * The parent of this node from a different tree.
747    * @type {?AutomationNode}
748    * @private
749    */
750   hostNode_: null,
752   /**
753    * A map from id to AutomationNode.
754    * @type {Object.<number, AutomationNode>}
755    * @private
756    */
757   axNodeDataCache_: null,
759   get id() {
760     var result = GetRootID(this.treeID);
762     // Don't return undefined, because the id is often passed directly
763     // as an argument to a native binding that expects only a valid number.
764     if (result === undefined)
765       return -1;
767     return result;
768   },
770   get: function(id) {
771     if (id == undefined)
772       return undefined;
774     if (id == this.id)
775       return this.wrapper;
777     var obj = this.axNodeDataCache_[id];
778     if (obj)
779       return obj;
781     obj = new AutomationNode(this);
782     privates(obj).impl.treeID = this.treeID;
783     privates(obj).impl.id = id;
784     this.axNodeDataCache_[id] = obj;
786     return obj;
787   },
789   remove: function(id) {
790     delete this.axNodeDataCache_[id];
791   },
793   destroy: function() {
794     this.dispatchEvent(schema.EventType.destroyed);
795   },
797   setHostNode(hostNode) {
798     this.hostNode_ = hostNode;
799   },
801   onAccessibilityEvent: function(eventParams) {
802     var targetNode = this.get(eventParams.targetID);
803     if (targetNode) {
804       var targetNodeImpl = privates(targetNode).impl;
805       targetNodeImpl.dispatchEvent(eventParams.eventType);
806     } else {
807       logging.WARNING('Got ' + eventParams.eventType +
808                       ' event on unknown node: ' + eventParams.targetID +
809                       '; this: ' + this.id);
810     }
811     return true;
812   },
814   toString: function() {
815     function toStringInternal(node, indent) {
816       if (!node)
817         return '';
818       var output =
819           new Array(indent).join(' ') +
820           AutomationNodeImpl.prototype.toString.call(node) +
821           '\n';
822       indent += 2;
823       for (var i = 0; i < node.children.length; i++)
824         output += toStringInternal(node.children[i], indent);
825       return output;
826     }
827     return toStringInternal(this, 0);
828   },
831 var AutomationNode = utils.expose('AutomationNode',
832                                   AutomationNodeImpl,
833                                   { functions: ['doDefault',
834                                                 'find',
835                                                 'findAll',
836                                                 'focus',
837                                                 'makeVisible',
838                                                 'matches',
839                                                 'setSelection',
840                                                 'showContextMenu',
841                                                 'addEventListener',
842                                                 'removeEventListener',
843                                                 'domQuerySelector',
844                                                 'toString' ],
845                                     readonly: publicAttributes.concat(
846                                               ['parent',
847                                                'firstChild',
848                                                'lastChild',
849                                                'children',
850                                                'previousSibling',
851                                                'nextSibling',
852                                                'isRootNode',
853                                                'role',
854                                                'state',
855                                                'location',
856                                                'indexInParent',
857                                                'root']) });
859 var AutomationRootNode = utils.expose('AutomationRootNode',
860                                       AutomationRootNodeImpl,
861                                       { superclass: AutomationNode });
863 AutomationRootNode.get = function(treeID) {
864   return AutomationRootNodeImpl.get(treeID);
867 AutomationRootNode.getOrCreate = function(treeID) {
868   return AutomationRootNodeImpl.getOrCreate(treeID);
871 AutomationRootNode.destroy = function(treeID) {
872   AutomationRootNodeImpl.destroy(treeID);
875 exports.AutomationNode = AutomationNode;
876 exports.AutomationRootNode = AutomationRootNode;