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 // Custom bindings for the automation API.
6 var AutomationNode
= require('automationNode').AutomationNode
;
7 var AutomationRootNode
= require('automationNode').AutomationRootNode
;
8 var automation
= require('binding').Binding
.create('automation');
9 var automationInternal
=
10 require('binding').Binding
.create('automationInternal').generate();
11 var eventBindings
= require('event_bindings');
12 var Event
= eventBindings
.Event
;
13 var forEach
= require('utils').forEach
;
14 var lastError
= require('lastError');
15 var logging
= requireNative('logging');
16 var schema
= requireNative('automationInternal').GetSchemaAdditions();
19 * A namespace to export utility functions to other files in automation.
21 window
.automationUtil = function() {};
23 // TODO(aboxhall): Look into using WeakMap
24 var idToAutomationRootNode
= {};
25 var idToCallback
= {};
27 var DESKTOP_TREE_ID
= 0;
29 automationUtil
.storeTreeCallback = function(id
, callback
) {
33 var targetTree
= idToAutomationRootNode
[id
];
35 // If we haven't cached the tree, hold the callback until the tree is
36 // populated by the initial onAccessibilityEvent call.
37 if (id
in idToCallback
)
38 idToCallback
[id
].push(callback
);
40 idToCallback
[id
] = [callback
];
47 * Global list of tree change observers.
48 * @type {Array<TreeChangeObserver>}
50 automationUtil
.treeChangeObservers
= [];
52 automation
.registerCustomHook(function(bindingsAPI
) {
53 var apiFunctions
= bindingsAPI
.apiFunctions
;
55 // TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj.
56 apiFunctions
.setHandleRequest('getTree', function getTree(tabId
, callback
) {
57 // enableTab() ensures the renderer for the active or specified tab has
58 // accessibility enabled, and fetches its ax tree id to use as
59 // a key in the idToAutomationRootNode map. The callback to
60 // enableTab is bound to the callback passed in to getTree(), so that once
61 // the tree is available (either due to having been cached earlier, or after
62 // an accessibility event occurs which causes the tree to be populated), the
63 // callback can be called.
64 automationInternal
.enableTab(tabId
, function onEnable(id
) {
65 if (lastError
.hasError(chrome
)) {
69 automationUtil
.storeTreeCallback(id
, callback
);
73 var desktopTree
= null;
74 apiFunctions
.setHandleRequest('getDesktop', function(callback
) {
76 idToAutomationRootNode
[DESKTOP_TREE_ID
];
78 if (DESKTOP_TREE_ID
in idToCallback
)
79 idToCallback
[DESKTOP_TREE_ID
].push(callback
);
81 idToCallback
[DESKTOP_TREE_ID
] = [callback
];
83 // TODO(dtseng): Disable desktop tree once desktop object goes out of
85 automationInternal
.enableDesktop(function() {
86 if (lastError
.hasError(chrome
)) {
87 delete idToAutomationRootNode
[
94 callback(desktopTree
);
98 function removeTreeChangeObserver(observer
) {
99 var observers
= automationUtil
.treeChangeObservers
;
100 for (var i
= 0; i
< observers
.length
; i
++) {
101 if (observer
== observers
[i
])
102 observers
.splice(i
, 1);
105 apiFunctions
.setHandleRequest('removeTreeChangeObserver', function(observer
) {
106 removeTreeChangeObserver(observer
);
109 function addTreeChangeObserver(observer
) {
110 removeTreeChangeObserver(observer
);
111 automationUtil
.treeChangeObservers
.push(observer
);
113 apiFunctions
.setHandleRequest('addTreeChangeObserver', function(observer
) {
114 addTreeChangeObserver(observer
);
119 // Listen to the automationInternal.onAccessibilityEvent event, which is
120 // essentially a proxy for the AccessibilityHostMsg_Events IPC from the
122 automationInternal
.onAccessibilityEvent
.addListener(function(data
) {
123 var id
= data
.treeID
;
124 var targetTree
= idToAutomationRootNode
[id
];
126 // If this is the first time we've gotten data for this tree, it will
127 // contain all of the tree's data, so create a new tree which will be
128 // bootstrapped from |data|.
129 targetTree
= new AutomationRootNode(id
);
130 idToAutomationRootNode
[id
] = targetTree
;
132 if (!privates(targetTree
).impl
.onAccessibilityEvent(data
))
135 // If we're not waiting on a callback to getTree(), we can early out here.
136 if (!(id
in idToCallback
))
139 // We usually get a 'placeholder' tree first, which doesn't have any url
140 // attribute or child nodes. If we've got that, wait for the full tree before
141 // calling the callback.
142 // TODO(dmazzoni): Don't send down placeholder (crbug.com/397553)
143 if (id
!= DESKTOP_TREE_ID
&& !targetTree
.attributes
.url
&&
144 targetTree
.children
.length
== 0) {
148 // If the tree wasn't available when getTree() was called, the callback will
149 // have been cached in idToCallback, so call and delete it now that we
150 // have the complete tree.
151 for (var i
= 0; i
< idToCallback
[id
].length
; i
++) {
152 console
.log('calling getTree() callback');
153 var callback
= idToCallback
[id
][i
];
154 callback(targetTree
);
156 delete idToCallback
[id
];
159 automationInternal
.onAccessibilityTreeDestroyed
.addListener(function(id
) {
160 var targetTree
= idToAutomationRootNode
[id
];
162 privates(targetTree
).impl
.destroy();
163 delete idToAutomationRootNode
[id
];
165 logging
.WARNING('no targetTree to destroy');
167 delete idToAutomationRootNode
[id
];
170 exports
.binding
= automation
.generate();
172 // Add additional accessibility bindings not specified in the automation IDL.
173 // Accessibility and automation share some APIs (see
174 // ui/accessibility/ax_enums.idl).
175 forEach(schema
, function(k
, v
) {
176 exports
.binding
[k
] = v
;