Merge AccessibilityBridge and AccessibilityBridgeDelegate (flutter/engine#36597)
* Impl * FlutterPlatformNodeDelegateMac * Format * Rename file * Windows: Compile * format * Fix tests * Fix doc * More doc * More comments * Format * Update names * Format * Compile * Change to unique * Revert as shared * Doc fixes * Make windows bridge weak * Fix win compile * Format * move weak
This commit is contained in:
@@ -2580,9 +2580,9 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPlug
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Info.plist
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacDelegate.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacDelegate.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacDelegateTest.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm
|
||||
@@ -3079,9 +3079,9 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_view.h
|
||||
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/flutter_linux.h
|
||||
FILE: ../../../flutter/shell/platform/windows/accessibility_alert.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/accessibility_alert.h
|
||||
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_windows.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_windows.h
|
||||
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_windows.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_windows.h
|
||||
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_windows_unittests.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/accessibility_root_node.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/accessibility_root_node.h
|
||||
FILE: ../../../flutter/shell/platform/windows/angle_surface_manager.cc
|
||||
|
||||
@@ -19,9 +19,7 @@ constexpr int kHasScrollingAction =
|
||||
FlutterSemanticsAction::kFlutterSemanticsActionScrollDown;
|
||||
|
||||
// AccessibilityBridge
|
||||
AccessibilityBridge::AccessibilityBridge(
|
||||
std::unique_ptr<AccessibilityBridgeDelegate> delegate)
|
||||
: delegate_(std::move(delegate)) {
|
||||
AccessibilityBridge::AccessibilityBridge() {
|
||||
event_generator_.SetTree(&tree_);
|
||||
tree_.AddObserver(static_cast<ui::AXTreeObserver*>(this));
|
||||
}
|
||||
@@ -107,7 +105,7 @@ void AccessibilityBridge::CommitUpdates() {
|
||||
continue;
|
||||
}
|
||||
|
||||
delegate_->OnAccessibilityEvent(targeted_event);
|
||||
OnAccessibilityEvent(targeted_event);
|
||||
}
|
||||
event_generator_.ClearEvents();
|
||||
}
|
||||
@@ -128,20 +126,16 @@ const ui::AXTreeData& AccessibilityBridge::GetAXTreeData() const {
|
||||
}
|
||||
|
||||
const std::vector<ui::AXEventGenerator::TargetedEvent>
|
||||
AccessibilityBridge::GetPendingEvents() {
|
||||
AccessibilityBridge::GetPendingEvents() const {
|
||||
std::vector<ui::AXEventGenerator::TargetedEvent> result(
|
||||
event_generator_.begin(), event_generator_.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
void AccessibilityBridge::UpdateDelegate(
|
||||
std::unique_ptr<AccessibilityBridgeDelegate> delegate) {
|
||||
delegate_ = std::move(delegate);
|
||||
// Recreate FlutterPlatformNodeDelegates since they may contain stale state
|
||||
// from the previous AccessibilityBridgeDelegate.
|
||||
void AccessibilityBridge::RecreateNodeDelegates() {
|
||||
for (const auto& [node_id, old_platform_node_delegate] : id_wrapper_map_) {
|
||||
std::shared_ptr<FlutterPlatformNodeDelegate> platform_node_delegate =
|
||||
delegate_->CreateFlutterPlatformNodeDelegate();
|
||||
CreateFlutterPlatformNodeDelegate();
|
||||
platform_node_delegate->Init(
|
||||
std::static_pointer_cast<FlutterPlatformNodeDelegate::OwnerBridge>(
|
||||
shared_from_this()),
|
||||
@@ -166,7 +160,7 @@ void AccessibilityBridge::OnRoleChanged(ui::AXTree* tree,
|
||||
|
||||
void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) {
|
||||
BASE_DCHECK(node);
|
||||
id_wrapper_map_[node->id()] = delegate_->CreateFlutterPlatformNodeDelegate();
|
||||
id_wrapper_map_[node->id()] = CreateFlutterPlatformNodeDelegate();
|
||||
id_wrapper_map_[node->id()]->Init(
|
||||
std::static_pointer_cast<FlutterPlatformNodeDelegate::OwnerBridge>(
|
||||
shared_from_this()),
|
||||
@@ -629,7 +623,7 @@ void AccessibilityBridge::SetLastFocusedId(AccessibilityNodeId node_id) {
|
||||
auto last_focused_child =
|
||||
GetFlutterPlatformNodeDelegateFromID(last_focused_id_);
|
||||
if (!last_focused_child.expired()) {
|
||||
delegate_->DispatchAccessibilityAction(
|
||||
DispatchAccessibilityAction(
|
||||
last_focused_id_,
|
||||
FlutterSemanticsAction::
|
||||
kFlutterSemanticsActionDidLoseAccessibilityFocus,
|
||||
@@ -659,11 +653,4 @@ gfx::RectF AccessibilityBridge::RelativeToGlobalBounds(const ui::AXNode* node,
|
||||
clip_bounds);
|
||||
}
|
||||
|
||||
void AccessibilityBridge::DispatchAccessibilityAction(
|
||||
AccessibilityNodeId target,
|
||||
FlutterSemanticsAction action,
|
||||
fml::MallocMapping data) {
|
||||
delegate_->DispatchAccessibilityAction(target, action, std::move(data));
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@@ -31,85 +31,20 @@ namespace flutter {
|
||||
/// 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, one must subclass this class and provide their own
|
||||
/// implementation of FlutterPlatformNodeDelegate.
|
||||
///
|
||||
/// To use this class, you must provide your own implementation of
|
||||
/// FlutterPlatformNodeDelegate and AccessibilityBridgeDelegate.
|
||||
/// AccessibilityBridge must be created as a shared_ptr, since some methods
|
||||
/// acquires its weak_ptr.
|
||||
class AccessibilityBridge
|
||||
: public std::enable_shared_from_this<AccessibilityBridge>,
|
||||
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,
|
||||
fml::MallocMapping 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::shared_ptr<FlutterPlatformNodeDelegate>
|
||||
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().
|
||||
explicit AccessibilityBridge(
|
||||
std::unique_ptr<AccessibilityBridgeDelegate> delegate);
|
||||
~AccessibilityBridge();
|
||||
AccessibilityBridge();
|
||||
virtual ~AccessibilityBridge();
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief The ID of the root node in the accessibility tree. In Flutter,
|
||||
@@ -168,12 +103,39 @@ class AccessibilityBridge
|
||||
/// events in AccessibilityBridgeDelegate::OnAccessibilityEvent in
|
||||
/// case one may decide to handle an event differently based on
|
||||
/// all pending events.
|
||||
const std::vector<ui::AXEventGenerator::TargetedEvent> GetPendingEvents();
|
||||
const std::vector<ui::AXEventGenerator::TargetedEvent> GetPendingEvents()
|
||||
const;
|
||||
|
||||
protected:
|
||||
//---------------------------------------------------------------------------
|
||||
/// @brief Handle accessibility events generated due to accessibility
|
||||
/// tree changes. These events are 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 Creates a platform specific FlutterPlatformNodeDelegate.
|
||||
/// Ownership passes to the caller. This method will be called
|
||||
/// 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::shared_ptr<FlutterPlatformNodeDelegate>
|
||||
CreateFlutterPlatformNodeDelegate() = 0;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/// @brief Update the AccessibilityBridgeDelegate stored in the
|
||||
/// accessibility bridge to a new one.
|
||||
void UpdateDelegate(std::unique_ptr<AccessibilityBridgeDelegate> delegate);
|
||||
/// @brief Recreate all FlutterPlatformNodeDelegates.
|
||||
///
|
||||
/// This can be useful for subclasses when updating some
|
||||
/// properties that are used by node delegates, such as views.
|
||||
/// Each node is recreated using
|
||||
/// CreateFlutterPlatformNodeDelegate, then initialized using
|
||||
/// AXNodes from their corresponding old one.
|
||||
void RecreateNodeDelegates();
|
||||
|
||||
private:
|
||||
// See FlutterSemanticsNode in embedder.h
|
||||
@@ -220,7 +182,6 @@ class AccessibilityBridge
|
||||
std::unordered_map<int32_t, SemanticsCustomAction>
|
||||
pending_semantics_custom_action_updates_;
|
||||
AccessibilityNodeId last_focused_id_ = ui::AXNode::kInvalidAXID;
|
||||
std::unique_ptr<AccessibilityBridgeDelegate> delegate_;
|
||||
|
||||
void InitAXTree(const ui::AXTreeUpdate& initial_state);
|
||||
|
||||
@@ -295,11 +256,6 @@ class AccessibilityBridge
|
||||
gfx::NativeViewAccessible GetNativeAccessibleFromId(
|
||||
AccessibilityNodeId id) override;
|
||||
|
||||
// |FlutterPlatformNodeDelegate::OwnerBridge|
|
||||
void DispatchAccessibilityAction(AccessibilityNodeId target,
|
||||
FlutterSemanticsAction action,
|
||||
fml::MallocMapping data) override;
|
||||
|
||||
// |FlutterPlatformNodeDelegate::OwnerBridge|
|
||||
gfx::RectF RelativeToGlobalBounds(const ui::AXNode* node,
|
||||
bool& offscreen,
|
||||
|
||||
@@ -37,9 +37,8 @@ FlutterSemanticsNode CreateSemanticsNode(
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeTest, basicTest) {
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
|
||||
std::vector<int32_t> children{1, 2};
|
||||
FlutterSemanticsNode root = CreateSemanticsNode(0, "root", &children);
|
||||
@@ -67,11 +66,8 @@ TEST(AccessibilityBridgeTest, basicTest) {
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) {
|
||||
TestAccessibilityBridgeDelegate* delegate =
|
||||
new TestAccessibilityBridgeDelegate();
|
||||
std::unique_ptr<TestAccessibilityBridgeDelegate> ptr(delegate);
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(std::move(ptr));
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
|
||||
std::vector<int32_t> children{1};
|
||||
FlutterSemanticsNode root = CreateSemanticsNode(0, "root", &children);
|
||||
@@ -90,7 +86,7 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) {
|
||||
|
||||
EXPECT_EQ(child1_node->GetChildCount(), 0);
|
||||
EXPECT_EQ(child1_node->GetName(), "child 1");
|
||||
delegate->accessibility_events.clear();
|
||||
bridge->accessibility_events.clear();
|
||||
|
||||
// Add a child to root.
|
||||
root.child_count = 2;
|
||||
@@ -108,20 +104,18 @@ TEST(AccessibilityBridgeTest, canFireChildrenChangedCorrectly) {
|
||||
EXPECT_EQ(root_node->GetChildCount(), 2);
|
||||
EXPECT_EQ(root_node->GetData().child_ids[0], 1);
|
||||
EXPECT_EQ(root_node->GetData().child_ids[1], 2);
|
||||
EXPECT_EQ(delegate->accessibility_events.size(), size_t{2});
|
||||
EXPECT_EQ(bridge->accessibility_events.size(), size_t{2});
|
||||
std::set<ui::AXEventGenerator::Event> actual_event{
|
||||
delegate->accessibility_events.begin(),
|
||||
delegate->accessibility_events.end()};
|
||||
bridge->accessibility_events.begin(), bridge->accessibility_events.end()};
|
||||
EXPECT_THAT(actual_event,
|
||||
Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED));
|
||||
EXPECT_THAT(actual_event,
|
||||
Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED));
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeTest, canUpdateDelegate) {
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
TEST(AccessibilityBridgeTest, canRecreateNodeDelegates) {
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
|
||||
std::vector<int32_t> children{1};
|
||||
FlutterSemanticsNode root = CreateSemanticsNode(0, "root", &children);
|
||||
@@ -135,8 +129,8 @@ TEST(AccessibilityBridgeTest, canUpdateDelegate) {
|
||||
auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1);
|
||||
EXPECT_FALSE(root_node.expired());
|
||||
EXPECT_FALSE(child1_node.expired());
|
||||
// Update Delegate
|
||||
bridge->UpdateDelegate(std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
|
||||
bridge->RecreateNodeDelegates();
|
||||
|
||||
// Old tree is destroyed.
|
||||
EXPECT_TRUE(root_node.expired());
|
||||
@@ -154,11 +148,8 @@ TEST(AccessibilityBridgeTest, canUpdateDelegate) {
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrectly) {
|
||||
TestAccessibilityBridgeDelegate* delegate =
|
||||
new TestAccessibilityBridgeDelegate();
|
||||
std::unique_ptr<TestAccessibilityBridgeDelegate> ptr(delegate);
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(std::move(ptr));
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
FlutterSemanticsNode root = CreateSemanticsNode(0, "root");
|
||||
root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField;
|
||||
bridge->AddFlutterSemanticsNodeUpdate(&root);
|
||||
@@ -166,7 +157,7 @@ TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrectly) {
|
||||
|
||||
const ui::AXTreeData& tree = bridge->GetAXTreeData();
|
||||
EXPECT_EQ(tree.sel_anchor_object_id, ui::AXNode::kInvalidAXID);
|
||||
delegate->accessibility_events.clear();
|
||||
bridge->accessibility_events.clear();
|
||||
|
||||
// Update the selection.
|
||||
root.text_selection_base = 0;
|
||||
@@ -179,17 +170,16 @@ TEST(AccessibilityBridgeTest, canHandleSelectionChangeCorrectly) {
|
||||
EXPECT_EQ(tree.sel_anchor_offset, 0);
|
||||
EXPECT_EQ(tree.sel_focus_object_id, 0);
|
||||
EXPECT_EQ(tree.sel_focus_offset, 5);
|
||||
ASSERT_EQ(delegate->accessibility_events.size(), size_t{2});
|
||||
EXPECT_EQ(delegate->accessibility_events[0],
|
||||
ASSERT_EQ(bridge->accessibility_events.size(), size_t{2});
|
||||
EXPECT_EQ(bridge->accessibility_events[0],
|
||||
ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED);
|
||||
EXPECT_EQ(delegate->accessibility_events[1],
|
||||
EXPECT_EQ(bridge->accessibility_events[1],
|
||||
ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeTest, doesNotAssignEditableRootToSelectableText) {
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
FlutterSemanticsNode root = CreateSemanticsNode(0, "root");
|
||||
root.flags = static_cast<FlutterSemanticsFlag>(
|
||||
FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField |
|
||||
@@ -204,9 +194,8 @@ TEST(AccessibilityBridgeTest, doesNotAssignEditableRootToSelectableText) {
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeTest, ToggleHasToggleButtonRole) {
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
FlutterSemanticsNode root = CreateSemanticsNode(0, "root");
|
||||
root.flags = static_cast<FlutterSemanticsFlag>(
|
||||
FlutterSemanticsFlag::kFlutterSemanticsFlagHasToggledState |
|
||||
@@ -220,9 +209,8 @@ TEST(AccessibilityBridgeTest, ToggleHasToggleButtonRole) {
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeTest, SliderHasSliderRole) {
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
FlutterSemanticsNode root = CreateSemanticsNode(0, "root");
|
||||
root.flags = static_cast<FlutterSemanticsFlag>(
|
||||
FlutterSemanticsFlag::kFlutterSemanticsFlagIsSlider |
|
||||
@@ -242,9 +230,8 @@ TEST(AccessibilityBridgeTest, SliderHasSliderRole) {
|
||||
// https://github.com/flutter/flutter/issues/96218
|
||||
// As this fix involved code run on all platforms, it is included here.
|
||||
TEST(AccessibilityBridgeTest, CanSetCheckboxChecked) {
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
FlutterSemanticsNode root = CreateSemanticsNode(0, "root");
|
||||
root.flags = static_cast<FlutterSemanticsFlag>(
|
||||
FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState |
|
||||
@@ -260,11 +247,8 @@ TEST(AccessibilityBridgeTest, CanSetCheckboxChecked) {
|
||||
|
||||
// Verify that a node can be moved from one parent to another.
|
||||
TEST(AccessibilityBridgeTest, CanReparentNode) {
|
||||
TestAccessibilityBridgeDelegate* delegate =
|
||||
new TestAccessibilityBridgeDelegate();
|
||||
std::unique_ptr<TestAccessibilityBridgeDelegate> ptr(delegate);
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(std::move(ptr));
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
|
||||
std::vector<int32_t> root_children{1};
|
||||
std::vector<int32_t> child1_children{2};
|
||||
@@ -277,7 +261,7 @@ TEST(AccessibilityBridgeTest, CanReparentNode) {
|
||||
bridge->AddFlutterSemanticsNodeUpdate(&child1);
|
||||
bridge->AddFlutterSemanticsNodeUpdate(&child2);
|
||||
bridge->CommitUpdates();
|
||||
delegate->accessibility_events.clear();
|
||||
bridge->accessibility_events.clear();
|
||||
|
||||
// Reparent child2 from child1 to the root.
|
||||
child1.child_count = 0;
|
||||
@@ -307,30 +291,27 @@ TEST(AccessibilityBridgeTest, CanReparentNode) {
|
||||
EXPECT_EQ(child2_node->GetChildCount(), 0);
|
||||
EXPECT_EQ(child2_node->GetName(), "child 2");
|
||||
|
||||
ASSERT_EQ(delegate->accessibility_events.size(), size_t{5});
|
||||
ASSERT_EQ(bridge->accessibility_events.size(), size_t{5});
|
||||
|
||||
// Child2 is moved from child1 to root.
|
||||
EXPECT_THAT(delegate->accessibility_events,
|
||||
EXPECT_THAT(bridge->accessibility_events,
|
||||
Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED).Times(2));
|
||||
EXPECT_THAT(delegate->accessibility_events,
|
||||
EXPECT_THAT(bridge->accessibility_events,
|
||||
Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED).Times(1));
|
||||
|
||||
// Child1 is no longer a parent. It loses its group role and disables its
|
||||
// 'clip children' attribute.
|
||||
EXPECT_THAT(
|
||||
delegate->accessibility_events,
|
||||
bridge->accessibility_events,
|
||||
Contains(ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED).Times(1));
|
||||
EXPECT_THAT(delegate->accessibility_events,
|
||||
EXPECT_THAT(bridge->accessibility_events,
|
||||
Contains(ui::AXEventGenerator::Event::ROLE_CHANGED).Times(1));
|
||||
}
|
||||
|
||||
// Verify that multiple nodes can be moved to new parents.
|
||||
TEST(AccessibilityBridgeTest, CanReparentMultipleNodes) {
|
||||
TestAccessibilityBridgeDelegate* delegate =
|
||||
new TestAccessibilityBridgeDelegate();
|
||||
std::unique_ptr<TestAccessibilityBridgeDelegate> ptr(delegate);
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(std::move(ptr));
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
|
||||
int32_t root_id = 0;
|
||||
int32_t intermediary1_id = 1;
|
||||
@@ -359,7 +340,7 @@ TEST(AccessibilityBridgeTest, CanReparentMultipleNodes) {
|
||||
bridge->AddFlutterSemanticsNodeUpdate(&leaf2);
|
||||
bridge->AddFlutterSemanticsNodeUpdate(&leaf3);
|
||||
bridge->CommitUpdates();
|
||||
delegate->accessibility_events.clear();
|
||||
bridge->accessibility_events.clear();
|
||||
|
||||
// Swap intermediary 1's and intermediary2's children.
|
||||
int32_t new_intermediary1_children[] = {leaf2_id, leaf3_id};
|
||||
@@ -414,20 +395,17 @@ TEST(AccessibilityBridgeTest, CanReparentMultipleNodes) {
|
||||
|
||||
// Intermediary 1 and intermediary 2 have new children.
|
||||
// Leaf 1, 2, and 3 are all moved.
|
||||
ASSERT_EQ(delegate->accessibility_events.size(), size_t{5});
|
||||
EXPECT_THAT(delegate->accessibility_events,
|
||||
ASSERT_EQ(bridge->accessibility_events.size(), size_t{5});
|
||||
EXPECT_THAT(bridge->accessibility_events,
|
||||
Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED).Times(2));
|
||||
EXPECT_THAT(delegate->accessibility_events,
|
||||
EXPECT_THAT(bridge->accessibility_events,
|
||||
Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED).Times(3));
|
||||
}
|
||||
|
||||
// Verify that a node with a child can be moved from one parent to another.
|
||||
TEST(AccessibilityBridgeTest, CanReparentNodeWithChild) {
|
||||
TestAccessibilityBridgeDelegate* delegate =
|
||||
new TestAccessibilityBridgeDelegate();
|
||||
std::unique_ptr<TestAccessibilityBridgeDelegate> ptr(delegate);
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(std::move(ptr));
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
|
||||
int32_t root_id = 0;
|
||||
int32_t intermediary1_id = 1;
|
||||
@@ -449,7 +427,7 @@ TEST(AccessibilityBridgeTest, CanReparentNodeWithChild) {
|
||||
bridge->AddFlutterSemanticsNodeUpdate(&intermediary2);
|
||||
bridge->AddFlutterSemanticsNodeUpdate(&leaf1);
|
||||
bridge->CommitUpdates();
|
||||
delegate->accessibility_events.clear();
|
||||
bridge->accessibility_events.clear();
|
||||
|
||||
// Move intermediary1 from root to intermediary 2.
|
||||
int32_t new_root_children[] = {intermediary2_id};
|
||||
@@ -489,19 +467,19 @@ TEST(AccessibilityBridgeTest, CanReparentNodeWithChild) {
|
||||
EXPECT_EQ(leaf1_node->GetChildCount(), 0);
|
||||
EXPECT_EQ(leaf1_node->GetName(), "leaf 1");
|
||||
|
||||
ASSERT_EQ(delegate->accessibility_events.size(), size_t{5});
|
||||
ASSERT_EQ(bridge->accessibility_events.size(), size_t{5});
|
||||
|
||||
EXPECT_THAT(delegate->accessibility_events,
|
||||
EXPECT_THAT(bridge->accessibility_events,
|
||||
Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED).Times(2));
|
||||
EXPECT_THAT(delegate->accessibility_events,
|
||||
EXPECT_THAT(bridge->accessibility_events,
|
||||
Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED).Times(1));
|
||||
|
||||
// Intermediary 2 becomes a parent node. It updates to group role and enables
|
||||
// its 'clip children' attribute.
|
||||
EXPECT_THAT(
|
||||
delegate->accessibility_events,
|
||||
bridge->accessibility_events,
|
||||
Contains(ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED).Times(1));
|
||||
EXPECT_THAT(delegate->accessibility_events,
|
||||
EXPECT_THAT(bridge->accessibility_events,
|
||||
Contains(ui::AXEventGenerator::Event::ROLE_CHANGED).Times(1));
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,8 @@ namespace flutter {
|
||||
namespace testing {
|
||||
|
||||
TEST(FlutterPlatformNodeDelegateTest, NodeDelegateHasUniqueId) {
|
||||
TestAccessibilityBridgeDelegate* delegate =
|
||||
new TestAccessibilityBridgeDelegate();
|
||||
std::unique_ptr<TestAccessibilityBridgeDelegate> ptr(delegate);
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(std::move(ptr));
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
|
||||
// Add node 0: root.
|
||||
FlutterSemanticsNode node0{sizeof(FlutterSemanticsNode), 0};
|
||||
@@ -41,11 +38,8 @@ TEST(FlutterPlatformNodeDelegateTest, NodeDelegateHasUniqueId) {
|
||||
}
|
||||
|
||||
TEST(FlutterPlatformNodeDelegateTest, canPerfomActions) {
|
||||
TestAccessibilityBridgeDelegate* delegate =
|
||||
new TestAccessibilityBridgeDelegate();
|
||||
std::unique_ptr<TestAccessibilityBridgeDelegate> ptr(delegate);
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(std::move(ptr));
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
FlutterSemanticsNode root;
|
||||
root.id = 0;
|
||||
root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField;
|
||||
@@ -69,29 +63,28 @@ TEST(FlutterPlatformNodeDelegateTest, canPerfomActions) {
|
||||
ui::AXActionData action_data;
|
||||
action_data.action = ax::mojom::Action::kDoDefault;
|
||||
accessibility->AccessibilityPerformAction(action_data);
|
||||
EXPECT_EQ(delegate->performed_actions.size(), size_t{1});
|
||||
EXPECT_EQ(delegate->performed_actions[0],
|
||||
EXPECT_EQ(bridge->performed_actions.size(), size_t{1});
|
||||
EXPECT_EQ(bridge->performed_actions[0],
|
||||
FlutterSemanticsAction::kFlutterSemanticsActionTap);
|
||||
|
||||
action_data.action = ax::mojom::Action::kFocus;
|
||||
accessibility->AccessibilityPerformAction(action_data);
|
||||
EXPECT_EQ(delegate->performed_actions.size(), size_t{2});
|
||||
EXPECT_EQ(bridge->performed_actions.size(), size_t{2});
|
||||
EXPECT_EQ(
|
||||
delegate->performed_actions[1],
|
||||
bridge->performed_actions[1],
|
||||
FlutterSemanticsAction::kFlutterSemanticsActionDidGainAccessibilityFocus);
|
||||
|
||||
action_data.action = ax::mojom::Action::kScrollToMakeVisible;
|
||||
accessibility->AccessibilityPerformAction(action_data);
|
||||
EXPECT_EQ(delegate->performed_actions.size(), size_t{3});
|
||||
EXPECT_EQ(delegate->performed_actions[2],
|
||||
EXPECT_EQ(bridge->performed_actions.size(), size_t{3});
|
||||
EXPECT_EQ(bridge->performed_actions[2],
|
||||
FlutterSemanticsAction::kFlutterSemanticsActionShowOnScreen);
|
||||
}
|
||||
|
||||
TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) {
|
||||
// Set up a flutter accessibility node.
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
FlutterSemanticsNode root;
|
||||
root.id = 0;
|
||||
root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField;
|
||||
@@ -115,9 +108,8 @@ TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) {
|
||||
}
|
||||
|
||||
TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) {
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
FlutterSemanticsNode root;
|
||||
root.id = 0;
|
||||
root.label = "root";
|
||||
@@ -162,9 +154,8 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) {
|
||||
}
|
||||
|
||||
TEST(FlutterPlatformNodeDelegateTest, canCalculateOffScreenBoundsCorrectly) {
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
FlutterSemanticsNode root;
|
||||
root.id = 0;
|
||||
root.label = "root";
|
||||
@@ -209,9 +200,8 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateOffScreenBoundsCorrectly) {
|
||||
}
|
||||
|
||||
TEST(FlutterPlatformNodeDelegateTest, canUseOwnerBridge) {
|
||||
std::shared_ptr<AccessibilityBridge> bridge =
|
||||
std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<TestAccessibilityBridgeDelegate>());
|
||||
std::shared_ptr<TestAccessibilityBridge> bridge =
|
||||
std::make_shared<TestAccessibilityBridge>();
|
||||
FlutterSemanticsNode root;
|
||||
root.id = 0;
|
||||
root.label = "root";
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
namespace flutter {
|
||||
|
||||
std::shared_ptr<FlutterPlatformNodeDelegate>
|
||||
TestAccessibilityBridgeDelegate::CreateFlutterPlatformNodeDelegate() {
|
||||
TestAccessibilityBridge::CreateFlutterPlatformNodeDelegate() {
|
||||
return std::make_unique<FlutterPlatformNodeDelegate>();
|
||||
};
|
||||
|
||||
void TestAccessibilityBridgeDelegate::OnAccessibilityEvent(
|
||||
void TestAccessibilityBridge::OnAccessibilityEvent(
|
||||
ui::AXEventGenerator::TargetedEvent targeted_event) {
|
||||
accessibility_events.push_back(targeted_event.event_params.event);
|
||||
}
|
||||
|
||||
void TestAccessibilityBridgeDelegate::DispatchAccessibilityAction(
|
||||
void TestAccessibilityBridge::DispatchAccessibilityAction(
|
||||
AccessibilityNodeId target,
|
||||
FlutterSemanticsAction action,
|
||||
fml::MallocMapping data) {
|
||||
|
||||
@@ -9,21 +9,25 @@
|
||||
|
||||
namespace flutter {
|
||||
|
||||
class TestAccessibilityBridgeDelegate
|
||||
: public AccessibilityBridge::AccessibilityBridgeDelegate {
|
||||
class TestAccessibilityBridge : public AccessibilityBridge {
|
||||
public:
|
||||
TestAccessibilityBridgeDelegate() = default;
|
||||
using AccessibilityBridge::RecreateNodeDelegates;
|
||||
|
||||
TestAccessibilityBridge() = default;
|
||||
|
||||
void OnAccessibilityEvent(
|
||||
ui::AXEventGenerator::TargetedEvent targeted_event) override;
|
||||
void DispatchAccessibilityAction(AccessibilityNodeId target,
|
||||
FlutterSemanticsAction action,
|
||||
fml::MallocMapping data) override;
|
||||
std::shared_ptr<FlutterPlatformNodeDelegate>
|
||||
CreateFlutterPlatformNodeDelegate() override;
|
||||
|
||||
std::vector<ui::AXEventGenerator::Event> accessibility_events;
|
||||
std::vector<FlutterSemanticsAction> performed_actions;
|
||||
|
||||
protected:
|
||||
void OnAccessibilityEvent(
|
||||
ui::AXEventGenerator::TargetedEvent targeted_event) override;
|
||||
|
||||
std::shared_ptr<FlutterPlatformNodeDelegate>
|
||||
CreateFlutterPlatformNodeDelegate() override;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@@ -54,8 +54,8 @@ source_set("flutter_framework_source") {
|
||||
visibility = [ ":*" ]
|
||||
|
||||
sources = [
|
||||
"framework/Source/AccessibilityBridgeMacDelegate.h",
|
||||
"framework/Source/AccessibilityBridgeMacDelegate.mm",
|
||||
"framework/Source/AccessibilityBridgeMac.h",
|
||||
"framework/Source/AccessibilityBridgeMac.mm",
|
||||
"framework/Source/FlutterAppDelegate.mm",
|
||||
"framework/Source/FlutterBackingStore.h",
|
||||
"framework/Source/FlutterBackingStore.mm",
|
||||
@@ -183,7 +183,7 @@ executable("flutter_desktop_darwin_unittests") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"framework/Source/AccessibilityBridgeMacDelegateTest.mm",
|
||||
"framework/Source/AccessibilityBridgeMacTest.mm",
|
||||
"framework/Source/FlutterChannelKeyResponderUnittests.mm",
|
||||
"framework/Source/FlutterEmbedderExternalTextureUnittests.mm",
|
||||
"framework/Source/FlutterEmbedderKeyResponderUnittests.mm",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_ACCESSIBILITYBRIDGEMACDELEGATE_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_ACCESSIBILITYBRIDGEMACDELEGATE_H_
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_ACCESSIBILITY_BRIDGE_MAC_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_ACCESSIBILITY_BRIDGE_MAC_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@@ -15,27 +15,41 @@
|
||||
namespace flutter {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/// The macOS implementation of AccessibilityBridge::AccessibilityBridgeDelegate.
|
||||
/// This delegate is used to create AccessibilityBridge in the macOS embedding.
|
||||
class AccessibilityBridgeMacDelegate : public AccessibilityBridge::AccessibilityBridgeDelegate {
|
||||
/// The macOS implementation of AccessibilityBridge.
|
||||
///
|
||||
/// This interacts with macOS accessibility APIs, which includes routing
|
||||
/// accessibility events fired from the framework to macOS, routing native
|
||||
/// macOS accessibility events to the framework, and creating macOS-specific
|
||||
/// FlutterPlatformNodeDelegate objects for each node in the semantics tree.
|
||||
///
|
||||
/// AccessibilityBridgeMac must be created as a shared_ptr, since some methods
|
||||
/// acquires its weak_ptr.
|
||||
class AccessibilityBridgeMac : public AccessibilityBridge {
|
||||
public:
|
||||
//---------------------------------------------------------------------------
|
||||
/// @brief Creates an AccessibilityBridgeMacDelegate.
|
||||
/// @param[in] flutter_engine The weak reference to the FlutterEngine.
|
||||
/// @param[in] view_controller The weak reference to the FlutterViewController.
|
||||
explicit AccessibilityBridgeMacDelegate(__weak FlutterEngine* flutter_engine,
|
||||
__weak FlutterViewController* view_controller);
|
||||
virtual ~AccessibilityBridgeMacDelegate() = default;
|
||||
explicit AccessibilityBridgeMac(__weak FlutterEngine* flutter_engine,
|
||||
__weak FlutterViewController* view_controller);
|
||||
virtual ~AccessibilityBridgeMac() = default;
|
||||
|
||||
// |AccessibilityBridge::AccessibilityBridgeDelegate|
|
||||
void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event) override;
|
||||
|
||||
// |AccessibilityBridge::AccessibilityBridgeDelegate|
|
||||
// |FlutterPlatformNodeDelegate::OwnerBridge|
|
||||
void DispatchAccessibilityAction(AccessibilityNodeId target,
|
||||
FlutterSemanticsAction action,
|
||||
fml::MallocMapping data) override;
|
||||
|
||||
// |AccessibilityBridge::AccessibilityBridgeDelegate|
|
||||
// Update the default view controller, and recreate the corresponding
|
||||
// accessibility node delegate.
|
||||
//
|
||||
// This is called by the engine when the default view controller is updated.
|
||||
void UpdateDefaultViewController(__weak FlutterViewController* view_controller);
|
||||
|
||||
protected:
|
||||
// |AccessibilityBridge|
|
||||
void OnAccessibilityEvent(ui::AXEventGenerator::TargetedEvent targeted_event) override;
|
||||
|
||||
// |AccessibilityBridge|
|
||||
std::shared_ptr<FlutterPlatformNodeDelegate> CreateFlutterPlatformNodeDelegate() override;
|
||||
|
||||
private:
|
||||
@@ -84,4 +98,4 @@ class AccessibilityBridgeMacDelegate : public AccessibilityBridge::Accessibility
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_ACCESSIBILITYBRIDGEMACDELEGATE_H_
|
||||
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_ACCESSIBILITY_BRIDGE_MAC_H_
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacDelegate.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h"
|
||||
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.h"
|
||||
@@ -20,21 +20,26 @@ static NSString* const kAccessibilityLiveRegionChangedNotification = @"AXLiveReg
|
||||
static NSString* const kAccessibilityExpandedChanged = @"AXExpandedChanged";
|
||||
static NSString* const kAccessibilityMenuItemSelectedNotification = @"AXMenuItemSelected";
|
||||
|
||||
AccessibilityBridgeMacDelegate::AccessibilityBridgeMacDelegate(
|
||||
__weak FlutterEngine* flutter_engine,
|
||||
__weak FlutterViewController* view_controller)
|
||||
AccessibilityBridgeMac::AccessibilityBridgeMac(__weak FlutterEngine* flutter_engine,
|
||||
__weak FlutterViewController* view_controller)
|
||||
: flutter_engine_(flutter_engine), view_controller_(view_controller) {}
|
||||
|
||||
void AccessibilityBridgeMacDelegate::OnAccessibilityEvent(
|
||||
void AccessibilityBridgeMac::UpdateDefaultViewController(
|
||||
__weak FlutterViewController* view_controller) {
|
||||
view_controller_ = view_controller;
|
||||
RecreateNodeDelegates();
|
||||
}
|
||||
|
||||
void AccessibilityBridgeMac::OnAccessibilityEvent(
|
||||
ui::AXEventGenerator::TargetedEvent targeted_event) {
|
||||
if (!flutter_engine_.viewController.viewLoaded || !flutter_engine_.viewController.view.window) {
|
||||
if (!view_controller_.viewLoaded || !view_controller_.view.window) {
|
||||
// Don't need to send accessibility events if the there is no view or window.
|
||||
return;
|
||||
}
|
||||
ui::AXNode* ax_node = targeted_event.node;
|
||||
std::vector<AccessibilityBridgeMacDelegate::NSAccessibilityEvent> events =
|
||||
std::vector<AccessibilityBridgeMac::NSAccessibilityEvent> events =
|
||||
MacOSEventsFromAXEvent(targeted_event.event_params.event, *ax_node);
|
||||
for (AccessibilityBridgeMacDelegate::NSAccessibilityEvent event : events) {
|
||||
for (AccessibilityBridgeMac::NSAccessibilityEvent event : events) {
|
||||
if (event.user_info != nil) {
|
||||
DispatchMacOSNotificationWithUserInfo(event.target, event.name, event.user_info);
|
||||
} else {
|
||||
@@ -43,20 +48,18 @@ void AccessibilityBridgeMacDelegate::OnAccessibilityEvent(
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<AccessibilityBridgeMacDelegate::NSAccessibilityEvent>
|
||||
AccessibilityBridgeMacDelegate::MacOSEventsFromAXEvent(ui::AXEventGenerator::Event event_type,
|
||||
const ui::AXNode& ax_node) const {
|
||||
std::vector<AccessibilityBridgeMac::NSAccessibilityEvent>
|
||||
AccessibilityBridgeMac::MacOSEventsFromAXEvent(ui::AXEventGenerator::Event event_type,
|
||||
const ui::AXNode& ax_node) const {
|
||||
// Gets the native_node with the node_id.
|
||||
NSCAssert(flutter_engine_, @"Flutter engine should not be deallocated");
|
||||
auto bridge = flutter_engine_.accessibilityBridge.lock();
|
||||
NSCAssert(bridge, @"Accessibility bridge in flutter engine must not be null.");
|
||||
auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(ax_node.id()).lock();
|
||||
auto platform_node_delegate = GetFlutterPlatformNodeDelegateFromID(ax_node.id()).lock();
|
||||
NSCAssert(platform_node_delegate, @"Event target must exist in accessibility bridge.");
|
||||
auto mac_platform_node_delegate =
|
||||
std::static_pointer_cast<FlutterPlatformNodeDelegateMac>(platform_node_delegate);
|
||||
gfx::NativeViewAccessible native_node = mac_platform_node_delegate->GetNativeViewAccessible();
|
||||
|
||||
std::vector<AccessibilityBridgeMacDelegate::NSAccessibilityEvent> events;
|
||||
std::vector<AccessibilityBridgeMac::NSAccessibilityEvent> events;
|
||||
switch (event_type) {
|
||||
case ui::AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED:
|
||||
if (ax_node.data().role == ax::mojom::Role::kTree) {
|
||||
@@ -140,12 +143,12 @@ AccessibilityBridgeMacDelegate::MacOSEventsFromAXEvent(ui::AXEventGenerator::Eve
|
||||
});
|
||||
// WebKit fires a notification both on the focused object and the page
|
||||
// root.
|
||||
const ui::AXTreeData& tree_data = bridge->GetAXTreeData();
|
||||
const ui::AXTreeData& tree_data = GetAXTreeData();
|
||||
int32_t focus = tree_data.focus_id;
|
||||
if (focus == ui::AXNode::kInvalidAXID || focus != tree_data.sel_anchor_object_id) {
|
||||
break; // Just fire a notification on the root.
|
||||
}
|
||||
auto focus_node = bridge->GetFlutterPlatformNodeDelegateFromID(focus).lock();
|
||||
auto focus_node = GetFlutterPlatformNodeDelegateFromID(focus).lock();
|
||||
if (!focus_node) {
|
||||
break; // Just fire a notification on the root.
|
||||
}
|
||||
@@ -184,7 +187,7 @@ AccessibilityBridgeMacDelegate::MacOSEventsFromAXEvent(ui::AXEventGenerator::Eve
|
||||
if (ax_node.data().HasState(ax::mojom::State::kEditable)) {
|
||||
events.push_back({
|
||||
.name = NSAccessibilityValueChangedNotification,
|
||||
.target = bridge->GetFlutterPlatformNodeDelegateFromID(AccessibilityBridge::kRootNodeId)
|
||||
.target = GetFlutterPlatformNodeDelegateFromID(AccessibilityBridge::kRootNodeId)
|
||||
.lock()
|
||||
->GetNativeViewAccessible(),
|
||||
.user_info = nil,
|
||||
@@ -292,10 +295,9 @@ AccessibilityBridgeMacDelegate::MacOSEventsFromAXEvent(ui::AXEventGenerator::Eve
|
||||
case ui::AXEventGenerator::Event::CHILDREN_CHANGED: {
|
||||
// NSAccessibilityCreatedNotification seems to be the only way to let
|
||||
// Voiceover pick up layout changes.
|
||||
NSCAssert(flutter_engine_.viewController, @"The viewController must not be nil");
|
||||
events.push_back({
|
||||
.name = NSAccessibilityCreatedNotification,
|
||||
.target = flutter_engine_.viewController.view.window,
|
||||
.target = view_controller_.view.window,
|
||||
.user_info = nil,
|
||||
});
|
||||
break;
|
||||
@@ -359,23 +361,23 @@ AccessibilityBridgeMacDelegate::MacOSEventsFromAXEvent(ui::AXEventGenerator::Eve
|
||||
return events;
|
||||
}
|
||||
|
||||
void AccessibilityBridgeMacDelegate::DispatchAccessibilityAction(ui::AXNode::AXID target,
|
||||
FlutterSemanticsAction action,
|
||||
fml::MallocMapping data) {
|
||||
void AccessibilityBridgeMac::DispatchAccessibilityAction(ui::AXNode::AXID target,
|
||||
FlutterSemanticsAction action,
|
||||
fml::MallocMapping data) {
|
||||
NSCAssert(flutter_engine_, @"Flutter engine should not be deallocated");
|
||||
NSCAssert(flutter_engine_.viewController.viewLoaded && flutter_engine_.viewController.view.window,
|
||||
NSCAssert(view_controller_.viewLoaded && view_controller_.view.window,
|
||||
@"The accessibility bridge should not receive accessibility actions if the flutter view"
|
||||
@"is not loaded or attached to a NSWindow.");
|
||||
[flutter_engine_ dispatchSemanticsAction:action toTarget:target withData:std::move(data)];
|
||||
}
|
||||
|
||||
std::shared_ptr<FlutterPlatformNodeDelegate>
|
||||
AccessibilityBridgeMacDelegate::CreateFlutterPlatformNodeDelegate() {
|
||||
return std::make_shared<FlutterPlatformNodeDelegateMac>(flutter_engine_, view_controller_);
|
||||
AccessibilityBridgeMac::CreateFlutterPlatformNodeDelegate() {
|
||||
return std::make_shared<FlutterPlatformNodeDelegateMac>(weak_from_this(), view_controller_);
|
||||
}
|
||||
|
||||
// Private method
|
||||
void AccessibilityBridgeMacDelegate::DispatchMacOSNotification(
|
||||
void AccessibilityBridgeMac::DispatchMacOSNotification(
|
||||
gfx::NativeViewAccessible native_node,
|
||||
NSAccessibilityNotificationName mac_notification) {
|
||||
NSCAssert(mac_notification, @"The notification must not be null.");
|
||||
@@ -383,7 +385,7 @@ void AccessibilityBridgeMacDelegate::DispatchMacOSNotification(
|
||||
NSAccessibilityPostNotification(native_node, mac_notification);
|
||||
}
|
||||
|
||||
void AccessibilityBridgeMacDelegate::DispatchMacOSNotificationWithUserInfo(
|
||||
void AccessibilityBridgeMac::DispatchMacOSNotificationWithUserInfo(
|
||||
gfx::NativeViewAccessible native_node,
|
||||
NSAccessibilityNotificationName mac_notification,
|
||||
NSDictionary* user_info) {
|
||||
@@ -393,12 +395,10 @@ void AccessibilityBridgeMacDelegate::DispatchMacOSNotificationWithUserInfo(
|
||||
NSAccessibilityPostNotificationWithUserInfo(native_node, mac_notification, user_info);
|
||||
}
|
||||
|
||||
bool AccessibilityBridgeMacDelegate::HasPendingEvent(ui::AXEventGenerator::Event event) const {
|
||||
bool AccessibilityBridgeMac::HasPendingEvent(ui::AXEventGenerator::Event event) const {
|
||||
NSCAssert(flutter_engine_, @"Flutter engine should not be deallocated");
|
||||
auto bridge = flutter_engine_.accessibilityBridge.lock();
|
||||
NSCAssert(bridge, @"Accessibility bridge in flutter engine must not be null.");
|
||||
std::vector<ui::AXEventGenerator::TargetedEvent> pending_events = bridge->GetPendingEvents();
|
||||
for (const auto& pending_event : bridge->GetPendingEvents()) {
|
||||
std::vector<ui::AXEventGenerator::TargetedEvent> pending_events = GetPendingEvents();
|
||||
for (const auto& pending_event : GetPendingEvents()) {
|
||||
if (pending_event.event_params.event == event) {
|
||||
return true;
|
||||
}
|
||||
@@ -3,19 +3,22 @@
|
||||
// found in the LICENSE file.
|
||||
#include "flutter/testing/testing.h"
|
||||
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacDelegate.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
|
||||
|
||||
namespace flutter::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
class AccessibilityBridgeMacDelegateSpy : public AccessibilityBridgeMacDelegate {
|
||||
class AccessibilityBridgeMacSpy : public AccessibilityBridgeMac {
|
||||
public:
|
||||
AccessibilityBridgeMacDelegateSpy(__weak FlutterEngine* flutter_engine,
|
||||
__weak FlutterViewController* view_controller)
|
||||
: AccessibilityBridgeMacDelegate(flutter_engine, view_controller) {}
|
||||
using AccessibilityBridgeMac::OnAccessibilityEvent;
|
||||
|
||||
AccessibilityBridgeMacSpy(__weak FlutterEngine* flutter_engine,
|
||||
__weak FlutterViewController* view_controller)
|
||||
: AccessibilityBridgeMac(flutter_engine, view_controller) {}
|
||||
|
||||
std::unordered_map<std::string, gfx::NativeViewAccessible> actual_notifications;
|
||||
|
||||
@@ -26,18 +29,40 @@ class AccessibilityBridgeMacDelegateSpy : public AccessibilityBridgeMacDelegate
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace flutter::testing
|
||||
|
||||
@interface AccessibilityBridgeTestEngine : FlutterEngine
|
||||
- (std::shared_ptr<flutter::AccessibilityBridgeMac>)
|
||||
createAccessibilityBridge:(nonnull FlutterEngine*)engine
|
||||
viewController:(nonnull FlutterViewController*)viewController;
|
||||
@end
|
||||
|
||||
@implementation AccessibilityBridgeTestEngine
|
||||
- (std::shared_ptr<flutter::AccessibilityBridgeMac>)
|
||||
createAccessibilityBridge:(nonnull FlutterEngine*)engine
|
||||
viewController:(nonnull FlutterViewController*)viewController {
|
||||
return std::make_shared<flutter::testing::AccessibilityBridgeMacSpy>(engine, viewController);
|
||||
}
|
||||
@end
|
||||
|
||||
namespace flutter::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns an engine configured for the text fixture resource configuration.
|
||||
FlutterEngine* CreateTestEngine() {
|
||||
NSString* fixtures = @(testing::GetFixturesPath());
|
||||
FlutterDartProject* project = [[FlutterDartProject alloc]
|
||||
initWithAssetsPath:fixtures
|
||||
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
|
||||
return [[FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
|
||||
return [[AccessibilityBridgeTestEngine alloc] initWithName:@"test"
|
||||
project:project
|
||||
allowHeadlessExecution:true];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(AccessibilityBridgeMacDelegateTest,
|
||||
sendsAccessibilityCreateNotificationToWindowOfFlutterView) {
|
||||
TEST(AccessibilityBridgeMacTest, sendsAccessibilityCreateNotificationToWindowOfFlutterView) {
|
||||
FlutterEngine* engine = CreateTestEngine();
|
||||
NSString* fixtures = @(testing::GetFixturesPath());
|
||||
FlutterDartProject* project = [[FlutterDartProject alloc]
|
||||
@@ -55,7 +80,8 @@ TEST(AccessibilityBridgeMacDelegateTest,
|
||||
// Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
|
||||
// can query semantics information from.
|
||||
engine.semanticsEnabled = YES;
|
||||
auto bridge = engine.accessibilityBridge.lock();
|
||||
auto bridge =
|
||||
std::reinterpret_pointer_cast<AccessibilityBridgeMacSpy>(engine.accessibilityBridge.lock());
|
||||
FlutterSemanticsNode root;
|
||||
root.id = 0;
|
||||
root.flags = static_cast<FlutterSemanticsFlag>(0);
|
||||
@@ -75,8 +101,6 @@ TEST(AccessibilityBridgeMacDelegateTest,
|
||||
bridge->CommitUpdates();
|
||||
auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
|
||||
|
||||
AccessibilityBridgeMacDelegateSpy spy(engine, viewController);
|
||||
|
||||
// Creates a targeted event.
|
||||
ui::AXTree tree;
|
||||
ui::AXNode ax_node(&tree, nullptr, 0, 0);
|
||||
@@ -88,15 +112,16 @@ TEST(AccessibilityBridgeMacDelegateTest,
|
||||
ax::mojom::EventFrom::kNone, intent);
|
||||
ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
|
||||
|
||||
spy.OnAccessibilityEvent(targeted_event);
|
||||
bridge->OnAccessibilityEvent(targeted_event);
|
||||
|
||||
EXPECT_EQ(spy.actual_notifications.size(), 1u);
|
||||
EXPECT_EQ(spy.actual_notifications.find([NSAccessibilityCreatedNotification UTF8String])->second,
|
||||
expectedTarget);
|
||||
EXPECT_EQ(bridge->actual_notifications.size(), 1u);
|
||||
EXPECT_EQ(
|
||||
bridge->actual_notifications.find([NSAccessibilityCreatedNotification UTF8String])->second,
|
||||
expectedTarget);
|
||||
[engine shutDownEngine];
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeMacDelegateTest, doesNotSendAccessibilityCreateNotificationWhenHeadless) {
|
||||
TEST(AccessibilityBridgeMacTest, doesNotSendAccessibilityCreateNotificationWhenHeadless) {
|
||||
FlutterEngine* engine = CreateTestEngine();
|
||||
NSString* fixtures = @(testing::GetFixturesPath());
|
||||
FlutterDartProject* project = [[FlutterDartProject alloc]
|
||||
@@ -108,7 +133,8 @@ TEST(AccessibilityBridgeMacDelegateTest, doesNotSendAccessibilityCreateNotificat
|
||||
// Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
|
||||
// can query semantics information from.
|
||||
engine.semanticsEnabled = YES;
|
||||
auto bridge = engine.accessibilityBridge.lock();
|
||||
auto bridge =
|
||||
std::reinterpret_pointer_cast<AccessibilityBridgeMacSpy>(engine.accessibilityBridge.lock());
|
||||
FlutterSemanticsNode root;
|
||||
root.id = 0;
|
||||
root.flags = static_cast<FlutterSemanticsFlag>(0);
|
||||
@@ -128,8 +154,6 @@ TEST(AccessibilityBridgeMacDelegateTest, doesNotSendAccessibilityCreateNotificat
|
||||
bridge->CommitUpdates();
|
||||
auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
|
||||
|
||||
AccessibilityBridgeMacDelegateSpy spy(engine, viewController);
|
||||
|
||||
// Creates a targeted event.
|
||||
ui::AXTree tree;
|
||||
ui::AXNode ax_node(&tree, nullptr, 0, 0);
|
||||
@@ -141,14 +165,14 @@ TEST(AccessibilityBridgeMacDelegateTest, doesNotSendAccessibilityCreateNotificat
|
||||
ax::mojom::EventFrom::kNone, intent);
|
||||
ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
|
||||
|
||||
spy.OnAccessibilityEvent(targeted_event);
|
||||
bridge->OnAccessibilityEvent(targeted_event);
|
||||
|
||||
// Does not send any notification if the engine is headless.
|
||||
EXPECT_EQ(spy.actual_notifications.size(), 0u);
|
||||
EXPECT_EQ(bridge->actual_notifications.size(), 0u);
|
||||
[engine shutDownEngine];
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeMacDelegateTest, doesNotSendAccessibilityCreateNotificationWhenNoWindow) {
|
||||
TEST(AccessibilityBridgeMacTest, doesNotSendAccessibilityCreateNotificationWhenNoWindow) {
|
||||
FlutterEngine* engine = CreateTestEngine();
|
||||
// Create a view controller without attaching it to a window.
|
||||
NSString* fixtures = @(testing::GetFixturesPath());
|
||||
@@ -162,7 +186,8 @@ TEST(AccessibilityBridgeMacDelegateTest, doesNotSendAccessibilityCreateNotificat
|
||||
// Setting up bridge so that the AccessibilityBridgeMacDelegateSpy
|
||||
// can query semantics information from.
|
||||
engine.semanticsEnabled = YES;
|
||||
auto bridge = engine.accessibilityBridge.lock();
|
||||
auto bridge =
|
||||
std::reinterpret_pointer_cast<AccessibilityBridgeMacSpy>(engine.accessibilityBridge.lock());
|
||||
FlutterSemanticsNode root;
|
||||
root.id = 0;
|
||||
root.flags = static_cast<FlutterSemanticsFlag>(0);
|
||||
@@ -182,8 +207,6 @@ TEST(AccessibilityBridgeMacDelegateTest, doesNotSendAccessibilityCreateNotificat
|
||||
bridge->CommitUpdates();
|
||||
auto platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
|
||||
|
||||
AccessibilityBridgeMacDelegateSpy spy(engine, viewController);
|
||||
|
||||
// Creates a targeted event.
|
||||
ui::AXTree tree;
|
||||
ui::AXNode ax_node(&tree, nullptr, 0, 0);
|
||||
@@ -195,10 +218,10 @@ TEST(AccessibilityBridgeMacDelegateTest, doesNotSendAccessibilityCreateNotificat
|
||||
ax::mojom::EventFrom::kNone, intent);
|
||||
ui::AXEventGenerator::TargetedEvent targeted_event(&ax_node, event_params);
|
||||
|
||||
spy.OnAccessibilityEvent(targeted_event);
|
||||
bridge->OnAccessibilityEvent(targeted_event);
|
||||
|
||||
// Does not send any notification if the flutter view is not attached to a NSWindow.
|
||||
EXPECT_EQ(spy.actual_notifications.size(), 0u);
|
||||
EXPECT_EQ(bridge->actual_notifications.size(), 0u);
|
||||
[engine shutDownEngine];
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacDelegate.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h"
|
||||
@@ -189,7 +188,7 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) _engine;
|
||||
|
||||
// The private member for accessibility.
|
||||
std::shared_ptr<flutter::AccessibilityBridge> _bridge;
|
||||
std::shared_ptr<flutter::AccessibilityBridgeMac> _bridge;
|
||||
|
||||
// The project being run by this engine.
|
||||
FlutterDartProject* _project;
|
||||
@@ -418,8 +417,7 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
[_renderer setFlutterView:controller.flutterView];
|
||||
|
||||
if (_semanticsEnabled && _bridge) {
|
||||
_bridge->UpdateDelegate(
|
||||
std::make_unique<flutter::AccessibilityBridgeMacDelegate>(self, _viewController));
|
||||
_bridge->UpdateDefaultViewController(_viewController);
|
||||
}
|
||||
|
||||
if (!controller && !_allowHeadlessExecution) {
|
||||
@@ -551,7 +549,7 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
return _embedderAPI;
|
||||
}
|
||||
|
||||
- (std::weak_ptr<flutter::AccessibilityBridge>)accessibilityBridge {
|
||||
- (std::weak_ptr<flutter::AccessibilityBridgeMac>)accessibilityBridge {
|
||||
return _bridge;
|
||||
}
|
||||
|
||||
@@ -601,12 +599,17 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
if (!_semanticsEnabled && _bridge) {
|
||||
_bridge.reset();
|
||||
} else if (_semanticsEnabled && !_bridge) {
|
||||
_bridge = std::make_shared<flutter::AccessibilityBridge>(
|
||||
std::make_unique<flutter::AccessibilityBridgeMacDelegate>(self, self.viewController));
|
||||
_bridge = [self createAccessibilityBridge:self viewController:self.viewController];
|
||||
}
|
||||
_embedderAPI.UpdateSemanticsEnabled(_engine, _semanticsEnabled);
|
||||
}
|
||||
|
||||
- (std::shared_ptr<flutter::AccessibilityBridgeMac>)
|
||||
createAccessibilityBridge:(nonnull FlutterEngine*)engine
|
||||
viewController:(nonnull FlutterViewController*)viewController {
|
||||
return std::make_shared<flutter::AccessibilityBridgeMac>(engine, _viewController);
|
||||
}
|
||||
|
||||
- (void)dispatchSemanticsAction:(FlutterSemanticsAction)action
|
||||
toTarget:(uint16_t)target
|
||||
withData:(fml::MallocMapping)data {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "flutter/shell/platform/common/accessibility_bridge.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h"
|
||||
@@ -31,7 +31,7 @@
|
||||
*/
|
||||
@property(nonatomic) FlutterEngineProcTable& embedderAPI;
|
||||
|
||||
@property(nonatomic, readonly) std::weak_ptr<flutter::AccessibilityBridge> accessibilityBridge;
|
||||
@property(nonatomic, readonly) std::weak_ptr<flutter::AccessibilityBridgeMac> accessibilityBridge;
|
||||
|
||||
/**
|
||||
* True if the semantics is enabled. The Flutter framework starts sending
|
||||
@@ -94,3 +94,14 @@
|
||||
withData:(fml::MallocMapping)data;
|
||||
|
||||
@end
|
||||
|
||||
@interface FlutterEngine (TestMethods)
|
||||
/* Creates an accessibility bridge with the provided parameters.
|
||||
*
|
||||
* By default this method calls AccessibilityBridgeMac's initializer. Exposing
|
||||
* this method allows unit tests to override in order to capture information.
|
||||
*/
|
||||
- (std::shared_ptr<flutter::AccessibilityBridgeMac>)
|
||||
createAccessibilityBridge:(nonnull FlutterEngine*)engine
|
||||
viewController:(nonnull FlutterViewController*)viewController;
|
||||
@end
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
|
||||
|
||||
#include "flutter/shell/platform/common/accessibility_bridge.h"
|
||||
#include "flutter/shell/platform/common/flutter_platform_node_delegate.h"
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
|
||||
@@ -19,9 +20,8 @@ namespace flutter {
|
||||
/// AXPlatformNodeMac to manage the macOS-specific accessibility objects.
|
||||
class FlutterPlatformNodeDelegateMac : public FlutterPlatformNodeDelegate {
|
||||
public:
|
||||
explicit FlutterPlatformNodeDelegateMac(
|
||||
__weak FlutterEngine* engine,
|
||||
__weak FlutterViewController* view_controller);
|
||||
FlutterPlatformNodeDelegateMac(std::weak_ptr<AccessibilityBridge> bridge,
|
||||
__weak FlutterViewController* view_controller);
|
||||
virtual ~FlutterPlatformNodeDelegateMac();
|
||||
|
||||
void Init(std::weak_ptr<OwnerBridge> bridge, ui::AXNode* node) override;
|
||||
@@ -49,7 +49,7 @@ class FlutterPlatformNodeDelegateMac : public FlutterPlatformNodeDelegate {
|
||||
|
||||
private:
|
||||
ui::AXPlatformNode* ax_platform_node_;
|
||||
__weak FlutterEngine* engine_;
|
||||
std::weak_ptr<AccessibilityBridge> bridge_;
|
||||
__weak FlutterViewController* view_controller_;
|
||||
|
||||
gfx::RectF ConvertBoundsFromLocalToScreen(
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
namespace flutter { // namespace
|
||||
|
||||
FlutterPlatformNodeDelegateMac::FlutterPlatformNodeDelegateMac(
|
||||
__weak FlutterEngine* engine,
|
||||
std::weak_ptr<AccessibilityBridge> bridge,
|
||||
__weak FlutterViewController* view_controller)
|
||||
: engine_(engine), view_controller_(view_controller) {}
|
||||
: bridge_(std::move(bridge)), view_controller_(view_controller) {}
|
||||
|
||||
void FlutterPlatformNodeDelegateMac::Init(std::weak_ptr<OwnerBridge> bridge, ui::AXNode* node) {
|
||||
FlutterPlatformNodeDelegate::Init(bridge, node);
|
||||
@@ -48,9 +48,8 @@ gfx::NativeViewAccessible FlutterPlatformNodeDelegateMac::GetNativeViewAccessibl
|
||||
gfx::NativeViewAccessible FlutterPlatformNodeDelegateMac::GetParent() {
|
||||
gfx::NativeViewAccessible parent = FlutterPlatformNodeDelegate::GetParent();
|
||||
if (!parent) {
|
||||
NSCAssert(engine_, @"Flutter engine should not be deallocated");
|
||||
NSCAssert(engine_.viewController.viewLoaded, @"Flutter view must be loaded");
|
||||
return engine_.viewController.flutterView;
|
||||
NSCAssert(view_controller_.viewLoaded, @"Flutter view must be loaded");
|
||||
return view_controller_.flutterView;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
@@ -80,8 +79,7 @@ std::string FlutterPlatformNodeDelegateMac::GetLiveRegionText() const {
|
||||
if (!text.empty()) {
|
||||
return text;
|
||||
};
|
||||
NSCAssert(engine_, @"Flutter engine should not be deallocated");
|
||||
auto bridge_ptr = engine_.accessibilityBridge.lock();
|
||||
auto bridge_ptr = bridge_.lock();
|
||||
NSCAssert(bridge_ptr, @"Accessibility bridge in flutter engine must not be null.");
|
||||
for (int32_t child : GetData().child_ids) {
|
||||
auto delegate_child = bridge_ptr->GetFlutterPlatformNodeDelegateFromID(child).lock();
|
||||
@@ -105,14 +103,11 @@ gfx::RectF FlutterPlatformNodeDelegateMac::ConvertBoundsFromLocalToScreen(
|
||||
// it converts the bounds from flutter coordinates to macOS coordinates.
|
||||
ns_local_bounds.origin.y = -ns_local_bounds.origin.y - ns_local_bounds.size.height;
|
||||
|
||||
NSCAssert(engine_, @"Flutter engine should not be deallocated");
|
||||
NSCAssert(engine_.viewController.viewLoaded, @"Flutter view must be loaded.");
|
||||
NSRect ns_view_bounds =
|
||||
[engine_.viewController.flutterView convertRectFromBacking:ns_local_bounds];
|
||||
NSRect ns_window_bounds = [engine_.viewController.flutterView convertRect:ns_view_bounds
|
||||
toView:nil];
|
||||
NSCAssert(view_controller_.viewLoaded, @"Flutter view must be loaded.");
|
||||
NSRect ns_view_bounds = [view_controller_.flutterView convertRectFromBacking:ns_local_bounds];
|
||||
NSRect ns_window_bounds = [view_controller_.flutterView convertRect:ns_view_bounds toView:nil];
|
||||
NSRect ns_screen_bounds =
|
||||
[[engine_.viewController.flutterView window] convertRectToScreen:ns_window_bounds];
|
||||
[[view_controller_.flutterView window] convertRectToScreen:ns_window_bounds];
|
||||
gfx::RectF screen_bounds(ns_screen_bounds.origin.x, ns_screen_bounds.origin.y,
|
||||
ns_screen_bounds.size.width, ns_screen_bounds.size.height);
|
||||
return screen_bounds;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "flutter/testing/testing.h"
|
||||
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacDelegate.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.h"
|
||||
|
||||
@@ -1427,7 +1427,7 @@ TEST(FlutterTextInputPluginTest, CanWorkWithFlutterTextField) {
|
||||
engine.semanticsEnabled = YES;
|
||||
|
||||
auto bridge = engine.accessibilityBridge.lock();
|
||||
FlutterPlatformNodeDelegateMac delegate(engine, viewController);
|
||||
FlutterPlatformNodeDelegateMac delegate(bridge, viewController);
|
||||
ui::AXTree tree;
|
||||
ui::AXNode ax_node(&tree, nullptr, 0, 0);
|
||||
ui::AXNodeData node_data;
|
||||
@@ -1486,7 +1486,7 @@ TEST(FlutterTextInputPluginTest, CanNotBecomeResponderIfNoViewController) {
|
||||
engine.semanticsEnabled = YES;
|
||||
|
||||
auto bridge = engine.accessibilityBridge.lock();
|
||||
FlutterPlatformNodeDelegateMac delegate(engine, viewController);
|
||||
FlutterPlatformNodeDelegateMac delegate(bridge, viewController);
|
||||
ui::AXTree tree;
|
||||
ui::AXNode ax_node(&tree, nullptr, 0, 0);
|
||||
ui::AXNodeData node_data;
|
||||
|
||||
@@ -44,7 +44,7 @@ TEST(FlutterTextInputSemanticsObjectTest, DoesInitialize) {
|
||||
engine.semanticsEnabled = YES;
|
||||
|
||||
auto bridge = engine.accessibilityBridge.lock();
|
||||
FlutterPlatformNodeDelegateMac delegate(engine, viewController);
|
||||
FlutterPlatformNodeDelegateMac delegate(bridge, viewController);
|
||||
ui::AXTree tree;
|
||||
ui::AXNode ax_node(&tree, nullptr, 0, 0);
|
||||
ui::AXNodeData node_data;
|
||||
|
||||
@@ -40,8 +40,8 @@ source_set("flutter_windows_source") {
|
||||
sources = [
|
||||
"accessibility_alert.cc",
|
||||
"accessibility_alert.h",
|
||||
"accessibility_bridge_delegate_windows.cc",
|
||||
"accessibility_bridge_delegate_windows.h",
|
||||
"accessibility_bridge_windows.cc",
|
||||
"accessibility_bridge_windows.h",
|
||||
"accessibility_root_node.cc",
|
||||
"accessibility_root_node.h",
|
||||
"angle_surface_manager.cc",
|
||||
@@ -175,7 +175,7 @@ executable("flutter_windows_unittests") {
|
||||
|
||||
# Common Windows test sources.
|
||||
sources = [
|
||||
"accessibility_bridge_delegate_windows_unittests.cc",
|
||||
"accessibility_bridge_windows_unittests.cc",
|
||||
"direct_manipulation_unittests.cc",
|
||||
"dpi_utils_unittests.cc",
|
||||
"flutter_project_bundle_unittests.cc",
|
||||
|
||||
@@ -2,30 +2,28 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter/shell/platform/windows/accessibility_bridge_delegate_windows.h"
|
||||
#include "flutter/shell/platform/windows/accessibility_bridge_windows.h"
|
||||
|
||||
#include "flutter/shell/platform/windows/flutter_platform_node_delegate_windows.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_view.h"
|
||||
#include "flutter/third_party/accessibility/ax/platform/ax_platform_node_delegate_base.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
AccessibilityBridgeDelegateWindows::AccessibilityBridgeDelegateWindows(
|
||||
FlutterWindowsEngine* engine)
|
||||
: engine_(engine) {
|
||||
AccessibilityBridgeWindows::AccessibilityBridgeWindows(
|
||||
FlutterWindowsEngine* engine,
|
||||
FlutterWindowsView* view)
|
||||
: engine_(engine), view_(view) {
|
||||
assert(engine_);
|
||||
assert(view_);
|
||||
}
|
||||
|
||||
void AccessibilityBridgeDelegateWindows::OnAccessibilityEvent(
|
||||
void AccessibilityBridgeWindows::OnAccessibilityEvent(
|
||||
ui::AXEventGenerator::TargetedEvent targeted_event) {
|
||||
ui::AXNode* ax_node = targeted_event.node;
|
||||
ui::AXEventGenerator::Event event_type = targeted_event.event_params.event;
|
||||
|
||||
// Look up the flutter platform node delegate.
|
||||
auto bridge = engine_->accessibility_bridge().lock();
|
||||
assert(bridge);
|
||||
auto node_delegate =
|
||||
bridge->GetFlutterPlatformNodeDelegateFromID(ax_node->id()).lock();
|
||||
GetFlutterPlatformNodeDelegateFromID(ax_node->id()).lock();
|
||||
assert(node_delegate);
|
||||
std::shared_ptr<FlutterPlatformNodeDelegateWindows> win_delegate =
|
||||
std::static_pointer_cast<FlutterPlatformNodeDelegateWindows>(
|
||||
@@ -138,7 +136,7 @@ void AccessibilityBridgeDelegateWindows::OnAccessibilityEvent(
|
||||
}
|
||||
}
|
||||
|
||||
void AccessibilityBridgeDelegateWindows::DispatchAccessibilityAction(
|
||||
void AccessibilityBridgeWindows::DispatchAccessibilityAction(
|
||||
AccessibilityNodeId target,
|
||||
FlutterSemanticsAction action,
|
||||
fml::MallocMapping data) {
|
||||
@@ -146,17 +144,18 @@ void AccessibilityBridgeDelegateWindows::DispatchAccessibilityAction(
|
||||
}
|
||||
|
||||
std::shared_ptr<FlutterPlatformNodeDelegate>
|
||||
AccessibilityBridgeDelegateWindows::CreateFlutterPlatformNodeDelegate() {
|
||||
return std::make_shared<FlutterPlatformNodeDelegateWindows>(engine_);
|
||||
AccessibilityBridgeWindows::CreateFlutterPlatformNodeDelegate() {
|
||||
return std::make_shared<FlutterPlatformNodeDelegateWindows>(
|
||||
shared_from_this(), view_);
|
||||
}
|
||||
|
||||
void AccessibilityBridgeDelegateWindows::DispatchWinAccessibilityEvent(
|
||||
void AccessibilityBridgeWindows::DispatchWinAccessibilityEvent(
|
||||
std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate,
|
||||
DWORD event_type) {
|
||||
node_delegate->DispatchWinAccessibilityEvent(event_type);
|
||||
}
|
||||
|
||||
void AccessibilityBridgeDelegateWindows::SetFocus(
|
||||
void AccessibilityBridgeWindows::SetFocus(
|
||||
std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate) {
|
||||
node_delegate->SetFocus();
|
||||
}
|
||||
@@ -2,59 +2,68 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_DELEGATE_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_DELEGATE_H_
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_WINDOWS_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_WINDOWS_H_
|
||||
|
||||
#include "flutter/shell/platform/common/accessibility_bridge.h"
|
||||
|
||||
#include "flutter/shell/platform/windows/flutter_platform_node_delegate_windows.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_view.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
class FlutterWindowsEngine;
|
||||
class FlutterPlatformNodeDelegateWindows;
|
||||
|
||||
// The Win32 implementation of AccessibilityBridgeDelegate.
|
||||
// The Win32 implementation of AccessibilityBridge.
|
||||
//
|
||||
// Handles requests from the accessibility bridge to interact with Windows
|
||||
// accessibility APIs. This includes routing accessibility events fired from
|
||||
// the framework to Windows, routing native Windows accessibility events to the
|
||||
// framework, and creating Windows-specific FlutterPlatformNodeDelegate objects
|
||||
// for each node in the semantics tree.
|
||||
class AccessibilityBridgeDelegateWindows
|
||||
: public AccessibilityBridge::AccessibilityBridgeDelegate {
|
||||
// This interacts with Windows accessibility APIs, which includes routing
|
||||
// accessibility events fired from the framework to Windows, routing native
|
||||
// Windows accessibility events to the framework, and creating Windows-specific
|
||||
// FlutterPlatformNodeDelegate objects for each node in the semantics tree.
|
||||
///
|
||||
/// AccessibilityBridgeWindows must be created as a shared_ptr, since some
|
||||
/// methods acquires its weak_ptr.
|
||||
class AccessibilityBridgeWindows : public AccessibilityBridge {
|
||||
public:
|
||||
explicit AccessibilityBridgeDelegateWindows(FlutterWindowsEngine* engine);
|
||||
virtual ~AccessibilityBridgeDelegateWindows() = default;
|
||||
AccessibilityBridgeWindows(FlutterWindowsEngine* engine,
|
||||
FlutterWindowsView* view);
|
||||
virtual ~AccessibilityBridgeWindows() = default;
|
||||
|
||||
// |AccessibilityBridge::AccessibilityBridgeDelegate|
|
||||
void OnAccessibilityEvent(
|
||||
ui::AXEventGenerator::TargetedEvent targeted_event) override;
|
||||
|
||||
// |AccessibilityBridge::AccessibilityBridgeDelegate|
|
||||
// |AccessibilityBridge|
|
||||
void DispatchAccessibilityAction(AccessibilityNodeId target,
|
||||
FlutterSemanticsAction action,
|
||||
fml::MallocMapping data) override;
|
||||
|
||||
// |AccessibilityBridge::AccessibilityBridgeDelegate|
|
||||
std::shared_ptr<FlutterPlatformNodeDelegate>
|
||||
CreateFlutterPlatformNodeDelegate() override;
|
||||
|
||||
// Dispatches a Windows accessibility event of the specified type, generated
|
||||
// by the accessibility node associated with the specified semantics node.
|
||||
//
|
||||
// This is a virtual method for the convenience of unit tests.
|
||||
virtual void DispatchWinAccessibilityEvent(
|
||||
std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate,
|
||||
DWORD event_type);
|
||||
|
||||
// Sets the accessibility focus to the accessibility node associated with the
|
||||
// specified semantics node.
|
||||
//
|
||||
// This is a virtual method for the convenience of unit tests.
|
||||
virtual void SetFocus(
|
||||
std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate);
|
||||
|
||||
protected:
|
||||
// |AccessibilityBridge|
|
||||
void OnAccessibilityEvent(
|
||||
ui::AXEventGenerator::TargetedEvent targeted_event) override;
|
||||
|
||||
// |AccessibilityBridge|
|
||||
std::shared_ptr<FlutterPlatformNodeDelegate>
|
||||
CreateFlutterPlatformNodeDelegate() override;
|
||||
|
||||
private:
|
||||
FlutterWindowsEngine* engine_;
|
||||
FlutterWindowsView* view_;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_DELEGATE_H_
|
||||
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_WINDOWS_H_
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter/shell/platform/windows/accessibility_bridge_delegate_windows.h"
|
||||
#include "flutter/shell/platform/windows/accessibility_bridge_windows.h"
|
||||
|
||||
#include <comdef.h>
|
||||
#include <comutil.h>
|
||||
@@ -33,11 +33,13 @@ struct MsaaEvent {
|
||||
};
|
||||
|
||||
// Accessibility bridge delegate that captures events dispatched to the OS.
|
||||
class AccessibilityBridgeDelegateWindowsSpy
|
||||
: public AccessibilityBridgeDelegateWindows {
|
||||
class AccessibilityBridgeWindowsSpy : public AccessibilityBridgeWindows {
|
||||
public:
|
||||
explicit AccessibilityBridgeDelegateWindowsSpy(FlutterWindowsEngine* engine)
|
||||
: AccessibilityBridgeDelegateWindows(engine) {}
|
||||
using AccessibilityBridgeWindows::OnAccessibilityEvent;
|
||||
|
||||
explicit AccessibilityBridgeWindowsSpy(FlutterWindowsEngine* engine,
|
||||
FlutterWindowsView* view)
|
||||
: AccessibilityBridgeWindows(engine, view) {}
|
||||
|
||||
void DispatchWinAccessibilityEvent(
|
||||
std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate,
|
||||
@@ -50,7 +52,7 @@ class AccessibilityBridgeDelegateWindowsSpy
|
||||
focused_nodes_.push_back(node_delegate->GetAXNode()->id());
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
void ResetRecords() {
|
||||
dispatched_events_.clear();
|
||||
focused_nodes_.clear();
|
||||
}
|
||||
@@ -66,16 +68,31 @@ class AccessibilityBridgeDelegateWindowsSpy
|
||||
std::vector<int32_t> focused_nodes_;
|
||||
};
|
||||
|
||||
// A FlutterWindowsEngine whose accessibility bridge is a
|
||||
// AccessibilityBridgeWindowsSpy.
|
||||
class FlutterWindowsEngineSpy : public FlutterWindowsEngine {
|
||||
public:
|
||||
explicit FlutterWindowsEngineSpy(const FlutterProjectBundle& project)
|
||||
: FlutterWindowsEngine(project) {}
|
||||
|
||||
protected:
|
||||
virtual std::shared_ptr<AccessibilityBridge> CreateAccessibilityBridge(
|
||||
FlutterWindowsEngine* engine,
|
||||
FlutterWindowsView* view) override {
|
||||
return std::make_shared<AccessibilityBridgeWindowsSpy>(engine, view);
|
||||
}
|
||||
};
|
||||
|
||||
// Returns an engine instance configured with dummy project path values, and
|
||||
// overridden methods for sending platform messages, so that the engine can
|
||||
// respond as if the framework were connected.
|
||||
std::unique_ptr<FlutterWindowsEngine> GetTestEngine() {
|
||||
std::unique_ptr<FlutterWindowsEngineSpy> GetTestEngine() {
|
||||
FlutterDesktopEngineProperties properties = {};
|
||||
properties.assets_path = L"C:\\foo\\flutter_assets";
|
||||
properties.icu_data_path = L"C:\\foo\\icudtl.dat";
|
||||
properties.aot_library_path = L"C:\\foo\\aot.so";
|
||||
FlutterProjectBundle project(properties);
|
||||
auto engine = std::make_unique<FlutterWindowsEngine>(project);
|
||||
auto engine = std::make_unique<FlutterWindowsEngineSpy>(project);
|
||||
|
||||
EngineModifier modifier(engine.get());
|
||||
modifier.embedder_api().UpdateSemanticsEnabled =
|
||||
@@ -142,6 +159,14 @@ ui::AXNode* AXNodeFromID(std::shared_ptr<AccessibilityBridge> bridge,
|
||||
return node_delegate ? node_delegate->GetAXNode() : nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<AccessibilityBridgeWindowsSpy> GetAccessibilityBridgeSpy(
|
||||
FlutterWindowsEngine* engine) {
|
||||
FlutterWindowsEngineSpy* engine_spy =
|
||||
reinterpret_cast<FlutterWindowsEngineSpy*>(engine);
|
||||
return std::reinterpret_pointer_cast<AccessibilityBridgeWindowsSpy>(
|
||||
engine_spy->accessibility_bridge().lock());
|
||||
}
|
||||
|
||||
void ExpectWinEventFromAXEvent(int32_t node_id,
|
||||
ui::AXEventGenerator::Event ax_event,
|
||||
DWORD expected_event) {
|
||||
@@ -151,19 +176,19 @@ void ExpectWinEventFromAXEvent(int32_t node_id,
|
||||
view.SetEngine(GetTestEngine());
|
||||
view.OnUpdateSemanticsEnabled(true);
|
||||
|
||||
auto bridge = view.GetEngine()->accessibility_bridge().lock();
|
||||
auto bridge = GetAccessibilityBridgeSpy(view.GetEngine());
|
||||
PopulateAXTree(bridge);
|
||||
|
||||
AccessibilityBridgeDelegateWindowsSpy spy(view.GetEngine());
|
||||
spy.OnAccessibilityEvent({AXNodeFromID(bridge, node_id),
|
||||
{ax_event, ax::mojom::EventFrom::kNone, {}}});
|
||||
ASSERT_EQ(spy.dispatched_events().size(), 1);
|
||||
EXPECT_EQ(spy.dispatched_events()[0].event_type, expected_event);
|
||||
bridge->ResetRecords();
|
||||
bridge->OnAccessibilityEvent({AXNodeFromID(bridge, node_id),
|
||||
{ax_event, ax::mojom::EventFrom::kNone, {}}});
|
||||
ASSERT_EQ(bridge->dispatched_events().size(), 1);
|
||||
EXPECT_EQ(bridge->dispatched_events()[0].event_type, expected_event);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, GetParent) {
|
||||
TEST(AccessibilityBridgeWindows, GetParent) {
|
||||
auto window_binding_handler =
|
||||
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
|
||||
FlutterWindowsView view(std::move(window_binding_handler));
|
||||
@@ -179,7 +204,7 @@ TEST(AccessibilityBridgeDelegateWindows, GetParent) {
|
||||
node1_delegate->GetParent());
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, GetParentOnRootRetunsNullptr) {
|
||||
TEST(AccessibilityBridgeWindows, GetParentOnRootRetunsNullptr) {
|
||||
auto window_binding_handler =
|
||||
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
|
||||
FlutterWindowsView view(std::move(window_binding_handler));
|
||||
@@ -193,7 +218,7 @@ TEST(AccessibilityBridgeDelegateWindows, GetParentOnRootRetunsNullptr) {
|
||||
ASSERT_TRUE(node0_delegate->GetParent() == nullptr);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, DispatchAccessibilityAction) {
|
||||
TEST(AccessibilityBridgeWindows, DispatchAccessibilityAction) {
|
||||
auto window_binding_handler =
|
||||
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
|
||||
FlutterWindowsView view(std::move(window_binding_handler));
|
||||
@@ -214,101 +239,99 @@ TEST(AccessibilityBridgeDelegateWindows, DispatchAccessibilityAction) {
|
||||
return kSuccess;
|
||||
}));
|
||||
|
||||
AccessibilityBridgeDelegateWindows delegate(view.GetEngine());
|
||||
AccessibilityBridgeWindows delegate(view.GetEngine(), &view);
|
||||
delegate.DispatchAccessibilityAction(1, kFlutterSemanticsActionCopy, {});
|
||||
EXPECT_EQ(actual_action, kFlutterSemanticsActionCopy);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilityEventAlert) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityEventAlert) {
|
||||
ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::ALERT,
|
||||
EVENT_SYSTEM_ALERT);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilityEventChildrenChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityEventChildrenChanged) {
|
||||
ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::CHILDREN_CHANGED,
|
||||
EVENT_OBJECT_REORDER);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilityEventFocusChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityEventFocusChanged) {
|
||||
auto window_binding_handler =
|
||||
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
|
||||
FlutterWindowsView view(std::move(window_binding_handler));
|
||||
view.SetEngine(GetTestEngine());
|
||||
view.OnUpdateSemanticsEnabled(true);
|
||||
|
||||
auto bridge = view.GetEngine()->accessibility_bridge().lock();
|
||||
auto bridge = GetAccessibilityBridgeSpy(view.GetEngine());
|
||||
PopulateAXTree(bridge);
|
||||
|
||||
AccessibilityBridgeDelegateWindowsSpy spy(view.GetEngine());
|
||||
spy.OnAccessibilityEvent({AXNodeFromID(bridge, 1),
|
||||
{ui::AXEventGenerator::Event::FOCUS_CHANGED,
|
||||
ax::mojom::EventFrom::kNone,
|
||||
{}}});
|
||||
ASSERT_EQ(spy.dispatched_events().size(), 1);
|
||||
EXPECT_EQ(spy.dispatched_events()[0].event_type, EVENT_OBJECT_FOCUS);
|
||||
bridge->ResetRecords();
|
||||
bridge->OnAccessibilityEvent({AXNodeFromID(bridge, 1),
|
||||
{ui::AXEventGenerator::Event::FOCUS_CHANGED,
|
||||
ax::mojom::EventFrom::kNone,
|
||||
{}}});
|
||||
ASSERT_EQ(bridge->dispatched_events().size(), 1);
|
||||
EXPECT_EQ(bridge->dispatched_events()[0].event_type, EVENT_OBJECT_FOCUS);
|
||||
|
||||
ASSERT_EQ(spy.focused_nodes().size(), 1);
|
||||
EXPECT_EQ(spy.focused_nodes()[0], 1);
|
||||
ASSERT_EQ(bridge->focused_nodes().size(), 1);
|
||||
EXPECT_EQ(bridge->focused_nodes()[0], 1);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilityEventIgnoredChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityEventIgnoredChanged) {
|
||||
// Static test nodes with no text, hint, or scrollability are ignored.
|
||||
ExpectWinEventFromAXEvent(4, ui::AXEventGenerator::Event::IGNORED_CHANGED,
|
||||
EVENT_OBJECT_HIDE);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows,
|
||||
OnAccessibilityImageAnnotationChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityImageAnnotationChanged) {
|
||||
ExpectWinEventFromAXEvent(
|
||||
1, ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED,
|
||||
EVENT_OBJECT_NAMECHANGE);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilityLiveRegionChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityLiveRegionChanged) {
|
||||
ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::LIVE_REGION_CHANGED,
|
||||
EVENT_OBJECT_LIVEREGIONCHANGED);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilityNameChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityNameChanged) {
|
||||
ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::NAME_CHANGED,
|
||||
EVENT_OBJECT_NAMECHANGE);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilityHScrollPosChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityHScrollPosChanged) {
|
||||
ExpectWinEventFromAXEvent(
|
||||
1, ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED,
|
||||
EVENT_SYSTEM_SCROLLINGEND);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilityVScrollPosChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityVScrollPosChanged) {
|
||||
ExpectWinEventFromAXEvent(
|
||||
1, ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED,
|
||||
EVENT_SYSTEM_SCROLLINGEND);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilitySelectedChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilitySelectedChanged) {
|
||||
ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::SELECTED_CHANGED,
|
||||
EVENT_OBJECT_VALUECHANGE);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows,
|
||||
OnAccessibilitySelectedChildrenChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilitySelectedChildrenChanged) {
|
||||
ExpectWinEventFromAXEvent(
|
||||
2, ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED,
|
||||
EVENT_OBJECT_SELECTIONWITHIN);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilitySubtreeCreated) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilitySubtreeCreated) {
|
||||
ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::SUBTREE_CREATED,
|
||||
EVENT_OBJECT_SHOW);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilityValueChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityValueChanged) {
|
||||
ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::VALUE_CHANGED,
|
||||
EVENT_OBJECT_VALUECHANGE);
|
||||
}
|
||||
|
||||
TEST(AccessibilityBridgeDelegateWindows, OnAccessibilityStateChanged) {
|
||||
TEST(AccessibilityBridgeWindows, OnAccessibilityStateChanged) {
|
||||
ExpectWinEventFromAXEvent(
|
||||
1, ui::AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED,
|
||||
EVENT_OBJECT_STATECHANGE);
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "flutter/shell/platform/windows/flutter_platform_node_delegate_windows.h"
|
||||
|
||||
#include "flutter/shell/platform/windows/accessibility_bridge_windows.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_view.h"
|
||||
#include "flutter/third_party/accessibility/ax/ax_clipping_behavior.h"
|
||||
#include "flutter/third_party/accessibility/ax/ax_coordinate_system.h"
|
||||
@@ -13,9 +14,11 @@
|
||||
namespace flutter {
|
||||
|
||||
FlutterPlatformNodeDelegateWindows::FlutterPlatformNodeDelegateWindows(
|
||||
FlutterWindowsEngine* engine)
|
||||
: engine_(engine) {
|
||||
assert(engine_);
|
||||
std::weak_ptr<AccessibilityBridge> bridge,
|
||||
FlutterWindowsView* view)
|
||||
: bridge_(bridge), view_(view) {
|
||||
assert(!bridge_.expired());
|
||||
assert(view_);
|
||||
}
|
||||
|
||||
FlutterPlatformNodeDelegateWindows::~FlutterPlatformNodeDelegateWindows() {
|
||||
@@ -53,7 +56,7 @@ gfx::NativeViewAccessible FlutterPlatformNodeDelegateWindows::HitTestSync(
|
||||
}
|
||||
|
||||
// If any child in this node's subtree contains the point, return that child.
|
||||
auto bridge = engine_->accessibility_bridge().lock();
|
||||
auto bridge = bridge_.lock();
|
||||
assert(bridge);
|
||||
for (const ui::AXNode* child : GetAXNode()->children()) {
|
||||
std::shared_ptr<FlutterPlatformNodeDelegateWindows> win_delegate =
|
||||
@@ -80,19 +83,15 @@ gfx::Rect FlutterPlatformNodeDelegateWindows::GetBoundsRect(
|
||||
coordinate_system, clipping_behavior, offscreen_result);
|
||||
POINT origin{bounds.x(), bounds.y()};
|
||||
POINT extent{bounds.x() + bounds.width(), bounds.y() + bounds.height()};
|
||||
ClientToScreen(engine_->view()->GetPlatformWindow(), &origin);
|
||||
ClientToScreen(engine_->view()->GetPlatformWindow(), &extent);
|
||||
ClientToScreen(view_->GetPlatformWindow(), &origin);
|
||||
ClientToScreen(view_->GetPlatformWindow(), &extent);
|
||||
return gfx::Rect(origin.x, origin.y, extent.x - origin.x,
|
||||
extent.y - origin.y);
|
||||
}
|
||||
|
||||
void FlutterPlatformNodeDelegateWindows::DispatchWinAccessibilityEvent(
|
||||
DWORD event_type) {
|
||||
FlutterWindowsView* view = engine_->view();
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
HWND hwnd = view->GetPlatformWindow();
|
||||
HWND hwnd = view_->GetPlatformWindow();
|
||||
if (!hwnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,25 +2,24 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_H_
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_WINDOWS_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_WINDOWS_H_
|
||||
|
||||
#include "flutter/shell/platform/common/flutter_platform_node_delegate.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_view.h"
|
||||
#include "flutter/third_party/accessibility/ax/platform/ax_platform_node.h"
|
||||
#include "flutter/third_party/accessibility/ax/platform/ax_unique_id.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
class FlutterWindowsEngine;
|
||||
|
||||
// The Windows implementation of FlutterPlatformNodeDelegate.
|
||||
//
|
||||
// This class implements a wrapper around the Windows accessibility objects
|
||||
// that compose the accessibility tree.
|
||||
class FlutterPlatformNodeDelegateWindows : public FlutterPlatformNodeDelegate {
|
||||
public:
|
||||
explicit FlutterPlatformNodeDelegateWindows(FlutterWindowsEngine* engine);
|
||||
FlutterPlatformNodeDelegateWindows(std::weak_ptr<AccessibilityBridge> bridge,
|
||||
FlutterWindowsView* view);
|
||||
virtual ~FlutterPlatformNodeDelegateWindows();
|
||||
|
||||
// |ui::AXPlatformNodeDelegate|
|
||||
@@ -51,9 +50,10 @@ class FlutterPlatformNodeDelegateWindows : public FlutterPlatformNodeDelegate {
|
||||
|
||||
private:
|
||||
ui::AXPlatformNode* ax_platform_node_;
|
||||
FlutterWindowsEngine* engine_;
|
||||
std::weak_ptr<AccessibilityBridge> bridge_;
|
||||
FlutterWindowsView* view_;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_H_
|
||||
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_PLATFORM_NODE_DELEGATE_WINDOWS_H_
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h"
|
||||
#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h"
|
||||
#include "flutter/shell/platform/common/path_utils.h"
|
||||
#include "flutter/shell/platform/windows/accessibility_bridge_delegate_windows.h"
|
||||
#include "flutter/shell/platform/windows/accessibility_bridge_windows.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_view.h"
|
||||
#include "flutter/shell/platform/windows/system_utils.h"
|
||||
#include "flutter/shell/platform/windows/task_runner.h"
|
||||
@@ -610,12 +610,17 @@ void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) {
|
||||
if (!semantics_enabled_ && accessibility_bridge_) {
|
||||
accessibility_bridge_.reset();
|
||||
} else if (semantics_enabled_ && !accessibility_bridge_) {
|
||||
accessibility_bridge_ = std::make_shared<AccessibilityBridge>(
|
||||
std::make_unique<AccessibilityBridgeDelegateWindows>(this));
|
||||
accessibility_bridge_ = CreateAccessibilityBridge(this, view());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<AccessibilityBridge>
|
||||
FlutterWindowsEngine::CreateAccessibilityBridge(FlutterWindowsEngine* engine,
|
||||
FlutterWindowsView* view) {
|
||||
return std::make_shared<AccessibilityBridgeWindows>(engine, view);
|
||||
}
|
||||
|
||||
gfx::NativeViewAccessible FlutterWindowsEngine::GetNativeAccessibleFromId(
|
||||
AccessibilityNodeId id) {
|
||||
if (!accessibility_bridge_) {
|
||||
|
||||
@@ -244,6 +244,15 @@ class FlutterWindowsEngine {
|
||||
// Updates accessibility, e.g. switch to high contrast mode
|
||||
void UpdateAccessibilityFeatures(FlutterAccessibilityFeature flags);
|
||||
|
||||
protected:
|
||||
// Creates an accessibility bridge with the provided parameters.
|
||||
//
|
||||
// By default this method calls AccessibilityBridge's constructor. Exposing
|
||||
// this method allows unit tests to override in order to capture information.
|
||||
virtual std::shared_ptr<AccessibilityBridge> CreateAccessibilityBridge(
|
||||
FlutterWindowsEngine* engine,
|
||||
FlutterWindowsView* view);
|
||||
|
||||
private:
|
||||
// Allows swapping out embedder_api_ calls in tests.
|
||||
friend class EngineModifier;
|
||||
|
||||
Reference in New Issue
Block a user