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.
5 #include "chrome/renderer/extensions/automation_internal_custom_bindings.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/thread_task_runner_handle.h"
10 #include "base/values.h"
11 #include "chrome/common/extensions/chrome_extension_messages.h"
12 #include "chrome/common/extensions/manifest_handlers/automation.h"
13 #include "content/public/renderer/render_frame.h"
14 #include "content/public/renderer/render_thread.h"
15 #include "content/public/renderer/render_view.h"
16 #include "extensions/common/extension.h"
17 #include "extensions/common/manifest.h"
18 #include "extensions/renderer/script_context.h"
19 #include "ipc/message_filter.h"
20 #include "ui/accessibility/ax_enums.h"
21 #include "ui/accessibility/ax_node.h"
25 // Helper to convert an enum to a V8 object.
26 template <typename EnumType
>
27 v8::Local
<v8::Object
> ToEnumObject(v8::Isolate
* isolate
,
30 v8::Local
<v8::Object
> object
= v8::Object::New(isolate
);
31 for (int i
= start_after
+ 1; i
<= end_at
; ++i
) {
32 v8::Local
<v8::String
> value
= v8::String::NewFromUtf8(
33 isolate
, ui::ToString(static_cast<EnumType
>(i
)).c_str());
34 object
->Set(value
, value
);
41 namespace extensions
{
43 TreeCache::TreeCache() {}
44 TreeCache::~TreeCache() {}
46 class AutomationMessageFilter
: public IPC::MessageFilter
{
48 explicit AutomationMessageFilter(AutomationInternalCustomBindings
* owner
)
52 content::RenderThread::Get()->AddFilter(this);
53 task_runner_
= base::ThreadTaskRunnerHandle::Get();
62 bool OnMessageReceived(const IPC::Message
& message
) override
{
63 task_runner_
->PostTask(
66 &AutomationMessageFilter::OnMessageReceivedOnRenderThread
,
69 // Always return false in case there are multiple
70 // AutomationInternalCustomBindings instances attached to the same thread.
74 void OnFilterRemoved() override
{
79 void OnMessageReceivedOnRenderThread(const IPC::Message
& message
) {
81 owner_
->OnMessageReceived(message
);
84 ~AutomationMessageFilter() override
{
91 content::RenderThread::Get()->RemoveFilter(this);
95 AutomationInternalCustomBindings
* owner_
;
97 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner_
;
99 DISALLOW_COPY_AND_ASSIGN(AutomationMessageFilter
);
102 AutomationInternalCustomBindings::AutomationInternalCustomBindings(
103 ScriptContext
* context
) : ObjectBackedNativeHandler(context
) {
104 // It's safe to use base::Unretained(this) here because these bindings
105 // will only be called on a valid AutomationInternalCustomBindings instance
106 // and none of the functions have any side effects.
107 #define ROUTE_FUNCTION(FN) \
109 base::Bind(&AutomationInternalCustomBindings::FN, \
110 base::Unretained(this)))
112 ROUTE_FUNCTION(IsInteractPermitted
);
113 ROUTE_FUNCTION(GetSchemaAdditions
);
114 ROUTE_FUNCTION(GetRoutingID
);
115 ROUTE_FUNCTION(StartCachingAccessibilityTrees
);
116 ROUTE_FUNCTION(DestroyAccessibilityTree
);
117 ROUTE_FUNCTION(GetRootID
);
118 ROUTE_FUNCTION(GetParentID
);
119 ROUTE_FUNCTION(GetChildCount
);
120 ROUTE_FUNCTION(GetChildIDAtIndex
);
121 ROUTE_FUNCTION(GetIndexInParent
);
122 ROUTE_FUNCTION(GetState
);
123 ROUTE_FUNCTION(GetRole
);
124 ROUTE_FUNCTION(GetLocation
);
125 ROUTE_FUNCTION(GetStringAttribute
);
126 ROUTE_FUNCTION(GetBoolAttribute
);
127 ROUTE_FUNCTION(GetIntAttribute
);
128 ROUTE_FUNCTION(GetFloatAttribute
);
129 ROUTE_FUNCTION(GetIntListAttribute
);
130 ROUTE_FUNCTION(GetHtmlAttribute
);
132 #undef ROUTE_FUNCTION
135 AutomationInternalCustomBindings::~AutomationInternalCustomBindings() {
137 message_filter_
->Detach();
138 STLDeleteContainerPairSecondPointers(tree_id_to_tree_cache_map_
.begin(),
139 tree_id_to_tree_cache_map_
.end());
142 void AutomationInternalCustomBindings::OnMessageReceived(
143 const IPC::Message
& message
) {
144 IPC_BEGIN_MESSAGE_MAP(AutomationInternalCustomBindings
, message
)
145 IPC_MESSAGE_HANDLER(ExtensionMsg_AccessibilityEvent
, OnAccessibilityEvent
)
146 IPC_END_MESSAGE_MAP()
149 void AutomationInternalCustomBindings::IsInteractPermitted(
150 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
151 const Extension
* extension
= context()->extension();
153 const AutomationInfo
* automation_info
= AutomationInfo::Get(extension
);
154 CHECK(automation_info
);
155 args
.GetReturnValue().Set(
156 v8::Boolean::New(GetIsolate(), automation_info
->interact
));
159 void AutomationInternalCustomBindings::GetRoutingID(
160 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
161 int routing_id
= context()->GetRenderFrame()->GetRenderView()->GetRoutingID();
162 args
.GetReturnValue().Set(v8::Integer::New(GetIsolate(), routing_id
));
165 void AutomationInternalCustomBindings::StartCachingAccessibilityTrees(
166 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
167 if (!message_filter_
)
168 message_filter_
= new AutomationMessageFilter(this);
171 void AutomationInternalCustomBindings::GetSchemaAdditions(
172 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
173 v8::Local
<v8::Object
> additions
= v8::Object::New(GetIsolate());
176 v8::String::NewFromUtf8(GetIsolate(), "EventType"),
177 ToEnumObject(GetIsolate(), ui::AX_EVENT_NONE
, ui::AX_EVENT_LAST
));
180 v8::String::NewFromUtf8(GetIsolate(), "RoleType"),
181 ToEnumObject(GetIsolate(), ui::AX_ROLE_NONE
, ui::AX_ROLE_LAST
));
184 v8::String::NewFromUtf8(GetIsolate(), "StateType"),
185 ToEnumObject(GetIsolate(), ui::AX_STATE_NONE
, ui::AX_STATE_LAST
));
188 v8::String::NewFromUtf8(GetIsolate(), "TreeChangeType"),
189 ToEnumObject(GetIsolate(), ui::AX_MUTATION_NONE
, ui::AX_MUTATION_LAST
));
191 args
.GetReturnValue().Set(additions
);
194 void AutomationInternalCustomBindings::DestroyAccessibilityTree(
195 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
196 if (args
.Length() != 1 || !args
[0]->IsNumber()) {
197 ThrowInvalidArgumentsException(args
);
201 int tree_id
= args
[0]->Int32Value();
202 auto iter
= tree_id_to_tree_cache_map_
.find(tree_id
);
203 if (iter
== tree_id_to_tree_cache_map_
.end())
206 TreeCache
* cache
= iter
->second
;
207 tree_id_to_tree_cache_map_
.erase(tree_id
);
208 axtree_to_tree_cache_map_
.erase(&cache
->tree
);
213 // Access the cached accessibility trees and properties of their nodes.
216 void AutomationInternalCustomBindings::GetRootID(
217 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
218 if (args
.Length() != 1 || !args
[0]->IsNumber()) {
219 ThrowInvalidArgumentsException(args
);
223 int tree_id
= args
[0]->Int32Value();
224 const auto iter
= tree_id_to_tree_cache_map_
.find(tree_id
);
225 if (iter
== tree_id_to_tree_cache_map_
.end())
228 TreeCache
* cache
= iter
->second
;
229 ui::AXNode
* root
= cache
->tree
.root();
231 // The root can be null if this is called from an onTreeChange callback.
235 int root_id
= root
->id();
236 args
.GetReturnValue().Set(v8::Integer::New(GetIsolate(), root_id
));
239 void AutomationInternalCustomBindings::GetParentID(
240 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
241 ui::AXNode
* node
= nullptr;
242 if (!GetNodeHelper(args
, nullptr, &node
))
248 int parent_id
= node
->parent()->id();
249 args
.GetReturnValue().Set(v8::Integer::New(GetIsolate(), parent_id
));
252 void AutomationInternalCustomBindings::GetChildCount(
253 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
254 ui::AXNode
* node
= nullptr;
255 if (!GetNodeHelper(args
, nullptr, &node
))
258 int child_count
= node
->child_count();
259 args
.GetReturnValue().Set(v8::Integer::New(GetIsolate(), child_count
));
262 void AutomationInternalCustomBindings::GetChildIDAtIndex(
263 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
264 if (args
.Length() < 3 || !args
[2]->IsNumber()) {
265 ThrowInvalidArgumentsException(args
);
269 ui::AXNode
* node
= nullptr;
270 if (!GetNodeHelper(args
, nullptr, &node
))
273 int index
= args
[2]->Int32Value();
274 if (index
< 0 || index
>= node
->child_count())
277 int child_id
= node
->children()[index
]->id();
278 args
.GetReturnValue().Set(v8::Integer::New(GetIsolate(), child_id
));
281 void AutomationInternalCustomBindings::GetIndexInParent(
282 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
283 ui::AXNode
* node
= nullptr;
284 if (!GetNodeHelper(args
, nullptr, &node
))
287 int index_in_parent
= node
->index_in_parent();
288 args
.GetReturnValue().Set(v8::Integer::New(GetIsolate(), index_in_parent
));
291 void AutomationInternalCustomBindings::GetState(
292 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
293 ui::AXNode
* node
= nullptr;
294 if (!GetNodeHelper(args
, nullptr, &node
))
297 v8::Local
<v8::Object
> state(v8::Object::New(GetIsolate()));
298 uint32 state_pos
= 0, state_shifter
= node
->data().state
;
299 while (state_shifter
) {
300 if (state_shifter
& 1) {
301 std::string key
= ToString(static_cast<ui::AXState
>(state_pos
));
302 state
->Set(CreateV8String(key
),
303 v8::Boolean::New(GetIsolate(), true));
305 state_shifter
= state_shifter
>> 1;
309 args
.GetReturnValue().Set(state
);
312 void AutomationInternalCustomBindings::GetRole(
313 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
314 ui::AXNode
* node
= nullptr;
315 if (!GetNodeHelper(args
, nullptr, &node
))
318 std::string role_name
= ui::ToString(node
->data().role
);
319 args
.GetReturnValue().Set(
320 v8::String::NewFromUtf8(GetIsolate(), role_name
.c_str()));
323 void AutomationInternalCustomBindings::GetLocation(
324 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
326 ui::AXNode
* node
= nullptr;
327 if (!GetNodeHelper(args
, &cache
, &node
))
330 v8::Local
<v8::Object
> location_obj(v8::Object::New(GetIsolate()));
331 gfx::Rect location
= node
->data().location
;
332 location
.Offset(cache
->location_offset
);
333 location_obj
->Set(CreateV8String("left"),
334 v8::Integer::New(GetIsolate(), location
.x()));
335 location_obj
->Set(CreateV8String("top"),
336 v8::Integer::New(GetIsolate(), location
.y()));
337 location_obj
->Set(CreateV8String("width"),
338 v8::Integer::New(GetIsolate(), location
.width()));
339 location_obj
->Set(CreateV8String("height"),
340 v8::Integer::New(GetIsolate(), location
.height()));
341 args
.GetReturnValue().Set(location_obj
);
344 void AutomationInternalCustomBindings::GetStringAttribute(
345 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
346 ui::AXNode
* node
= nullptr;
347 std::string attribute_name
;
348 if (!GetAttributeHelper(args
, &node
, &attribute_name
))
351 ui::AXStringAttribute attribute
= ui::ParseAXStringAttribute(attribute_name
);
352 std::string attr_value
;
353 if (!node
->data().GetStringAttribute(attribute
, &attr_value
))
356 args
.GetReturnValue().Set(
357 v8::String::NewFromUtf8(GetIsolate(), attr_value
.c_str()));
360 void AutomationInternalCustomBindings::GetBoolAttribute(
361 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
362 ui::AXNode
* node
= nullptr;
363 std::string attribute_name
;
364 if (!GetAttributeHelper(args
, &node
, &attribute_name
))
367 ui::AXBoolAttribute attribute
= ui::ParseAXBoolAttribute(attribute_name
);
369 if (!node
->data().GetBoolAttribute(attribute
, &attr_value
))
372 args
.GetReturnValue().Set(v8::Boolean::New(GetIsolate(), attr_value
));
375 void AutomationInternalCustomBindings::GetIntAttribute(
376 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
377 ui::AXNode
* node
= nullptr;
378 std::string attribute_name
;
379 if (!GetAttributeHelper(args
, &node
, &attribute_name
))
382 ui::AXIntAttribute attribute
= ui::ParseAXIntAttribute(attribute_name
);
384 if (!node
->data().GetIntAttribute(attribute
, &attr_value
))
387 args
.GetReturnValue().Set(v8::Integer::New(GetIsolate(), attr_value
));
390 void AutomationInternalCustomBindings::GetFloatAttribute(
391 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
392 ui::AXNode
* node
= nullptr;
393 std::string attribute_name
;
394 if (!GetAttributeHelper(args
, &node
, &attribute_name
))
397 ui::AXFloatAttribute attribute
= ui::ParseAXFloatAttribute(attribute_name
);
400 if (!node
->data().GetFloatAttribute(attribute
, &attr_value
))
403 args
.GetReturnValue().Set(v8::Number::New(GetIsolate(), attr_value
));
406 void AutomationInternalCustomBindings::GetIntListAttribute(
407 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
408 ui::AXNode
* node
= nullptr;
409 std::string attribute_name
;
410 if (!GetAttributeHelper(args
, &node
, &attribute_name
))
413 ui::AXIntListAttribute attribute
=
414 ui::ParseAXIntListAttribute(attribute_name
);
415 if (!node
->data().HasIntListAttribute(attribute
))
417 const std::vector
<int32
>& attr_value
=
418 node
->data().GetIntListAttribute(attribute
);
420 v8::Local
<v8::Array
> result(v8::Array::New(GetIsolate(), attr_value
.size()));
421 for (size_t i
= 0; i
< attr_value
.size(); ++i
)
422 result
->Set(static_cast<uint32
>(i
),
423 v8::Integer::New(GetIsolate(), attr_value
[i
]));
424 args
.GetReturnValue().Set(result
);
427 void AutomationInternalCustomBindings::GetHtmlAttribute(
428 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
429 ui::AXNode
* node
= nullptr;
430 std::string attribute_name
;
431 if (!GetAttributeHelper(args
, &node
, &attribute_name
))
434 std::string attr_value
;
435 if (!node
->data().GetHtmlAttribute(attribute_name
.c_str(), &attr_value
))
438 args
.GetReturnValue().Set(
439 v8::String::NewFromUtf8(GetIsolate(), attr_value
.c_str()));
446 void AutomationInternalCustomBindings::ThrowInvalidArgumentsException(
447 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
448 GetIsolate()->ThrowException(
449 v8::String::NewFromUtf8(
451 "Invalid arguments to AutomationInternalCustomBindings function",
452 v8::NewStringType::kNormal
).ToLocalChecked());
455 << "Invalid arguments to AutomationInternalCustomBindings function"
456 << context()->GetStackTraceAsString();
459 bool AutomationInternalCustomBindings::GetNodeHelper(
460 const v8::FunctionCallbackInfo
<v8::Value
>& args
,
461 TreeCache
** out_cache
,
462 ui::AXNode
** out_node
) {
463 if (args
.Length() < 2 || !args
[0]->IsNumber() || !args
[1]->IsNumber()) {
464 ThrowInvalidArgumentsException(args
);
468 int tree_id
= args
[0]->Int32Value();
469 int node_id
= args
[1]->Int32Value();
471 const auto iter
= tree_id_to_tree_cache_map_
.find(tree_id
);
472 if (iter
== tree_id_to_tree_cache_map_
.end())
475 TreeCache
* cache
= iter
->second
;
476 ui::AXNode
* node
= cache
->tree
.GetFromId(node_id
);
483 return node
!= nullptr;
486 bool AutomationInternalCustomBindings::GetAttributeHelper(
487 const v8::FunctionCallbackInfo
<v8::Value
>& args
,
488 ui::AXNode
** out_node
,
489 std::string
* out_attribute_name
) {
490 if (args
.Length() != 3 ||
491 !args
[2]->IsString()) {
492 ThrowInvalidArgumentsException(args
);
496 TreeCache
* cache
= nullptr;
497 if (!GetNodeHelper(args
, &cache
, out_node
))
500 *out_attribute_name
= *v8::String::Utf8Value(args
[2]);
504 v8::Local
<v8::Value
> AutomationInternalCustomBindings::CreateV8String(
506 return v8::String::NewFromUtf8(
507 GetIsolate(), str
, v8::String::kNormalString
, strlen(str
));
510 v8::Local
<v8::Value
> AutomationInternalCustomBindings::CreateV8String(
511 const std::string
& str
) {
512 return v8::String::NewFromUtf8(
513 GetIsolate(), str
.c_str(), v8::String::kNormalString
, str
.length());
517 // Handle accessibility events from the browser process.
520 void AutomationInternalCustomBindings::OnAccessibilityEvent(
521 const ExtensionMsg_AccessibilityEventParams
& params
) {
522 int tree_id
= params
.tree_id
;
524 auto iter
= tree_id_to_tree_cache_map_
.find(tree_id
);
525 if (iter
== tree_id_to_tree_cache_map_
.end()) {
526 cache
= new TreeCache();
528 cache
->tree_id
= params
.tree_id
;
529 cache
->tree
.SetDelegate(this);
530 tree_id_to_tree_cache_map_
.insert(std::make_pair(tree_id
, cache
));
531 axtree_to_tree_cache_map_
.insert(std::make_pair(&cache
->tree
, cache
));
533 cache
= iter
->second
;
536 cache
->location_offset
= params
.location_offset
;
537 if (!cache
->tree
.Unserialize(params
.update
)) {
538 LOG(ERROR
) << cache
->tree
.error();
542 v8::HandleScope
handle_scope(GetIsolate());
543 v8::Context::Scope
context_scope(context()->v8_context());
544 v8::Local
<v8::Array
> args(v8::Array::New(GetIsolate(), 1U));
545 v8::Local
<v8::Object
> event_params(v8::Object::New(GetIsolate()));
546 event_params
->Set(CreateV8String("treeID"),
547 v8::Integer::New(GetIsolate(), params
.tree_id
));
548 event_params
->Set(CreateV8String("targetID"),
549 v8::Integer::New(GetIsolate(), params
.id
));
550 event_params
->Set(CreateV8String("eventType"),
551 CreateV8String(ToString(params
.event_type
)));
552 args
->Set(0U, event_params
);
553 context()->DispatchEvent("automationInternal.onAccessibilityEvent", args
);
556 void AutomationInternalCustomBindings::OnNodeWillBeDeleted(ui::AXTree
* tree
,
559 api::automation::TREE_CHANGE_TYPE_NODEREMOVED
,
563 void AutomationInternalCustomBindings::OnSubtreeWillBeDeleted(
566 // This isn't strictly needed, as OnNodeWillBeDeleted will already be
567 // called. We could send a JS event for this only if it turns out to
568 // be needed for something.
571 void AutomationInternalCustomBindings::OnNodeCreated(ui::AXTree
* tree
,
573 // Not needed, this is called in the middle of an update so it's not
574 // safe to trigger JS from here. Wait for the notification in
575 // OnAtomicUpdateFinished instead.
578 void AutomationInternalCustomBindings::OnNodeChanged(ui::AXTree
* tree
,
580 // Not needed, this is called in the middle of an update so it's not
581 // safe to trigger JS from here. Wait for the notification in
582 // OnAtomicUpdateFinished instead.
585 void AutomationInternalCustomBindings::OnAtomicUpdateFinished(
588 const std::vector
<ui::AXTreeDelegate::Change
>& changes
) {
589 auto iter
= axtree_to_tree_cache_map_
.find(tree
);
590 if (iter
== axtree_to_tree_cache_map_
.end())
593 for (auto change
: changes
) {
594 ui::AXNode
* node
= change
.node
;
595 switch (change
.type
) {
598 api::automation::TREE_CHANGE_TYPE_NODECREATED
,
601 case SUBTREE_CREATED
:
603 api::automation::TREE_CHANGE_TYPE_SUBTREECREATED
,
608 api::automation::TREE_CHANGE_TYPE_NODECHANGED
,
615 void AutomationInternalCustomBindings::SendTreeChangeEvent(
616 api::automation::TreeChangeType change_type
,
619 auto iter
= axtree_to_tree_cache_map_
.find(tree
);
620 if (iter
== axtree_to_tree_cache_map_
.end())
623 int tree_id
= iter
->second
->tree_id
;
625 v8::HandleScope
handle_scope(GetIsolate());
626 v8::Context::Scope
context_scope(context()->v8_context());
627 v8::Local
<v8::Array
> args(v8::Array::New(GetIsolate(), 3U));
628 args
->Set(0U, v8::Integer::New(GetIsolate(), tree_id
));
629 args
->Set(1U, v8::Integer::New(GetIsolate(), node
->id()));
630 args
->Set(2U, CreateV8String(ToString(change_type
)));
631 context()->DispatchEvent("automationInternal.onTreeChange", args
);
634 } // namespace extensions