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 e68edce85d3ff451f7ef206e19c291edfaf64043..c10c21145ba4dd5e91a41b44f9e6c665881364e9 100644 |
--- a/chrome/renderer/extensions/automation_internal_custom_bindings.cc |
+++ b/chrome/renderer/extensions/automation_internal_custom_bindings.cc |
@@ -393,7 +393,10 @@ private: |
AutomationInternalCustomBindings::AutomationInternalCustomBindings( |
ScriptContext* context) |
- : ObjectBackedNativeHandler(context), is_active_profile_(true) { |
+ : ObjectBackedNativeHandler(context), |
+ is_active_profile_(true), |
+ tree_change_observer_overall_filter_( |
+ api::automation::TREE_CHANGE_OBSERVER_FILTER_NOTREECHANGES) { |
// 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. |
@@ -406,6 +409,8 @@ AutomationInternalCustomBindings::AutomationInternalCustomBindings( |
ROUTE_FUNCTION(GetRoutingID); |
ROUTE_FUNCTION(StartCachingAccessibilityTrees); |
ROUTE_FUNCTION(DestroyAccessibilityTree); |
+ ROUTE_FUNCTION(AddTreeChangeObserver); |
+ ROUTE_FUNCTION(RemoveTreeChangeObserver); |
ROUTE_FUNCTION(GetChildIDAtIndex); |
#undef ROUTE_FUNCTION |
@@ -732,6 +737,51 @@ void AutomationInternalCustomBindings::DestroyAccessibilityTree( |
delete cache; |
} |
+void AutomationInternalCustomBindings::AddTreeChangeObserver( |
+ const v8::FunctionCallbackInfo<v8::Value>& args) { |
+ if (args.Length() != 2 || !args[0]->IsNumber() || !args[1]->IsString()) { |
+ ThrowInvalidArgumentsException(this); |
+ return; |
+ } |
+ |
+ TreeChangeObserver observer; |
+ observer.id = args[0]->Int32Value(); |
+ std::string filter_str = *v8::String::Utf8Value(args[1]); |
+ observer.filter = api::automation::ParseTreeChangeObserverFilter(filter_str); |
+ |
+ tree_change_observers_.push_back(observer); |
+ UpdateOverallTreeChangeObserverFilter(); |
+} |
+ |
+void AutomationInternalCustomBindings::RemoveTreeChangeObserver( |
+ const v8::FunctionCallbackInfo<v8::Value>& args) { |
+ if (args.Length() != 1 || !args[0]->IsNumber()) { |
+ ThrowInvalidArgumentsException(this); |
+ return; |
+ } |
+ |
+ int observer_id = args[0]->Int32Value(); |
+ |
+ for (auto iter = tree_change_observers_.begin(); |
+ iter != tree_change_observers_.end(); ++iter) { |
+ if (iter->id == observer_id) { |
+ tree_change_observers_.erase(iter); |
+ break; |
+ } |
+ } |
+ |
+ UpdateOverallTreeChangeObserverFilter(); |
+} |
+ |
+void AutomationInternalCustomBindings::UpdateOverallTreeChangeObserverFilter() { |
+ tree_change_observer_overall_filter_ = |
+ api::automation::TREE_CHANGE_OBSERVER_FILTER_NOTREECHANGES; |
+ for (const auto& observer : tree_change_observers_) { |
+ tree_change_observer_overall_filter_ = |
+ std::max(observer.filter, tree_change_observer_overall_filter_); |
+ } |
+} |
+ |
void AutomationInternalCustomBindings::RouteTreeIDFunction( |
const std::string& name, |
TreeIDFunction callback) { |
@@ -816,15 +866,19 @@ void AutomationInternalCustomBindings::OnAccessibilityEvent( |
// Update the internal state whether it's the active profile or not. |
cache->location_offset = params.location_offset; |
+ deleted_node_ids_.clear(); |
if (!cache->tree.Unserialize(params.update)) { |
LOG(ERROR) << cache->tree.error(); |
return; |
} |
- // Don't send the event if it's not the active profile. |
+ // Don't send any events if it's not the active profile. |
if (!is_active_profile) |
return; |
+ SendNodesRemovedEvent(&cache->tree, deleted_node_ids_); |
+ deleted_node_ids_.clear(); |
+ |
v8::Isolate* isolate = GetIsolate(); |
v8::HandleScope handle_scope(isolate); |
v8::Context::Scope context_scope(context()->v8_context()); |
@@ -847,6 +901,7 @@ void AutomationInternalCustomBindings::OnNodeWillBeDeleted(ui::AXTree* tree, |
SendTreeChangeEvent( |
api::automation::TREE_CHANGE_TYPE_NODEREMOVED, |
tree, node); |
+ deleted_node_ids_.push_back(node->id()); |
} |
void AutomationInternalCustomBindings::OnSubtreeWillBeDeleted( |
@@ -909,6 +964,62 @@ void AutomationInternalCustomBindings::SendTreeChangeEvent( |
if (!is_active_profile_) |
return; |
+ // Always notify the custom bindings when there's a node with a child tree |
+ // ID that might need to be loaded. |
+ if (node->data().HasIntAttribute(ui::AX_ATTR_CHILD_TREE_ID)) |
+ SendChildTreeIDEvent(tree, node); |
+ |
+ switch (tree_change_observer_overall_filter_) { |
+ case api::automation::TREE_CHANGE_OBSERVER_FILTER_NOTREECHANGES: |
+ default: |
+ return; |
+ case api::automation::TREE_CHANGE_OBSERVER_FILTER_LIVEREGIONTREECHANGES: |
+ if (!node->data().HasStringAttribute(ui::AX_ATTR_CONTAINER_LIVE_STATUS) && |
+ node->data().role != ui::AX_ROLE_ALERT) { |
+ return; |
+ } |
+ break; |
+ case api::automation::TREE_CHANGE_OBSERVER_FILTER_ALLTREECHANGES: |
+ break; |
+ } |
+ |
+ 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; |
+ |
+ v8::Isolate* isolate = GetIsolate(); |
+ v8::HandleScope handle_scope(isolate); |
+ v8::Context::Scope context_scope(context()->v8_context()); |
+ |
+ for (const auto& observer : tree_change_observers_) { |
+ switch (observer.filter) { |
+ case api::automation::TREE_CHANGE_OBSERVER_FILTER_NOTREECHANGES: |
+ default: |
+ continue; |
+ case api::automation::TREE_CHANGE_OBSERVER_FILTER_LIVEREGIONTREECHANGES: |
+ if (!node->data().HasStringAttribute( |
+ ui::AX_ATTR_CONTAINER_LIVE_STATUS) && |
+ node->data().role != ui::AX_ROLE_ALERT) { |
+ continue; |
+ } |
+ break; |
+ case api::automation::TREE_CHANGE_OBSERVER_FILTER_ALLTREECHANGES: |
+ break; |
+ } |
+ |
+ v8::Local<v8::Array> args(v8::Array::New(GetIsolate(), 4U)); |
+ args->Set(0U, v8::Integer::New(GetIsolate(), observer.id)); |
+ args->Set(1U, v8::Integer::New(GetIsolate(), tree_id)); |
+ args->Set(2U, v8::Integer::New(GetIsolate(), node->id())); |
+ args->Set(3U, CreateV8String(isolate, ToString(change_type))); |
+ context()->DispatchEvent("automationInternal.onTreeChange", args); |
+ } |
+} |
+ |
+void AutomationInternalCustomBindings::SendChildTreeIDEvent(ui::AXTree* tree, |
+ ui::AXNode* node) { |
auto iter = axtree_to_tree_cache_map_.find(tree); |
if (iter == axtree_to_tree_cache_map_.end()) |
return; |
@@ -918,11 +1029,31 @@ void AutomationInternalCustomBindings::SendTreeChangeEvent( |
v8::Isolate* isolate = GetIsolate(); |
v8::HandleScope handle_scope(isolate); |
v8::Context::Scope context_scope(context()->v8_context()); |
- v8::Local<v8::Array> args(v8::Array::New(GetIsolate(), 3U)); |
+ v8::Local<v8::Array> args(v8::Array::New(GetIsolate(), 2U)); |
args->Set(0U, v8::Integer::New(GetIsolate(), tree_id)); |
args->Set(1U, v8::Integer::New(GetIsolate(), node->id())); |
- args->Set(2U, CreateV8String(isolate, ToString(change_type))); |
- context()->DispatchEvent("automationInternal.onTreeChange", args); |
+ context()->DispatchEvent("automationInternal.onChildTreeID", args); |
+} |
+ |
+void AutomationInternalCustomBindings::SendNodesRemovedEvent( |
+ ui::AXTree* tree, |
+ const std::vector<int>& ids) { |
+ 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; |
+ |
+ v8::Isolate* isolate = GetIsolate(); |
+ v8::HandleScope handle_scope(isolate); |
+ v8::Context::Scope context_scope(context()->v8_context()); |
+ v8::Local<v8::Array> args(v8::Array::New(GetIsolate(), 2U)); |
+ args->Set(0U, v8::Integer::New(GetIsolate(), tree_id)); |
+ v8::Local<v8::Array> nodes(v8::Array::New(GetIsolate(), ids.size())); |
+ args->Set(1U, nodes); |
+ for (size_t i = 0; i < ids.size(); ++i) |
+ nodes->Set(i, v8::Integer::New(GetIsolate(), ids[i])); |
+ context()->DispatchEvent("automationInternal.onNodesRemoved", args); |
} |
} // namespace extensions |