Commit 68b571ee authored by dtseng@chromium.org's avatar dtseng@chromium.org

Add human readable programmatic enum name/values to chrome.automation.

This adds three new bindings to the automation API:
chrome.automation.Event
chrome.automation.Role
chrome.automation.State

Each is an object mapping an enum name to its value. Currently, this is an identity mapping. However, with the way the cl constructs the bindings, we could in the future substitute with integer values (and eliminate the stringification on either end of our extension/browser IPC).

BUG=308003003

Review URL: https://codereview.chromium.org/304293002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@273747 0039d316-1c4b-4281-b951-d872f2087c98
parent 95e517c1
...@@ -8,9 +8,29 @@ ...@@ -8,9 +8,29 @@
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/values.h" #include "base/values.h"
#include "chrome/common/extensions/manifest_handlers/automation.h" #include "chrome/common/extensions/manifest_handlers/automation.h"
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/common/extension.h" #include "extensions/common/extension.h"
#include "extensions/common/manifest.h" #include "extensions/common/manifest.h"
#include "extensions/renderer/script_context.h" #include "extensions/renderer/script_context.h"
#include "ui/accessibility/ax_enums.h"
namespace {
// Helper to convert an enum to a V8 object.
template <typename EnumType>
v8::Local<v8::Object> ToEnumObject(v8::Isolate* isolate,
EnumType start_after,
EnumType end_at) {
v8::Local<v8::Object> object = v8::Object::New(isolate);
for (int i = start_after + 1; i <= end_at; ++i) {
v8::Local<v8::String> value = v8::String::NewFromUtf8(
isolate, ui::ToString(static_cast<EnumType>(i)).c_str());
object->Set(value, value);
}
return object;
}
} // namespace
namespace extensions { namespace extensions {
...@@ -20,6 +40,10 @@ AutomationInternalCustomBindings::AutomationInternalCustomBindings( ...@@ -20,6 +40,10 @@ AutomationInternalCustomBindings::AutomationInternalCustomBindings(
"IsInteractPermitted", "IsInteractPermitted",
base::Bind(&AutomationInternalCustomBindings::IsInteractPermitted, base::Bind(&AutomationInternalCustomBindings::IsInteractPermitted,
base::Unretained(this))); base::Unretained(this)));
RouteFunction(
"GetSchemaAdditions",
base::Bind(&AutomationInternalCustomBindings::GetSchemaAdditions,
base::Unretained(this)));
} }
AutomationInternalCustomBindings::~AutomationInternalCustomBindings() { AutomationInternalCustomBindings::~AutomationInternalCustomBindings() {
...@@ -35,4 +59,23 @@ void AutomationInternalCustomBindings::IsInteractPermitted( ...@@ -35,4 +59,23 @@ void AutomationInternalCustomBindings::IsInteractPermitted(
v8::Boolean::New(GetIsolate(), automation_info->interact)); v8::Boolean::New(GetIsolate(), automation_info->interact));
} }
void AutomationInternalCustomBindings::GetSchemaAdditions(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Local<v8::Object> additions = v8::Object::New(GetIsolate());
additions->Set(
v8::String::NewFromUtf8(GetIsolate(), "EventType"),
ToEnumObject(GetIsolate(), ui::AX_EVENT_NONE, ui::AX_EVENT_LAST));
additions->Set(
v8::String::NewFromUtf8(GetIsolate(), "RoleType"),
ToEnumObject(GetIsolate(), ui::AX_ROLE_NONE, ui::AX_ROLE_LAST));
additions->Set(
v8::String::NewFromUtf8(GetIsolate(), "StateType"),
ToEnumObject(GetIsolate(), ui::AX_STATE_NONE, ui::AX_STATE_LAST));
args.GetReturnValue().Set(additions);
}
} // namespace extensions } // namespace extensions
...@@ -20,8 +20,14 @@ class AutomationInternalCustomBindings : public ObjectBackedNativeHandler { ...@@ -20,8 +20,14 @@ class AutomationInternalCustomBindings : public ObjectBackedNativeHandler {
virtual ~AutomationInternalCustomBindings(); virtual ~AutomationInternalCustomBindings();
private: private:
// Returns whether this extension has the "interact" permission set (either
// explicitly or implicitly after manifest parsing).
void IsInteractPermitted(const v8::FunctionCallbackInfo<v8::Value>& args); void IsInteractPermitted(const v8::FunctionCallbackInfo<v8::Value>& args);
// Returns an object with bindings that will be added to the
// chrome.automation namespace.
void GetSchemaAdditions(const v8::FunctionCallbackInfo<v8::Value>& args);
DISALLOW_COPY_AND_ASSIGN(AutomationInternalCustomBindings); DISALLOW_COPY_AND_ASSIGN(AutomationInternalCustomBindings);
}; };
......
...@@ -10,7 +10,10 @@ var automationInternal = ...@@ -10,7 +10,10 @@ var automationInternal =
require('binding').Binding.create('automationInternal').generate(); require('binding').Binding.create('automationInternal').generate();
var eventBindings = require('event_bindings'); var eventBindings = require('event_bindings');
var Event = eventBindings.Event; var Event = eventBindings.Event;
var forEach = require('utils').forEach;
var lastError = require('lastError'); var lastError = require('lastError');
var schema =
requireNative('automationInternal').GetSchemaAdditions();
// TODO(aboxhall): Look into using WeakMap // TODO(aboxhall): Look into using WeakMap
var idToAutomationTree = {}; var idToAutomationTree = {};
...@@ -116,3 +119,10 @@ automationInternal.onAccessibilityEvent.addListener(function(data) { ...@@ -116,3 +119,10 @@ automationInternal.onAccessibilityEvent.addListener(function(data) {
}); });
exports.binding = automation.generate(); 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;
});
...@@ -9,7 +9,7 @@ var allTests = [ ...@@ -9,7 +9,7 @@ var allTests = [
return node.role == 'textField'; return node.role == 'textField';
}); });
assertTrue(!!firstTextField); assertTrue(!!firstTextField);
firstTextField.addEventListener('focus', function(e) { firstTextField.addEventListener(EventType.focus, function(e) {
chrome.test.succeed(); chrome.test.succeed();
}, true); }, true);
firstTextField.doDefault(); firstTextField.doDefault();
...@@ -21,7 +21,7 @@ var allTests = [ ...@@ -21,7 +21,7 @@ var allTests = [
return node.role == 'button' && node.state.focusable; return node.role == 'button' && node.state.focusable;
}); });
assertTrue(!!firstFocusableNode); assertTrue(!!firstFocusableNode);
firstFocusableNode.addEventListener('focus', function(e) { firstFocusableNode.addEventListener(EventType.focus, function(e) {
chrome.test.succeed(); chrome.test.succeed();
}, true); }, true);
firstFocusableNode.focus(); firstFocusableNode.focus();
......
...@@ -5,6 +5,11 @@ ...@@ -5,6 +5,11 @@
var assertEq = chrome.test.assertEq; var assertEq = chrome.test.assertEq;
var assertFalse = chrome.test.assertFalse; var assertFalse = chrome.test.assertFalse;
var assertTrue = chrome.test.assertTrue; var assertTrue = chrome.test.assertTrue;
var EventType = chrome.automation.EventType;
var RoleType = chrome.automation.RoleType;
var StateType = chrome.automation.StateType;
var tree = null; var tree = null;
function findAutomationNode(root, condition) { function findAutomationNode(root, condition) {
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
var allTests = [ var allTests = [
function testGetDesktop() { function testGetDesktop() {
chrome.automation.getDesktop(function(tree) { chrome.automation.getDesktop(function(tree) {
assertEq('desktop', tree.root.role); assertEq(RoleType.desktop, tree.root.role);
assertEq('window', tree.root.firstChild().role); assertEq(RoleType.window, tree.root.firstChild().role);
chrome.test.succeed(); chrome.test.succeed();
}); });
}, },
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
var allTests = [ var allTests = [
function testSimpleAction() { function testSimpleAction() {
var okButton = tree.root.firstChild().firstChild(); var okButton = tree.root.firstChild().firstChild();
okButton.addEventListener('focus', function() { okButton.addEventListener(EventType.focus, function() {
chrome.test.succeed(); chrome.test.succeed();
}, true); }, true);
okButton.focus(); okButton.focus();
......
...@@ -6,6 +6,10 @@ var assertEq = chrome.test.assertEq; ...@@ -6,6 +6,10 @@ var assertEq = chrome.test.assertEq;
var assertFalse = chrome.test.assertFalse; var assertFalse = chrome.test.assertFalse;
var assertTrue = chrome.test.assertTrue; var assertTrue = chrome.test.assertTrue;
var EventType = chrome.automation.EventType;
var RoleType = chrome.automation.RoleType;
var StateType = chrome.automation.StateType;
var tree = null; var tree = null;
function setUpAndRunTests(allTests) { function setUpAndRunTests(allTests) {
...@@ -25,4 +29,3 @@ function setUpAndRunTests(allTests) { ...@@ -25,4 +29,3 @@ function setUpAndRunTests(allTests) {
}); });
}); });
} }
...@@ -5,10 +5,11 @@ ...@@ -5,10 +5,11 @@
var allTests = [ var allTests = [
function testEventListenerTarget() { function testEventListenerTarget() {
var cancelButton = tree.root.firstChild().children()[2]; var cancelButton = tree.root.firstChild().children()[2];
assertEq('Cancel', cancelButton.attributes['name']); assertEq('Cancel', cancelButton.attributes.name);
cancelButton.addEventListener('focus', function onFocusTarget(event) { cancelButton.addEventListener(EventType.focus,
function onFocusTarget(event) {
window.setTimeout(function() { window.setTimeout(function() {
cancelButton.removeEventListener('focus', onFocusTarget); cancelButton.removeEventListener(EventType.focus, onFocusTarget);
chrome.test.succeed(); chrome.test.succeed();
}, 0); }, 0);
}); });
...@@ -16,56 +17,60 @@ var allTests = [ ...@@ -16,56 +17,60 @@ var allTests = [
}, },
function testEventListenerBubble() { function testEventListenerBubble() {
var cancelButton = tree.root.firstChild().children()[2]; var cancelButton = tree.root.firstChild().children()[2];
assertEq('Cancel', cancelButton.attributes['name']); assertEq('Cancel', cancelButton.attributes.name);
var cancelButtonGotEvent = false; var cancelButtonGotEvent = false;
cancelButton.addEventListener('focus', function onFocusBubble(event) { cancelButton.addEventListener(EventType.focus,
function onFocusBubble(event) {
cancelButtonGotEvent = true; cancelButtonGotEvent = true;
cancelButton.removeEventListener('focus', onFocusBubble); cancelButton.removeEventListener(EventType.focus, onFocusBubble);
}); });
tree.root.addEventListener('focus', function onFocusBubbleRoot(event) { tree.root.addEventListener(EventType.focus,
function onFocusBubbleRoot(event) {
assertEq('focus', event.type); assertEq('focus', event.type);
assertEq(cancelButton, event.target); assertEq(cancelButton, event.target);
assertTrue(cancelButtonGotEvent); assertTrue(cancelButtonGotEvent);
tree.root.removeEventListener('focus', onFocusBubbleRoot); tree.root.removeEventListener(EventType.focus, onFocusBubbleRoot);
chrome.test.succeed(); chrome.test.succeed();
}); });
cancelButton.focus(); cancelButton.focus();
}, },
function testStopPropagation() { function testStopPropagation() {
var cancelButton = tree.root.firstChild().children()[2]; var cancelButton = tree.root.firstChild().children()[2];
assertEq('Cancel', cancelButton.attributes['name']); assertEq('Cancel', cancelButton.attributes.name);
function onFocusStopPropRoot(event) { function onFocusStopPropRoot(event) {
tree.root.removeEventListener('focus', onFocusStopPropRoot); tree.root.removeEventListener(EventType.focus, onFocusStopPropRoot);
chrome.test.fail("Focus event was propagated to root"); chrome.test.fail("Focus event was propagated to root");
}; };
cancelButton.addEventListener('focus', function onFocusStopProp(event) { cancelButton.addEventListener(EventType.focus,
cancelButton.removeEventListener('focus', onFocusStopProp); function onFocusStopProp(event) {
cancelButton.removeEventListener(EventType.focus, onFocusStopProp);
event.stopPropagation(); event.stopPropagation();
window.setTimeout((function() { window.setTimeout((function() {
tree.root.removeEventListener('focus', onFocusStopPropRoot); tree.root.removeEventListener(EventType.focus, onFocusStopPropRoot);
chrome.test.succeed(); chrome.test.succeed();
}).bind(this), 0); }).bind(this), 0);
}); });
tree.root.addEventListener('focus', onFocusStopPropRoot); tree.root.addEventListener(EventType.focus, onFocusStopPropRoot);
cancelButton.focus(); cancelButton.focus();
}, },
function testEventListenerCapture() { function testEventListenerCapture() {
var cancelButton = tree.root.firstChild().children()[2]; var cancelButton = tree.root.firstChild().children()[2];
assertEq('Cancel', cancelButton.attributes['name']); assertEq('Cancel', cancelButton.attributes.name);
var cancelButtonGotEvent = false; var cancelButtonGotEvent = false;
function onFocusCapture(event) { function onFocusCapture(event) {
cancelButtonGotEvent = true; cancelButtonGotEvent = true;
cancelButton.removeEventListener('focus', onFocusCapture); cancelButton.removeEventListener(EventType.focus, onFocusCapture);
chrome.test.fail("Focus event was not captured by root"); chrome.test.fail("Focus event was not captured by root");
}; };
cancelButton.addEventListener('focus', onFocusCapture); cancelButton.addEventListener(EventType.focus, onFocusCapture);
tree.root.addEventListener('focus', function onFocusCaptureRoot(event) { tree.root.addEventListener(EventType.focus,
function onFocusCaptureRoot(event) {
assertEq('focus', event.type); assertEq('focus', event.type);
assertEq(cancelButton, event.target); assertEq(cancelButton, event.target);
assertFalse(cancelButtonGotEvent); assertFalse(cancelButtonGotEvent);
event.stopPropagation(); event.stopPropagation();
tree.root.removeEventListener('focus', onFocusCaptureRoot); tree.root.removeEventListener(EventType.focus, onFocusCaptureRoot);
tree.root.removeEventListener('focus', onFocusCapture); tree.root.removeEventListener(EventType.focus, onFocusCapture);
window.setTimeout(chrome.test.succeed.bind(this), 0); window.setTimeout(chrome.test.succeed.bind(this), 0);
}, true); }, true);
cancelButton.focus(); cancelButton.focus();
......
...@@ -19,7 +19,8 @@ var allTests = [ ...@@ -19,7 +19,8 @@ var allTests = [
assertTrue('height' in okButton.location, 'no height in location'); assertTrue('height' in okButton.location, 'no height in location');
assertTrue('width' in okButton.location, 'no width in location'); assertTrue('width' in okButton.location, 'no width in location');
tree.root.addEventListener('childrenChanged', assertOkButtonLocation); tree.root.addEventListener(
EventType.childrenChanged, assertOkButtonLocation);
chrome.tabs.executeScript({ 'code': chrome.tabs.executeScript({ 'code':
'document.querySelector("button")' + 'document.querySelector("button")' +
'.setAttribute("style", "position: absolute; left: 100; top: 200; ' + '.setAttribute("style", "position: absolute; left: 100; top: 200; ' +
......
...@@ -5,14 +5,14 @@ ...@@ -5,14 +5,14 @@
// Do not test orientation or hover attributes (similar to exclusions on native // Do not test orientation or hover attributes (similar to exclusions on native
// accessibility), since they can be inconsistent depending on the environment. // accessibility), since they can be inconsistent depending on the environment.
var RemoveUntestedStates = function(state) { var RemoveUntestedStates = function(state) {
delete state['horizontal']; delete state[StateType.horizontal];
delete state['hovered']; delete state[StateType.hovered];
delete state['vertical']; delete state[StateType.vertical];
}; };
var allTests = [ var allTests = [
function testSimplePage() { function testSimplePage() {
var title = tree.root.attributes['docTitle']; var title = tree.root.attributes.docTitle;
assertEq('Automation Tests', title); assertEq('Automation Tests', title);
RemoveUntestedStates(tree.root.state); RemoveUntestedStates(tree.root.state);
assertEq( assertEq(
...@@ -22,7 +22,7 @@ var allTests = [ ...@@ -22,7 +22,7 @@ var allTests = [
assertEq(1, children.length); assertEq(1, children.length);
var body = children[0]; var body = children[0];
assertEq('body', body.attributes['htmlTag']); assertEq('body', body.attributes.htmlTag);
RemoveUntestedStates(body.state); RemoveUntestedStates(body.state);
assertEq({enabled: true, readOnly: true}, assertEq({enabled: true, readOnly: true},
...@@ -31,19 +31,19 @@ var allTests = [ ...@@ -31,19 +31,19 @@ var allTests = [
var contentChildren = body.children(); var contentChildren = body.children();
assertEq(3, contentChildren.length); assertEq(3, contentChildren.length);
var okButton = contentChildren[0]; var okButton = contentChildren[0];
assertEq('Ok', okButton.attributes['name']); assertEq('Ok', okButton.attributes.name);
RemoveUntestedStates(okButton.state); RemoveUntestedStates(okButton.state);
assertEq({enabled: true, focusable: true, readOnly: true}, assertEq({enabled: true, focusable: true, readOnly: true},
okButton.state); okButton.state);
var userNameInput = contentChildren[1]; var userNameInput = contentChildren[1];
assertEq('Username', assertEq('Username',
userNameInput.attributes['description']); userNameInput.attributes.description);
RemoveUntestedStates(userNameInput.state); RemoveUntestedStates(userNameInput.state);
assertEq({enabled: true, focusable: true}, assertEq({enabled: true, focusable: true},
userNameInput.state); userNameInput.state);
var cancelButton = contentChildren[2]; var cancelButton = contentChildren[2];
assertEq('Cancel', assertEq('Cancel',
cancelButton.attributes['name']); cancelButton.attributes.name);
RemoveUntestedStates(cancelButton.state); RemoveUntestedStates(cancelButton.state);
assertEq({enabled: true, focusable: true, readOnly: true}, assertEq({enabled: true, focusable: true, readOnly: true},
cancelButton.state); cancelButton.state);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment