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

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

Issue 1458723002: Finish implementing ChromeVox Next active indicator. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@load_key_map
Patch Set: Made test expectations approximate Created 5 years, 1 month 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 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()) {

Powered by Google App Engine
This is Rietveld 408576698