OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // Custom bindings for the automation API. | 5 // Custom bindings for the automation API. |
6 var AutomationNode = require('automationNode').AutomationNode; | 6 var AutomationNode = require('automationNode').AutomationNode; |
7 var AutomationRootNode = require('automationNode').AutomationRootNode; | 7 var AutomationRootNode = require('automationNode').AutomationRootNode; |
8 var automation = require('binding').Binding.create('automation'); | 8 var automation = require('binding').Binding.create('automation'); |
9 var automationInternal = | 9 var automationInternal = |
10 require('binding').Binding.create('automationInternal').generate(); | 10 require('binding').Binding.create('automationInternal').generate(); |
11 var eventBindings = require('event_bindings'); | 11 var eventBindings = require('event_bindings'); |
12 var Event = eventBindings.Event; | 12 var Event = eventBindings.Event; |
13 var exceptionHandler = require('uncaught_exception_handler'); | 13 var exceptionHandler = require('uncaught_exception_handler'); |
14 var forEach = require('utils').forEach; | 14 var forEach = require('utils').forEach; |
15 var lastError = require('lastError'); | 15 var lastError = require('lastError'); |
16 var logging = requireNative('logging'); | 16 var logging = requireNative('logging'); |
17 var nativeAutomationInternal = requireNative('automationInternal'); | 17 var nativeAutomationInternal = requireNative('automationInternal'); |
18 var GetRoutingID = nativeAutomationInternal.GetRoutingID; | 18 var GetRoutingID = nativeAutomationInternal.GetRoutingID; |
19 var GetSchemaAdditions = nativeAutomationInternal.GetSchemaAdditions; | 19 var GetSchemaAdditions = nativeAutomationInternal.GetSchemaAdditions; |
20 var DestroyAccessibilityTree = | 20 var DestroyAccessibilityTree = |
21 nativeAutomationInternal.DestroyAccessibilityTree; | 21 nativeAutomationInternal.DestroyAccessibilityTree; |
22 var GetIntAttribute = nativeAutomationInternal.GetIntAttribute; | 22 var GetIntAttribute = nativeAutomationInternal.GetIntAttribute; |
23 var StartCachingAccessibilityTrees = | 23 var StartCachingAccessibilityTrees = |
24 nativeAutomationInternal.StartCachingAccessibilityTrees; | 24 nativeAutomationInternal.StartCachingAccessibilityTrees; |
| 25 var AddTreeChangeObserver = nativeAutomationInternal.AddTreeChangeObserver; |
| 26 var RemoveTreeChangeObserver = |
| 27 nativeAutomationInternal.RemoveTreeChangeObserver; |
25 var schema = GetSchemaAdditions(); | 28 var schema = GetSchemaAdditions(); |
26 | 29 |
27 /** | 30 /** |
28 * A namespace to export utility functions to other files in automation. | 31 * A namespace to export utility functions to other files in automation. |
29 */ | 32 */ |
30 window.automationUtil = function() {}; | 33 window.automationUtil = function() {}; |
31 | 34 |
32 // TODO(aboxhall): Look into using WeakMap | 35 // TODO(aboxhall): Look into using WeakMap |
33 var idToCallback = {}; | 36 var idToCallback = {}; |
34 | 37 |
(...skipping 11 matching lines...) Expand all Loading... |
46 idToCallback[id].push(callback); | 49 idToCallback[id].push(callback); |
47 else | 50 else |
48 idToCallback[id] = [callback]; | 51 idToCallback[id] = [callback]; |
49 } else { | 52 } else { |
50 callback(targetTree); | 53 callback(targetTree); |
51 } | 54 } |
52 }; | 55 }; |
53 | 56 |
54 /** | 57 /** |
55 * Global list of tree change observers. | 58 * Global list of tree change observers. |
56 * @type {Array<TreeChangeObserver>} | 59 * @type {Object<number, TreeChangeObserver>} |
57 */ | 60 */ |
58 automationUtil.treeChangeObservers = []; | 61 automationUtil.treeChangeObserverMap = {}; |
| 62 |
| 63 /** |
| 64 * The id of the next tree change observer. |
| 65 * @type {number} |
| 66 */ |
| 67 automationUtil.nextTreeChangeObserverId = 1; |
59 | 68 |
60 automation.registerCustomHook(function(bindingsAPI) { | 69 automation.registerCustomHook(function(bindingsAPI) { |
61 var apiFunctions = bindingsAPI.apiFunctions; | 70 var apiFunctions = bindingsAPI.apiFunctions; |
62 | 71 |
63 // TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj. | 72 // TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj. |
64 apiFunctions.setHandleRequest('getTree', function getTree(tabID, callback) { | 73 apiFunctions.setHandleRequest('getTree', function getTree(tabID, callback) { |
65 var routingID = GetRoutingID(); | 74 var routingID = GetRoutingID(); |
66 StartCachingAccessibilityTrees(); | 75 StartCachingAccessibilityTrees(); |
67 | 76 |
68 // enableTab() ensures the renderer for the active or specified tab has | 77 // enableTab() ensures the renderer for the active or specified tab has |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
103 callback(); | 112 callback(); |
104 return; | 113 return; |
105 } | 114 } |
106 }); | 115 }); |
107 } else { | 116 } else { |
108 callback(desktopTree); | 117 callback(desktopTree); |
109 } | 118 } |
110 }); | 119 }); |
111 | 120 |
112 function removeTreeChangeObserver(observer) { | 121 function removeTreeChangeObserver(observer) { |
113 var observers = automationUtil.treeChangeObservers; | 122 for (var id in automationUtil.treeChangeObserverMap) { |
114 for (var i = 0; i < observers.length; i++) { | 123 if (automationUtil.treeChangeObserverMap[id] == observer) { |
115 if (observer == observers[i]) | 124 RemoveTreeChangeObserver(id); |
116 observers.splice(i, 1); | 125 delete automationUtil.treeChangeObserverMap[id]; |
| 126 return; |
| 127 } |
117 } | 128 } |
118 } | 129 } |
119 apiFunctions.setHandleRequest('removeTreeChangeObserver', function(observer) { | 130 apiFunctions.setHandleRequest('removeTreeChangeObserver', function(observer) { |
120 removeTreeChangeObserver(observer); | 131 removeTreeChangeObserver(observer); |
121 }); | 132 }); |
122 | 133 |
123 function addTreeChangeObserver(observer) { | 134 function addTreeChangeObserver(filter, observer) { |
124 removeTreeChangeObserver(observer); | 135 removeTreeChangeObserver(observer); |
125 automationUtil.treeChangeObservers.push(observer); | 136 var id = automationUtil.nextTreeChangeObserverId++; |
| 137 AddTreeChangeObserver(id, filter); |
| 138 automationUtil.treeChangeObserverMap[id] = observer; |
126 } | 139 } |
127 apiFunctions.setHandleRequest('addTreeChangeObserver', function(observer) { | 140 apiFunctions.setHandleRequest('addTreeChangeObserver', |
128 addTreeChangeObserver(observer); | 141 function(filter, observer) { |
| 142 addTreeChangeObserver(filter, observer); |
129 }); | 143 }); |
130 | 144 |
131 apiFunctions.setHandleRequest('setDocumentSelection', function(params) { | 145 apiFunctions.setHandleRequest('setDocumentSelection', function(params) { |
132 var anchorNodeImpl = privates(params.anchorObject).impl; | 146 var anchorNodeImpl = privates(params.anchorObject).impl; |
133 var focusNodeImpl = privates(params.focusObject).impl; | 147 var focusNodeImpl = privates(params.focusObject).impl; |
134 if (anchorNodeImpl.treeID !== focusNodeImpl.treeID) | 148 if (anchorNodeImpl.treeID !== focusNodeImpl.treeID) |
135 throw new Error('Selection anchor and focus must be in the same tree.'); | 149 throw new Error('Selection anchor and focus must be in the same tree.'); |
136 if (anchorNodeImpl.treeID === DESKTOP_TREE_ID) { | 150 if (anchorNodeImpl.treeID === DESKTOP_TREE_ID) { |
137 throw new Error('Use AutomationNode.setSelection to set the selection ' + | 151 throw new Error('Use AutomationNode.setSelection to set the selection ' + |
138 'in the desktop tree.'); | 152 'in the desktop tree.'); |
139 } | 153 } |
140 automationInternal.performAction({ treeID: anchorNodeImpl.treeID, | 154 automationInternal.performAction({ treeID: anchorNodeImpl.treeID, |
141 automationNodeID: anchorNodeImpl.id, | 155 automationNodeID: anchorNodeImpl.id, |
142 actionType: 'setSelection'}, | 156 actionType: 'setSelection'}, |
143 { focusNodeID: focusNodeImpl.id, | 157 { focusNodeID: focusNodeImpl.id, |
144 anchorOffset: params.anchorOffset, | 158 anchorOffset: params.anchorOffset, |
145 focusOffset: params.focusOffset }); | 159 focusOffset: params.focusOffset }); |
146 }); | 160 }); |
147 | 161 |
148 }); | 162 }); |
149 | 163 |
150 automationInternal.onTreeChange.addListener(function(treeID, | 164 automationInternal.onChildTreeID.addListener(function(treeID, |
| 165 nodeID) { |
| 166 var tree = AutomationRootNode.getOrCreate(treeID); |
| 167 if (!tree) |
| 168 return; |
| 169 |
| 170 var node = privates(tree).impl.get(nodeID); |
| 171 if (!node) |
| 172 return; |
| 173 |
| 174 // A WebView in the desktop tree has a different AX tree as its child. |
| 175 // When we encounter a WebView with a child AX tree id that we don't |
| 176 // currently have cached, explicitly request that AX tree from the |
| 177 // browser process and set up a callback when it loads to attach that |
| 178 // tree as a child of this node and fire appropriate events. |
| 179 var childTreeID = GetIntAttribute(treeID, nodeID, 'childTreeId'); |
| 180 if (!childTreeID) |
| 181 return; |
| 182 |
| 183 var subroot = AutomationRootNode.get(childTreeID); |
| 184 if (!subroot) { |
| 185 automationUtil.storeTreeCallback(childTreeID, function(root) { |
| 186 privates(root).impl.setHostNode(node); |
| 187 |
| 188 if (root.docLoaded) |
| 189 privates(root).impl.dispatchEvent(schema.EventType.loadComplete); |
| 190 |
| 191 privates(node).impl.dispatchEvent(schema.EventType.childrenChanged); |
| 192 }); |
| 193 |
| 194 automationInternal.enableFrame(childTreeID); |
| 195 } else { |
| 196 privates(subroot).impl.setHostNode(node); |
| 197 } |
| 198 }); |
| 199 |
| 200 automationInternal.onTreeChange.addListener(function(observerID, |
| 201 treeID, |
151 nodeID, | 202 nodeID, |
152 changeType) { | 203 changeType) { |
153 var tree = AutomationRootNode.getOrCreate(treeID); | 204 var tree = AutomationRootNode.getOrCreate(treeID); |
154 if (!tree) | 205 if (!tree) |
155 return; | 206 return; |
156 | 207 |
157 var node = privates(tree).impl.get(nodeID); | 208 var node = privates(tree).impl.get(nodeID); |
158 if (!node) | 209 if (!node) |
159 return; | 210 return; |
160 | 211 |
161 if (node.role == 'webView' || node.role == 'embeddedObject') { | 212 var observer = automationUtil.treeChangeObserverMap[observerID]; |
162 // A WebView in the desktop tree has a different AX tree as its child. | 213 if (!observer) |
163 // When we encounter a WebView with a child AX tree id that we don't | 214 return; |
164 // currently have cached, explicitly request that AX tree from the | |
165 // browser process and set up a callback when it loads to attach that | |
166 // tree as a child of this node and fire appropriate events. | |
167 var childTreeID = GetIntAttribute(treeID, nodeID, 'childTreeId'); | |
168 if (!childTreeID) | |
169 return; | |
170 | 215 |
171 var subroot = AutomationRootNode.get(childTreeID); | 216 try { |
172 if (!subroot) { | 217 observer({target: node, type: changeType}); |
173 automationUtil.storeTreeCallback(childTreeID, function(root) { | 218 } catch (e) { |
174 privates(root).impl.setHostNode(node); | 219 exceptionHandler.handle('Error in tree change observer for ' + |
175 | 220 treeChange.type, e); |
176 if (root.docLoaded) | |
177 privates(root).impl.dispatchEvent(schema.EventType.loadComplete); | |
178 | |
179 privates(node).impl.dispatchEvent(schema.EventType.childrenChanged); | |
180 }); | |
181 | |
182 automationInternal.enableFrame(childTreeID); | |
183 } else { | |
184 privates(subroot).impl.setHostNode(node); | |
185 } | |
186 } | |
187 | |
188 var treeChange = {target: node, type: changeType}; | |
189 | |
190 // Make a copy of the observers in case one of these callbacks tries | |
191 // to change the list of observers. | |
192 var observers = automationUtil.treeChangeObservers.slice(); | |
193 for (var i = 0; i < observers.length; i++) { | |
194 try { | |
195 observers[i](treeChange); | |
196 } catch (e) { | |
197 exceptionHandler.handle('Error in tree change observer for ' + | |
198 treeChange.type, e); | |
199 } | |
200 } | |
201 | |
202 if (changeType == schema.TreeChangeType.nodeRemoved) { | |
203 privates(tree).impl.remove(nodeID); | |
204 } | 221 } |
205 }); | 222 }); |
206 | 223 |
| 224 automationInternal.onNodesRemoved.addListener(function(treeID, nodeIDs) { |
| 225 var tree = AutomationRootNode.getOrCreate(treeID); |
| 226 if (!tree) |
| 227 return; |
| 228 |
| 229 for (var i = 0; i < nodeIDs.length; i++) { |
| 230 privates(tree).impl.remove(nodeIDs[i]); |
| 231 } |
| 232 }); |
| 233 |
207 // Listen to the automationInternal.onAccessibilityEvent event, which is | 234 // Listen to the automationInternal.onAccessibilityEvent event, which is |
208 // essentially a proxy for the AccessibilityHostMsg_Events IPC from the | 235 // essentially a proxy for the AccessibilityHostMsg_Events IPC from the |
209 // renderer. | 236 // renderer. |
210 automationInternal.onAccessibilityEvent.addListener(function(data) { | 237 automationInternal.onAccessibilityEvent.addListener(function(data) { |
211 var id = data.treeID; | 238 var id = data.treeID; |
212 var targetTree = AutomationRootNode.getOrCreate(id); | 239 var targetTree = AutomationRootNode.getOrCreate(id); |
213 | 240 |
214 if (!privates(targetTree).impl.onAccessibilityEvent(data)) | 241 if (!privates(targetTree).impl.onAccessibilityEvent(data)) |
215 return; | 242 return; |
216 | 243 |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
251 }); | 278 }); |
252 | 279 |
253 exports.binding = automation.generate(); | 280 exports.binding = automation.generate(); |
254 | 281 |
255 // Add additional accessibility bindings not specified in the automation IDL. | 282 // Add additional accessibility bindings not specified in the automation IDL. |
256 // Accessibility and automation share some APIs (see | 283 // Accessibility and automation share some APIs (see |
257 // ui/accessibility/ax_enums.idl). | 284 // ui/accessibility/ax_enums.idl). |
258 forEach(schema, function(k, v) { | 285 forEach(schema, function(k, v) { |
259 exports.binding[k] = v; | 286 exports.binding[k] = v; |
260 }); | 287 }); |
OLD | NEW |