automationInternal.onTreeChange.addListener(function(treeID, nodeID, changeType) { var tree = AutomationRootNode.getOrCreate(treeID); if (!tree) return; var node = privates(tree).impl.get(nodeID); if (!node) return; if (node.role == 'webView' || node.role == 'embeddedObject') { // A WebView in the desktop tree has a different AX tree as its child. // When we encounter a WebView with a child AX tree id that we don't // currently have cached, explicitly request that AX tree from the // browser process and set up a callback when it loads to attach that // tree as a child of this node and fire appropriate events. var childTreeID = GetIntAttribute(treeID, nodeID, 'childTreeId'); if (!childTreeID) return; var subroot = AutomationRootNode.get(childTreeID); if (!subroot) { automationUtil.storeTreeCallback(childTreeID, function(root) { privates(root).impl.setHostNode(node); if (root.docLoaded) privates(root).impl.dispatchEvent(schema.EventType.loadComplete); privates(node).impl.dispatchEvent(schema.EventType.childrenChanged); }); automationInternal.enableFrame(childTreeID); } else { privates(subroot).impl.setHostNode(node); } } var treeChange = {target: node, type: changeType}; // Make a copy of the observers in case one of these callbacks tries // to change the list of observers. var observers = automationUtil.treeChangeObservers.slice(); for (var i = 0; i < observers.length; i++) { try { observers[i](treeChange); } catch (e) { exceptionHandler.handle('Error in tree change observer for ' + treeChange.type, e); } } if (changeType == schema.TreeChangeType.nodeRemoved) { privates(tree).impl.remove(nodeID); } });
automationInternal.onAccessibilityTreeDestroyed.addListener(function(id) { // Destroy the AutomationRootNode. var targetTree = AutomationRootNode.get(id); if (targetTree) { privates(targetTree).impl.destroy(); AutomationRootNode.destroy(id); } else { logging.WARNING('no targetTree to destroy'); } // Destroy the native cache of the accessibility tree. DestroyAccessibilityTree(id); });
automationInternal.enableDesktop(function(treeId) { if (hasLastError()) { AutomationRootNode.destroy(treeId); desktopId = undefined; callback(); return; } desktopId = treeId; desktopTree = AutomationRootNode.getOrCreate(desktopId); callback(desktopTree); // TODO(dtseng): Disable desktop tree once desktop object goes out of // scope. });
automationInternal.enableDesktop(routingID, function() { if (lastError.hasError(chrome)) { AutomationRootNode.destroy(DESKTOP_TREE_ID); callback(); return; } });
automationInternal.onGetTextLocationResult.addListener(function( textLocationParams) { var targetTree = AutomationRootNode.get(textLocationParams.treeID); if (!targetTree) return; privates(targetTree).impl.onGetTextLocationResult(textLocationParams); });
automationInternal.onAccessibilityEvent.addListener(function(data) { var id = data.treeID; var targetTree = AutomationRootNode.getOrCreate(id); 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.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++) { var callback = idToCallback[id][i]; callback(targetTree); } delete idToCallback[id]; });
automationInternal.onActionResult.addListener(function( treeID, requestID, result) { var targetTree = AutomationRootNode.get(treeID); if (!targetTree) return; privates(targetTree).impl.onActionResult(requestID, result); });
automationInternal.onNodesRemoved.addListener(function(treeID, nodeIDs) { var tree = AutomationRootNode.getOrCreate(treeID); if (!tree) return; for (var i = 0; i < nodeIDs.length; i++) { privates(tree).impl.remove(nodeIDs[i]); } });
automationUtil.updateFocusedNode = function() { automationUtil.focusedNode = null; var focusedNodeInfo = GetFocus(DESKTOP_TREE_ID); if (!focusedNodeInfo) return; var tree = AutomationRootNode.getOrCreate(focusedNodeInfo.treeId); if (tree) { automationUtil.focusedNode = privates(tree).impl.get(focusedNodeInfo.nodeId); } };
automationUtil.getFocus = function() { if (desktopId === undefined) return; var focusedNodeInfo = GetFocusNative(desktopId); if (!focusedNodeInfo) return null; var tree = AutomationRootNode.getOrCreate(focusedNodeInfo.treeId); if (tree) return privates(tree).impl.get(focusedNodeInfo.nodeId); };
automationInternal.onChildTreeID.addListener(function(treeID, nodeID) { var tree = AutomationRootNode.getOrCreate(treeID); if (!tree) return; var node = privates(tree).impl.get(nodeID); if (!node) return; // A WebView in the desktop tree has a different AX tree as its child. // When we encounter a WebView with a child AX tree id that we don't // currently have cached, explicitly request that AX tree from the // browser process and set up a callback when it loads to attach that // tree as a child of this node and fire appropriate events. var childTreeID = GetIntAttribute(treeID, nodeID, 'childTreeId'); if (!childTreeID) return; var subroot = AutomationRootNode.get(childTreeID); if (!subroot) { automationUtil.storeTreeCallback(childTreeID, function(root) { // Return early if the root has already been attached. if (root.parent) return; privates(root).impl.setHostNode(node); if (root.docLoaded) privates(root).impl.dispatchEvent(schema.EventType.loadComplete); privates(node).impl.dispatchEvent(schema.EventType.childrenChanged); }); automationInternal.enableFrame(childTreeID); } else { privates(subroot).impl.setHostNode(node); } });
automationUtil.storeTreeCallback = function(id, callback) { if (!callback) return; var targetTree = AutomationRootNode.get(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); } };
automationInternal.onAccessibilityEvent.addListener(function(eventParams) { var id = eventParams.treeID; var targetTree = AutomationRootNode.getOrCreate(id); // When we get a focus event, ignore the actual event target, and instead // check what node has focus globally. If that represents a focus change, // fire a focus event on the correct target. if (eventParams.eventType == schema.EventType.focus) { var previousFocusedNode = automationUtil.focusedNode; automationUtil.updateFocusedNode(); if (automationUtil.focusedNode && automationUtil.focusedNode == previousFocusedNode) { return; } if (automationUtil.focusedNode) { targetTree = automationUtil.focusedNode.root; eventParams.treeID = privates(targetTree).impl.treeID; eventParams.targetID = privates(automationUtil.focusedNode).impl.id; } } if (!privates(targetTree).impl.onAccessibilityEvent(eventParams)) 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.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++) { var callback = idToCallback[id][i]; callback(targetTree); } delete idToCallback[id]; });
automationInternal.onChildTreeID.addListener(function(childTreeId) { var targetTree = AutomationRootNode.get(childTreeId); // If the tree is already loded, or if we previously requested it be loaded // (i.e. have a callback for it), don't try to do so again. if (targetTree || idToCallback[childTreeId]) return; // A WebView in the desktop tree has a different AX tree as its child. // When we encounter a WebView with a child AX tree id that we don't // currently have cached, explicitly request that AX tree from the // browser process and set up a callback when it loads to attach that // tree as a child of this node and fire appropriate events. automationUtil.storeTreeCallback(childTreeId, function(root) { privates(root).impl.dispatchEvent('loadComplete', 'page'); }, true); automationInternal.enableFrame(childTreeId); });
apiFunctions.setHandleRequest('getDesktop', function(callback) { StartCachingAccessibilityTrees(); if (desktopId !== undefined) desktopTree = AutomationRootNode.get(desktopId); if (!desktopTree) { automationInternal.enableDesktop(function(treeId) { if (hasLastError()) { AutomationRootNode.destroy(treeId); desktopId = undefined; callback(); return; } desktopId = treeId; desktopTree = AutomationRootNode.getOrCreate(desktopId); callback(desktopTree); // TODO(dtseng): Disable desktop tree once desktop object goes out of // scope. }); } else { callback(desktopTree); } });
automationInternal.onTreeChange.addListener(function(observerID, treeID, nodeID, changeType) { var tree = AutomationRootNode.getOrCreate(treeID); if (!tree) return; var node = privates(tree).impl.get(nodeID); if (!node) return; var observer = automationUtil.treeChangeObserverMap[observerID]; if (!observer) return; try { observer({target: node, type: changeType}); } catch (e) { exceptionHandler.handle('Error in tree change observer for ' + changeType, e); } });
apiFunctions.setHandleRequest('getDesktop', function(callback) { StartCachingAccessibilityTrees(); desktopTree = AutomationRootNode.get(DESKTOP_TREE_ID); if (!desktopTree) { if (DESKTOP_TREE_ID in idToCallback) idToCallback[DESKTOP_TREE_ID].push(callback); else idToCallback[DESKTOP_TREE_ID] = [callback]; var routingID = GetRoutingID(); // TODO(dtseng): Disable desktop tree once desktop object goes out of // scope. automationInternal.enableDesktop(routingID, function() { if (lastError.hasError(chrome)) { AutomationRootNode.destroy(DESKTOP_TREE_ID); callback(); return; } }); } else { callback(desktopTree); } });
automationInternal.onAccessibilityEvent.addListener(function(eventParams) { var id = eventParams.treeID; var targetTree = AutomationRootNode.getOrCreate(id); if (eventParams.eventType == 'blur') { // Work around an issue where Chrome sends us 'blur' events on the // root node when nothing has focus, we need to treat those as focus // events but otherwise not handle blur events specially. var node = privates(targetTree).impl.get(eventParams.targetID); if (!node) return; if (node == node.root) automationUtil.updateFocusedNodeOnBlur(); } else if (eventParams.eventType == 'mediaStartedPlaying' || eventParams.eventType == 'mediaStoppedPlaying') { // These events are global to the tree. eventParams.targetID = privates(targetTree).impl.id; } else { var previousFocusedNode = automationUtil.focusedNode; automationUtil.updateFocusedNode(); // Fire focus events if necessary. if (automationUtil.focusedNode && automationUtil.focusedNode != previousFocusedNode) { var eventParamsCopy = {}; for (var key in eventParams) eventParamsCopy[key] = eventParams[key]; eventParamsCopy['eventType'] = 'focus'; eventParamsCopy['treeID'] = privates(automationUtil.focusedNode.root).impl.treeID; eventParamsCopy['targetID'] = privates(automationUtil.focusedNode).impl.id; privates(automationUtil.focusedNode.root) .impl.onAccessibilityEvent(eventParamsCopy); } } // Note that focus type events have already been handled above if there was a // focused node. All other events, even non-focus events that triggered a // focus dispatch, still need to have their original event fired. if ((!automationUtil.focusedNode || eventParams.eventType != 'focus') && !privates(targetTree).impl.onAccessibilityEvent(eventParams)) 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 != desktopId && !targetTree.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++) { var callback = idToCallback[id][i]; callback(targetTree); } delete idToCallback[id]; });