From fdc0b718892529a2134f30232ea80466bcbbac69 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Fri, 6 May 2016 11:26:01 -0700 Subject: [PATCH] Move node implementation to Objective C (#2652) * Move node implementation to Objective C This changes AccessibilityBridge::Node (C++) to be AccessibilityNode (objc) in order to have the nodes returned directly as ui accessibility elements. This buys us a few things: 1) We can now return an element tree instead of a flat list, thus negating the need to maintain a global rect and global translation 2) We use less memory since we don't need to instantiate both nodes and their ensuing accessibility elements 3) accessibility elements can now implement the methods necessary to respond to events like user scrolling https://github.com/flutter/flutter/issues/1664 --- .../framework/Source/accessibility_bridge.h | 43 ++- .../framework/Source/accessibility_bridge.mm | 315 +++++++++--------- 2 files changed, 187 insertions(+), 171 deletions(-) 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