// Copyright 2013 The Flutter 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 FLUTTER_SHELL_PLATFORM_COMMON_CPP_ACCESSIBILITY_BRIDGE_H_ #define FLUTTER_SHELL_PLATFORM_COMMON_CPP_ACCESSIBILITY_BRIDGE_H_ #include #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/third_party/accessibility/ax/ax_event_generator.h" #include "flutter/third_party/accessibility/ax/ax_tree.h" #include "flutter/third_party/accessibility/ax/ax_tree_observer.h" #include "flutter/third_party/accessibility/ax/platform/ax_platform_node_delegate.h" #include "flutter_platform_node_delegate.h" namespace flutter { //------------------------------------------------------------------------------ /// Use this class to maintain an accessibility tree. This class consumes /// semantics updates from the embedder API and produces an accessibility tree /// in the native format. /// /// The bridge creates an AXTree to hold the semantics data that comes from /// Flutter semantics updates. The tree holds AXNode[s] which contain the /// semantics information for semantics node. The AXTree ressemble the Flutter /// semantics tree in the Flutter framework. The bridge also uses /// FlutterPlatformNodeDelegate to wrap each AXNode in order to provide /// an accessibility tree in the native format. /// /// This class takes in a AccessibilityBridgeDelegate instance and is in charge /// of its lifecycle. The delegate are used to handle the accessibility events /// and actions. /// /// To use this class, you must provide your own implementation of /// FlutterPlatformNodeDelegate and AccessibilityBridgeDelegate. class AccessibilityBridge : public std::enable_shared_from_this, public FlutterPlatformNodeDelegate::OwnerBridge, private ui::AXTreeObserver { public: //----------------------------------------------------------------------------- /// Delegate to handle requests from the accessibility bridge. The requests /// include sending accessibility event to native accessibility system, /// routing accessibility action to the Flutter framework, and creating /// platform specific FlutterPlatformNodeDelegate. /// /// The accessibility events are generated when accessibility tree changes. /// These events must be sent to the native accessibility system through /// the native API for the system to pick up the changes /// (e.g. NSAccessibilityPostNotification in MacOS). /// /// The accessibility actions are generated by the native accessibility system /// when users interacted with the assistive technologies. Those actions /// needed to be sent to the Flutter framework. /// /// Each platform needs to implement the FlutterPlatformNodeDelegate and /// returns its platform specific instance of FlutterPlatformNodeDelegate /// in this delegate. class AccessibilityBridgeDelegate { public: virtual ~AccessibilityBridgeDelegate() = default; //--------------------------------------------------------------------------- /// @brief Handle accessibility events generated due to accessibility /// tree changes. These events are generated in accessibility /// bridge and needed to be sent to native accessibility system. /// See ui::AXEventGenerator::Event for possible events. /// /// @param[in] targeted_event The object that contains both the /// generated event and the event target. virtual void OnAccessibilityEvent( ui::AXEventGenerator::TargetedEvent targeted_event) = 0; //--------------------------------------------------------------------------- /// @brief Dispatch accessibility action back to the Flutter framework. /// These actions are generated in the native accessibility /// system when users interact with the assistive technologies. /// For example, a /// FlutterSemanticsAction::kFlutterSemanticsActionTap is /// fired when user click or touch the screen. /// /// @param[in] target The semantics node id of the action /// target. /// @param[in] action The generated flutter semantics action. /// @param[in] data Additional data associated with the /// action. virtual void DispatchAccessibilityAction(AccessibilityNodeId target, FlutterSemanticsAction action, std::vector data) = 0; //--------------------------------------------------------------------------- /// @brief Creates a platform specific FlutterPlatformNodeDelegate. /// Ownership passes to the caller. This method will be called /// by accessibility bridge whenever a new AXNode is created in /// AXTree. Each platform needs to implement this method in /// order to inject its subclass into the accessibility bridge. virtual std::unique_ptr CreateFlutterPlatformNodeDelegate() = 0; }; //----------------------------------------------------------------------------- /// @brief Creates a new instance of a accessibility bridge. /// /// @param[in] user_data A custom pointer to the data of your /// choice. This pointer can be retrieve later /// through GetUserData(). AccessibilityBridge(std::unique_ptr delegate); ~AccessibilityBridge(); //------------------------------------------------------------------------------ /// @brief Adds a semantics node update to the pending semantics update. /// Calling this method alone will NOT update the semantics tree. /// To flush the pending updates, call the CommitUpdates(). /// /// @param[in] node A pointer to the semantics node update. void AddFlutterSemanticsNodeUpdate(const FlutterSemanticsNode* node); //------------------------------------------------------------------------------ /// @brief Adds a custom semantics action update to the pending semantics /// update. Calling this method alone will NOT update the /// semantics tree. To flush the pending updates, call the /// CommitUpdates(). /// /// @param[in] action A pointer to the custom semantics action /// update. void AddFlutterSemanticsCustomActionUpdate( const FlutterSemanticsCustomAction* action); //------------------------------------------------------------------------------ /// @brief Flushes the pending updates and applies them to this /// accessibility bridge. Calling this with no pending updates /// does nothing, and callers should call this method at the end /// of an atomic batch to avoid leaving the tree in a unstable /// state. For example if a node reparents from A to B, callers /// should only call this method when both removal from A and /// addition to B are in the pending updates. void CommitUpdates(); //------------------------------------------------------------------------------ /// @brief Get the flutter platform node delegate with the given id from /// this accessibility bridge. Returns expired weak_ptr if the /// delegate associated with the id does not exist or has been /// removed from the accessibility tree. /// /// @param[in] id The id of the flutter accessibility node you want /// to retrieve. std::weak_ptr GetFlutterPlatformNodeDelegateFromID(AccessibilityNodeId id) const; //------------------------------------------------------------------------------ /// @brief Get the ax tree data from this accessibility bridge. The tree /// data contains information such as the id of the node that /// has the keyboard focus or the text selection range. const ui::AXTreeData& GetAXTreeData() const; private: // See FlutterSemanticsNode in embedder.h typedef struct { int32_t id; FlutterSemanticsFlag flags; FlutterSemanticsAction actions; int32_t text_selection_base; int32_t text_selection_extent; int32_t scroll_child_count; int32_t scroll_index; double scroll_position; double scroll_extent_max; double scroll_extent_min; double elevation; double thickness; std::string label; std::string hint; std::string value; std::string increased_value; std::string decreased_value; FlutterTextDirection text_direction; FlutterRect rect; FlutterTransformation transform; std::vector children_in_traversal_order; std::vector custom_accessibility_actions; } SemanticsNode; // See FlutterSemanticsCustomAction in embedder.h typedef struct { int32_t id; FlutterSemanticsAction override_action; std::string label; std::string hint; } SemanticsCustomAction; std::unordered_map> id_wrapper_map_; ui::AXTree tree_; ui::AXEventGenerator event_generator_; std::unordered_map pending_semantics_node_updates_; std::unordered_map pending_semantics_custom_action_updates_; AccessibilityNodeId last_focused_id_ = ui::AXNode::kInvalidAXID; std::unique_ptr delegate_; void InitAXTree(const ui::AXTreeUpdate& initial_state); void GetSubTreeList(SemanticsNode target, std::vector& result); void ConvertFluterUpdate(const SemanticsNode& node, ui::AXTreeUpdate& tree_update); void SetRoleFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetStateFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetActionsFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetBooleanAttributesFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetIntAttributesFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetIntListAttributesFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetStringListAttributesFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetNameFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetValueFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node); void SetTreeData(const SemanticsNode& node, ui::AXTreeUpdate& tree_update); SemanticsNode FromFlutterSemanticsNode( const FlutterSemanticsNode* flutter_node); SemanticsCustomAction FromFlutterSemanticsCustomAction( const FlutterSemanticsCustomAction* flutter_custom_action); // |AXTreeObserver| void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; // |AXTreeObserver| void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override; // |AXTreeObserver| void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override; // |AXTreeObserver| void OnNodeDeleted(ui::AXTree* tree, AccessibilityNodeId node_id) override; // |AXTreeObserver| void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override; // |AXTreeObserver| void OnRoleChanged(ui::AXTree* tree, ui::AXNode* node, ax::mojom::Role old_role, ax::mojom::Role new_role) override; // |AXTreeObserver| void OnAtomicUpdateFinished( ui::AXTree* tree, bool root_changed, const std::vector& changes) override; // |FlutterPlatformNodeDelegate::OwnerBridge| void SetLastFocusedId(AccessibilityNodeId node_id) override; // |FlutterPlatformNodeDelegate::OwnerBridge| AccessibilityNodeId GetLastFocusedId() override; // |FlutterPlatformNodeDelegate::OwnerBridge| gfx::NativeViewAccessible GetNativeAccessibleFromId( AccessibilityNodeId id) override; // |FlutterPlatformNodeDelegate::OwnerBridge| void DispatchAccessibilityAction(AccessibilityNodeId target, FlutterSemanticsAction action, std::vector data) override; // |FlutterPlatformNodeDelegate::OwnerBridge| gfx::RectF RelativeToGlobalBounds(const ui::AXNode* node, bool& offscreen, bool clip_bounds) override; BASE_DISALLOW_COPY_AND_ASSIGN(AccessibilityBridge); }; } // namespace flutter #endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_ACCESSIBILITY_BRIDGE_H_