Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / renderer / resources / extensions / automation_custom_bindings.js
blobae84e27949fd3baaca5a8ab0f0819b402a5ebc7e
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
3 // found in the LICENSE file.
5 // Custom bindings for the automation API.
6 var AutomationNode = require('automationNode').AutomationNode;
7 var AutomationRootNode = require('automationNode').AutomationRootNode;
8 var automation = require('binding').Binding.create('automation');
9 var automationInternal =
10     require('binding').Binding.create('automationInternal').generate();
11 var eventBindings = require('event_bindings');
12 var Event = eventBindings.Event;
13 var forEach = require('utils').forEach;
14 var lastError = require('lastError');
15 var logging = requireNative('logging');
16 var nativeAutomationInternal = requireNative('automationInternal');
17 var GetRoutingID = nativeAutomationInternal.GetRoutingID;
18 var GetSchemaAdditions = nativeAutomationInternal.GetSchemaAdditions;
19 var DestroyAccessibilityTree =
20     nativeAutomationInternal.DestroyAccessibilityTree;
21 var GetIntAttribute = nativeAutomationInternal.GetIntAttribute;
22 var StartCachingAccessibilityTrees =
23     nativeAutomationInternal.StartCachingAccessibilityTrees;
24 var schema = GetSchemaAdditions();
26 /**
27  * A namespace to export utility functions to other files in automation.
28  */
29 window.automationUtil = function() {};
31 // TODO(aboxhall): Look into using WeakMap
32 var idToCallback = {};
34 var DESKTOP_TREE_ID = 0;
36 automationUtil.storeTreeCallback = function(id, callback) {
37   if (!callback)
38     return;
40   var targetTree = AutomationRootNode.get(id);
41   if (!targetTree) {
42     // If we haven't cached the tree, hold the callback until the tree is
43     // populated by the initial onAccessibilityEvent call.
44     if (id in idToCallback)
45       idToCallback[id].push(callback);
46     else
47       idToCallback[id] = [callback];
48   } else {
49     callback(targetTree);
50   }
53 /**
54  * Global list of tree change observers.
55  * @type {Array<TreeChangeObserver>}
56  */
57 automationUtil.treeChangeObservers = [];
59 automation.registerCustomHook(function(bindingsAPI) {
60   var apiFunctions = bindingsAPI.apiFunctions;
62   // TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj.
63   apiFunctions.setHandleRequest('getTree', function getTree(tabID, callback) {
64     var routingID = GetRoutingID();
65     StartCachingAccessibilityTrees();
67     // enableTab() ensures the renderer for the active or specified tab has
68     // accessibility enabled, and fetches its ax tree id to use as
69     // a key in the idToAutomationRootNode map. The callback to
70     // enableTab is bound to the callback passed in to getTree(), so that once
71     // the tree is available (either due to having been cached earlier, or after
72     // an accessibility event occurs which causes the tree to be populated), the
73     // callback can be called.
74     var params = { routingID: routingID, tabID: tabID };
75     automationInternal.enableTab(params,
76         function onEnable(id) {
77           if (lastError.hasError(chrome)) {
78             callback();
79             return;
80           }
81           automationUtil.storeTreeCallback(id, callback);
82         });
83   });
85   var desktopTree = null;
86   apiFunctions.setHandleRequest('getDesktop', function(callback) {
87     StartCachingAccessibilityTrees();
88     desktopTree = AutomationRootNode.get(DESKTOP_TREE_ID);
89     if (!desktopTree) {
90       if (DESKTOP_TREE_ID in idToCallback)
91         idToCallback[DESKTOP_TREE_ID].push(callback);
92       else
93         idToCallback[DESKTOP_TREE_ID] = [callback];
95       var routingID = GetRoutingID();
97       // TODO(dtseng): Disable desktop tree once desktop object goes out of
98       // scope.
99       automationInternal.enableDesktop(routingID, function() {
100         if (lastError.hasError(chrome)) {
101           AutomationRootNode.destroy(DESKTOP_TREE_ID);
102           callback();
103           return;
104         }
105       });
106     } else {
107       callback(desktopTree);
108     }
109   });
111   function removeTreeChangeObserver(observer) {
112     var observers = automationUtil.treeChangeObservers;
113     for (var i = 0; i < observers.length; i++) {
114       if (observer == observers[i])
115         observers.splice(i, 1);
116     }
117   }
118   apiFunctions.setHandleRequest('removeTreeChangeObserver', function(observer) {
119     removeTreeChangeObserver(observer);
120   });
122   function addTreeChangeObserver(observer) {
123     removeTreeChangeObserver(observer);
124     automationUtil.treeChangeObservers.push(observer);
125   }
126   apiFunctions.setHandleRequest('addTreeChangeObserver', function(observer) {
127     addTreeChangeObserver(observer);
128   });
132 automationInternal.onTreeChange.addListener(function(treeID,
133                                                      nodeID,
134                                                      changeType) {
135   var tree = AutomationRootNode.getOrCreate(treeID);
136   if (!tree)
137     return;
139   var node = privates(tree).impl.get(nodeID);
140   if (!node)
141     return;
143   if (node.role == 'webView' || node.role == 'embeddedObject') {
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 (!childTreeID)
151       return;
153     var subroot = AutomationRootNode.get(childTreeID);
154     if (!subroot) {
155       automationUtil.storeTreeCallback(childTreeID, function(root) {
156         privates(root).impl.setHostNode(node);
158         if (root.docLoaded)
159           privates(root).impl.dispatchEvent(schema.EventType.loadComplete);
161         privates(node).impl.dispatchEvent(schema.EventType.childrenChanged);
162       });
164       automationInternal.enableFrame(childTreeID);
165     } else {
166       privates(subroot).impl.setHostNode(node);
167     }
168   }
170   var treeChange = {target: node, type: changeType};
172   // Make a copy of the observers in case one of these callbacks tries
173   // to change the list of observers.
174   var observers = automationUtil.treeChangeObservers.slice();
175   for (var i = 0; i < observers.length; i++) {
176     try {
177       observers[i](treeChange);
178     } catch (e) {
179       logging.WARNING('Error in tree change observer for ' +
180           treeChange.type + ': ' + e.message +
181           '\nStack trace: ' + e.stack);
182     }
183   }
185   if (changeType == schema.TreeChangeType.nodeRemoved) {
186     privates(tree).impl.remove(nodeID);
187   }
190 // Listen to the automationInternal.onAccessibilityEvent event, which is
191 // essentially a proxy for the AccessibilityHostMsg_Events IPC from the
192 // renderer.
193 automationInternal.onAccessibilityEvent.addListener(function(data) {
194   var id = data.treeID;
195   var targetTree = AutomationRootNode.getOrCreate(id);
197   if (!privates(targetTree).impl.onAccessibilityEvent(data))
198     return;
200   // If we're not waiting on a callback to getTree(), we can early out here.
201   if (!(id in idToCallback))
202     return;
204   // We usually get a 'placeholder' tree first, which doesn't have any url
205   // attribute or child nodes. If we've got that, wait for the full tree before
206   // calling the callback.
207   // TODO(dmazzoni): Don't send down placeholder (crbug.com/397553)
208   if (id != DESKTOP_TREE_ID && !targetTree.url &&
209       targetTree.children.length == 0)
210     return;
212   // If the tree wasn't available when getTree() was called, the callback will
213   // have been cached in idToCallback, so call and delete it now that we
214   // have the complete tree.
215   for (var i = 0; i < idToCallback[id].length; i++) {
216     var callback = idToCallback[id][i];
217     callback(targetTree);
218   }
219   delete idToCallback[id];
222 automationInternal.onAccessibilityTreeDestroyed.addListener(function(id) {
223   // Destroy the AutomationRootNode.
224   var targetTree = AutomationRootNode.get(id);
225   if (targetTree) {
226     privates(targetTree).impl.destroy();
227     AutomationRootNode.destroy(id);
228   } else {
229     logging.WARNING('no targetTree to destroy');
230   }
232   // Destroy the native cache of the accessibility tree.
233   DestroyAccessibilityTree(id);
236 exports.binding = automation.generate();
238 // Add additional accessibility bindings not specified in the automation IDL.
239 // Accessibility and automation share some APIs (see
240 // ui/accessibility/ax_enums.idl).
241 forEach(schema, function(k, v) {
242   exports.binding[k] = v;