Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(182)

Side by Side Diff: chrome/renderer/resources/extensions/automation_custom_bindings.js

Issue 1457683009: Complete live region support in ChromeVox Next. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressed last feedback Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698