Home | History | Annotate | Download | only in extensions
      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