Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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
6 changes: 6 additions & 0 deletions lib/ui/semantics/semantics_node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace blink {

constexpr int32_t kMinPlatfromViewId = -1;

SemanticsNode::SemanticsNode() = default;

SemanticsNode::SemanticsNode(const SemanticsNode& other) = default;
Expand All @@ -22,4 +24,8 @@ bool SemanticsNode::HasFlag(SemanticsFlags flag) {
return (flags & static_cast<int32_t>(flag)) != 0;
}

bool SemanticsNode::IsPlatformViewNode() const {
return platformViewId > kMinPlatfromViewId;
}

} // namespace blink
3 changes: 3 additions & 0 deletions lib/ui/semantics/semantics_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ struct SemanticsNode {
bool HasAction(SemanticsAction action);
bool HasFlag(SemanticsFlags flag);

// Whether this node is for embeded platform views.
bool IsPlatformViewNode() const;

int32_t id = 0;
int32_t flags = 0;
int32_t actions = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@
composition_order_.push_back(view_id);
}

NSObject<FlutterPlatformView>* FlutterPlatformViewsController::GetPlatformViewByID(int view_id) {
if (views_.empty()) {
return nil;
}
return views_[view_id].get();
}

std::vector<SkCanvas*> FlutterPlatformViewsController::GetCurrentCanvases() {
std::vector<SkCanvas*> canvases;
for (size_t i = 0; i < composition_order_.size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ class FlutterPlatformViewsController {

void PrerollCompositeEmbeddedView(int view_id);

// Returns the `FlutterPlatformView` object associated with the view_id.
//
// If the `FlutterPlatformViewsController` does not contain any `FlutterPlatformView` object or
// a `FlutterPlatformView` object asscociated with the view_id cannot be found, the method returns
// nil.
NSObject<FlutterPlatformView>* GetPlatformViewByID(int view_id);

std::vector<SkCanvas*> GetCurrentCanvases();

SkCanvas* CompositeEmbeddedView(int view_id, const flow::EmbeddedViewParams& params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ namespace shell {
class AccessibilityBridge;
} // namespace shell

@class FlutterPlatformViewSemanticsContainer;

/**
* A node in the iOS semantics tree.
*/
Expand Down Expand Up @@ -71,6 +73,11 @@ class AccessibilityBridge;
*/
@property(nonatomic, strong) NSMutableArray<SemanticsObject*>* children;

/**
* Used if this SemanticsObject is for a platform view.
*/
@property(strong, nonatomic) FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer;

- (BOOL)nodeWillCauseLayoutChange:(const blink::SemanticsNode*)node;

#pragma mark - Designated initializers
Expand Down Expand Up @@ -108,12 +115,31 @@ class AccessibilityBridge;
@interface FlutterSemanticsObject : SemanticsObject
@end

/**
* Designated to act as an accessibility container of a platform view.
*
* This object does not take any accessibility actions on its own, nor has any accessibility
* label/value/trait/hint... on its own. The accessibility data will be handled by the platform
* view.
*
* See also:
* * `SemanticsObject` for the other type of semantics objects.
* * `FlutterSemanticsObject` for default implementation of `SemanticsObject`.
*/
@interface FlutterPlatformViewSemanticsContainer : UIAccessibilityElement

- (instancetype)init __attribute__((unavailable("Use initWithAccessibilityContainer: instead")));

@end

namespace shell {
class PlatformViewIOS;

class AccessibilityBridge final {
public:
AccessibilityBridge(UIView* view, PlatformViewIOS* platform_view);
AccessibilityBridge(UIView* view,
PlatformViewIOS* platform_view,
FlutterPlatformViewsController* platform_views_controller);
~AccessibilityBridge();

void UpdateSemantics(blink::SemanticsNodeUpdates nodes,
Expand All @@ -129,6 +155,10 @@ class AccessibilityBridge final {

fml::WeakPtr<AccessibilityBridge> GetWeakPtr();

FlutterPlatformViewsController* GetPlatformViewsController() const {
return platform_views_controller_;
};

void clearState();

private:
Expand All @@ -139,6 +169,7 @@ class AccessibilityBridge final {

UIView* view_;
PlatformViewIOS* platform_view_;
FlutterPlatformViewsController* platform_views_controller_;
fml::scoped_nsobject<NSMutableDictionary<NSNumber*, SemanticsObject*>> objects_;
fml::scoped_nsprotocol<FlutterBasicMessageChannel*> accessibility_channel_;
fml::WeakPtrFactory<AccessibilityBridge> weak_factory_;
Expand Down
68 changes: 65 additions & 3 deletions shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import <UIKit/UIKit.h>

#include "flutter/fml/logging.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
#include "flutter/shell/platform/darwin/ios/platform_view_ios.h"

namespace {
Expand Down Expand Up @@ -127,6 +128,7 @@ - (void)dealloc {
[_children release];
_parent = nil;
_container.get().semanticsObject = nil;
[_platformViewSemanticsContainer release];
[super dealloc];
}

Expand All @@ -152,6 +154,9 @@ - (BOOL)nodeWillCauseScroll:(const blink::SemanticsNode*)node {
}

- (BOOL)hasChildren {
if (_node.IsPlatformViewNode()) {
return YES;
}
return [self.children count] != 0;
}

Expand All @@ -165,6 +170,7 @@ - (BOOL)isAccessibilityElement {
// We enforce in the framework that no other useful semantics are merged with these nodes.
if ([self node].HasFlag(blink::SemanticsFlags::kScopesRoute))
return false;

return ([self node].flags != 0 &&
[self node].flags != static_cast<int32_t>(blink::SemanticsFlags::kIsHidden)) ||
![self node].label.empty() || ![self node].value.empty() || ![self node].hint.empty() ||
Expand Down Expand Up @@ -396,6 +402,25 @@ - (UIAccessibilityTraits)accessibilityTraits {

@end

@implementation FlutterPlatformViewSemanticsContainer

// Method declared as unavailable in the interface
- (instancetype)init {
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}

- (instancetype)initWithAccessibilityContainer:(id)container {
FML_CHECK(container);
if (self = [super initWithAccessibilityContainer:container]) {
self.isAccessibilityElement = NO;
}
return self;
}

@end

@implementation SemanticsObjectContainer {
SemanticsObject* _semanticsObject;
fml::WeakPtr<shell::AccessibilityBridge> _bridge;
Expand Down Expand Up @@ -426,7 +451,12 @@ - (instancetype)initWithSemanticsObject:(SemanticsObject*)semanticsObject
#pragma mark - UIAccessibilityContainer overrides

- (NSInteger)accessibilityElementCount {
return [[_semanticsObject children] count] + 1;
NSInteger count = [[_semanticsObject children] count] + 1;
// Need to create an additional child that acts as accessibility container for the platform view.
if (_semanticsObject.node.IsPlatformViewNode()) {
count++;
}
return count;
}

- (nullable id)accessibilityElementAtIndex:(NSInteger)index {
Expand All @@ -435,7 +465,16 @@ - (nullable id)accessibilityElementAtIndex:(NSInteger)index {
if (index == 0) {
return _semanticsObject;
}

// Return the additional child acts as a container of platform view. The
// platformViewSemanticsContainer was created and cached in the updateSemantics path.
if (_semanticsObject.node.IsPlatformViewNode() && index == [self accessibilityElementCount] - 1) {
FML_CHECK(_semanticsObject.platformViewSemanticsContainer != nil);
return _semanticsObject.platformViewSemanticsContainer;
}

SemanticsObject* child = [_semanticsObject children][index - 1];

if ([child hasChildren])
return [child accessibilityContainer];
return child;
Expand All @@ -444,6 +483,12 @@ - (nullable id)accessibilityElementAtIndex:(NSInteger)index {
- (NSInteger)indexOfAccessibilityElement:(id)element {
if (element == _semanticsObject)
return 0;

// FlutterPlatformViewSemanticsContainer is always the last element of its parent.
if ([element isKindOfClass:[FlutterPlatformViewSemanticsContainer class]]) {
return [self accessibilityElementCount] - 1;
}

NSMutableArray<SemanticsObject*>* children = [_semanticsObject children];
for (size_t i = 0; i < [children count]; i++) {
SemanticsObject* child = children[i];
Expand Down Expand Up @@ -485,9 +530,12 @@ - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {

namespace shell {

AccessibilityBridge::AccessibilityBridge(UIView* view, PlatformViewIOS* platform_view)
AccessibilityBridge::AccessibilityBridge(UIView* view,
PlatformViewIOS* platform_view,
FlutterPlatformViewsController* platform_views_controller)
: view_(view),
platform_view_(platform_view),
platform_views_controller_(platform_views_controller),
objects_([[NSMutableDictionary alloc] init]),
weak_factory_(this),
previous_route_id_(0),
Expand Down Expand Up @@ -525,7 +573,7 @@ - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
layoutChanged = layoutChanged || [object nodeWillCauseLayoutChange:&node];
scrollOccured = scrollOccured || [object nodeWillCauseScroll:&node];
[object setSemanticsNode:&node];
const NSUInteger newChildCount = node.childrenInTraversalOrder.size();
NSUInteger newChildCount = node.childrenInTraversalOrder.size();
NSMutableArray* newChildren =
[[[NSMutableArray alloc] initWithCapacity:newChildCount] autorelease];
for (NSUInteger i = 0; i < newChildCount; ++i) {
Expand Down Expand Up @@ -555,6 +603,20 @@ - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
}
object.accessibilityCustomActions = accessibilityCustomActions;
}

if (object.node.IsPlatformViewNode()) {
shell::FlutterPlatformViewsController* controller = GetPlatformViewsController();
if (controller) {
object.platformViewSemanticsContainer = [[FlutterPlatformViewSemanticsContainer alloc]
initWithAccessibilityContainer:[object accessibilityContainer]];
UIView* platformView = [controller->GetPlatformViewByID(object.node.platformViewId) view];
if (platformView) {
object.platformViewSemanticsContainer.accessibilityElements = @[ platformView ];
}
}
} else if (object.platformViewSemanticsContainer) {
[object.platformViewSemanticsContainer release];
}
}

SemanticsObject* root = objects_.get()[@(kRootNodeId)];
Expand Down
6 changes: 4 additions & 2 deletions shell/platform/darwin/ios/platform_view_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@

if (accessibility_bridge_) {
accessibility_bridge_.reset(
new AccessibilityBridge(static_cast<FlutterView*>(owner_controller_.get().view), this));
new AccessibilityBridge(static_cast<FlutterView*>(owner_controller_.get().view), this,
[owner_controller.get() platformViewsController]));
}
// Do not call `NotifyCreated()` here - let FlutterViewController take care
// of that when its Viewport is sized. If `NotifyCreated()` is called here,
Expand Down Expand Up @@ -96,7 +97,8 @@
}
if (enabled && !accessibility_bridge_) {
accessibility_bridge_ = std::make_unique<AccessibilityBridge>(
static_cast<FlutterView*>(owner_controller_.get().view), this);
static_cast<FlutterView*>(owner_controller_.get().view), this,
[owner_controller_.get() platformViewsController]);
} else if (!enabled && accessibility_bridge_) {
accessibility_bridge_.reset();
}
Expand Down