diff --git a/sky/shell/platform/ios/framework/Source/accessibility_bridge.h b/sky/shell/platform/ios/framework/Source/accessibility_bridge.h index dd603c259b9..f4946579ce0 100644 --- a/sky/shell/platform/ios/framework/Source/accessibility_bridge.h +++ b/sky/shell/platform/ios/framework/Source/accessibility_bridge.h @@ -22,6 +22,36 @@ #include "third_party/skia/include/core/SkRect.h" #include "third_party/skia/include/utils/SkMatrix44.h" +namespace sky { +namespace shell { +class AccessibilityBridge; +} +} + +@interface AccessibilityNode : NSObject + +/** + * The globally unique identifier for this node. + */ +@property(nonatomic, readonly) uint32_t uid; + +/** + * The parent of this node in the node tree. Will be nil for the root node and + * during transient state changes. + */ +@property(nonatomic, readonly) AccessibilityNode* parent; + +/** + * This node's children in the node tree. + */ +@property(nonatomic, readonly) NSArray* children; + +- (instancetype)init __attribute__((unavailable("Use initWithBridge instead"))); +- (instancetype)initWithBridge:(sky::shell::AccessibilityBridge*)bridge + uid:(uint32_t)uid NS_DESIGNATED_INITIALIZER; + +@end + namespace sky { namespace shell { @@ -38,21 +68,18 @@ class AccessibilityBridge final : public semantics::SemanticsListener { ~AccessibilityBridge() override; void UpdateSemanticsTree(mojo::Array) override; + AccessibilityNode* UpdateNode(const semantics::SemanticsNodePtr& node); + void RemoveNode(AccessibilityNode* node); base::WeakPtr AsWeakPtr(); + FlutterView* getView() { return view_; } + private: - class Node; - - scoped_refptr UpdateNode(const semantics::SemanticsNodePtr& node); - void RemoveNode(scoped_refptr node); - - NSArray* CreateAccessibleElements() const NS_RETURNS_RETAINED; - // See class docs above about ownership relationship FlutterView* view_; semantics::SemanticsServerPtr semantics_server_; - std::map> nodes_; + NSMutableDictionary* nodes_; mojo::StrongBinding binding_; diff --git a/sky/shell/platform/ios/framework/Source/accessibility_bridge.mm b/sky/shell/platform/ios/framework/Source/accessibility_bridge.mm index 409759e60b9..aa680d14f59 100644 --- a/sky/shell/platform/ios/framework/Source/accessibility_bridge.mm +++ b/sky/shell/platform/ios/framework/Source/accessibility_bridge.mm @@ -4,15 +4,14 @@ #include "sky/shell/platform/ios/framework/Source/accessibility_bridge.h" -#include +#import #include "mojo/public/cpp/application/connect.h" -namespace sky { -namespace shell { - namespace { +static const uint32_t RootNodeId = 0; + // Contains better abstractions than the raw Mojo data structure struct Geometry { Geometry& operator=(const semantics::SemanticGeometryPtr& other) { @@ -23,163 +22,163 @@ struct Geometry { return *this; } - SkMatrix44 transform; + SkMatrix44 transform = + SkMatrix44(SkMatrix44::Identity_Constructor::kIdentity_Constructor); SkRect rect; }; } // anonymous namespace -// Class that holds information about accessibility nodes, which are used -// to construct iOS accessibility elements -class AccessibilityBridge::Node final - : public base::RefCounted { - public: - static const uint32_t kUninitializedNodeId = -1; +@implementation AccessibilityNode { + sky::shell::AccessibilityBridge* _bridge; - Node(AccessibilityBridge*, const semantics::SemanticsNodePtr&); - - void Update(const semantics::SemanticsNodePtr& node); - void PopulateAccessibleElements(NSMutableArray* accessibleElements); - - uint32_t id_ = kUninitializedNodeId; - std::vector> children_; - Node* parent_ = nullptr; - - private: - friend class base::RefCounted; - - ~Node(); - - void ValidateGlobalRect(); - void ValidateGlobalTransform(); - - AccessibilityBridge* bridge_; - - semantics::SemanticFlagsPtr flags_; - semantics::SemanticStringsPtr strings_; - Geometry geometry_; - - std::unique_ptr global_transform_; - std::unique_ptr global_rect_; - - DISALLOW_COPY_AND_ASSIGN(Node); -}; - -AccessibilityBridge::Node::Node(AccessibilityBridge* bridge, - const semantics::SemanticsNodePtr& node) - : bridge_(bridge) { - Update(node); + semantics::SemanticFlagsPtr _flags; + semantics::SemanticStringsPtr _strings; + Geometry _geometry; } -void AccessibilityBridge::Node::Update( - const semantics::SemanticsNodePtr& node) { - if (id_ == kUninitializedNodeId) { - id_ = node->id; - } - DCHECK(id_ == node->id); +#pragma mark - Override base class designated initializers - if (!node->flags.is_null()) { - flags_ = node->flags.Pass(); +// Method declared as unavailable in the interface +- (instancetype)init { + [self release]; + [super doesNotRecognizeSelector:_cmd]; + return nil; +} + +#pragma mark - Designated initializers + +- (instancetype)initWithBridge:(sky::shell::AccessibilityBridge*)bridge + uid:(uint32_t)uid { + DCHECK(bridge != nil) << "bridge must be set"; + DCHECK(uid >= RootNodeId); + self = [super init]; + + if (self) { + _bridge = bridge; + _uid = uid; } - if (!node->strings.is_null()) { - strings_ = node->strings.Pass(); + return self; +} + +#pragma mark - Semantics node methods + +- (void)update:(const semantics::SemanticsNodePtr&)mojoNode { + DCHECK(_uid == mojoNode->id); + + if (!mojoNode->flags.is_null()) { + _flags = mojoNode->flags.Pass(); } - if (!node->geometry.is_null()) { - geometry_ = node->geometry.Pass(); + if (!mojoNode->strings.is_null()) { + _strings = mojoNode->strings.Pass(); } - if (!node->children.is_null()) { - // Mark children for removal - for (scoped_refptr child : children_) { - DCHECK(child->parent_ != nullptr); - child->parent_ = nullptr; + if (!mojoNode->geometry.is_null()) { + _geometry = mojoNode->geometry; + } + + if (!mojoNode->children.is_null()) { + // Mark existing children as orphans + NSArray* oldChildren = _children; + for (AccessibilityNode* child in oldChildren) { + DCHECK(child->_parent != nil); + child->_parent = nil; } // Set the new list of children - std::vector> children; - for (const semantics::SemanticsNodePtr& childNode : node->children) { - scoped_refptr child = bridge_->UpdateNode(childNode); - child->parent_ = this; - children.push_back(child); + NSMutableArray* children = [[NSMutableArray alloc] init]; + _children = children; + for (const semantics::SemanticsNodePtr& mojoChild : mojoNode->children) { + AccessibilityNode* child = _bridge->UpdateNode(mojoChild); + child->_parent = self; + [children insertObject:child atIndex:0]; } - children.swap(children_); - // Remove those children that are still marked for removal - for (scoped_refptr child : children) { - if (child->parent_ == nullptr) { - bridge_->RemoveNode(child); + // Remove those children that are still marked as orphans + for (AccessibilityNode* child in oldChildren) { + if (child->_parent == nil) { + _bridge->RemoveNode(child); } } - } - - global_transform_.release(); - global_rect_.release(); -} - -void AccessibilityBridge::Node::ValidateGlobalTransform() { - if (global_transform_ != nullptr) { - return; - } - - if (parent_ == nullptr) { - global_transform_.reset(new SkMatrix44(geometry_.transform)); - } else { - parent_->ValidateGlobalTransform(); - global_transform_.reset( - new SkMatrix44(geometry_.transform * *(parent_->global_transform_))); + [oldChildren release]; } } -void AccessibilityBridge::Node::ValidateGlobalRect() { - if (global_rect_ != nullptr) { - return; - } +- (void)remove { + _parent = nil; + _bridge->RemoveNode(self); +} - ValidateGlobalTransform(); +#pragma mark - UIAccessibility overrides +- (BOOL)isAccessibilityElement { + // Per UIAccessibilityContainer, accessibility containers MUST return + // NO to -isAccessibilityElement + return (_children.count == 0); +} + +- (NSString*)accessibilityLabel { + return (_strings.is_null() || _strings->label.get().empty()) + ? nil + : @(_string->label.data()); +} + +- (UIAccessibilityTraits)accessibilityTraits { + // TODO(tvolkert): We need more semantic info in the mojom definition + // in order to distinguish buttons, links, sliders, etc. + return _flags->canBeTapped ? UIAccessibilityTraitButton + : UIAccessibilityTraitNone; +} + +- (CGRect)accessibilityFrame { SkPoint quad[4]; - geometry_.rect.toQuad(quad); + _geometry.rect.toQuad(quad); for (auto& point : quad) { SkScalar vector[4] = {point.x(), point.y(), 0, 1}; - global_transform_->mapScalars(vector); + _geometry.transform.mapScalars(vector); point.set(vector[0], vector[1]); } - - global_rect_.reset(new SkRect()); - global_rect_.get()->set(quad, 4); + SkRect result; + result.set(quad, 4); + return CGRectMake(result.x(), result.y(), result.width(), result.height()); } -void AccessibilityBridge::Node::PopulateAccessibleElements( - NSMutableArray* accessibleElements) { - if (!geometry_.rect.isEmpty()) { - UIAccessibilityElement* element = [[UIAccessibilityElement alloc] - initWithAccessibilityContainer:bridge_->view_]; - element.isAccessibilityElement = YES; - ValidateGlobalRect(); - element.accessibilityFrame = - CGRectMake(global_rect_->x(), global_rect_->y(), global_rect_->width(), - global_rect_->height()); - if (flags_->canBeTapped) { - // TODO(tvolkert): What about links? We need semantic info in the mojom - // definition - element.accessibilityTraits = UIAccessibilityTraitButton; - } - if (!strings_->label.get().empty()) { - element.accessibilityLabel = - [NSString stringWithUTF8String:strings_->label.data()]; - } - [accessibleElements insertObject:element atIndex:0]; - [element release]; - } +#pragma mark - UIAccessibilityElement protocol - for (scoped_refptr child : children_) { - child->PopulateAccessibleElements(accessibleElements); - } +- (id)accessibilityContainer { + return (_uid == RootNodeId) ? _bridge->getView() : _parent; } -AccessibilityBridge::Node::~Node() {} +#pragma mark - UIAccessibilityContainer overrides + +- (NSInteger)accessibilityElementCount { + return _children.count; +} + +- (nullable id)accessibilityElementAtIndex:(NSInteger)index { + return (index < 0 || index >= (NSInteger)_children.count ? nil + : _children[index]); +} + +- (NSInteger)indexOfAccessibilityElement:(id)element { + return (_children == nil) ? NSNotFound : [_children indexOfObject:element]; +} + +#pragma mark - Misc + +- (void)dealloc { + [_children release]; + [super dealloc]; +} + +@end + +#pragma mark - AccessibilityBridge impl + +namespace sky { +namespace shell { AccessibilityBridge::AccessibilityBridge(FlutterView* view, mojo::ServiceProvider* serviceProvider) @@ -188,17 +187,19 @@ AccessibilityBridge::AccessibilityBridge(FlutterView* view, mojo::InterfaceHandle listener; binding_.Bind(&listener); semantics_server_->AddSemanticsListener(listener.Pass()); + + nodes_ = [[NSMutableDictionary alloc] init]; } void AccessibilityBridge::UpdateSemanticsTree( - mojo::Array nodes) { - for (const semantics::SemanticsNodePtr& node : nodes) { - UpdateNode(node); + mojo::Array mojoNodes) { + for (const semantics::SemanticsNodePtr& mojoNode : mojoNodes) { + auto node = UpdateNode(mojoNode); + if (mojoNode->id == RootNodeId && view_.accessibilityElements == nil) { + view_.accessibilityElements = @[ node ]; + } } - - NSArray* accessibleElements = CreateAccessibleElements(); - view_.accessibilityElements = accessibleElements; - [accessibleElements release]; + DCHECK(view_.accessibilityElements != nil); UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); @@ -208,45 +209,33 @@ base::WeakPtr AccessibilityBridge::AsWeakPtr() { return weak_factory_.GetWeakPtr(); } -scoped_refptr AccessibilityBridge::UpdateNode( - const semantics::SemanticsNodePtr& node) { - scoped_refptr persistentNode; - const auto& iter = nodes_.find(node->id); - if (iter == nodes_.end()) { - persistentNode = new Node(this, node); - nodes_[node->id] = persistentNode; - } else { - persistentNode = iter->second; - persistentNode->Update(node); +AccessibilityNode* AccessibilityBridge::UpdateNode( + const semantics::SemanticsNodePtr& mojoNode) { + AccessibilityNode* node = nodes_[@(mojoNode->id)]; + if (node == nil) { + node = [[AccessibilityNode alloc] initWithBridge:this uid:mojoNode->id]; + DCHECK(nodes_ != nil); + nodes_[@(mojoNode->id)] = node; + [node release]; } - DCHECK(persistentNode != nullptr); - return persistentNode; + [node update:mojoNode]; + return node; } -void AccessibilityBridge::RemoveNode(scoped_refptr node) { - DCHECK(nodes_.find(node->id_) != nodes_.end()); - DCHECK(nodes_.at(node->id_)->parent_ == nullptr); - nodes_.erase(node->id_); - for (scoped_refptr& child : node->children_) { - child->parent_ = nullptr; - RemoveNode(child); +void AccessibilityBridge::RemoveNode(AccessibilityNode* node) { + [node retain]; + DCHECK(nodes_[@(node.uid)] != nil); + DCHECK(nodes_[@(node.uid)].parent == nil); + [nodes_ removeObjectForKey:@(node.uid)]; + for (AccessibilityNode* child in node.children) { + [child remove]; } + [node release]; } -NSArray* AccessibilityBridge::CreateAccessibleElements() const - NS_RETURNS_RETAINED { - NSMutableArray* accessibleElements = [[NSMutableArray alloc] init]; - for (const auto& iter : nodes_) { - // TODO(tvolkert): There should only ever be 1 root. Keep a reference - // to it so we don't have to look for it here. - if (iter.second->parent_ == nullptr) { - iter.second->PopulateAccessibleElements(accessibleElements); - } - } - return accessibleElements; +AccessibilityBridge::~AccessibilityBridge() { + [nodes_ release]; } -AccessibilityBridge::~AccessibilityBridge() {} - } // namespace shell } // namespace sky