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 forEach = require('utils').forEach; | 13 var forEach = require('utils').forEach; |
14 var lastError = require('lastError'); | 14 var lastError = require('lastError'); |
15 var logging = requireNative('logging'); | 15 var logging = requireNative('logging'); |
16 var nativeAutomationInternal = requireNative('automationInternal'); | 16 var nativeAutomationInternal = requireNative('automationInternal'); |
17 var GetRoutingID = nativeAutomationInternal.GetRoutingID; | 17 var GetRoutingID = nativeAutomationInternal.GetRoutingID; |
18 var GetSchemaAdditions = nativeAutomationInternal.GetSchemaAdditions; | 18 var GetSchemaAdditions = nativeAutomationInternal.GetSchemaAdditions; |
19 var DestroyAccessibilityTree = | |
20 nativeAutomationInternal.DestroyAccessibilityTree; | |
21 var GetIntAttribute = nativeAutomationInternal.GetIntAttribute; | |
22 var StartCachingAccessibilityTrees = | |
23 nativeAutomationInternal.StartCachingAccessibilityTrees; | |
19 var schema = GetSchemaAdditions(); | 24 var schema = GetSchemaAdditions(); |
20 | 25 |
21 /** | 26 /** |
22 * A namespace to export utility functions to other files in automation. | 27 * A namespace to export utility functions to other files in automation. |
23 */ | 28 */ |
24 window.automationUtil = function() {}; | 29 window.automationUtil = function() {}; |
25 | 30 |
26 // TODO(aboxhall): Look into using WeakMap | 31 // TODO(aboxhall): Look into using WeakMap |
27 var idToAutomationRootNode = {}; | |
28 var idToCallback = {}; | 32 var idToCallback = {}; |
29 | 33 |
30 var DESKTOP_TREE_ID = 0; | 34 var DESKTOP_TREE_ID = 0; |
31 | 35 |
32 automationUtil.storeTreeCallback = function(id, callback) { | 36 automationUtil.storeTreeCallback = function(id, callback) { |
33 if (!callback) | 37 if (!callback) |
34 return; | 38 return; |
35 | 39 |
36 var targetTree = idToAutomationRootNode[id]; | 40 var targetTree = AutomationRootNode.get(id); |
37 if (!targetTree) { | 41 if (!targetTree) { |
38 // If we haven't cached the tree, hold the callback until the tree is | 42 // If we haven't cached the tree, hold the callback until the tree is |
39 // populated by the initial onAccessibilityEvent call. | 43 // populated by the initial onAccessibilityEvent call. |
40 if (id in idToCallback) | 44 if (id in idToCallback) |
41 idToCallback[id].push(callback); | 45 idToCallback[id].push(callback); |
42 else | 46 else |
43 idToCallback[id] = [callback]; | 47 idToCallback[id] = [callback]; |
44 } else { | 48 } else { |
45 callback(targetTree); | 49 callback(targetTree); |
46 } | 50 } |
47 }; | 51 }; |
48 | 52 |
49 /** | 53 /** |
50 * Global list of tree change observers. | 54 * Global list of tree change observers. |
51 * @type {Array<TreeChangeObserver>} | 55 * @type {Array<TreeChangeObserver>} |
52 */ | 56 */ |
53 automationUtil.treeChangeObservers = []; | 57 automationUtil.treeChangeObservers = []; |
54 | 58 |
55 automation.registerCustomHook(function(bindingsAPI) { | 59 automation.registerCustomHook(function(bindingsAPI) { |
56 var apiFunctions = bindingsAPI.apiFunctions; | 60 var apiFunctions = bindingsAPI.apiFunctions; |
57 | 61 |
58 // TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj. | 62 // TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj. |
59 apiFunctions.setHandleRequest('getTree', function getTree(tabID, callback) { | 63 apiFunctions.setHandleRequest('getTree', function getTree(tabID, callback) { |
60 var routingID = GetRoutingID(); | 64 var routingID = GetRoutingID(); |
65 StartCachingAccessibilityTrees(); | |
61 | 66 |
62 // enableTab() ensures the renderer for the active or specified tab has | 67 // enableTab() ensures the renderer for the active or specified tab has |
63 // accessibility enabled, and fetches its ax tree id to use as | 68 // accessibility enabled, and fetches its ax tree id to use as |
64 // a key in the idToAutomationRootNode map. The callback to | 69 // a key in the idToAutomationRootNode map. The callback to |
65 // enableTab is bound to the callback passed in to getTree(), so that once | 70 // enableTab is bound to the callback passed in to getTree(), so that once |
66 // the tree is available (either due to having been cached earlier, or after | 71 // the tree is available (either due to having been cached earlier, or after |
67 // an accessibility event occurs which causes the tree to be populated), the | 72 // an accessibility event occurs which causes the tree to be populated), the |
68 // callback can be called. | 73 // callback can be called. |
69 var params = { routingID: routingID, tabID: tabID }; | 74 var params = { routingID: routingID, tabID: tabID }; |
70 automationInternal.enableTab(params, | 75 automationInternal.enableTab(params, |
71 function onEnable(id) { | 76 function onEnable(id) { |
72 if (lastError.hasError(chrome)) { | 77 if (lastError.hasError(chrome)) { |
73 callback(); | 78 callback(); |
74 return; | 79 return; |
75 } | 80 } |
76 automationUtil.storeTreeCallback(id, callback); | 81 automationUtil.storeTreeCallback(id, callback); |
77 }); | 82 }); |
78 }); | 83 }); |
79 | 84 |
80 var desktopTree = null; | 85 var desktopTree = null; |
81 apiFunctions.setHandleRequest('getDesktop', function(callback) { | 86 apiFunctions.setHandleRequest('getDesktop', function(callback) { |
82 desktopTree = | 87 StartCachingAccessibilityTrees(); |
83 idToAutomationRootNode[DESKTOP_TREE_ID]; | 88 desktopTree = AutomationRootNode.get(DESKTOP_TREE_ID); |
84 if (!desktopTree) { | 89 if (!desktopTree) { |
85 if (DESKTOP_TREE_ID in idToCallback) | 90 if (DESKTOP_TREE_ID in idToCallback) |
86 idToCallback[DESKTOP_TREE_ID].push(callback); | 91 idToCallback[DESKTOP_TREE_ID].push(callback); |
87 else | 92 else |
88 idToCallback[DESKTOP_TREE_ID] = [callback]; | 93 idToCallback[DESKTOP_TREE_ID] = [callback]; |
89 | 94 |
90 var routingID = GetRoutingID(); | 95 var routingID = GetRoutingID(); |
91 | 96 |
92 // TODO(dtseng): Disable desktop tree once desktop object goes out of | 97 // TODO(dtseng): Disable desktop tree once desktop object goes out of |
93 // scope. | 98 // scope. |
94 automationInternal.enableDesktop(routingID, function() { | 99 automationInternal.enableDesktop(routingID, function() { |
95 if (lastError.hasError(chrome)) { | 100 if (lastError.hasError(chrome)) { |
96 delete idToAutomationRootNode[ | 101 AutomationRootNode.destroy(DESKTOP_TREE_ID); |
97 DESKTOP_TREE_ID]; | |
98 callback(); | 102 callback(); |
99 return; | 103 return; |
100 } | 104 } |
101 }); | 105 }); |
102 } else { | 106 } else { |
103 callback(desktopTree); | 107 callback(desktopTree); |
104 } | 108 } |
105 }); | 109 }); |
106 | 110 |
107 function removeTreeChangeObserver(observer) { | 111 function removeTreeChangeObserver(observer) { |
(...skipping 10 matching lines...) Expand all Loading... | |
118 function addTreeChangeObserver(observer) { | 122 function addTreeChangeObserver(observer) { |
119 removeTreeChangeObserver(observer); | 123 removeTreeChangeObserver(observer); |
120 automationUtil.treeChangeObservers.push(observer); | 124 automationUtil.treeChangeObservers.push(observer); |
121 } | 125 } |
122 apiFunctions.setHandleRequest('addTreeChangeObserver', function(observer) { | 126 apiFunctions.setHandleRequest('addTreeChangeObserver', function(observer) { |
123 addTreeChangeObserver(observer); | 127 addTreeChangeObserver(observer); |
124 }); | 128 }); |
125 | 129 |
126 }); | 130 }); |
127 | 131 |
132 automationInternal.onTreeChange.addListener(function(treeID, | |
133 nodeID, | |
134 changeType) { | |
135 var tree = AutomationRootNode.get(treeID); | |
136 if (!tree) | |
137 return; | |
138 | |
139 var node = privates(tree).impl.get(nodeID); | |
140 if (!node) | |
141 return; | |
142 | |
143 if (node.role == 'webView') { | |
144 // A WebView in the desktop tree has a different AX tree as its child. | |
145 // When we encounter a WebView with a child AX tree id that we don't | |
146 // currently have cached, explicitly request that AX tree from the | |
147 // browser process and set up a callback when it loads to attach that | |
148 // tree as a child of this node and fire appropriate events. | |
149 var childTreeID = GetIntAttribute(treeID, nodeID, 'childTreeId'); | |
150 if (!AutomationRootNode.get(childTreeID)) { | |
151 automationUtil.storeTreeCallback(childTreeID, function(root) { | |
152 privates(root).impl.hostNode = node; | |
not at google - send to devlin
2015/06/17 21:14:48
Glancing at this code (and not understanding it),
dmazzoni
2015/06/18 07:23:53
Done.
not at google - send to devlin
2015/06/18 17:34:38
Is there a semantically reasonable function that c
dmazzoni
2015/06/18 21:31:53
I'm not sure why - I consider automation_custom_bi
| |
153 | |
154 if (root.docLoaded) | |
155 privates(root).impl.dispatchEvent(schema.EventType.loadComplete); | |
156 | |
157 privates(node).impl.dispatchEvent(schema.EventType.childrenChanged); | |
158 }); | |
159 | |
160 automationInternal.enableFrame(childTreeID); | |
161 } | |
162 } | |
163 | |
164 var treeChange = {target: node, type: changeType}; | |
165 var observers = automationUtil.treeChangeObservers; | |
not at google - send to devlin
2015/06/17 21:14:48
I don't know where this code came from, but note t
dmazzoni
2015/06/18 07:23:53
Good idea. I changed it to make a copy of the arra
| |
166 for (var i = 0; i < observers.length; i++) { | |
167 try { | |
168 observers[i](treeChange); | |
169 } catch (e) { | |
170 logging.WARNING('Error in tree change observer for ' + | |
171 treeChange.type + ': ' + e.message + | |
172 '\nStack trace: ' + e.stack); | |
173 } | |
174 } | |
175 | |
176 if (changeType == schema.TreeChangeType.nodeRemoved) { | |
177 privates(tree).impl.remove(nodeID); | |
178 } | |
179 }); | |
180 | |
128 // Listen to the automationInternal.onAccessibilityEvent event, which is | 181 // Listen to the automationInternal.onAccessibilityEvent event, which is |
129 // essentially a proxy for the AccessibilityHostMsg_Events IPC from the | 182 // essentially a proxy for the AccessibilityHostMsg_Events IPC from the |
130 // renderer. | 183 // renderer. |
131 automationInternal.onAccessibilityEvent.addListener(function(data) { | 184 automationInternal.onAccessibilityEvent.addListener(function(data) { |
132 var id = data.treeID; | 185 var id = data.treeID; |
133 var targetTree = idToAutomationRootNode[id]; | 186 var targetTree = AutomationRootNode.getOrCreate(id); |
134 if (!targetTree) { | 187 |
135 // If this is the first time we've gotten data for this tree, it will | |
136 // contain all of the tree's data, so create a new tree which will be | |
137 // bootstrapped from |data|. | |
138 targetTree = new AutomationRootNode(id); | |
139 idToAutomationRootNode[id] = targetTree; | |
140 } | |
141 if (!privates(targetTree).impl.onAccessibilityEvent(data)) | 188 if (!privates(targetTree).impl.onAccessibilityEvent(data)) |
142 return; | 189 return; |
143 | 190 |
144 // If we're not waiting on a callback to getTree(), we can early out here. | 191 // If we're not waiting on a callback to getTree(), we can early out here. |
145 if (!(id in idToCallback)) | 192 if (!(id in idToCallback)) |
146 return; | 193 return; |
147 | 194 |
148 // We usually get a 'placeholder' tree first, which doesn't have any url | 195 // We usually get a 'placeholder' tree first, which doesn't have any url |
149 // attribute or child nodes. If we've got that, wait for the full tree before | 196 // attribute or child nodes. If we've got that, wait for the full tree before |
150 // calling the callback. | 197 // calling the callback. |
151 // TODO(dmazzoni): Don't send down placeholder (crbug.com/397553) | 198 // TODO(dmazzoni): Don't send down placeholder (crbug.com/397553) |
152 if (id != DESKTOP_TREE_ID && !targetTree.attributes.url && | 199 if (id != DESKTOP_TREE_ID && !targetTree.url && |
153 targetTree.children.length == 0) { | 200 targetTree.children.length == 0) { |
154 return; | 201 return; |
155 } | 202 } |
156 | 203 |
157 // If the tree wasn't available when getTree() was called, the callback will | 204 // If the tree wasn't available when getTree() was called, the callback will |
158 // have been cached in idToCallback, so call and delete it now that we | 205 // have been cached in idToCallback, so call and delete it now that we |
159 // have the complete tree. | 206 // have the complete tree. |
160 for (var i = 0; i < idToCallback[id].length; i++) { | 207 for (var i = 0; i < idToCallback[id].length; i++) { |
161 console.log('calling getTree() callback'); | |
162 var callback = idToCallback[id][i]; | 208 var callback = idToCallback[id][i]; |
163 callback(targetTree); | 209 callback(targetTree); |
164 } | 210 } |
165 delete idToCallback[id]; | 211 delete idToCallback[id]; |
166 }); | 212 }); |
167 | 213 |
168 automationInternal.onAccessibilityTreeDestroyed.addListener(function(id) { | 214 automationInternal.onAccessibilityTreeDestroyed.addListener(function(id) { |
169 var targetTree = idToAutomationRootNode[id]; | 215 // Destroy the AutomationRootNode. |
216 var targetTree = AutomationRootNode.get(id); | |
170 if (targetTree) { | 217 if (targetTree) { |
171 privates(targetTree).impl.destroy(); | 218 privates(targetTree).impl.destroy(); |
172 delete idToAutomationRootNode[id]; | 219 AutomationRootNode.destroy(id); |
173 } else { | 220 } else { |
174 logging.WARNING('no targetTree to destroy'); | 221 logging.WARNING('no targetTree to destroy'); |
175 } | 222 } |
176 delete idToAutomationRootNode[id]; | 223 |
224 // Destroy the native cache of the accessibility tree. | |
225 DestroyAccessibilityTree(id); | |
177 }); | 226 }); |
178 | 227 |
179 exports.binding = automation.generate(); | 228 exports.binding = automation.generate(); |
180 | 229 |
181 // Add additional accessibility bindings not specified in the automation IDL. | 230 // Add additional accessibility bindings not specified in the automation IDL. |
182 // Accessibility and automation share some APIs (see | 231 // Accessibility and automation share some APIs (see |
183 // ui/accessibility/ax_enums.idl). | 232 // ui/accessibility/ax_enums.idl). |
184 forEach(schema, function(k, v) { | 233 forEach(schema, function(k, v) { |
185 exports.binding[k] = v; | 234 exports.binding[k] = v; |
186 }); | 235 }); |
OLD | NEW |