Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / renderer / resources / extensions / automation / automation_node.js
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.
4
5 var AutomationEvent = require('automationEvent').AutomationEvent;
6 var automationInternal =
7     require('binding').Binding.create('automationInternal').generate();
8 var utils = require('utils');
9
10 /**
11  * A single node in the Automation tree.
12  * @param {AutomationTree} owner The owning tree.
13  * @constructor
14  */
15 var AutomationNodeImpl = function(owner) {
16   this.owner = owner;
17   this.childIds = [];
18   this.attributes = {};
19   this.listeners = {};
20   this.location = { left: 0, top: 0, width: 0, height: 0 };
21 };
22
23 AutomationNodeImpl.prototype = {
24   id: -1,
25   role: '',
26   loaded: false,
27   state: { busy: true },
28
29   parent: function() {
30     return this.owner.get(this.parentID);
31   },
32
33   firstChild: function() {
34     var node = this.owner.get(this.childIds[0]);
35     return node;
36   },
37
38   lastChild: function() {
39     var childIds = this.childIds;
40     var node = this.owner.get(childIds[childIds.length - 1]);
41     return node;
42   },
43
44   children: function() {
45     var children = [];
46     for (var i = 0, childID; childID = this.childIds[i]; i++)
47       children.push(this.owner.get(childID));
48     return children;
49   },
50
51   previousSibling: function() {
52     var parent = this.parent();
53     if (parent && this.indexInParent > 0)
54       return parent.children()[this.indexInParent - 1];
55     return undefined;
56   },
57
58   nextSibling: function() {
59     var parent = this.parent();
60     if (parent && this.indexInParent < parent.children().length)
61       return parent.children()[this.indexInParent + 1];
62     return undefined;
63   },
64
65   doDefault: function() {
66     this.performAction_('doDefault');
67   },
68
69   focus: function() {
70     this.performAction_('focus');
71   },
72
73   makeVisible: function() {
74     this.performAction_('makeVisible');
75   },
76
77   setSelection: function(startIndex, endIndex) {
78     this.performAction_('setSelection',
79                         {startIndex: startIndex, endIndex: endIndex});
80   },
81
82   addEventListener: function(eventType, callback, capture) {
83     this.removeEventListener(eventType, callback);
84     if (!this.listeners[eventType])
85       this.listeners[eventType] = [];
86     this.listeners[eventType].push({callback: callback, capture: capture});
87   },
88
89   // TODO(dtseng/aboxhall): Check this impl against spec.
90   removeEventListener: function(eventType, callback) {
91     if (this.listeners[eventType]) {
92       var listeners = this.listeners[eventType];
93       for (var i = 0; i < listeners.length; i++) {
94         if (callback === listeners[i].callback)
95           listeners.splice(i, 1);
96       }
97     }
98   },
99
100   dispatchEvent: function(eventType) {
101     var path = [];
102     var parent = this.parent();
103     while (parent) {
104       path.push(parent);
105       // TODO(aboxhall/dtseng): handle unloaded parent node
106       parent = parent.parent();
107     }
108     path.push(this.owner.wrapper);
109     var event = new AutomationEvent(eventType, this.wrapper);
110
111     // Dispatch the event through the propagation path in three phases:
112     // - capturing: starting from the root and going down to the target's parent
113     // - targeting: dispatching the event on the target itself
114     // - bubbling: starting from the target's parent, going back up to the root.
115     // At any stage, a listener may call stopPropagation() on the event, which
116     // will immediately stop event propagation through this path.
117     if (this.dispatchEventAtCapturing_(event, path)) {
118       if (this.dispatchEventAtTargeting_(event, path))
119         this.dispatchEventAtBubbling_(event, path);
120     }
121   },
122
123   toString: function() {
124     return 'node id=' + this.id +
125         ' role=' + this.role +
126         ' state=' + JSON.stringify(this.state) +
127         ' childIds=' + JSON.stringify(this.childIds) +
128         ' attributes=' + JSON.stringify(this.attributes);
129   },
130
131   dispatchEventAtCapturing_: function(event, path) {
132     privates(event).impl.eventPhase = Event.CAPTURING_PHASE;
133     for (var i = path.length - 1; i >= 0; i--) {
134       this.fireEventListeners_(path[i], event);
135       if (privates(event).impl.propagationStopped)
136         return false;
137     }
138     return true;
139   },
140
141   dispatchEventAtTargeting_: function(event) {
142     privates(event).impl.eventPhase = Event.AT_TARGET;
143     this.fireEventListeners_(this.wrapper, event);
144     return !privates(event).impl.propagationStopped;
145   },
146
147   dispatchEventAtBubbling_: function(event, path) {
148     privates(event).impl.eventPhase = Event.BUBBLING_PHASE;
149     for (var i = 0; i < path.length; i++) {
150       this.fireEventListeners_(path[i], event);
151       if (privates(event).impl.propagationStopped)
152         return false;
153     }
154     return true;
155   },
156
157   fireEventListeners_: function(node, event) {
158     var nodeImpl = privates(node).impl;
159     var listeners = nodeImpl.listeners[event.type];
160     if (!listeners)
161       return;
162
163     var eventPhase = event.eventPhase;
164     for (var i = 0; i < listeners.length; i++) {
165       if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture)
166         continue;
167       if (eventPhase == Event.BUBBLING_PHASE && listeners[i].capture)
168         continue;
169
170       try {
171         listeners[i].callback(event);
172       } catch (e) {
173         console.error('Error in event handler for ' + event.type +
174                       'during phase ' + eventPhase + ': ' +
175                       e.message + '\nStack trace: ' + e.stack);
176       }
177     }
178   },
179
180   performAction_: function(actionType, opt_args) {
181     // Not yet initialized.
182     if (!this.owner.processID ||
183         !this.owner.routingID ||
184         !this.wrapper.id)
185       return;
186     automationInternal.performAction({processID: this.owner.processID,
187                                       routingID: this.owner.routingID,
188                                       automationNodeID: this.wrapper.id,
189                                       actionType: actionType},
190                                      opt_args || {});
191   }
192 };
193
194 var AutomationNode = utils.expose('AutomationNode',
195                                   AutomationNodeImpl,
196                                   { functions: ['parent',
197                                                 'firstChild',
198                                                 'lastChild',
199                                                 'children',
200                                                 'previousSibling',
201                                                 'nextSibling',
202                                                 'doDefault',
203                                                 'focus',
204                                                 'makeVisible',
205                                                 'setSelection',
206                                                 'addEventListener',
207                                                 'removeEventListener'],
208                                     readonly: ['id',
209                                                'role',
210                                                'state',
211                                                'location',
212                                                'attributes',
213                                                'loaded'] });
214
215 exports.AutomationNode = AutomationNode;