Chromium Code Reviews| 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 0ecab81397b8b05c932a459ba20d7a77358b9463..fee3ae11a570602987b9b8075eedf6052cff27ac 100644 |
| --- a/chrome/renderer/extensions/automation_internal_custom_bindings.cc |
| +++ b/chrome/renderer/extensions/automation_internal_custom_bindings.cc |
| @@ -63,6 +63,81 @@ v8::Local<v8::Value> CreateV8String(v8::Isolate* isolate, |
| v8::String::kNormalString, str.length()); |
| } |
| +v8::Local<v8::Object> RectToV8Object(v8::Isolate* isolate, |
| + const gfx::Rect& rect) { |
| + v8::Local<v8::Object> result(v8::Object::New(isolate)); |
| + result->Set(CreateV8String(isolate, "left"), |
| + v8::Integer::New(isolate, rect.x())); |
| + result->Set(CreateV8String(isolate, "top"), |
| + v8::Integer::New(isolate, rect.y())); |
| + result->Set(CreateV8String(isolate, "width"), |
| + v8::Integer::New(isolate, rect.width())); |
| + result->Set(CreateV8String(isolate, "height"), |
| + v8::Integer::New(isolate, rect.height())); |
| + return result; |
| +} |
| + |
| +// Compute the bounding box of a node, fixing nodes with empty bounds by |
| +// unioning the bounds of their children. |
| +static gfx::Rect ComputeLocalNodeBounds(TreeCache* cache, ui::AXNode* node) { |
| + gfx::Rect bounds = node->data().location; |
| + if (bounds.width() > 0 && bounds.height() > 0) |
| + return bounds; |
| + |
| + // Compute the bounds of each child. |
| + for (size_t i = 0; i < node->children().size(); i++) { |
| + ui::AXNode* child = node->children()[i]; |
| + gfx::Rect child_bounds = ComputeLocalNodeBounds(cache, child); |
| + |
| + // Ignore children that don't have valid bounds themselves. |
| + if (child_bounds.width() == 0 || child_bounds.height() == 0) |
| + continue; |
| + |
| + // For the first valid child, just set the bounds to that child's bounds. |
| + if (bounds.width() == 0 || bounds.height() == 0) { |
| + bounds = child_bounds; |
| + continue; |
| + } |
| + |
| + // Union each additional child's bounds. |
| + bounds.Union(child_bounds); |
| + } |
| + |
| + return bounds; |
| +} |
| + |
| +// Compute the bounding box of a node in global coordinates, walking up the |
| +// parent hierarchy to offset by frame offsets and scroll offsets. |
| +static gfx::Rect ComputeGlobalNodeBounds(TreeCache* cache, ui::AXNode* node) { |
| + gfx::Rect bounds = ComputeLocalNodeBounds(cache, node); |
| + ui::AXNode* parent = node->parent(); |
| + bool need_to_offset_web_area = node->data().role == ui::AX_ROLE_WEB_AREA || |
| + node->data().role == ui::AX_ROLE_ROOT_WEB_AREA; |
| + while (parent) { |
| + if (bounds.IsEmpty()) { |
| + bounds = parent->data().location; |
| + } else if (need_to_offset_web_area && parent->data().location.width() > 0 && |
| + parent->data().location.height() > 0) { |
| + bounds.Offset(parent->data().location.x(), parent->data().location.y()); |
| + need_to_offset_web_area = false; |
| + } |
| + |
| + if (parent->data().role == ui::AX_ROLE_WEB_AREA || |
| + parent->data().role == ui::AX_ROLE_ROOT_WEB_AREA) { |
| + int sx = 0; |
| + int sy = 0; |
| + if (parent->data().GetIntAttribute(ui::AX_ATTR_SCROLL_X, &sx) && |
| + parent->data().GetIntAttribute(ui::AX_ATTR_SCROLL_Y, &sy)) { |
| + bounds.Offset(-sx, -sy); |
| + } |
| + need_to_offset_web_area = true; |
| + } |
| + parent = parent->parent(); |
| + } |
| + |
| + return bounds; |
| +} |
| + |
| // |
| // Helper class that helps implement bindings for a JavaScript function |
| // that takes a single input argument consisting of a Tree ID. Looks up |
| @@ -202,6 +277,59 @@ class NodeIDPlusAttributeWrapper |
| NodeIDPlusAttributeFunction function_; |
| }; |
| +// |
| +// Helper class that helps implement bindings for a JavaScript function |
| +// that takes four input arguments: a tree ID, node ID, and integer start |
| +// and end indices. Looks up the TreeCache and the AXNode and passes them |
| +// to the function passed to the constructor. |
| +// |
| + |
| +typedef void (*NodeIDPlusRangeFunction)(v8::Isolate* isolate, |
| + v8::ReturnValue<v8::Value> result, |
| + TreeCache* cache, |
| + ui::AXNode* node, |
| + int start, |
| + int end); |
| + |
| +class NodeIDPlusRangeWrapper |
| + : public base::RefCountedThreadSafe<NodeIDPlusRangeWrapper> { |
| + public: |
| + NodeIDPlusRangeWrapper(AutomationInternalCustomBindings* automation_bindings, |
| + NodeIDPlusRangeFunction function) |
| + : automation_bindings_(automation_bindings), function_(function) {} |
| + |
| + void Run(const v8::FunctionCallbackInfo<v8::Value>& args) { |
| + v8::Isolate* isolate = automation_bindings_->GetIsolate(); |
| + if (args.Length() < 4 || !args[0]->IsNumber() || !args[1]->IsNumber() || |
| + !args[2]->IsNumber() || !args[3]->IsNumber()) { |
| + ThrowInvalidArgumentsException(automation_bindings_); |
| + } |
| + |
| + int tree_id = args[0]->Int32Value(); |
| + int node_id = args[1]->Int32Value(); |
| + int start = args[2]->Int32Value(); |
| + int end = args[3]->Int32Value(); |
| + |
| + TreeCache* cache = automation_bindings_->GetTreeCacheFromTreeID(tree_id); |
| + if (!cache) |
| + return; |
| + |
| + ui::AXNode* node = cache->tree.GetFromId(node_id); |
| + if (!node) |
| + return; |
| + |
| + function_(isolate, args.GetReturnValue(), cache, node, start, end); |
| + } |
| + |
| + private: |
| + virtual ~NodeIDPlusRangeWrapper() {} |
| + |
| + friend class base::RefCountedThreadSafe<NodeIDPlusRangeWrapper>; |
| + |
| + AutomationInternalCustomBindings* automation_bindings_; |
| + NodeIDPlusRangeFunction function_; |
| +}; |
| + |
| } // namespace |
| TreeCache::TreeCache() {} |
| @@ -378,18 +506,35 @@ AutomationInternalCustomBindings::AutomationInternalCustomBindings( |
| RouteNodeIDFunction( |
| "GetLocation", [](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result, |
| TreeCache* cache, ui::AXNode* node) { |
| - v8::Local<v8::Object> location_obj(v8::Object::New(isolate)); |
| - gfx::Rect location = node->data().location; |
| + gfx::Rect location = ComputeGlobalNodeBounds(cache, node); |
| + location.Offset(cache->location_offset); |
| + result.Set(RectToV8Object(isolate, location)); |
| + }); |
| + |
| + // Bindings that take a Tree ID and Node ID and string attribute name |
| + // and return a property of the node. |
| + |
| + RouteNodeIDPlusRangeFunction( |
| + "GetBoundsForRange", |
| + [](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result, |
| + TreeCache* cache, ui::AXNode* node, int start, int end) { |
| + gfx::Rect location = ComputeGlobalNodeBounds(cache, node); |
| location.Offset(cache->location_offset); |
| - location_obj->Set(CreateV8String(isolate, "left"), |
| - v8::Integer::New(isolate, location.x())); |
| - location_obj->Set(CreateV8String(isolate, "top"), |
| - v8::Integer::New(isolate, location.y())); |
| - location_obj->Set(CreateV8String(isolate, "width"), |
| - v8::Integer::New(isolate, location.width())); |
| - location_obj->Set(CreateV8String(isolate, "height"), |
| - v8::Integer::New(isolate, location.height())); |
| - result.Set(location_obj); |
| + if (node->data().role == ui::AX_ROLE_INLINE_TEXT_BOX) { |
| + std::string name = node->data().GetStringAttribute(ui::AX_ATTR_NAME); |
| + std::vector<int> character_offsets = |
| + node->data().GetIntListAttribute(ui::AX_ATTR_CHARACTER_OFFSETS); |
| + int len = |
| + static_cast<int>(std::min(name.size(), character_offsets.size())); |
| + if (start >= 0 && start <= len && end >= 0 && end <= len && |
| + end >= start) { |
| + int start_offset = start > 0 ? character_offsets[start - 1] : 0; |
| + int end_offset = end > 0 ? character_offsets[end - 1] : 0; |
| + location.set_x(location.x() + start_offset); |
|
Peter Lundblad
2015/11/20 11:15:03
Could this need to be extended for top-to-bottom t
dmazzoni
2015/11/20 23:35:14
Yes! We were already exposing this info, wasn't ha
|
| + location.set_width(end_offset - start_offset); |
| + } |
| + } |
| + result.Set(RectToV8Object(isolate, location)); |
| }); |
| // Bindings that take a Tree ID and Node ID and string attribute name |
| @@ -592,6 +737,14 @@ void AutomationInternalCustomBindings::RouteNodeIDPlusAttributeFunction( |
| RouteFunction(name, base::Bind(&NodeIDPlusAttributeWrapper::Run, wrapper)); |
| } |
| +void AutomationInternalCustomBindings::RouteNodeIDPlusRangeFunction( |
| + const std::string& name, |
| + NodeIDPlusRangeFunction callback) { |
| + scoped_refptr<NodeIDPlusRangeWrapper> wrapper = |
| + new NodeIDPlusRangeWrapper(this, callback); |
| + RouteFunction(name, base::Bind(&NodeIDPlusRangeWrapper::Run, wrapper)); |
| +} |
| + |
| void AutomationInternalCustomBindings::GetChildIDAtIndex( |
| const v8::FunctionCallbackInfo<v8::Value>& args) { |
| if (args.Length() < 3 || !args[2]->IsNumber()) { |