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

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

Issue 1155183006: Reimplement automation API on top of C++-backed AXTree. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@automation_faster_2
Patch Set: Rebase Created 5 years, 6 months 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 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
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 var childTreeID = GetIntAttribute(treeID, nodeID, 'childTreeId');
aboxhall 2015/06/11 17:42:46 Can you add a comment explaining what's going on i
dmazzoni 2015/06/12 17:51:36 Done. This code used to be in automation_node.js.
145 if (!AutomationRootNode.get(childTreeID)) {
146 automationUtil.storeTreeCallback(childTreeID, function(root) {
147 privates(root).impl.hostNode = node;
148
149 if (root.docLoaded)
150 privates(root).impl.dispatchEvent(schema.EventType.loadComplete);
151
152 privates(node).impl.dispatchEvent(schema.EventType.childrenChanged);
153 });
154
155 automationInternal.enableFrame(childTreeID);
156 }
157 }
158
159 var treeChange = {target: node, type: changeType};
160 var observers = automationUtil.treeChangeObservers;
161 for (var i = 0; i < observers.length; i++) {
162 try {
163 observers[i](treeChange);
164 } catch (e) {
165 console.error('Error in tree change observer for ' +
aboxhall 2015/06/11 17:42:46 Maybe use logging.WARNING - logs to stderr. https:
dmazzoni 2015/06/12 17:51:36 This is intended for extension developers. Which i
166 treeChange.type + ': ' + e.message +
167 '\nStack trace: ' + e.stack);
168 }
169 }
170
171 //
172 // TODO: delete AutomationNode if 'removeNode' event
173 //
174 });
175
128 // Listen to the automationInternal.onAccessibilityEvent event, which is 176 // Listen to the automationInternal.onAccessibilityEvent event, which is
129 // essentially a proxy for the AccessibilityHostMsg_Events IPC from the 177 // essentially a proxy for the AccessibilityHostMsg_Events IPC from the
130 // renderer. 178 // renderer.
131 automationInternal.onAccessibilityEvent.addListener(function(data) { 179 automationInternal.onAccessibilityEvent.addListener(function(data) {
132 var id = data.treeID; 180 var id = data.treeID;
133 var targetTree = idToAutomationRootNode[id]; 181 var targetTree = AutomationRootNode.getOrCreate(id);
134 if (!targetTree) { 182
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)) 183 if (!privates(targetTree).impl.onAccessibilityEvent(data))
142 return; 184 return;
143 185
144 // If we're not waiting on a callback to getTree(), we can early out here. 186 // If we're not waiting on a callback to getTree(), we can early out here.
145 if (!(id in idToCallback)) 187 if (!(id in idToCallback))
146 return; 188 return;
147 189
148 // We usually get a 'placeholder' tree first, which doesn't have any url 190 // 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 191 // attribute or child nodes. If we've got that, wait for the full tree before
150 // calling the callback. 192 // calling the callback.
151 // TODO(dmazzoni): Don't send down placeholder (crbug.com/397553) 193 // TODO(dmazzoni): Don't send down placeholder (crbug.com/397553)
152 if (id != DESKTOP_TREE_ID && !targetTree.attributes.url && 194 if (id != DESKTOP_TREE_ID && !targetTree.url &&
153 targetTree.children.length == 0) { 195 targetTree.children.length == 0) {
154 return; 196 return;
155 } 197 }
156 198
157 // If the tree wasn't available when getTree() was called, the callback will 199 // 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 200 // have been cached in idToCallback, so call and delete it now that we
159 // have the complete tree. 201 // have the complete tree.
160 for (var i = 0; i < idToCallback[id].length; i++) { 202 for (var i = 0; i < idToCallback[id].length; i++) {
161 console.log('calling getTree() callback');
162 var callback = idToCallback[id][i]; 203 var callback = idToCallback[id][i];
163 callback(targetTree); 204 callback(targetTree);
164 } 205 }
165 delete idToCallback[id]; 206 delete idToCallback[id];
166 }); 207 });
167 208
168 automationInternal.onAccessibilityTreeDestroyed.addListener(function(id) { 209 automationInternal.onAccessibilityTreeDestroyed.addListener(function(id) {
169 var targetTree = idToAutomationRootNode[id]; 210 // Destroy the native cache of the accessibility tree.
211 DestroyAccessibilityTree(id);
212
213 // Destroy the AutomationRootNode.
214 var targetTree = AutomationRootNode.get(id);
170 if (targetTree) { 215 if (targetTree) {
171 privates(targetTree).impl.destroy(); 216 privates(targetTree).impl.destroy();
172 delete idToAutomationRootNode[id]; 217 AutomationRootNode.destroy(id);
173 } else { 218 } else {
174 logging.WARNING('no targetTree to destroy'); 219 logging.WARNING('no targetTree to destroy');
175 } 220 }
176 delete idToAutomationRootNode[id];
177 }); 221 });
178 222
179 exports.binding = automation.generate(); 223 exports.binding = automation.generate();
180 224
181 // Add additional accessibility bindings not specified in the automation IDL. 225 // Add additional accessibility bindings not specified in the automation IDL.
182 // Accessibility and automation share some APIs (see 226 // Accessibility and automation share some APIs (see
183 // ui/accessibility/ax_enums.idl). 227 // ui/accessibility/ax_enums.idl).
184 forEach(schema, function(k, v) { 228 forEach(schema, function(k, v) {
185 exports.binding[k] = v; 229 exports.binding[k] = v;
186 }); 230 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698