blob: c4e77bfd7bc75a67f199cbd8b9aa1c8eb4a062b9 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#ifndef SRC_UI_A11Y_LIB_SEMANTICS_SEMANTIC_TREE_H_
#define SRC_UI_A11Y_LIB_SEMANTICS_SEMANTIC_TREE_H_
#include <fuchsia/accessibility/semantics/cpp/fidl.h>
#include <fuchsia/math/cpp/fidl.h>
#include <lib/inspect/cpp/inspect.h>
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "src/ui/a11y/lib/semantics/semantics_event.h"
#include "src/ui/a11y/lib/semantics/typedefs.h"
#include "src/ui/a11y/lib/semantics/util/semantic_transform.h"
namespace a11y {
// A Semantic Tree represents the relationship of elements on an UI in the form of Semantic Nodes,
// which are provided by runtimes and normally consumed by assistive technology.
//
// This tree is always in a valid state, and accepts changes via calls to
// Update(). An update can be applied to the tree if it leaves the tree in a
// valid state, or rejected, if it leads to an invalid one.
//
// It is also true for a tree to be considered valid:
// 1. It can be either empty or starts with a root which id is equal to kRootNodeId.
// 2. The tree is acyclic.
// 3. Every child id pointed by its parent must exist in the tree.
class SemanticTree {
public:
static constexpr uint32_t kRootNodeId = 0;
// Represents a single tree update. It can be a deletion or a node.
class TreeUpdate {
public:
TreeUpdate(uint32_t delete_node_id);
TreeUpdate(fuchsia::accessibility::semantics::Node node);
bool has_delete_node_id() const;
bool has_node() const;
const uint32_t& delete_node_id() const;
uint32_t TakeDeleteNodeId();
const fuchsia::accessibility::semantics::Node& node() const;
fuchsia::accessibility::semantics::Node TakeNode();
std::string ToString() const;
private:
// If present, deletes the node with |node_id|.
std::optional<uint32_t> delete_node_id_;
// If present, uses node to update in the tree.
std::optional<fuchsia::accessibility::semantics::Node> node_;
};
using TreeUpdates = std::vector<TreeUpdate>;
using ActionHandlerCallback = fit::function<void(
uint32_t node_id, fuchsia::accessibility::semantics::Action action,
fuchsia::accessibility::semantics::SemanticListener::OnAccessibilityActionRequestedCallback
callback)>;
using HitTestingHandlerCallback = fit::function<void(
fuchsia::math::PointF local_point,
fuchsia::accessibility::semantics::SemanticListener::HitTestCallback callback)>;
using SemanticsEventCallback = fit::function<void(SemanticsEventInfo event_info)>;
static constexpr char kUpdateCountInspectNodeName[] = "tree_update_count";
static constexpr char kTreeDumpInspectPropertyName[] = "semantic_tree_root";
static constexpr char kTreeDumpFailedError[] = "tree_dump_failed";
// A SemanticTree object is normally maintained by a semantics provider while
// being consumed by a semantics consumer (such as a screen reader).
explicit SemanticTree(inspect::Node inspect_node = inspect::Node());
virtual ~SemanticTree() = default;
// The two methods below set the handlers for dealing with assistive technology requests.
void set_action_handler(ActionHandlerCallback action_handler) {
action_handler_ = std::move(action_handler);
}
void set_hit_testing_handler(HitTestingHandlerCallback hit_testing_handler) {
hit_testing_handler_ = std::move(hit_testing_handler);
}
// Sets callback invoked on semantics events.
void set_semantics_event_callback(SemanticsEventCallback semantics_event_callback) {
semantics_event_callback_ = std::move(semantics_event_callback);
}
// Returns the node with |node_id|, nullptr otherwise.
const fuchsia::accessibility::semantics::Node* GetNode(uint32_t node_id) const;
// Returns the next node for which |filter| returns true (in depth first manner) from the node
// with |node_id|, or nullptr if none exists.
// Note: Currently O(N). TODO(https://fxbug.dev/42060491): improve this.
virtual const fuchsia::accessibility::semantics::Node* GetNextNode(uint32_t node_id,
a11y::NodeFilter filter) const;
virtual const fuchsia::accessibility::semantics::Node* GetNextNode(
uint32_t node_id, a11y::NodeFilterWithParent filter) const;
// Returns the previous node for which |filter| returns true (in depth first manner) from the node
// with |node_id|, or nullptr if none exists.
// Note: Currently O(N). TODO(https://fxbug.dev/42060491): improve this.
virtual const fuchsia::accessibility::semantics::Node* GetPreviousNode(
uint32_t node_id, a11y::NodeFilter filter) const;
virtual const fuchsia::accessibility::semantics::Node* GetPreviousNode(
uint32_t node_id, a11y::NodeFilterWithParent filter) const;
// Returns the parent node of the node with |node_id| if found, nullptr otherwise.
// Currently O(N). TODO(https://fxbug.dev/42059816): improve this.
virtual const fuchsia::accessibility::semantics::Node* GetParentNode(uint32_t node_id) const;
// Returns a SemanticTransform to transform node-local coordinates to
// view logical space.
std::optional<SemanticTransform> GetNodeToRootTransform(uint32_t node_id) const;
// Applies the node updates in |updates| if they leave the final resulting
// tree in a valid state, returning true if the operation was successful. If
// the updates leave the resulting tree in an invalid state, the updates are
// not applied and this method returns false.
//
// A node can be either a full or a partial node. A full node is one where all
// its values are filled. A partial node omits some field values, and is
// intended to be merged with an existing one.
//
// Updates can be loosely classified in three main categories:
// 1. Insertions: a new full node is being added with an node id that isn't
// present on the tree.
// 2. Node partial update: a node which already exists on the tree is being
// updated with new information. The new node is merged with the old one such
// as all fields are copied from the new node if present, keeping the old ones
// if not.
// 3. Deletion: a node is marked to be deleted from the tree.
virtual bool Update(TreeUpdates updates);
// Clears all nodes of the tree.
void Clear();
// Returns the number of nodes in this tree.
size_t Size() const { return nodes_.size(); }
// Performs accessibility action on this tree. This request is passed to
// |action_handler_| which is received at construction time.
void PerformAccessibilityAction(
uint32_t node_id, fuchsia::accessibility::semantics::Action action,
fuchsia::accessibility::semantics::SemanticListener::OnAccessibilityActionRequestedCallback
callback) const;
// Performs hit testing on this tree. This request is passed to
// |hit_testing_handler_| which is received at construction time.
void PerformHitTesting(
fuchsia::math::PointF local_point,
fuchsia::accessibility::semantics::SemanticListener::HitTestCallback callback) const;
// Processes a semantic event that refers to this tree. The semantic event is passed to
// |semantic_event_handler| to be processed, which is normally set in this object during
// construction time.
virtual void OnSemanticsEvent(SemanticsEventInfo event_info);
// Debug aid.
// - Do not rely on the concrete format for testing or any other purpose.
// - Do not rely on this function being runtime efficient; it is not guaranteed to be.
std::string ToString() const;
private:
// Validates the state of the resulting tree if the pending updates in |nodes_to_be_updated_| were
// added. Note that the updates are not commited until ApplyNodeUpdates() is called.
bool ValidateUpdate(std::unordered_set<uint32_t>* visited_nodes);
// Adds |node| to the list of nodes to be updated. If |node| is already
// in the tree, it is merged before being added to the list.
void MarkNodeForUpdate(fuchsia::accessibility::semantics::Node node);
// Applies nodes in |nodes_to_be_updated_| in the tree. |visited_nodes| contains all
// nodes that are reachable from the root, and should be considered for an
// update. If a node is present in the tree but not in |visited_nodes|, it
// gets deleted, as it is not reachable anymore.
void ApplyNodeUpdates(const std::unordered_set<uint32_t>& visited_nodes);
// Returns true if a node has a label OR represents a button.
// Returns false otherwise.
bool NodeIsDescribable(const fuchsia::accessibility::semantics::Node* node) const;
// Fill out an inspect tree based on the current semantic information
void FillInspectTree(inspect::Node node, inspect::Inspector* inspector) const;
// Keeps all node updates to this tree which were not applied yet. Nodes are
// just copied to their final location in the tree once a validation confirms
// a well-formed tree. This also serves as an optimization to reset a tree
// state that is not valid, by simply not applying this list of updates. An
// optional value indicates that the node is a partial update, where a empty
// one indicates a delletion.
std::unordered_map<uint32_t, std::optional<fuchsia::accessibility::semantics::Node>>
nodes_to_be_updated_;
// Nodes from this tree. If not empty, there must be a node which node id is
// equal to kRootNodeId. It is also garanteed that this tree is always valid.
std::unordered_map<uint32_t /*node_id*/, fuchsia::accessibility::semantics::Node> nodes_;
// Handler responsible for answering calls to PerformAccessibilityAction().
ActionHandlerCallback action_handler_;
// Handler responsible for answering calls to PerformHitTesting().
HitTestingHandlerCallback hit_testing_handler_;
// Callback invoked on semantics events.
SemanticsEventCallback semantics_event_callback_;
// Inpsect node to which to publish debug info.
inspect::Node inspect_node_;
// Number of updates received.
uint64_t update_count_ = 0;
// Inspect property to store the number of updates received.
inspect::UintProperty inspect_property_update_count_;
// Inspect node to store a dump of the semantic tree.
inspect::LazyNode inspect_node_tree_dump_;
};
} // namespace a11y
#endif // SRC_UI_A11Y_LIB_SEMANTICS_SEMANTIC_TREE_H_