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 = 16 requireNative('automationInternal').GetSchemaAdditions(); 17 18 // TODO(aboxhall): Look into using WeakMap 19 var idToAutomationRootNode = {}; 20 var 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 */ 28 var createAutomationRootNodeID = function(pid, rid) { 29 return pid + '_' + rid; 30 }; 31 32 var DESKTOP_TREE_ID = createAutomationRootNodeID(0, 0); 33 34 automation.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. 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 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 121 exports.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). 126 forEach(schema, function(k, v) { 127 exports.binding[k] = v; 128 }); 129