| 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 |