Add testing/scripts/OWNERS
[chromium-blink-merge.git] / chrome / renderer / resources / extensions / automation_custom_bindings.js
blob7a7878dcd1de51510d7e756abb7627c28ff3ec52
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 schema = requireNative('automationInternal').GetSchemaAdditions();
18 // TODO(aboxhall): Look into using WeakMap
19 var idToAutomationRootNode = {};
20 var idToCallback = {};
22 // TODO(dtseng): Move out to automation/automation_util.js or as a static member
23 // of AutomationRootNode to keep this file clean.
25  * Creates an id associated with a particular AutomationRootNode based upon a
26  * renderer/renderer host pair's process and routing id.
27  */
28 var createAutomationRootNodeID = function(pid, rid) {
29   return pid + '_' + rid;
32 var DESKTOP_TREE_ID = createAutomationRootNodeID(0, 0);
34 automation.registerCustomHook(function(bindingsAPI) {
35   var apiFunctions = bindingsAPI.apiFunctions;
37   // TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj.
38   apiFunctions.setHandleRequest('getTree', function getTree(tabId, callback) {
39     // enableTab() ensures the renderer for the active or specified tab has
40     // accessibility enabled, and fetches its process and routing ids to use as
41     // a key in the idToAutomationRootNode map. The callback to enableTab is is
42     // bound to the callback passed in to getTree(), so that once the tree is
43     // available (either due to having been cached earlier, or after an
44     // accessibility event occurs which causes the tree to be populated), the
45     // callback can be called.
46     automationInternal.enableTab(tabId, function onEnable(pid, rid) {
47       if (lastError.hasError(chrome)) {
48         callback();
49         return;
50       }
51       var id = createAutomationRootNodeID(pid, rid);
52       var targetTree = idToAutomationRootNode[id];
53       if (!targetTree) {
54         // If we haven't cached the tree, hold the callback until the tree is
55         // populated by the initial onAccessibilityEvent call.
56         if (id in idToCallback)
57           idToCallback[id].push(callback);
58         else
59           idToCallback[id] = [callback];
60       } else {
61         callback(targetTree);
62       }
63     });
64   });
66   var desktopTree = null;
67   apiFunctions.setHandleRequest('getDesktop', function(callback) {
68     desktopTree = idToAutomationRootNode[DESKTOP_TREE_ID];
69     if (!desktopTree) {
70       if (DESKTOP_TREE_ID in idToCallback)
71         idToCallback[DESKTOP_TREE_ID].push(callback);
72       else
73         idToCallback[DESKTOP_TREE_ID] = [callback];
75       // TODO(dtseng): Disable desktop tree once desktop object goes out of
76       // scope.
77       automationInternal.enableDesktop(function() {
78         if (lastError.hasError(chrome)) {
79           delete idToAutomationRootNode[DESKTOP_TREE_ID];
80           callback();
81           return;
82         }
83       });
84     } else {
85       callback(desktopTree);
86     }
87   });
88 });
90 // Listen to the automationInternal.onAccessibilityEvent event, which is
91 // essentially a proxy for the AccessibilityHostMsg_Events IPC from the
92 // renderer.
93 automationInternal.onAccessibilityEvent.addListener(function(data) {
94   var pid = data.processID;
95   var rid = data.routingID;
96   var id = createAutomationRootNodeID(pid, rid);
97   var targetTree = idToAutomationRootNode[id];
98   if (!targetTree) {
99     // If this is the first time we've gotten data for this tree, it will
100     // contain all of the tree's data, so create a new tree which will be
101     // bootstrapped from |data|.
102     targetTree = new AutomationRootNode(pid, rid);
103     idToAutomationRootNode[id] = targetTree;
104   }
105   if (!privates(targetTree).impl.onAccessibilityEvent(data))
106     return;
108   // If we're not waiting on a callback to getTree(), we can early out here.
109   if (!(id in idToCallback))
110     return;
112   // We usually get a 'placeholder' tree first, which doesn't have any url
113   // attribute or child nodes. If we've got that, wait for the full tree before
114   // calling the callback.
115   // TODO(dmazzoni): Don't send down placeholder (crbug.com/397553)
116   if (id != DESKTOP_TREE_ID && !targetTree.attributes.url &&
117       targetTree.children.length == 0) {
118     return;
119   }
121   // If the tree wasn't available when getTree() was called, the callback will
122   // have been cached in idToCallback, so call and delete it now that we
123   // have the complete tree.
124   for (var i = 0; i < idToCallback[id].length; i++) {
125     console.log('calling getTree() callback');
126     var callback = idToCallback[id][i];
127     callback(targetTree);
128   }
129   delete idToCallback[id];
132 automationInternal.onAccessibilityTreeDestroyed.addListener(function(pid, rid) {
133   var id = createAutomationRootNodeID(pid, rid);
134   var targetTree = idToAutomationRootNode[id];
135   if (targetTree) {
136     privates(targetTree).impl.destroy();
137     delete idToAutomationRootNode[id];
138   } else {
139     logging.WARNING('no targetTree to destroy');
140   }
141   delete idToAutomationRootNode[id];
144 exports.binding = automation.generate();
146 // Add additional accessibility bindings not specified in the automation IDL.
147 // Accessibility and automation share some APIs (see
148 // ui/accessibility/ax_enums.idl).
149 forEach(schema, function(k, v) {
150   exports.binding[k] = v;