// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+var AutomationEvent = require('automationEvent').AutomationEvent;
+var automationInternal =
+ require('binding').Binding.create('automationInternal').generate();
var utils = require('utils');
/**
*/
var AutomationNodeImpl = function(owner) {
this.owner = owner;
- this.child_ids = [];
-
+ this.childIds = [];
this.attributes = {};
+ this.listeners = {};
+ this.location = { left: 0, top: 0, width: 0, height: 0 };
};
AutomationNodeImpl.prototype = {
+ id: -1,
+ role: '',
+ loaded: false,
+ state: { busy: true },
+
parent: function() {
- return this.owner.get(this.parent_id);
+ return this.owner.get(this.parentID);
},
firstChild: function() {
- var node = this.owner.get(this.child_ids[0]);
+ var node = this.owner.get(this.childIds[0]);
return node;
},
lastChild: function() {
- var child_ids = this.child_ids;
- var node = this.owner.get(child_ids[child_ids.length - 1]);
+ var childIds = this.childIds;
+ var node = this.owner.get(childIds[childIds.length - 1]);
return node;
},
children: function() {
var children = [];
- for (var i = 0, child_id; child_id = this.child_ids[i]; i++)
- children.push(this.owner.get(child_id));
+ for (var i = 0, childID; childID = this.childIds[i]; i++)
+ children.push(this.owner.get(childID));
return children;
},
previousSibling: function() {
var parent = this.parent();
- if (parent && this.index_in_parent > 0)
- return parent.children()[this.index_in_parent - 1];
+ if (parent && this.indexInParent > 0)
+ return parent.children()[this.indexInParent - 1];
return undefined;
},
nextSibling: function() {
var parent = this.parent();
- if (parent && this.index_in_parent < parent.children().length)
- return parent.children()[this.index_in_parent + 1];
+ if (parent && this.indexInParent < parent.children().length)
+ return parent.children()[this.indexInParent + 1];
return undefined;
},
-};
+ doDefault: function() {
+ this.performAction_('doDefault');
+ },
+
+ focus: function() {
+ this.performAction_('focus');
+ },
+
+ makeVisible: function() {
+ this.performAction_('makeVisible');
+ },
+
+ setSelection: function(startIndex, endIndex) {
+ this.performAction_('setSelection',
+ {startIndex: startIndex, endIndex: endIndex});
+ },
+
+ addEventListener: function(eventType, callback, capture) {
+ this.removeEventListener(eventType, callback);
+ if (!this.listeners[eventType])
+ this.listeners[eventType] = [];
+ this.listeners[eventType].push({callback: callback, capture: capture});
+ },
+
+ // TODO(dtseng/aboxhall): Check this impl against spec.
+ removeEventListener: function(eventType, callback) {
+ if (this.listeners[eventType]) {
+ var listeners = this.listeners[eventType];
+ for (var i = 0; i < listeners.length; i++) {
+ if (callback === listeners[i].callback)
+ listeners.splice(i, 1);
+ }
+ }
+ },
+
+ dispatchEvent: function(eventType) {
+ var path = [];
+ var parent = this.parent();
+ while (parent) {
+ path.push(parent);
+ // TODO(aboxhall/dtseng): handle unloaded parent node
+ parent = parent.parent();
+ }
+ path.push(this.owner.wrapper);
+ var event = new AutomationEvent(eventType, this.wrapper);
+
+ // Dispatch the event through the propagation path in three phases:
+ // - capturing: starting from the root and going down to the target's parent
+ // - targeting: dispatching the event on the target itself
+ // - bubbling: starting from the target's parent, going back up to the root.
+ // At any stage, a listener may call stopPropagation() on the event, which
+ // will immediately stop event propagation through this path.
+ if (this.dispatchEventAtCapturing_(event, path)) {
+ if (this.dispatchEventAtTargeting_(event, path))
+ this.dispatchEventAtBubbling_(event, path);
+ }
+ },
+
+ toString: function() {
+ return 'node id=' + this.id +
+ ' role=' + this.role +
+ ' state=' + JSON.stringify(this.state) +
+ ' childIds=' + JSON.stringify(this.childIds) +
+ ' attributes=' + JSON.stringify(this.attributes);
+ },
+
+ dispatchEventAtCapturing_: function(event, path) {
+ privates(event).impl.eventPhase = Event.CAPTURING_PHASE;
+ for (var i = path.length - 1; i >= 0; i--) {
+ this.fireEventListeners_(path[i], event);
+ if (privates(event).impl.propagationStopped)
+ return false;
+ }
+ return true;
+ },
+
+ dispatchEventAtTargeting_: function(event) {
+ privates(event).impl.eventPhase = Event.AT_TARGET;
+ this.fireEventListeners_(this.wrapper, event);
+ return !privates(event).impl.propagationStopped;
+ },
+
+ dispatchEventAtBubbling_: function(event, path) {
+ privates(event).impl.eventPhase = Event.BUBBLING_PHASE;
+ for (var i = 0; i < path.length; i++) {
+ this.fireEventListeners_(path[i], event);
+ if (privates(event).impl.propagationStopped)
+ return false;
+ }
+ return true;
+ },
+
+ fireEventListeners_: function(node, event) {
+ var nodeImpl = privates(node).impl;
+ var listeners = nodeImpl.listeners[event.type];
+ if (!listeners)
+ return;
+
+ var eventPhase = event.eventPhase;
+ for (var i = 0; i < listeners.length; i++) {
+ if (eventPhase == Event.CAPTURING_PHASE && !listeners[i].capture)
+ continue;
+ if (eventPhase == Event.BUBBLING_PHASE && listeners[i].capture)
+ continue;
+
+ try {
+ listeners[i].callback(event);
+ } catch (e) {
+ console.error('Error in event handler for ' + event.type +
+ 'during phase ' + eventPhase + ': ' +
+ e.message + '\nStack trace: ' + e.stack);
+ }
+ }
+ },
+
+ performAction_: function(actionType, opt_args) {
+ // Not yet initialized.
+ if (!this.owner.processID ||
+ !this.owner.routingID ||
+ !this.wrapper.id)
+ return;
+ automationInternal.performAction({processID: this.owner.processID,
+ routingID: this.owner.routingID,
+ automationNodeID: this.wrapper.id,
+ actionType: actionType},
+ opt_args || {});
+ }
+};
var AutomationNode = utils.expose('AutomationNode',
AutomationNodeImpl,
- ['parent',
- 'firstChild',
- 'lastChild',
- 'children',
- 'previousSibling',
- 'nextSibling']);
+ { functions: ['parent',
+ 'firstChild',
+ 'lastChild',
+ 'children',
+ 'previousSibling',
+ 'nextSibling',
+ 'doDefault',
+ 'focus',
+ 'makeVisible',
+ 'setSelection',
+ 'addEventListener',
+ 'removeEventListener'],
+ readonly: ['id',
+ 'role',
+ 'state',
+ 'location',
+ 'attributes',
+ 'loaded'] });
+
exports.AutomationNode = AutomationNode;