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:
Tong Mu
2022-11-03 23:55:55 -07:00
committed by GitHub
parent 88dc1e2444
commit 4bbd9b6eb1
26 changed files with 436 additions and 431 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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));
}

View File

@@ -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";

View File

@@ -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) {

View File

@@ -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

View File

@@ -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",

View File

@@ -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_

View File

@@ -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;
}

View File

@@ -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];
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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(

View File

@@ -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;

View File

@@ -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"

View File

@@ -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;

View File

@@ -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;

View File

@@ -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",

View File

@@ -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();
}

View File

@@ -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_

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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_

View File

@@ -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_) {

View File

@@ -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;