blob: 1a7d4c1afe3c24a24650c68706563ca227707727 [file]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_
#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_
#include <optional>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "content/common/content_export.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
#include "ui/accessibility/platform/browser_accessibility_manager.h"
namespace ui {
class MotionEventAndroid;
class AXPlatformTreeManagerDelegate;
} // namespace ui
namespace content {
// A Java counterpart will be generated for this enum.
// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.content.browser.accessibility
enum ScrollDirection { FORWARD, BACKWARD, UP, DOWN, LEFT, RIGHT };
// From android.view.accessibility.AccessibilityNodeInfo in Java:
enum AndroidMovementGranularity {
ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER = 1,
ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD = 2,
ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE = 4
};
// From androidx.core.view.accessibility.AccessibilityNodeInfoCompat
enum class ExtendedSelectionOffsetType {
OFFSET_TYPE_TEXT = 0,
OFFSET_TYPE_CHILD = 1,
};
// From android.view.accessibility.AccessibilityEvent in Java:
enum {
ANDROID_ACCESSIBILITY_EVENT_CONTENT_CHANGE_TYPE_UNDEFINED = 0,
ANDROID_ACCESSIBILITY_EVENT_CONTENT_CHANGE_TYPE_TEXT = 2,
ANDROID_ACCESSIBILITY_EVENT_CONTENT_CHANGE_TYPE_PANE_TITLE = 8,
ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGED = 16,
ANDROID_ACCESSIBILITY_EVENT_CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 64,
ANDROID_ACCESSIBILITY_EVENT_CONTENT_CHANGE_TYPE_CONTENT_INVALID = 1024,
ANDROID_ACCESSIBILITY_EVENT_TEXT_SELECTION_CHANGED = 8192,
ANDROID_ACCESSIBILITY_EVENT_CONTENT_CHANGE_TYPE_EXPANDED = 16384,
ANDROID_ACCESSIBILITY_EVENT_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 131072
};
// From android.view.accessibility.AccessibilityEvent in Java:
enum {
ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGE_TYPE_UNDEFINED = 0,
ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGE_TYPE_IN_COMPOSITION = 1,
ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGE_TYPE_COMMITTED_BY_IME = 2,
ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGE_TYPE_CONVERSION_SUGGESTION_SELECTED_BY_IME =
4,
};
class BrowserAccessibilityAndroid;
class WebContentsAccessibilityAndroid;
class CONTENT_EXPORT BrowserAccessibilityManagerAndroid
: public ui::BrowserAccessibilityManager {
public:
// Creates the platform-specific BrowserAccessibilityManager.
static BrowserAccessibilityManager* Create(
const ui::AXTreeUpdate& initial_tree,
ui::AXNodeIdDelegate& node_id_delegate,
ui::AXPlatformTreeManagerDelegate* delegate);
static BrowserAccessibilityManager* Create(
ui::AXNodeIdDelegate& node_id_delegate,
ui::AXPlatformTreeManagerDelegate* delegate);
BrowserAccessibilityManagerAndroid(
const ui::AXTreeUpdate& initial_tree,
base::WeakPtr<WebContentsAccessibilityAndroid> web_contents_accessibility,
ui::AXNodeIdDelegate& node_id_delegate,
ui::AXPlatformTreeManagerDelegate* delegate);
BrowserAccessibilityManagerAndroid(
const BrowserAccessibilityManagerAndroid&) = delete;
BrowserAccessibilityManagerAndroid& operator=(
const BrowserAccessibilityManagerAndroid&) = delete;
~BrowserAccessibilityManagerAndroid() override;
static ui::AXTreeUpdate GetEmptyDocument();
void set_allow_image_descriptions_for_testing(bool is_allowed) {
allow_image_descriptions_for_testing_ = is_allowed;
}
const absl::flat_hash_set<int32_t>& nodes_already_cleared_for_test() const {
return nodes_already_cleared_;
}
void set_web_contents_accessibility(
base::WeakPtr<WebContentsAccessibilityAndroid> wcax) {
web_contents_accessibility_ = std::move(wcax);
}
void ResetWebContentsAccessibility();
// State properties defined from Java-side code.
bool ShouldAllowImageDescriptions();
// Consume hover event if necessary, and return true if it did.
bool OnHoverEvent(const ui::MotionEventAndroid& event);
// AXTreeManager overrides.
void FireFocusEvent(ui::AXNode* node) override;
// BrowserAccessibilityManager overrides.
ui::BrowserAccessibility* GetFocus() const override;
ui::BrowserAccessibility* GetAccessibilityFocus() const override;
void SendLocationChangeEvents(
const std::vector<ui::AXLocationChange>& changes) override;
ui::AXNode* RetargetForEvents(ui::AXNode* node,
RetargetEventType type) const override;
void FireSourceEvent(ax::mojom::Event event_type,
ui::BrowserAccessibility* node,
int action_request_id) override;
void FireGeneratedEvent(ui::AXEventGenerator::Event event_type,
const ui::AXNode* node) override;
void FireAriaNotificationEvent(
ui::BrowserAccessibility* node,
const std::string& announcement,
ax::mojom::AriaNotificationPriority priority_property,
ax::mojom::AriaNotificationInterrupt interrupt_property,
const std::string& type) override;
void FireLocationChanged(ui::BrowserAccessibility* node);
// Helper functions to compute the next start and end index when moving
// forwards or backwards by character, word, or line. This part is
// unit-tested; the Java interfaces above are just wrappers. Both of these
// take a single cursor index as input and return the boundaries surrounding
// the next character, word or line.
// The function returns false if the cursor cannot be moved further in the
// given direction and granularity.
bool NextAtGranularity(int32_t granularity,
int cursor_index,
BrowserAccessibilityAndroid* node,
int32_t* start_index,
int32_t* end_index);
bool PreviousAtGranularity(int32_t granularity,
int cursor_index,
BrowserAccessibilityAndroid* node,
int32_t* start_index,
int32_t* end_index);
// Helper method to clear AccessibilityNodeInfo cache on given node
void ClearNodeInfoCacheForGivenId(int32_t unique_id);
std::u16string GenerateAccessibilityNodeInfoString(int32_t unique_id);
std::optional<std::vector<std::string>> GetMetadataForTree() const;
struct AndroidPosition {
raw_ptr<BrowserAccessibilityAndroid> node = nullptr;
int offset = -1;
ExtendedSelectionOffsetType offset_type =
ExtendedSelectionOffsetType::OFFSET_TYPE_TEXT;
};
struct SelectionRange {
AndroidPosition anchor;
AndroidPosition focus;
};
// Returns the selection fitted to Android accessibility tree and Selection
// API restrictions. If the output has value, all fields of the
// `SelectionRange` are populated.
std::optional<SelectionRange> GetSelectionRange() const;
// Creates an AXPosition for the given `node`, `offset`, and `offset_type`
// that are received from Android. Returns Null Position if the offset is not
// valid.
ui::BrowserAccessibility::AXPosition ConvertAndroidSelectionPositionToChrome(
BrowserAccessibilityAndroid* node,
int32_t offset,
ExtendedSelectionOffsetType offset_type);
protected:
std::unique_ptr<ui::BrowserAccessibility> CreateBrowserAccessibility(
ui::AXNode* node) override;
void OnAccessibilityEventsProcessedForExperiment();
private:
// AXTreeObserver overrides.
void OnAtomicUpdateStarting(
ui::AXTree* tree,
const absl::flat_hash_set<ui::AXNodeID>& deleting_nodes,
const absl::flat_hash_set<ui::AXNodeID>& reparenting_nodes) override;
void OnAtomicUpdateFinished(
ui::AXTree* tree,
bool root_changed,
const std::vector<ui::AXTreeObserver::Change>& changes) override;
WebContentsAccessibilityAndroid* GetWebContentsAXFromRootManager() const;
// This gives BrowserAccessibilityManager::Create access to the class
// constructor.
friend class ui::BrowserAccessibilityManager;
// Handle a hover event from the renderer process.
void HandleHoverEvent(ui::BrowserAccessibility* node);
void FireDocumentSelectionChangedEvent(WebContentsAccessibilityAndroid* wcax);
// Given `node_id`, `offset`, and `affinity` which represent a selection
// position in Chrome accessibility tree, creates an appropriate selection
// position in Android accessibility tree. This is done by ensuring that the
// anchor node exists on Android (if not, moves to the highest leaf node which
// is ancestor of the node). Returns empty if conversion is not possible.
std::optional<AndroidPosition> ConvertChromeSelectionPositionToAndroid(
ui::AXNodeID node_id,
int offset,
ax::mojom::TextAffinity affinity,
bool is_backward) const;
// A weak reference to WebContentsAccessibility for reaching Java layer.
// Only the root manager has the reference. Should be accessed through
// |GetWebContentsAXFromRootManager| rather than directly.
base::WeakPtr<WebContentsAccessibilityAndroid> web_contents_accessibility_;
// True if this instance should force enable the image descriptions feature
// for testing. This allows us to mock generated image descriptions and test
// tree dumps for nodes without creating web_contents_accessibility_android.
bool allow_image_descriptions_for_testing_ = false;
// A set of |unique_id| values for nodes cleared from the cache
// with each atomic update to prevent superfluous cache clear calls.
absl::flat_hash_set<int32_t> nodes_already_cleared_;
};
} // namespace content
#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_