blob: 7a7878dcd1de51510d7e756abb7627c28ff3ec52 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom bindings for the automation API.
var AutomationNode = require('automationNode').AutomationNode;
var AutomationRootNode = require('automationNode').AutomationRootNode;
var automation = require('binding').Binding.create('automation');
var automationInternal =
require('binding').Binding.create('automationInternal').generate();
var eventBindings = require('event_bindings');
var Event = eventBindings.Event;
var forEach = require('utils').forEach;
var lastError = require('lastError');
var logging = requireNative('logging');
var schema = requireNative('automationInternal').GetSchemaAdditions();
// TODO(aboxhall): Look into using WeakMap
var idToAutomationRootNode = {};
var idToCallback = {};
// TODO(dtseng): Move out to automation/automation_util.js or as a static member
// of AutomationRootNode to keep this file clean.
/*
* Creates an id associated with a particular AutomationRootNode based upon a
* renderer/renderer host pair's process and routing id.
*/
var createAutomationRootNodeID = function(pid, rid) {
return pid + '_' + rid;
};
var DESKTOP_TREE_ID = createAutomationRootNodeID(0, 0);
automation.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
// TODO(aboxhall, dtseng): Make this return the speced AutomationRootNode obj.
apiFunctions.setHandleRequest('getTree', function getTree(tabId, callback) {
// enableTab() ensures the renderer for the active or specified tab has
// accessibility enabled, and fetches its process and routing ids to use as
// a key in the idToAutomationRootNode map. The callback to enableTab is is
// bound to the callback passed in to getTree(), so that once the tree is
// available (either due to having been cached earlier, or after an
// accessibility event occurs which causes the tree to be populated), the
// callback can be called.
automationInternal.enableTab(tabId, function onEnable(pid, rid) {
if (lastError.hasError(chrome)) {
callback();
return;
}
var id = createAutomationRootNodeID(pid, rid);
var targetTree = idToAutomationRootNode[id];
if (!targetTree) {
// If we haven't cached the tree, hold the callback until the tree is
// populated by the initial onAccessibilityEvent call.
if (id in idToCallback)
idToCallback[id].push(callback);
else
idToCallback[id] = [callback];
} else {
callback(targetTree);
}
});
});
var desktopTree = null;
apiFunctions.setHandleRequest('getDesktop', function(callback) {
desktopTree = idToAutomationRootNode[DESKTOP_TREE_ID];
if (!desktopTree) {
if (DESKTOP_TREE_ID in idToCallback)
idToCallback[DESKTOP_TREE_ID].push(callback);
else
idToCallback[DESKTOP_TREE_ID] = [callback];
// TODO(dtseng): Disable desktop tree once desktop object goes out of
// scope.
automationInternal.enableDesktop(function() {
if (lastError.hasError(chrome)) {
delete idToAutomationRootNode[DESKTOP_TREE_ID];
callback();
return;
}
});
} else {
callback(desktopTree);
}
});
});
// Listen to the automationInternal.onAccessibilityEvent event, which is
// essentially a proxy for the AccessibilityHostMsg_Events IPC from the
// renderer.
automationInternal.onAccessibilityEvent.addListener(function(data) {
var pid = data.processID;
var rid = data.routingID;
var id = createAutomationRootNodeID(pid, rid);
var targetTree = idToAutomationRootNode[id];
if (!targetTree) {
// If this is the first time we've gotten data for this tree, it will
// contain all of the tree's data, so create a new tree which will be
// bootstrapped from |data|.
targetTree = new AutomationRootNode(pid, rid);
idToAutomationRootNode[id] = targetTree;
}
if (!privates(targetTree).impl.onAccessibilityEvent(data))
return;
// If we're not waiting on a callback to getTree(), we can early out here.
if (!(id in idToCallback))
return;
// We usually get a 'placeholder' tree first, which doesn't have any url
// attribute or child nodes. If we've got that, wait for the full tree before
// calling the callback.
// TODO(dmazzoni): Don't send down placeholder (crbug.com/397553)
if (id != DESKTOP_TREE_ID && !targetTree.attributes.url &&
targetTree.children.length == 0) {
return;
}
// If the tree wasn't available when getTree() was called, the callback will
// have been cached in idToCallback, so call and delete it now that we
// have the complete tree.
for (var i = 0; i < idToCallback[id].length; i++) {
console.log('calling getTree() callback');
var callback = idToCallback[id][i];
callback(targetTree);
}
delete idToCallback[id];
});
automationInternal.onAccessibilityTreeDestroyed.addListener(function(pid, rid) {
var id = createAutomationRootNodeID(pid, rid);
var targetTree = idToAutomationRootNode[id];
if (targetTree) {
privates(targetTree).impl.destroy();
delete idToAutomationRootNode[id];
} else {
logging.WARNING('no targetTree to destroy');
}
delete idToAutomationRootNode[id];
});
exports.binding = automation.generate();
// Add additional accessibility bindings not specified in the automation IDL.
// Accessibility and automation share some APIs (see
// ui/accessibility/ax_enums.idl).
forEach(schema, function(k, v) {
exports.binding[k] = v;
});