• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
6var AutomationNode = require('automationNode').AutomationNode;
7var AutomationRootNode = require('automationNode').AutomationRootNode;
8var automation = require('binding').Binding.create('automation');
9var automationInternal =
10    require('binding').Binding.create('automationInternal').generate();
11var eventBindings = require('event_bindings');
12var Event = eventBindings.Event;
13var forEach = require('utils').forEach;
14var lastError = require('lastError');
15var schema =
16    requireNative('automationInternal').GetSchemaAdditions();
17
18// TODO(aboxhall): Look into using WeakMap
19var idToAutomationRootNode = {};
20var idToCallback = {};
21
22// TODO(dtseng): Move out to automation/automation_util.js or as a static member
23// of AutomationRootNode to keep this file clean.
24/*
25 * Creates an id associated with a particular AutomationRootNode based upon a
26 * renderer/renderer host pair's process and routing id.
27 */
28var createAutomationRootNodeID = function(pid, rid) {
29  return pid + '_' + rid;
30};
31
32var DESKTOP_TREE_ID = createAutomationRootNodeID(0, 0);
33
34automation.registerCustomHook(function(bindingsAPI) {
35  var apiFunctions = bindingsAPI.apiFunctions;
36
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  });
65
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];
74
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});
89
90// Listen to the automationInternal.onaccessibilityEvent event, which is
91// essentially a proxy for the AccessibilityHostMsg_Events IPC from the
92// renderer.
93automationInternal.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  privates(targetTree).impl.update(data);
106  var eventType = data.eventType;
107  if (eventType == 'loadComplete' || eventType == 'layoutComplete') {
108    // If the tree wasn't available when getTree() was called, the callback will
109    // have been cached in idToCallback, so call and delete it now that we
110    // have the complete tree.
111    if (id in idToCallback) {
112      for (var i = 0; i < idToCallback[id].length; i++) {
113        var callback = idToCallback[id][i];
114        callback(targetTree);
115      }
116      delete idToCallback[id];
117    }
118  }
119});
120
121exports.binding = automation.generate();
122
123// Add additional accessibility bindings not specified in the automation IDL.
124// Accessibility and automation share some APIs (see
125// ui/accessibility/ax_enums.idl).
126forEach(schema, function(k, v) {
127  exports.binding[k] = v;
128});
129