| 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..e68edce85d3ff451f7ef206e19c291edfaf64043 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,52 @@ 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 <= end && end <= len) {
|
| + int start_offset = start > 0 ? character_offsets[start - 1] : 0;
|
| + int end_offset = end > 0 ? character_offsets[end - 1] : 0;
|
| +
|
| + switch (node->data().GetIntAttribute(ui::AX_ATTR_TEXT_DIRECTION)) {
|
| + case ui::AX_TEXT_DIRECTION_LTR:
|
| + default:
|
| + location.set_x(location.x() + start_offset);
|
| + location.set_width(end_offset - start_offset);
|
| + break;
|
| + case ui::AX_TEXT_DIRECTION_RTL:
|
| + location.set_x(location.x() + location.width() - end_offset);
|
| + location.set_width(end_offset - start_offset);
|
| + break;
|
| + case ui::AX_TEXT_DIRECTION_TTB:
|
| + location.set_y(location.y() + start_offset);
|
| + location.set_height(end_offset - start_offset);
|
| + break;
|
| + case ui::AX_TEXT_DIRECTION_BTT:
|
| + location.set_y(location.y() + location.height() - end_offset);
|
| + location.set_height(end_offset - start_offset);
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + result.Set(RectToV8Object(isolate, location));
|
| });
|
|
|
| // Bindings that take a Tree ID and Node ID and string attribute name
|
| @@ -592,6 +754,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()) {
|
|
|