Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6949)

Unified Diff: chrome/renderer/extensions/automation_internal_custom_bindings.cc

Issue 1155183006: Reimplement automation API on top of C++-backed AXTree. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@automation_faster_2
Patch Set: Add support for input type html attribute Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/renderer/extensions/automation_internal_custom_bindings.cc
diff --git a/chrome/renderer/extensions/automation_internal_custom_bindings.cc b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
index ea8cb5753e795734b4b1a82e534ba7170f712a3a..ca2ee74885b7fe175abb6ea37450a065c3274a81 100644
--- a/chrome/renderer/extensions/automation_internal_custom_bindings.cc
+++ b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
@@ -39,6 +39,9 @@ v8::Local<v8::Object> ToEnumObject(v8::Isolate* isolate,
namespace extensions {
+TreeCache::TreeCache() {}
+TreeCache::~TreeCache() {}
+
class AutomationMessageFilter : public IPC::MessageFilter {
public:
explicit AutomationMessageFilter(AutomationInternalCustomBindings* owner)
@@ -46,6 +49,7 @@ class AutomationMessageFilter : public IPC::MessageFilter {
removed_(false) {
DCHECK(owner);
content::RenderThread::Get()->AddFilter(this);
+ task_runner_ = content::RenderThread::Get()->GetTaskRunner();
}
void Detach() {
@@ -55,10 +59,12 @@ class AutomationMessageFilter : public IPC::MessageFilter {
// IPC::MessageFilter
bool OnMessageReceived(const IPC::Message& message) override {
- if (owner_)
- return owner_->OnMessageReceived(message);
- else
- return false;
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &AutomationMessageFilter::OnMessageReceivedOnRenderThread,
+ this, message));
+ return false;
}
void OnFilterRemoved() override {
@@ -66,6 +72,11 @@ class AutomationMessageFilter : public IPC::MessageFilter {
}
private:
+ void OnMessageReceivedOnRenderThread(const IPC::Message& message) {
+ if (owner_)
+ owner_->OnMessageReceived(message);
+ }
+
~AutomationMessageFilter() override {
Remove();
}
@@ -79,6 +90,7 @@ private:
AutomationInternalCustomBindings* owner_;
bool removed_;
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
DISALLOW_COPY_AND_ASSIGN(AutomationMessageFilter);
};
@@ -88,24 +100,39 @@ AutomationInternalCustomBindings::AutomationInternalCustomBindings(
// It's safe to use base::Unretained(this) here because these bindings
// will only be called on a valid AutomationInternalCustomBindings instance
// and none of the functions have any side effects.
- RouteFunction(
- "IsInteractPermitted",
- base::Bind(&AutomationInternalCustomBindings::IsInteractPermitted,
- base::Unretained(this)));
- RouteFunction(
- "GetSchemaAdditions",
- base::Bind(&AutomationInternalCustomBindings::GetSchemaAdditions,
- base::Unretained(this)));
- RouteFunction(
- "GetRoutingID",
- base::Bind(&AutomationInternalCustomBindings::GetRoutingID,
- base::Unretained(this)));
-
- message_filter_ = new AutomationMessageFilter(this);
+ #define ROUTE_FUNCTION(FN) \
+ RouteFunction(#FN, \
+ base::Bind(&AutomationInternalCustomBindings::FN, \
+ base::Unretained(this)))
+
+ ROUTE_FUNCTION(IsInteractPermitted);
+ ROUTE_FUNCTION(GetSchemaAdditions);
+ ROUTE_FUNCTION(GetRoutingID);
+ ROUTE_FUNCTION(StartCachingAccessibilityTrees);
+ ROUTE_FUNCTION(DestroyAccessibilityTree);
+ ROUTE_FUNCTION(GetRootID);
+ ROUTE_FUNCTION(GetParentID);
+ ROUTE_FUNCTION(GetChildCount);
+ ROUTE_FUNCTION(GetChildIDAtIndex);
+ ROUTE_FUNCTION(GetIndexInParent);
+ ROUTE_FUNCTION(GetState);
+ ROUTE_FUNCTION(GetRole);
+ ROUTE_FUNCTION(GetLocation);
+ ROUTE_FUNCTION(GetStringAttribute);
+ ROUTE_FUNCTION(GetBoolAttribute);
+ ROUTE_FUNCTION(GetIntAttribute);
+ ROUTE_FUNCTION(GetFloatAttribute);
+ ROUTE_FUNCTION(GetIntListAttribute);
+ ROUTE_FUNCTION(GetHtmlAttribute);
+
+ #undef ROUTE_FUNCTION
}
AutomationInternalCustomBindings::~AutomationInternalCustomBindings() {
- message_filter_->Detach();
+ if (message_filter_)
+ message_filter_->Detach();
+ STLDeleteContainerPairSecondPointers(tree_id_to_tree_cache_map_.begin(),
+ tree_id_to_tree_cache_map_.end());
}
bool AutomationInternalCustomBindings::OnMessageReceived(
@@ -135,6 +162,12 @@ void AutomationInternalCustomBindings::GetRoutingID(
args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), routing_id));
}
+void AutomationInternalCustomBindings::StartCachingAccessibilityTrees(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (!message_filter_)
+ message_filter_ = new AutomationMessageFilter(this);
+}
+
void AutomationInternalCustomBindings::GetSchemaAdditions(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Local<v8::Object> additions = v8::Object::New(GetIsolate());
@@ -158,9 +191,460 @@ void AutomationInternalCustomBindings::GetSchemaAdditions(
args.GetReturnValue().Set(additions);
}
+void AutomationInternalCustomBindings::DestroyAccessibilityTree(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 1 || !args[0]->IsNumber()) {
+ ThrowInvalidArgumentsException(args);
+ return;
+ }
+
+ int tree_id = args[0]->Int32Value();
+ auto iter = tree_id_to_tree_cache_map_.find(tree_id);
+ if (iter == tree_id_to_tree_cache_map_.end())
+ return;
+
+ TreeCache* cache = iter->second;
+ tree_id_to_tree_cache_map_.erase(tree_id);
+ axtree_to_tree_cache_map_.erase(&cache->tree);
+ delete cache;
+}
+
+//
+// Access the cached accessibility trees and properties of their nodes.
+//
+
+void AutomationInternalCustomBindings::GetRootID(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() != 1 || !args[0]->IsNumber()) {
+ ThrowInvalidArgumentsException(args);
+ return;
+ }
+
+ int tree_id = args[0]->Int32Value();
+ const auto& iter = tree_id_to_tree_cache_map_.find(tree_id);
not at google - send to devlin 2015/06/17 21:14:47 I think this shouldn't be a const reference, just
dmazzoni 2015/06/18 07:23:53 Done.
+ if (iter == tree_id_to_tree_cache_map_.end())
+ return;
+
+ TreeCache* cache = iter->second;
+ int root_id = cache->tree.root()->id();
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), root_id));
+}
+
+void AutomationInternalCustomBindings::GetParentID(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
not at google - send to devlin 2015/06/17 21:14:48 Always nice to initialise pointers to null.
dmazzoni 2015/06/18 07:23:53 Done.
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ if (!node->parent())
+ return;
+
+ int parent_id = node->parent()->id();
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), parent_id));
+}
+
+void AutomationInternalCustomBindings::GetChildCount(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ int child_count = node->child_count();
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), child_count));
+}
+
+void AutomationInternalCustomBindings::GetChildIDAtIndex(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ if (args.Length() < 3 || !args[2]->IsNumber()) {
+ ThrowInvalidArgumentsException(args);
+ return;
+ }
+
+ ui::AXNode* node;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ int index = args[2]->Int32Value();
+ if (index < 0 || index >= node->child_count())
+ return;
+
+ int child_id = node->children()[index]->id();
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), child_id));
+}
+
+void AutomationInternalCustomBindings::GetIndexInParent(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ int index_in_parent = node->index_in_parent();
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), index_in_parent));
+}
+
+void AutomationInternalCustomBindings::GetState(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ base::DictionaryValue state_dict;
+ uint32 state_pos = 0, state_shifter = node->data().state;
+ while (state_shifter) {
+ if (state_shifter & 1) {
+ state_dict.SetBoolean(
+ ToString(static_cast<ui::AXState>(state_pos)), true);
+ }
+ state_shifter = state_shifter >> 1;
+ state_pos++;
+ }
+
+ SetReturnValueFromBaseValue(args, state_dict);
+}
+
+void AutomationInternalCustomBindings::GetRole(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
+ if (!GetNodeHelper(args, nullptr, &node))
+ return;
+
+ std::string role_name = ui::ToString(node->data().role);
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(GetIsolate(), role_name.c_str()));
+}
+
+void AutomationInternalCustomBindings::GetLocation(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ TreeCache* cache;
+ ui::AXNode* node;
+ if (!GetNodeHelper(args, &cache, &node))
+ return;
+
+ base::DictionaryValue location_dict;
+ gfx::Rect location = node->data().location;
+ location.Offset(cache->location_offset);
+ location_dict.SetInteger("left", location.x());
+ location_dict.SetInteger("top", location.y());
+ location_dict.SetInteger("width", location.width());
+ location_dict.SetInteger("height", location.height());
+
+ SetReturnValueFromBaseValue(args, location_dict);
+}
+
+void AutomationInternalCustomBindings::GetStringAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ ui::AXStringAttribute attribute = ui::ParseAXStringAttribute(attribute_name);
+ std::string attr_value;
+ if (!node->data().GetStringAttribute(attribute, &attr_value))
+ return;
+
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(GetIsolate(), attr_value.c_str()));
+}
+
+void AutomationInternalCustomBindings::GetBoolAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ ui::AXBoolAttribute attribute = ui::ParseAXBoolAttribute(attribute_name);
+ bool attr_value;
+ if (!node->data().GetBoolAttribute(attribute, &attr_value))
+ return;
+
+ args.GetReturnValue().Set(v8::Boolean::New(GetIsolate(), attr_value));
+}
+
+void AutomationInternalCustomBindings::GetIntAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ ui::AXIntAttribute attribute = ui::ParseAXIntAttribute(attribute_name);
+ int attr_value;
+ if (!node->data().GetIntAttribute(attribute, &attr_value))
+ return;
+
+ args.GetReturnValue().Set(v8::Integer::New(GetIsolate(), attr_value));
+}
+
+void AutomationInternalCustomBindings::GetFloatAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ ui::AXFloatAttribute attribute = ui::ParseAXFloatAttribute(attribute_name);
+ float attr_value;
+
+ if (!node->data().GetFloatAttribute(attribute, &attr_value))
+ return;
+
+ args.GetReturnValue().Set(v8::Number::New(GetIsolate(), attr_value));
+}
+
+void AutomationInternalCustomBindings::GetIntListAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ ui::AXIntListAttribute attribute =
+ ui::ParseAXIntListAttribute(attribute_name);
+ if (!node->data().HasIntListAttribute(attribute))
+ return;
+ const std::vector<int32>& attr_value =
+ node->data().GetIntListAttribute(attribute);
+
+ base::ListValue list;
+ for (int32 i : attr_value)
+ list.AppendInteger(i);
+ SetReturnValueFromBaseValue(args, list);
+}
+
+void AutomationInternalCustomBindings::GetHtmlAttribute(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ui::AXNode* node;
+ std::string attribute_name;
+ if (!GetAttributeHelper(args, &node, &attribute_name))
+ return;
+
+ std::string attr_value;
+ if (!node->data().GetHtmlAttribute(attribute_name.c_str(), &attr_value))
+ return;
+
+ args.GetReturnValue().Set(
+ v8::String::NewFromUtf8(GetIsolate(), attr_value.c_str()));
+}
+
+//
+// Helper functions.
+//
+
+void AutomationInternalCustomBindings::ThrowInvalidArgumentsException(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ GetIsolate()->ThrowException(
+ v8::String::NewFromUtf8(
+ GetIsolate(),
+ "Invalid arguments to AutomationInternalCustomBindings function",
+ v8::NewStringType::kNormal).ToLocalChecked());
+
+ std::string trace;
not at google - send to devlin 2015/06/17 21:14:48 This stringify-stack-trace utility looks handy. In
dmazzoni 2015/06/18 07:23:53 Done.
+ v8::Local<v8::StackTrace> stack_trace =
+ v8::StackTrace::CurrentStackTrace(GetIsolate(), 10);
+ for (size_t i = 0; i < (size_t)stack_trace->GetFrameCount(); ++i) {
not at google - send to devlin 2015/06/17 21:14:48 static_cast<size_t> not (size_t)?
dcheng 2015/06/17 22:55:09 How about just using int instead of casting?
dmazzoni 2015/06/18 07:23:53 Done.
+ v8::Local<v8::StackFrame> frame = stack_trace->GetFrame(i);
+ CHECK(!frame.IsEmpty());
+ trace += base::StringPrintf(
+ "\n at %s (%s:%d:%d)",
+ *v8::String::Utf8Value(frame->GetFunctionName()),
+ *v8::String::Utf8Value(frame->GetScriptName()),
+ frame->GetLineNumber(),
+ frame->GetColumn());
+ }
+ LOG(FATAL)
+ << "Invalid arguments to AutomationInternalCustomBindings function"
+ << trace;
+}
+
+bool AutomationInternalCustomBindings::GetNodeHelper(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ TreeCache** out_cache,
+ ui::AXNode** out_node) {
+ if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsNumber()) {
+ ThrowInvalidArgumentsException(args);
+ return false;
+ }
+
+ int tree_id = args[0]->Int32Value();
+ int node_id = args[1]->Int32Value();
+
+ const auto& iter = tree_id_to_tree_cache_map_.find(tree_id);
+ if (iter == tree_id_to_tree_cache_map_.end())
+ return false;
+
+ TreeCache* cache = iter->second;
+ ui::AXNode* node = cache->tree.GetFromId(node_id);
+
+ if (out_cache)
+ *out_cache = cache;
+ if (out_node)
+ *out_node = node;
+
+ return node != nullptr;
+}
+
+bool AutomationInternalCustomBindings::GetAttributeHelper(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ ui::AXNode** out_node,
+ std::string* out_attribute_name) {
+ if (args.Length() != 3 ||
+ !args[2]->IsString()) {
+ ThrowInvalidArgumentsException(args);
+ return false;
+ }
+
+ TreeCache* cache = nullptr;
+ if (!GetNodeHelper(args, &cache, out_node))
+ return false;
+
+ *out_attribute_name = *v8::String::Utf8Value(args[2]);
+ return true;
+}
+
+void AutomationInternalCustomBindings::SetReturnValueFromBaseValue(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ const base::Value& value) {
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Context::Scope context_scope(context()->v8_context());
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ v8::Local<v8::Value> v8_value =
+ converter->ToV8Value(&value, context()->v8_context());
+ args.GetReturnValue().Set(v8_value);
+}
+
+//
+// Handle accessibility events from the browser process.
+//
+
void AutomationInternalCustomBindings::OnAccessibilityEvent(
const ExtensionMsg_AccessibilityEventParams& params) {
- // TODO(dmazzoni): finish implementing this.
+ int tree_id = params.tree_id;
+ TreeCache* cache;
+ auto iter = tree_id_to_tree_cache_map_.find(tree_id);
+ if (iter == tree_id_to_tree_cache_map_.end()) {
+ cache = new TreeCache();
+ cache->tab_id = -1;
+ cache->tree_id = params.tree_id;
+ cache->tree.SetDelegate(this);
+ tree_id_to_tree_cache_map_.insert(std::make_pair(tree_id, cache));
+ axtree_to_tree_cache_map_.insert(std::make_pair(&cache->tree, cache));
+ } else {
+ cache = iter->second;
+ }
+
+ cache->location_offset = params.location_offset;
+ if (!cache->tree.Unserialize(params.update)) {
+ LOG(FATAL) << cache->tree.error();
+ return;
+ }
+
+ api::automation_internal::AXEventParams event_params;
+ event_params.tree_id = params.tree_id;
+ event_params.target_id = params.id;
+ event_params.event_type = ToString(params.event_type);
+ base::ListValue args;
+ args.Append(event_params.ToValue());
+
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Context::Scope context_scope(context()->v8_context());
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
+ v8::Local<v8::Value> v8_args =
+ converter->ToV8Value(&args, context()->v8_context());
+
+ DCHECK(v8_args->IsArray());
+ v8::Local<v8::Array> v8_args_array = v8_args.As<v8::Array>();
+ context()->DispatchEvent("automationInternal.onAccessibilityEvent",
+ v8_args_array);
+}
+
+void AutomationInternalCustomBindings::OnNodeWillBeDeleted(ui::AXTree* tree,
+ ui::AXNode* node) {
+ SendTreeChangeEvent(
+ api::automation::TREE_CHANGE_TYPE_NODEREMOVED,
+ tree, node);
+}
+
+void AutomationInternalCustomBindings::OnSubtreeWillBeDeleted(
+ ui::AXTree* tree,
+ ui::AXNode* node) {
+ // This isn't strictly needed, as OnNodeWillBeDeleted will already be
+ // called. We could send a JS event for this only if it turns out to
+ // be needed for something.
+}
+
+void AutomationInternalCustomBindings::OnNodeCreated(ui::AXTree* tree,
+ ui::AXNode* node) {
+ // Not needed, this is called in the middle of an update so it's not
+ // safe to trigger JS from here. Wait for the notification in
+ // OnAtomicUpdateFinished instead.
+}
+
+void AutomationInternalCustomBindings::OnNodeChanged(ui::AXTree* tree,
+ ui::AXNode* node) {
+ // Not needed, this is called in the middle of an update so it's not
+ // safe to trigger JS from here. Wait for the notification in
+ // OnAtomicUpdateFinished instead.
+}
+
+void AutomationInternalCustomBindings::OnAtomicUpdateFinished(
+ ui::AXTree* tree,
+ bool root_changed,
+ const std::vector<ui::AXTreeDelegate::Change>& changes) {
+ auto iter = axtree_to_tree_cache_map_.find(tree);
+ if (iter == axtree_to_tree_cache_map_.end())
+ return;
+
+ for (auto change : changes) {
+ ui::AXNode* node = change.node;
+ switch (change.type) {
+ case NODE_CREATED:
+ SendTreeChangeEvent(
+ api::automation::TREE_CHANGE_TYPE_NODECREATED,
+ tree, node);
+ break;
+ case SUBTREE_CREATED:
+ SendTreeChangeEvent(
+ api::automation::TREE_CHANGE_TYPE_SUBTREECREATED,
+ tree, node);
+ break;
+ case NODE_CHANGED:
+ SendTreeChangeEvent(
+ api::automation::TREE_CHANGE_TYPE_NODECHANGED,
+ tree, node);
+ break;
+ }
+ }
+}
+
+void AutomationInternalCustomBindings::SendTreeChangeEvent(
+ api::automation::TreeChangeType change_type,
+ ui::AXTree* tree,
+ ui::AXNode* node) {
+ auto iter = axtree_to_tree_cache_map_.find(tree);
+ if (iter == axtree_to_tree_cache_map_.end())
+ return;
+
+ int tree_id = iter->second->tree_id;
+ base::ListValue args;
+ args.AppendInteger(tree_id);
+ args.AppendInteger(node->id());
+ args.AppendString(ToString(change_type).c_str());
+
+ v8::HandleScope handle_scope(GetIsolate());
+ v8::Context::Scope context_scope(context()->v8_context());
+ scoped_ptr<content::V8ValueConverter> converter(
+ content::V8ValueConverter::create());
not at google - send to devlin 2015/06/17 21:14:47 You should avoid dealing with base::Values as much
dmazzoni 2015/06/18 07:23:53 OK, done. I got rid of all uses of V8ValueConverte
+ v8::Local<v8::Value> v8_args =
+ converter->ToV8Value(&args, context()->v8_context());
+
+ DCHECK(v8_args->IsArray());
+ v8::Local<v8::Array> v8_args_array = v8_args.As<v8::Array>();
+ context()->DispatchEvent("automationInternal.onTreeChange",
+ v8_args_array);
}
} // namespace extensions

Powered by Google App Engine
This is Rietveld 408576698