accessibility_bridge.cc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. // Copyright 2019 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.
  4. #include "fuchsia_web/webengine/browser/accessibility_bridge.h"
  5. #include <lib/sys/cpp/component_context.h>
  6. #include <lib/sys/inspect/cpp/component.h>
  7. #include <lib/ui/scenic/cpp/view_ref_pair.h>
  8. #include <algorithm>
  9. #include "base/callback.h"
  10. #include "base/fuchsia/fuchsia_logging.h"
  11. #include "base/fuchsia/process_context.h"
  12. #include "base/logging.h"
  13. #include "content/public/browser/global_routing_id.h"
  14. #include "content/public/browser/render_widget_host_view.h"
  15. #include "fuchsia_web/webengine/browser/frame_window_tree_host.h"
  16. #include "ui/accessibility/ax_action_data.h"
  17. #include "ui/gfx/geometry/rect_conversions.h"
  18. namespace {
  19. // TODO(https://crbug.com/973095): Update this value based on average and
  20. // maximum sizes of serialized Semantic Nodes.
  21. constexpr size_t kMaxNodesPerUpdate = 16;
  22. constexpr size_t kMaxNodesPerDelete =
  23. fuchsia::accessibility::semantics::MAX_NODES_PER_UPDATE;
  24. // Error allowed for each edge when converting from gfx::RectF to gfx::Rect.
  25. constexpr float kRectConversionError = 0.5;
  26. // Inspect node/property names.
  27. constexpr char kSemanticTreesInspectNodeName[] = "trees";
  28. constexpr char kSemanticTreeContentsInspectPropertyName[] = "contents";
  29. constexpr char kParentTreeInspectPropertyName[] = "parent_tree";
  30. constexpr char kParentNodeInspectPropertyName[] = "parent_node";
  31. // Returns the id of the offset container for |node|, or the root node id if
  32. // |node| does not specify an offset container.
  33. int32_t GetOffsetContainerId(const ui::AXTree* tree,
  34. const ui::AXNodeData& node_data) {
  35. int32_t offset_container_id = node_data.relative_bounds.offset_container_id;
  36. if (offset_container_id == -1)
  37. return tree->root()->id();
  38. return offset_container_id;
  39. }
  40. } // namespace
  41. AccessibilityBridge::AccessibilityBridge(
  42. fuchsia::accessibility::semantics::SemanticsManager* semantics_manager,
  43. FrameWindowTreeHost* window_tree_host,
  44. content::WebContents* web_contents,
  45. base::OnceCallback<bool(zx_status_t)> on_error_callback,
  46. inspect::Node inspect_node)
  47. : binding_(this),
  48. window_tree_host_(window_tree_host),
  49. web_contents_(web_contents),
  50. on_error_callback_(std::move(on_error_callback)),
  51. inspect_node_(std::move(inspect_node)) {
  52. DCHECK(web_contents_);
  53. Observe(web_contents_);
  54. semantics_manager->RegisterViewForSemantics(
  55. window_tree_host_->CreateViewRef(), binding_.NewBinding(),
  56. semantic_tree_.NewRequest());
  57. semantic_tree_.set_error_handler([this](zx_status_t status) {
  58. ZX_LOG(ERROR, status) << "SemanticTree disconnected";
  59. std::move(on_error_callback_).Run(ZX_ERR_INTERNAL);
  60. });
  61. // Set up inspect node for semantic trees.
  62. inspect_node_tree_dump_ = inspect_node_.CreateLazyNode(
  63. kSemanticTreesInspectNodeName,
  64. [this]() { return fpromise::make_ok_promise(FillInspectData()); });
  65. }
  66. inspect::Inspector AccessibilityBridge::FillInspectData() {
  67. inspect::Inspector inspector;
  68. // Add a node for each AXTree of which the accessibility bridge is aware.
  69. // The output for each tree has the following form:
  70. //
  71. // <tree id>:
  72. // contents: <string representation of tree contents>
  73. // parent_tree: <tree id of this tree's parent, if it has one>
  74. // parent_node: <node id of this tree's parent, if it has one>
  75. for (const auto& ax_tree : ax_trees_) {
  76. const ui::AXTree* ax_tree_ptr = ax_tree.second.get();
  77. inspect::Node inspect_node =
  78. inspector.GetRoot().CreateChild(ax_tree_ptr->GetAXTreeID().ToString());
  79. inspect_node.CreateString(kSemanticTreeContentsInspectPropertyName,
  80. ax_tree_ptr->ToString(), &inspector);
  81. auto tree_id_and_connection =
  82. tree_connections_.find(ax_tree_ptr->GetAXTreeID());
  83. if (tree_id_and_connection != tree_connections_.end()) {
  84. const TreeConnection& connection = tree_id_and_connection->second;
  85. inspect_node.CreateString(kParentTreeInspectPropertyName,
  86. connection.parent_tree_id.ToString(),
  87. &inspector);
  88. inspect_node.CreateUint(kParentNodeInspectPropertyName,
  89. connection.parent_node_id, &inspector);
  90. }
  91. inspector.emplace(std::move(inspect_node));
  92. }
  93. return inspector;
  94. }
  95. AccessibilityBridge::~AccessibilityBridge() {
  96. InterruptPendingActions();
  97. ax_trees_.clear();
  98. }
  99. void AccessibilityBridge::AddNodeToOffsetMapping(
  100. const ui::AXTree* tree,
  101. const ui::AXNodeData& node_data) {
  102. auto ax_tree_id = tree->GetAXTreeID();
  103. auto offset_container_id = GetOffsetContainerId(tree, node_data);
  104. offset_container_children_[std::make_pair(ax_tree_id, offset_container_id)]
  105. .insert(std::make_pair(ax_tree_id, node_data.id));
  106. }
  107. void AccessibilityBridge::RemoveNodeFromOffsetMapping(
  108. const ui::AXTree* tree,
  109. const ui::AXNodeData& node_data) {
  110. auto offset_container_children_it =
  111. offset_container_children_.find(std::make_pair(
  112. tree->GetAXTreeID(), GetOffsetContainerId(tree, node_data)));
  113. if (offset_container_children_it != offset_container_children_.end()) {
  114. offset_container_children_it->second.erase(
  115. std::make_pair(tree->GetAXTreeID(), node_data.id));
  116. }
  117. }
  118. void AccessibilityBridge::TryCommit() {
  119. if (commit_inflight_ || (to_delete_.empty() && to_update_.empty()) ||
  120. ShouldHoldCommit()) {
  121. return;
  122. }
  123. // Deletions come before updates because first the nodes are deleted, and
  124. // then we update the parents to no longer point at them.
  125. for (auto& batch : to_delete_) {
  126. semantic_tree_->DeleteSemanticNodes(std::move(batch));
  127. }
  128. to_delete_.clear();
  129. for (auto& batch : to_update_) {
  130. semantic_tree_->UpdateSemanticNodes(std::move(batch));
  131. }
  132. to_update_.clear();
  133. semantic_tree_->CommitUpdates(
  134. fit::bind_member(this, &AccessibilityBridge::OnCommitComplete));
  135. commit_inflight_ = true;
  136. }
  137. void AccessibilityBridge::OnCommitComplete() {
  138. // TODO(https://crbug.com/1134737): Separate updates of atomic updates and
  139. // don't allow all of them to be in the same commit.
  140. commit_inflight_ = false;
  141. TryCommit();
  142. }
  143. void AccessibilityBridge::AccessibilityEventReceived(
  144. const content::AXEventNotificationDetails& details) {
  145. // No need to process events if Fuchsia is not receiving them.
  146. if (!enable_semantic_updates_)
  147. return;
  148. const auto& id = details.ax_tree_id;
  149. if (!UpdateAXTreeID(id))
  150. return;
  151. auto ax_tree_it = ax_trees_.find(id);
  152. ui::AXSerializableTree* ax_tree;
  153. if (ax_tree_it == ax_trees_.end()) {
  154. auto new_tree = std::make_unique<ui::AXSerializableTree>();
  155. ax_tree = new_tree.get();
  156. ax_tree->AddObserver(this);
  157. ax_trees_[id] = std::move(new_tree);
  158. } else {
  159. ax_tree = ax_tree_it->second.get();
  160. }
  161. // Updates to AXTree must be applied first.
  162. for (const ui::AXTreeUpdate& update : details.updates) {
  163. if (!ax_tree->Unserialize(update)) {
  164. // If this fails, it is a fatal error that will cause an early exit.
  165. std::move(on_error_callback_).Run(ZX_ERR_INTERNAL);
  166. return;
  167. }
  168. }
  169. // Events to fire after tree has been updated.
  170. for (const ui::AXEvent& event : details.events) {
  171. if (event.event_type == ax::mojom::Event::kHitTestResult &&
  172. pending_hit_test_callbacks_.find(event.action_request_id) !=
  173. pending_hit_test_callbacks_.end()) {
  174. fuchsia::accessibility::semantics::Hit hit;
  175. hit.set_node_id(
  176. id_mapper_->ToFuchsiaNodeID(ax_tree->GetAXTreeID(), event.id, false));
  177. // Run the pending callback with the hit.
  178. pending_hit_test_callbacks_[event.action_request_id](std::move(hit));
  179. pending_hit_test_callbacks_.erase(event.action_request_id);
  180. } else if (event_received_callback_for_test_ &&
  181. event.event_type == ax::mojom::Event::kEndOfTest) {
  182. std::move(event_received_callback_for_test_).Run();
  183. }
  184. }
  185. }
  186. void AccessibilityBridge::OnAccessibilityActionRequested(
  187. uint32_t node_id,
  188. fuchsia::accessibility::semantics::Action action,
  189. OnAccessibilityActionRequestedCallback callback) {
  190. ui::AXActionData action_data = ui::AXActionData();
  191. // The requested action is not supported.
  192. if (!ConvertAction(action, &action_data.action)) {
  193. callback(false);
  194. return;
  195. }
  196. auto ax_id = id_mapper_->ToAXNodeID(node_id);
  197. if (!ax_id) {
  198. // Fuchsia is targeting a node that does not exist.
  199. callback(false);
  200. return;
  201. }
  202. action_data.target_node_id = ax_id->second;
  203. if (action == fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN) {
  204. ui::AXNode* node = nullptr;
  205. auto ax_tree_it = ax_trees_.find(ax_id->first);
  206. if (ax_tree_it != ax_trees_.end())
  207. node = ax_tree_it->second->GetFromId(action_data.target_node_id);
  208. if (!node) {
  209. callback(false);
  210. return;
  211. }
  212. // The scroll-to-make-visible action expects coordinates in the local
  213. // coordinate space of |node|. So, we need to translate node's bounds to the
  214. // origin.
  215. auto local_bounds = gfx::ToEnclosedRectIgnoringError(
  216. node->data().relative_bounds.bounds, kRectConversionError);
  217. local_bounds = gfx::Rect(local_bounds.size());
  218. action_data.target_rect = local_bounds;
  219. action_data.horizontal_scroll_alignment =
  220. ax::mojom::ScrollAlignment::kScrollAlignmentCenter;
  221. action_data.vertical_scroll_alignment =
  222. ax::mojom::ScrollAlignment::kScrollAlignmentCenter;
  223. action_data.scroll_behavior = ax::mojom::ScrollBehavior::kScrollIfVisible;
  224. }
  225. auto* frame =
  226. web_contents_->GetPrimaryMainFrame()->FromAXTreeID(ax_id->first);
  227. if (!frame) {
  228. // Fuchsia targeted a tree that does not exist.
  229. callback(false);
  230. return;
  231. }
  232. frame->AccessibilityPerformAction(action_data);
  233. callback(true);
  234. if (event_received_callback_for_test_) {
  235. // Perform an action with a corresponding event to signal the action has
  236. // been pumped through.
  237. action_data.action = ax::mojom::Action::kSignalEndOfTest;
  238. web_contents_->GetPrimaryMainFrame()->AccessibilityPerformAction(
  239. action_data);
  240. }
  241. }
  242. void AccessibilityBridge::HitTest(fuchsia::math::PointF local_point,
  243. HitTestCallback callback) {
  244. ui::AXActionData action_data;
  245. action_data.action = ax::mojom::Action::kHitTest;
  246. gfx::Point point;
  247. float device_scale_factor = GetDeviceScaleFactor();
  248. point.set_x(local_point.x * device_scale_factor);
  249. point.set_y(local_point.y * device_scale_factor);
  250. action_data.target_point = point;
  251. action_data.hit_test_event_to_fire = ax::mojom::Event::kHitTestResult;
  252. pending_hit_test_callbacks_[action_data.request_id] = std::move(callback);
  253. web_contents_->GetPrimaryMainFrame()->AccessibilityPerformAction(action_data);
  254. }
  255. void AccessibilityBridge::OnSemanticsModeChanged(
  256. bool updates_enabled,
  257. OnSemanticsModeChangedCallback callback) {
  258. // TODO(https://crbug.com/1134591): Fix the case when enabling / disabling
  259. // semantics can lead to race conditions.
  260. if (enable_semantic_updates_ == updates_enabled)
  261. return callback();
  262. enable_semantic_updates_ = updates_enabled;
  263. if (updates_enabled) {
  264. id_mapper_ = std::make_unique<NodeIDMapper>();
  265. // The first call to AccessibilityEventReceived after this call will be
  266. // the entire semantic tree.
  267. web_contents_->EnableWebContentsOnlyAccessibilityMode();
  268. } else {
  269. // The SemanticsManager will clear all state in this case, which is
  270. // mirrored here.
  271. ui::AXMode mode = web_contents_->GetAccessibilityMode();
  272. mode.set_mode(ui::AXMode::kWebContents, false);
  273. web_contents_->SetAccessibilityMode(mode);
  274. to_delete_.clear();
  275. to_update_.clear();
  276. commit_inflight_ = false;
  277. ax_trees_.clear();
  278. tree_connections_.clear();
  279. frame_id_to_tree_id_.clear();
  280. InterruptPendingActions();
  281. }
  282. // Notify the SemanticsManager that this request was handled.
  283. callback();
  284. }
  285. void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) {
  286. DCHECK(tree);
  287. DCHECK(node);
  288. AddNodeToOffsetMapping(tree, node->data());
  289. }
  290. void AccessibilityBridge::OnNodeWillBeDeleted(ui::AXTree* tree,
  291. ui::AXNode* node) {
  292. DCHECK(tree);
  293. DCHECK(node);
  294. // Remove the node from its offset container's list of children.
  295. RemoveNodeFromOffsetMapping(tree, node->data());
  296. // Also remove the mapping from deleted node to its offset children.
  297. offset_container_children_.erase(
  298. std::make_pair(tree->GetAXTreeID(), node->data().id));
  299. }
  300. void AccessibilityBridge::OnNodeDeleted(ui::AXTree* tree, int32_t node_id) {
  301. DCHECK(tree);
  302. AppendToDeleteList(
  303. id_mapper_->ToFuchsiaNodeID(tree->GetAXTreeID(), node_id, false));
  304. }
  305. void AccessibilityBridge::OnNodeDataChanged(
  306. ui::AXTree* tree,
  307. const ui::AXNodeData& old_node_data,
  308. const ui::AXNodeData& new_node_data) {
  309. DCHECK(tree);
  310. // If this node's offset container has changed, then we should remove it from
  311. // its old offset container's offset children and add it to its new offset
  312. // container's children.
  313. if (old_node_data.relative_bounds.offset_container_id !=
  314. new_node_data.relative_bounds.offset_container_id) {
  315. RemoveNodeFromOffsetMapping(tree, old_node_data);
  316. AddNodeToOffsetMapping(tree, new_node_data);
  317. }
  318. // If this node's bounds have changed, then we should update its offset
  319. // children's transforms to reflect the new bounds.
  320. if (old_node_data.relative_bounds.bounds ==
  321. new_node_data.relative_bounds.bounds) {
  322. return;
  323. }
  324. auto offset_container_children_it = offset_container_children_.find(
  325. std::make_pair(tree->GetAXTreeID(), old_node_data.id));
  326. if (offset_container_children_it == offset_container_children_.end())
  327. return;
  328. for (auto offset_child_id : offset_container_children_it->second) {
  329. auto* child_node = tree->GetFromId(offset_child_id.second);
  330. if (!child_node)
  331. continue;
  332. auto child_node_data = child_node->data();
  333. // If the offset container for |child_node| does NOT change during this
  334. // atomic update, then the update produced here will be correct.
  335. //
  336. // If the offset container for |child_node| DOES change during this atomic
  337. // update, then depending on the order of the individual node updates, the
  338. // update we produce here could be incorrect. However, in that case,
  339. // OnAtomicUpdateFinished() will see a change for |child_node|. By the time
  340. // that OnAtomicUpdateFinished() is called, offset_container_children_ will
  341. // be correct, so we can simply overwrite the existing update.
  342. auto* fuchsia_node =
  343. EnsureAndGetUpdatedNode(tree->GetAXTreeID(), child_node->data().id,
  344. /*replace_existing=*/true);
  345. DCHECK(fuchsia_node);
  346. }
  347. }
  348. void AccessibilityBridge::OnAtomicUpdateFinished(
  349. ui::AXTree* tree,
  350. bool root_changed,
  351. const std::vector<ui::AXTreeObserver::Change>& changes) {
  352. DCHECK(tree);
  353. if (root_changed)
  354. MaybeDisconnectTreeFromParentTree(tree);
  355. const bool is_main_frame_tree =
  356. tree->GetAXTreeID() ==
  357. web_contents_->GetPrimaryMainFrame()->GetAXTreeID();
  358. if (is_main_frame_tree)
  359. root_id_ = tree->root()->id();
  360. // Changes included here are only nodes that are still on the tree. Since this
  361. // indicates the end of an atomic update, it is safe to assume that these
  362. // nodes will not change until the next change arrives. Nodes that would be
  363. // deleted are already gone, which means that all updates collected here in
  364. // |to_update_| are going to be executed after |to_delete_|.
  365. for (const ui::AXTreeObserver::Change& change : changes) {
  366. const auto& node = change.node->data();
  367. // Get the updated fuchsia representation of the node. It's possible that
  368. // there's an existing update for this node from OnNodeDataChanged(). This
  369. // update may not have the correct offset container and/or transform, so we
  370. // should replace it.
  371. auto* fuchsia_node = EnsureAndGetUpdatedNode(tree->GetAXTreeID(), node.id,
  372. /*replace_existing=*/true);
  373. DCHECK(fuchsia_node);
  374. if (node.HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId)) {
  375. const auto child_tree_id = ui::AXTreeID::FromString(
  376. node.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
  377. tree_connections_[child_tree_id] = {node.id, tree->GetAXTreeID(), false};
  378. }
  379. }
  380. UpdateTreeConnections();
  381. UpdateFocus();
  382. // TODO(https://crbug.com/1134737): Separate updates of atomic updates and
  383. // don't allow all of them to be in the same commit.
  384. TryCommit();
  385. }
  386. void AccessibilityBridge::InterruptPendingActions() {
  387. // Acknowledge to the SemanticsManager if any actions have not been handled
  388. // upon destruction time or when semantic updates have been disabled.
  389. for (auto& callback : pending_hit_test_callbacks_) {
  390. fuchsia::accessibility::semantics::Hit hit;
  391. callback.second(std::move(hit));
  392. }
  393. pending_hit_test_callbacks_.clear();
  394. }
  395. float AccessibilityBridge::GetDeviceScaleFactor() {
  396. if (device_scale_factor_override_for_test_) {
  397. return *device_scale_factor_override_for_test_;
  398. }
  399. return window_tree_host_->scenic_scale_factor();
  400. }
  401. ui::AXSerializableTree* AccessibilityBridge::ax_tree_for_test() {
  402. if (ax_trees_.empty())
  403. return nullptr;
  404. return ax_trees_.cbegin()->second.get();
  405. }
  406. void AccessibilityBridge::UpdateTreeConnections() {
  407. std::vector<ui::AXTreeID> connections_to_remove;
  408. for (auto& kv : tree_connections_) {
  409. ui::AXSerializableTree* child_tree = nullptr;
  410. auto it = ax_trees_.find(kv.first);
  411. if (it != ax_trees_.end())
  412. child_tree = it->second.get();
  413. ui::AXSerializableTree* parent_tree = nullptr;
  414. ui::AXNode* ax_node = nullptr;
  415. const auto& parent_ax_tree_id = kv.second.parent_tree_id;
  416. auto parent_it = ax_trees_.find(parent_ax_tree_id);
  417. if (parent_it != ax_trees_.end())
  418. parent_tree = parent_it->second.get();
  419. if (parent_tree) {
  420. ax_node = parent_tree->GetFromId(kv.second.parent_node_id);
  421. if (ax_node) {
  422. ax_node = ax_node->HasStringAttribute(
  423. ax::mojom::StringAttribute::kChildTreeId)
  424. ? ax_node
  425. : nullptr;
  426. }
  427. }
  428. if (!child_tree && (!parent_tree || !ax_node)) {
  429. // Both the child tree and the parent tree are gone, so this connection is
  430. // no longer relevant.
  431. connections_to_remove.push_back(kv.first);
  432. continue;
  433. }
  434. if (!child_tree || (!parent_tree || !ax_node)) {
  435. // Only one side of the connection is still valid, so mark the trees as
  436. // disconnected and wait for either the connection to be done again or the
  437. // other side to drop.
  438. kv.second.is_connected = false;
  439. continue;
  440. }
  441. if (kv.second.is_connected)
  442. continue; // No work to do, trees connected and present.
  443. auto* fuchsia_node =
  444. EnsureAndGetUpdatedNode(parent_ax_tree_id, ax_node->id(),
  445. /*replace_existing=*/false);
  446. DCHECK(fuchsia_node);
  447. // Now, the connection really happens:
  448. // This node, from the parent tree, will have a child that points to the
  449. // root of the child tree.
  450. auto child_tree_root_id = id_mapper_->ToFuchsiaNodeID(
  451. child_tree->GetAXTreeID(), child_tree->root()->id(), false);
  452. fuchsia_node->mutable_child_ids()->push_back(child_tree_root_id);
  453. kv.second.is_connected = true; // Trees are connected!
  454. }
  455. for (const auto& to_delete : connections_to_remove) {
  456. tree_connections_.erase(to_delete);
  457. }
  458. }
  459. void AccessibilityBridge::UpdateFocus() {
  460. auto new_focused_node = GetFocusedNodeId();
  461. if (!new_focused_node && !last_focused_node_id_)
  462. return; // no node in focus, no new node in focus.
  463. const bool focus_changed = last_focused_node_id_ != new_focused_node;
  464. if (new_focused_node) {
  465. // If the new focus is the same as the old focus, we only want to set the
  466. // value in the node if it is part of the current update, meaning that its
  467. // data changed. This makes sure that it contains the focus information. If
  468. // it is not part of the current update, no need to send this information,
  469. // as it is redundant.
  470. auto* node = focus_changed
  471. ? EnsureAndGetUpdatedNode(new_focused_node->first,
  472. new_focused_node->second,
  473. /*replace_existing=*/false)
  474. : GetNodeIfChangingInUpdate(new_focused_node->first,
  475. new_focused_node->second);
  476. if (node)
  477. node->mutable_states()->set_has_input_focus(true);
  478. }
  479. if (last_focused_node_id_) {
  480. auto* node = focus_changed
  481. ? EnsureAndGetUpdatedNode(last_focused_node_id_->first,
  482. last_focused_node_id_->second,
  483. /*replace_existing=*/false)
  484. : nullptr /*already updated above*/;
  485. if (node)
  486. node->mutable_states()->set_has_input_focus(false);
  487. }
  488. last_focused_node_id_ = std::move(new_focused_node);
  489. }
  490. bool AccessibilityBridge::ShouldHoldCommit() {
  491. const auto& main_frame_tree_id =
  492. web_contents_->GetPrimaryMainFrame()->GetAXTreeID();
  493. auto main_tree_it = ax_trees_.find(main_frame_tree_id);
  494. if (main_tree_it == ax_trees_.end()) {
  495. // The main tree is not present yet, commit should be held.
  496. return true;
  497. }
  498. // Make sure that all trees are reachable from the main frame semantic tree.
  499. // If a tree is not reachable, this means that when committed it would result
  500. // in a dangling tree, which is not valid.
  501. for (const auto& kv : ax_trees_) {
  502. const ui::AXTreeID& tree_id = kv.first;
  503. if (tree_id == main_frame_tree_id)
  504. continue;
  505. auto it = tree_connections_.find(tree_id);
  506. if (it == tree_connections_.end())
  507. return true;
  508. }
  509. for (const auto& kv : tree_connections_) {
  510. if (!kv.second.is_connected) {
  511. // Trees are not connected, which means that a node is pointing to
  512. // something that is not present yet. Since this causes an invalid tree,
  513. // the commit should be held.
  514. return true;
  515. }
  516. }
  517. return false;
  518. }
  519. void AccessibilityBridge::RenderFrameDeleted(
  520. content::RenderFrameHost* render_frame_host) {
  521. DCHECK(render_frame_host);
  522. frame_id_to_tree_id_.erase(render_frame_host->GetGlobalId());
  523. const auto& id = render_frame_host->GetAXTreeID();
  524. auto it = ax_trees_.find(id);
  525. if (it != ax_trees_.end()) {
  526. it->second.get()->Destroy();
  527. MaybeDisconnectTreeFromParentTree(it->second.get());
  528. ax_trees_.erase(it);
  529. UpdateTreeConnections();
  530. TryCommit();
  531. }
  532. }
  533. bool AccessibilityBridge::UpdateAXTreeID(const ui::AXTreeID& tree_id) {
  534. auto* frame = content::RenderFrameHost::FromAXTreeID(tree_id);
  535. if (!frame)
  536. return false;
  537. auto frame_id = frame->GetGlobalId();
  538. DCHECK(frame_id);
  539. auto frame_iter = frame_id_to_tree_id_.find(frame_id);
  540. if (frame_iter == frame_id_to_tree_id_.end()) {
  541. // This is the first time this frame was seen. Save its AXTreeID.
  542. frame_id_to_tree_id_[frame_id] = tree_id;
  543. return true;
  544. }
  545. // This frame already exists. Check if the AXTreeID of this frame has changed.
  546. if (frame_iter->second == tree_id)
  547. return true; // No updates needed.
  548. const auto& old_tree_id = frame_iter->second;
  549. id_mapper_->UpdateAXTreeIDForCachedNodeIDs(old_tree_id, tree_id);
  550. auto old_tree_iter = ax_trees_.find(old_tree_id);
  551. if (old_tree_iter != ax_trees_.end()) {
  552. // This AXTree has changed its AXTreeID. Update the map with the new key.
  553. auto data = std::move(old_tree_iter->second);
  554. ax_trees_.erase(old_tree_iter);
  555. ax_trees_[tree_id] = std::move(data);
  556. // If this tree is connected to a parent tree or is the parent tree of
  557. // another tree, also update its ID in the tree connections map.
  558. auto connected_tree_iter = tree_connections_.find(old_tree_id);
  559. if (connected_tree_iter != tree_connections_.end()) {
  560. auto data = std::move(connected_tree_iter->second);
  561. tree_connections_.erase(connected_tree_iter);
  562. tree_connections_[tree_id] = std::move(data);
  563. MaybeDisconnectTreeFromParentTree(ax_trees_[tree_id].get());
  564. }
  565. for (auto& kv : tree_connections_) {
  566. if (kv.second.parent_tree_id == old_tree_id)
  567. kv.second.parent_tree_id = tree_id;
  568. }
  569. }
  570. frame_iter->second = tree_id;
  571. return true;
  572. }
  573. void AccessibilityBridge::MaybeDisconnectTreeFromParentTree(ui::AXTree* tree) {
  574. const auto& key = tree->GetAXTreeID();
  575. auto it = tree_connections_.find(key);
  576. if (it != tree_connections_.end())
  577. it->second.is_connected = false;
  578. }
  579. absl::optional<AccessibilityBridge::AXNodeID>
  580. AccessibilityBridge::GetFocusedNodeId() const {
  581. const auto& main_frame_tree_id =
  582. web_contents_->GetPrimaryMainFrame()->GetAXTreeID();
  583. const auto main_tree_it = ax_trees_.find(main_frame_tree_id);
  584. if (main_tree_it == ax_trees_.end())
  585. return absl::nullopt;
  586. const ui::AXSerializableTree* main_tree = main_tree_it->second.get();
  587. DCHECK(main_tree);
  588. const ui::AXTreeID& focused_tree_id = main_tree->data().focused_tree_id;
  589. if (focused_tree_id == ui::AXTreeIDUnknown())
  590. return absl::nullopt;
  591. const auto focused_tree_it = ax_trees_.find(focused_tree_id);
  592. if (focused_tree_it == ax_trees_.end())
  593. return absl::nullopt;
  594. const ui::AXSerializableTree* focused_tree = focused_tree_it->second.get();
  595. DCHECK(focused_tree);
  596. return GetFocusFromThisOrDescendantFrame(focused_tree);
  597. }
  598. absl::optional<AccessibilityBridge::AXNodeID>
  599. AccessibilityBridge::GetFocusFromThisOrDescendantFrame(
  600. const ui::AXSerializableTree* tree) const {
  601. DCHECK(tree);
  602. const auto focused_node_id = tree->data().focus_id;
  603. const auto* node = tree->GetFromId(focused_node_id);
  604. const auto root_id = tree->root() ? tree->root()->id() : ui::kInvalidAXNodeID;
  605. if (!node) {
  606. if (root_id != ui::kInvalidAXNodeID)
  607. return std::make_pair(tree->GetAXTreeID(), root_id);
  608. return absl::nullopt;
  609. }
  610. if (node->data().HasStringAttribute(
  611. ax::mojom::StringAttribute::kChildTreeId)) {
  612. const auto child_tree_id =
  613. ui::AXTreeID::FromString(node->data().GetStringAttribute(
  614. ax::mojom::StringAttribute::kChildTreeId));
  615. const auto child_tree_it = ax_trees_.find(child_tree_id);
  616. if (child_tree_it != ax_trees_.end())
  617. return GetFocusFromThisOrDescendantFrame(child_tree_it->second.get());
  618. }
  619. return std::make_pair(tree->GetAXTreeID(), node->id());
  620. }
  621. void AccessibilityBridge::AppendToDeleteList(uint32_t node_id) {
  622. if (to_delete_.empty() || to_delete_.back().size() == kMaxNodesPerDelete) {
  623. to_delete_.emplace_back();
  624. }
  625. to_delete_.back().push_back(std::move(node_id));
  626. }
  627. void AccessibilityBridge::AppendToUpdateList(
  628. fuchsia::accessibility::semantics::Node node) {
  629. if (to_update_.empty() || to_update_.back().size() == kMaxNodesPerUpdate) {
  630. to_update_.emplace_back();
  631. }
  632. to_update_.back().push_back(std::move(node));
  633. }
  634. fuchsia::accessibility::semantics::Node*
  635. AccessibilityBridge::GetNodeIfChangingInUpdate(const ui::AXTreeID& tree_id,
  636. ui::AXNodeID node_id) {
  637. auto fuchsia_node_id = id_mapper_->ToFuchsiaNodeID(tree_id, node_id, false);
  638. for (auto& update_batch : to_update_) {
  639. auto result =
  640. std::find_if(update_batch.rbegin(), update_batch.rend(),
  641. [&fuchsia_node_id](
  642. const fuchsia::accessibility::semantics::Node& node) {
  643. return node.node_id() == fuchsia_node_id;
  644. });
  645. if (result != update_batch.rend())
  646. return &(*result);
  647. }
  648. return nullptr;
  649. }
  650. fuchsia::accessibility::semantics::Node*
  651. AccessibilityBridge::EnsureAndGetUpdatedNode(const ui::AXTreeID& tree_id,
  652. ui::AXNodeID node_id,
  653. bool replace_existing) {
  654. auto* fuchsia_node = GetNodeIfChangingInUpdate(tree_id, node_id);
  655. if (fuchsia_node && !replace_existing)
  656. return fuchsia_node;
  657. auto ax_tree_it = ax_trees_.find(tree_id);
  658. if (ax_tree_it == ax_trees_.end())
  659. return nullptr;
  660. auto* tree = ax_tree_it->second.get();
  661. auto* ax_node = tree->GetFromId(node_id);
  662. if (!ax_node)
  663. return nullptr;
  664. int32_t offset_container_id = GetOffsetContainerId(tree, ax_node->data());
  665. const auto* container = tree->GetFromId(offset_container_id);
  666. DCHECK(container);
  667. const bool is_main_frame_tree =
  668. tree->GetAXTreeID() ==
  669. web_contents_->GetPrimaryMainFrame()->GetAXTreeID();
  670. const bool is_root = is_main_frame_tree ? node_id == root_id_ : false;
  671. float device_scale_factor =
  672. ax_node->id() == tree->root()->id() ? GetDeviceScaleFactor() : 0.0f;
  673. auto new_fuchsia_node =
  674. AXNodeDataToSemanticNode(*ax_node, *container, tree_id, is_root,
  675. device_scale_factor, id_mapper_.get());
  676. if (replace_existing && fuchsia_node) {
  677. *fuchsia_node = std::move(new_fuchsia_node);
  678. return fuchsia_node;
  679. }
  680. AppendToUpdateList(std::move(new_fuchsia_node));
  681. return &to_update_.back().back();
  682. }