Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

macOS: Update platform node when AXNodeData role changes #54364

Merged
merged 3 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions shell/platform/common/accessibility_bridge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ void AccessibilityBridge::OnRoleChanged(ui::AXTree* tree,
ax::mojom::Role old_role,
ax::mojom::Role new_role) {}

void AccessibilityBridge::OnNodeDataChanged(
ui::AXTree* tree,
const ui::AXNodeData& old_node_data,
const ui::AXNodeData& new_node_data) {
auto platform_view =
GetFlutterPlatformNodeDelegateFromID(new_node_data.id).lock();
if (platform_view) {
platform_view->NodeDataChanged(old_node_data, new_node_data);
}
}

void AccessibilityBridge::OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) {
BASE_DCHECK(node);
id_wrapper_map_[node->id()] = CreateFlutterPlatformNodeDelegate();
Expand Down
5 changes: 5 additions & 0 deletions shell/platform/common/accessibility_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ class AccessibilityBridge
ax::mojom::Role old_role,
ax::mojom::Role new_role) override;

// |AXTreeObserver|
void OnNodeDataChanged(ui::AXTree* tree,
const ui::AXNodeData& old_node_data,
const ui::AXNodeData& new_node_data) override;

// |AXTreeObserver|
void OnAtomicUpdateFinished(
ui::AXTree* tree,
Expand Down
6 changes: 6 additions & 0 deletions shell/platform/common/flutter_platform_node_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ class FlutterPlatformNodeDelegate : public ui::AXPlatformNodeDelegateBase {
/// Subclasses must call super.
virtual void Init(std::weak_ptr<OwnerBridge> bridge, ui::AXNode* node);

//------------------------------------------------------------------------------
// @brief Called when node was updated. Subclasses can override this
// to update platform nodes.
virtual void NodeDataChanged(const ui::AXNodeData& old_node_data,
const ui::AXNodeData& new_node_data) {}

//------------------------------------------------------------------------------
/// @brief Gets the underlying ax node for this platform node delegate.
ui::AXNode* GetAXNode() const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,11 @@
// If it is a text field, the value change notifications are handled by
// the FlutterTextField directly. Only need to make sure it is the
// first responder.
FlutterTextField* native_text_field =
(FlutterTextField*)mac_platform_node_delegate->GetNativeViewAccessible();
id native_text_field = mac_platform_node_delegate->GetNativeViewAccessible();
FML_DCHECK([native_text_field isKindOfClass:FlutterTextField.class]);
id focused = mac_platform_node_delegate->GetFocus();
if (!focused || native_text_field == focused) {
[native_text_field startEditing];
[(FlutterTextField*)native_text_field startEditing];
}
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class FlutterPlatformNodeDelegateMac : public FlutterPlatformNodeDelegate {

void Init(std::weak_ptr<OwnerBridge> bridge, ui::AXNode* node) override;

void NodeDataChanged(const ui::AXNodeData& old_node_data,
const ui::AXNodeData& new_node_data) override;

//---------------------------------------------------------------------------
/// @brief Gets the live region text of this node in UTF-8 format. This
/// is useful to determine the changes in between semantics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
NSCAssert(ax_platform_node_, @"Failed to create platform node.");
}

void FlutterPlatformNodeDelegateMac::NodeDataChanged(const ui::AXNodeData& old_node_data,
const ui::AXNodeData& new_node_data) {
if (old_node_data.IsTextField() && !new_node_data.IsTextField()) {
ax_platform_node_->Destroy();
ax_platform_node_ = ui::AXPlatformNode::Create(this);
} else if (!old_node_data.IsTextField() && new_node_data.IsTextField()) {
ax_platform_node_->Destroy();
ax_platform_node_ = new FlutterTextPlatformNode(this, view_controller_);
}
}

FlutterPlatformNodeDelegateMac::~FlutterPlatformNodeDelegateMac() {
// Destroy() also calls delete on itself.
ax_platform_node_->Destroy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,81 @@
EXPECT_EQ([native_text_field.stringValue isEqualToString:@"textfield"], YES);
}

TEST(FlutterPlatformNodeDelegateMac, ChangingFlagsUpdatesNativeViewAccessible) {
FlutterViewController* viewController = CreateTestViewController();
FlutterEngine* engine = viewController.engine;
[viewController loadView];

// Creates a NSWindow so that the native text field can become first responder.
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
window.contentView = viewController.view;
engine.semanticsEnabled = YES;

auto bridge = viewController.accessibilityBridge.lock();
// Initialize ax node data.
FlutterSemanticsNode2 root;
root.id = 0;
root.flags = static_cast<FlutterSemanticsFlag>(0);
root.actions = static_cast<FlutterSemanticsAction>(0);
root.label = "root";
root.hint = "";
root.value = "";
root.increased_value = "";
root.decreased_value = "";
root.tooltip = "";
root.child_count = 1;
int32_t children[] = {1};
root.children_in_traversal_order = children;
root.custom_accessibility_actions_count = 0;
root.rect = {0, 0, 100, 100}; // LTRB
root.transform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
bridge->AddFlutterSemanticsNodeUpdate(root);

double rectSize = 50;
double transformFactor = 0.5;

FlutterSemanticsNode2 child1;
child1.id = 1;
child1.flags = static_cast<FlutterSemanticsFlag>(0);
child1.actions = static_cast<FlutterSemanticsAction>(0);
child1.label = "";
child1.hint = "";
child1.value = "textfield";
child1.increased_value = "";
child1.decreased_value = "";
child1.tooltip = "";
child1.text_selection_base = -1;
child1.text_selection_extent = -1;
child1.child_count = 0;
child1.custom_accessibility_actions_count = 0;
child1.rect = {0, 0, rectSize, rectSize}; // LTRB
child1.transform = {transformFactor, 0, 0, 0, transformFactor, 0, 0, 0, 1};
bridge->AddFlutterSemanticsNodeUpdate(child1);

bridge->CommitUpdates();

auto child_platform_node_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
// Verify the accessibility attribute matches.
id native_accessibility = child_platform_node_delegate->GetNativeViewAccessible();
EXPECT_TRUE([[native_accessibility className] isEqualToString:@"AXPlatformNodeCocoa"]);

// Converting child to text field should produce `FlutterTextField` native view accessible.
child1.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField;
bridge->AddFlutterSemanticsNodeUpdate(child1);
bridge->CommitUpdates();

native_accessibility = child_platform_node_delegate->GetNativeViewAccessible();
EXPECT_TRUE([native_accessibility isKindOfClass:[FlutterTextField class]]);

child1.flags = static_cast<FlutterSemanticsFlag>(0);
bridge->AddFlutterSemanticsNodeUpdate(child1);
bridge->CommitUpdates();

native_accessibility = child_platform_node_delegate->GetNativeViewAccessible();
EXPECT_TRUE([[native_accessibility className] isEqualToString:@"AXPlatformNodeCocoa"]);
}

} // namespace flutter::testing
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ - (void)dealloc {
FlutterPlatformNodeDelegate* delegate = static_cast<FlutterPlatformNodeDelegate*>(GetDelegate());
bool offscreen;
auto bridge_ptr = delegate->GetOwnerBridge().lock();
if (!bridge_ptr) {
return NSZeroRect;
}
gfx::RectF bounds = bridge_ptr->RelativeToGlobalBounds(delegate->GetAXNode(), offscreen, true);

// Converts to NSRect to use NSView rect conversion.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,8 @@ - (BOOL)attached {
}

- (void)updateSemantics:(const FlutterSemanticsUpdate2*)update {
NSAssert(_engine.semanticsEnabled, @"Semantics must be enabled.");
// Semantics will be disabled when unfocusing application but the updateSemantics:
// callback is received in next run loop turn.
if (!_engine.semanticsEnabled) {
return;
}
Expand Down