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