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 utils = require('utils');
11 * A single node in the Automation tree.
12 * @param {AutomationTree} owner The owning tree.
15 var AutomationNodeImpl = function(owner) {
20 this.location = { left: 0, top: 0, width: 0, height: 0 };
23 AutomationNodeImpl.prototype = {
27 state: { busy: true },
30 return this.owner.get(this.parentID);
33 firstChild: function() {
34 var node = this.owner.get(this.childIds[0]);
38 lastChild: function() {
39 var childIds = this.childIds;
40 var node = this.owner.get(childIds[childIds.length - 1]);
44 children: function() {
46 for (var i = 0, childID; childID = this.childIds[i]; i++)
47 children.push(this.owner.get(childID));
51 previousSibling: function() {
52 var parent = this.parent();
53 if (parent && this.indexInParent > 0)
54 return parent.children()[this.indexInParent - 1];
58 nextSibling: function() {
59 var parent = this.parent();
60 if (parent && this.indexInParent < parent.children().length)
61 return parent.children()[this.indexInParent + 1];
65 doDefault: function() {
66 this.performAction_('doDefault');
70 this.performAction_('focus');
73 makeVisible: function() {
74 this.performAction_('makeVisible');
77 setSelection: function(startIndex, endIndex) {
78 this.performAction_('setSelection',
79 {startIndex: startIndex, endIndex: endIndex});
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});
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);
100 dispatchEvent: function(eventType) {
102 var parent = this.parent();
105 // TODO(aboxhall/dtseng): handle unloaded parent node
106 parent = parent.parent();
108 path.push(this.owner.wrapper);
109 var event = new AutomationEvent(eventType, this.wrapper);
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);
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);
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)
141 dispatchEventAtTargeting_: function(event) {
142 privates(event).impl.eventPhase = Event.AT_TARGET;
143 this.fireEventListeners_(this.wrapper, event);
144 return !privates(event).impl.propagationStopped;
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)
157 fireEventListeners_: function(node, event) {
158 var nodeImpl = privates(node).impl;
159 var listeners = nodeImpl.listeners[event.type];
163 var eventPhase = event.eventPhase;
164 for (var i = 0; i < listeners.length; i++) {
165 if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture)
167 if (eventPhase == Event.BUBBLING_PHASE && listeners[i].capture)
171 listeners[i].callback(event);
173 console.error('Error in event handler for ' + event.type +
174 'during phase ' + eventPhase + ': ' +
175 e.message + '\nStack trace: ' + e.stack);
180 performAction_: function(actionType, opt_args) {
181 // Not yet initialized.
182 if (!this.owner.processID ||
183 !this.owner.routingID ||
186 automationInternal.performAction({processID: this.owner.processID,
187 routingID: this.owner.routingID,
188 automationNodeID: this.wrapper.id,
189 actionType: actionType},
194 var AutomationNode = utils.expose('AutomationNode',
196 { functions: ['parent',
207 'removeEventListener'],
215 exports.AutomationNode = AutomationNode;