diff --git a/content_handler/accessibility_bridge.cc b/content_handler/accessibility_bridge.cc index a793d171ef77d..874d39983fdda 100644 --- a/content_handler/accessibility_bridge.cc +++ b/content_handler/accessibility_bridge.cc @@ -20,8 +20,9 @@ AccessibilityBridge::AccessibilityBridge(app::ApplicationContext* context) : writer_(context->ConnectToEnvironmentService()) {} void AccessibilityBridge::UpdateSemantics( - const std::vector& update) { - for (const auto& node : update) { + const blink::SemanticsNodeUpdates& update) { + for (const auto& update : update) { + const auto& node = update.second; semantics_nodes_[node.id] = node; } std::vector visited_nodes; diff --git a/content_handler/accessibility_bridge.h b/content_handler/accessibility_bridge.h index ef22c70a7aca3..073a1504a51f0 100644 --- a/content_handler/accessibility_bridge.h +++ b/content_handler/accessibility_bridge.h @@ -21,7 +21,7 @@ class AccessibilityBridge { // Update the internal representation of the semantics nodes, and write the // semantics to Context Service. - void UpdateSemantics(const std::vector& update); + void UpdateSemantics(const blink::SemanticsNodeUpdates& update); private: // Walk the semantics node tree starting at |id|, and store the id of each diff --git a/content_handler/runtime_holder.cc b/content_handler/runtime_holder.cc index 8d91901ed75b2..e94535c360d36 100644 --- a/content_handler/runtime_holder.cc +++ b/content_handler/runtime_holder.cc @@ -370,7 +370,7 @@ void RuntimeHolder::Render(std::unique_ptr layer_tree) { })); } -void RuntimeHolder::UpdateSemantics(std::vector update) { +void RuntimeHolder::UpdateSemantics(blink::SemanticsNodeUpdates update) { accessibility_bridge_->UpdateSemantics(update); } diff --git a/content_handler/runtime_holder.h b/content_handler/runtime_holder.h index b254b13f6903a..3fee8a1bb8623 100644 --- a/content_handler/runtime_holder.h +++ b/content_handler/runtime_holder.h @@ -65,7 +65,7 @@ class RuntimeHolder : public blink::RuntimeDelegate, std::string DefaultRouteName() override; void ScheduleFrame(bool regenerate_layer_tree = true) override; void Render(std::unique_ptr layer_tree) override; - void UpdateSemantics(std::vector update) override; + void UpdateSemantics(blink::SemanticsNodeUpdates update) override; void HandlePlatformMessage( fxl::RefPtr message) override; void DidCreateMainIsolate(Dart_Isolate isolate) override; diff --git a/lib/ui/semantics/semantics_node.h b/lib/ui/semantics/semantics_node.h index 51ad3d832b200..4308f962fcaf0 100644 --- a/lib/ui/semantics/semantics_node.h +++ b/lib/ui/semantics/semantics_node.h @@ -8,6 +8,7 @@ #include #include +#include #include #include "third_party/skia/include/core/SkMatrix44.h" @@ -70,6 +71,12 @@ struct SemanticsNode { std::vector children; }; +// Contains semantic nodes that need to be updated. +// +// The keys in the map are stable node IDd, and the values contain +// semantic information for the node corresponding to the ID. +using SemanticsNodeUpdates = std::unordered_map; + } // namespace blink #endif // FLUTTER_LIB_UI_SEMANTICS_SEMANTICS_NODE_H_ diff --git a/lib/ui/semantics/semantics_update.cc b/lib/ui/semantics/semantics_update.cc index e091433958659..fdc796e937b93 100644 --- a/lib/ui/semantics/semantics_update.cc +++ b/lib/ui/semantics/semantics_update.cc @@ -21,16 +21,16 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, SemanticsUpdate); DART_BIND_ALL(SemanticsUpdate, FOR_EACH_BINDING) fxl::RefPtr SemanticsUpdate::create( - std::vector nodes) { + SemanticsNodeUpdates nodes) { return fxl::MakeRefCounted(std::move(nodes)); } -SemanticsUpdate::SemanticsUpdate(std::vector nodes) +SemanticsUpdate::SemanticsUpdate(SemanticsNodeUpdates nodes) : nodes_(std::move(nodes)) {} SemanticsUpdate::~SemanticsUpdate() = default; -std::vector SemanticsUpdate::takeNodes() { +SemanticsNodeUpdates SemanticsUpdate::takeNodes() { return std::move(nodes_); } diff --git a/lib/ui/semantics/semantics_update.h b/lib/ui/semantics/semantics_update.h index 166e02e32e056..dbea854e4a002 100644 --- a/lib/ui/semantics/semantics_update.h +++ b/lib/ui/semantics/semantics_update.h @@ -5,8 +5,6 @@ #ifndef FLUTTER_LIB_UI_SEMANTICS_SEMANTICS_UPDATE_H_ #define FLUTTER_LIB_UI_SEMANTICS_SEMANTICS_UPDATE_H_ -#include - #include "flutter/lib/ui/semantics/semantics_node.h" #include "lib/tonic/dart_wrappable.h" @@ -23,18 +21,18 @@ class SemanticsUpdate : public fxl::RefCountedThreadSafe, public: ~SemanticsUpdate() override; - static fxl::RefPtr create(std::vector nodes); + static fxl::RefPtr create(SemanticsNodeUpdates nodes); - std::vector takeNodes(); + SemanticsNodeUpdates takeNodes(); void dispose(); static void RegisterNatives(tonic::DartLibraryNatives* natives); private: - explicit SemanticsUpdate(std::vector nodes); + explicit SemanticsUpdate(SemanticsNodeUpdates nodes); - std::vector nodes_; + SemanticsNodeUpdates nodes_; }; } // namespace blink diff --git a/lib/ui/semantics/semantics_update_builder.cc b/lib/ui/semantics/semantics_update_builder.cc index 81ba0671d79cf..43121d473a122 100644 --- a/lib/ui/semantics/semantics_update_builder.cc +++ b/lib/ui/semantics/semantics_update_builder.cc @@ -69,7 +69,7 @@ void SemanticsUpdateBuilder::updateNode(int id, node.transform.setColMajord(transform.data()); node.children = std::vector( children.data(), children.data() + children.num_elements()); - nodes_.push_back(node); + nodes_[id] = node; } fxl::RefPtr SemanticsUpdateBuilder::build() { diff --git a/lib/ui/semantics/semantics_update_builder.h b/lib/ui/semantics/semantics_update_builder.h index c27ca702fc181..e791a33960083 100644 --- a/lib/ui/semantics/semantics_update_builder.h +++ b/lib/ui/semantics/semantics_update_builder.h @@ -51,7 +51,7 @@ class SemanticsUpdateBuilder private: explicit SemanticsUpdateBuilder(); - std::vector nodes_; + SemanticsNodeUpdates nodes_; }; } // namespace blink diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index 88d1b217c605c..36650fe7fb70b 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -20,7 +20,7 @@ class RuntimeDelegate { virtual std::string DefaultRouteName() = 0; virtual void ScheduleFrame(bool regenerate_layer_tree = true) = 0; virtual void Render(std::unique_ptr layer_tree) = 0; - virtual void UpdateSemantics(std::vector update) = 0; + virtual void UpdateSemantics(blink::SemanticsNodeUpdates update) = 0; virtual void HandlePlatformMessage(fxl::RefPtr message) = 0; virtual void DidCreateMainIsolate(Dart_Isolate isolate); diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 437fc0172e086..8d91e7d1d11e9 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -662,7 +662,7 @@ void Engine::Render(std::unique_ptr layer_tree) { animator_->Render(std::move(layer_tree)); } -void Engine::UpdateSemantics(std::vector update) { +void Engine::UpdateSemantics(blink::SemanticsNodeUpdates update) { blink::Threads::Platform()->PostTask(fxl::MakeCopyable([ platform_view = platform_view_.lock(), update = std::move(update) ]() mutable { diff --git a/shell/common/engine.h b/shell/common/engine.h index b735f3510cc8d..fab9e47b66217 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -85,7 +85,7 @@ class Engine : public blink::RuntimeDelegate { // RuntimeDelegate methods: std::string DefaultRouteName() override; void Render(std::unique_ptr layer_tree) override; - void UpdateSemantics(std::vector update) override; + void UpdateSemantics(blink::SemanticsNodeUpdates update) override; void HandlePlatformMessage( fxl::RefPtr message) override; void DidCreateMainIsolate(Dart_Isolate isolate) override; diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc index fc331c962e9bc..37321372e5a2e 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -130,7 +130,7 @@ VsyncWaiter* PlatformView::GetVsyncWaiter() { return vsync_waiter_.get(); } -void PlatformView::UpdateSemantics(std::vector update) {} +void PlatformView::UpdateSemantics(blink::SemanticsNodeUpdates update) {} void PlatformView::HandlePlatformMessage( fxl::RefPtr message) { diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index d8c068c239d2c..1a21076f63f81 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -60,7 +60,7 @@ class PlatformView : public std::enable_shared_from_this { virtual bool ResourceContextMakeCurrent() = 0; - virtual void UpdateSemantics(std::vector update); + virtual void UpdateSemantics(blink::SemanticsNodeUpdates update); virtual void HandlePlatformMessage( fxl::RefPtr message); diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 65833e34ec96f..4b91caa0a6f93 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -460,7 +460,7 @@ bool PlatformViewAndroid::ResourceContextMakeCurrent() { } void PlatformViewAndroid::UpdateSemantics( - std::vector update) { + blink::SemanticsNodeUpdates update) { constexpr size_t kBytesPerNode = 33 * sizeof(int32_t); constexpr size_t kBytesPerChild = sizeof(int32_t); diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index 8720976aaa1b2..f207f65a870b1 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -93,7 +93,7 @@ class PlatformViewAndroid : public PlatformView { bool ResourceContextMakeCurrent() override; - void UpdateSemantics(std::vector update) override; + void UpdateSemantics(blink::SemanticsNodeUpdates update) override; void HandlePlatformMessage( fxl::RefPtr message) override; diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 4ba54b937290f..33b0420227b77 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -55,6 +55,8 @@ shared_library("create_flutter_framework_dylib") { "framework/Source/FlutterViewController.mm", "framework/Source/accessibility_bridge.h", "framework/Source/accessibility_bridge.mm", + "framework/Source/accessibility_text_entry.h", + "framework/Source/accessibility_text_entry.mm", "framework/Source/flutter_main_ios.h", "framework/Source/flutter_main_ios.mm", "framework/Source/flutter_touch_mapper.h", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 7eaa71ac1c8be..198381e4ebbce 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -5,6 +5,8 @@ #ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_ #define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_ +#import + #include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h" #include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" @@ -13,6 +15,33 @@ @property(nonatomic, assign) id textInputDelegate; - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; +/** + * The `UITextInput` implementation used to control text entry. + * + * This is used by `AccessibilityBridge` to forward interactions with iOS' + * accessibility system. + */ +- (UIView*)textInputView; + +@end + +/** An indexed position in the buffer of a Flutter text editing widget. */ +@interface FlutterTextPosition : UITextPosition + +@property(nonatomic, readonly) NSUInteger index; + ++ (instancetype)positionWithIndex:(NSUInteger)index; +- (instancetype)initWithIndex:(NSUInteger)index; + +@end + +/** A range of text in the buffer of a Flutter text editing widget. */ +@interface FlutterTextRange : UITextRange + +@property(nonatomic, readonly) NSRange range; + ++ (instancetype)rangeWithNSRange:(NSRange)range; + @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 86fb24e13828f..d7431ea7d91ec 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -41,16 +41,6 @@ static UITextAutocapitalizationType ToUITextAutocapitalizationType(NSString* inp #pragma mark - FlutterTextPosition -/** An indexed position in the buffer of a Flutter text editing widget. */ -@interface FlutterTextPosition : UITextPosition - -@property(nonatomic, readonly) NSUInteger index; - -+ (instancetype)positionWithIndex:(NSUInteger)index; -- (instancetype)initWithIndex:(NSUInteger)index; - -@end - @implementation FlutterTextPosition + (instancetype)positionWithIndex:(NSUInteger)index { @@ -69,15 +59,6 @@ - (instancetype)initWithIndex:(NSUInteger)index { #pragma mark - FlutterTextRange -/** A range of text in the buffer of a Flutter text editing widget. */ -@interface FlutterTextRange : UITextRange - -@property(nonatomic, readonly) NSRange range; - -+ (instancetype)rangeWithNSRange:(NSRange)range; - -@end - @implementation FlutterTextRange + (instancetype)rangeWithNSRange:(NSRange)range { @@ -536,8 +517,28 @@ - (void)deleteBackward { @end +/** + * Hides `FlutterTextInputView` from iOS accessibility system so it + * does not show up twice, once where it is in the `UIView` hierarchy, + * and a second time as part of the `SemanticsObject` hierarchy. + */ +@interface FlutterTextInputViewAccessibilityHider : UIView { +} + +@end + +@implementation FlutterTextInputViewAccessibilityHider { +} + +- (BOOL)accessibilityElementsHidden { + return YES; +} + +@end + @implementation FlutterTextInputPlugin { FlutterTextInputView* _view; + FlutterTextInputViewAccessibilityHider* _inputHider; } @synthesize textInputDelegate = _textInputDelegate; @@ -547,6 +548,7 @@ - (instancetype)init { if (self) { _view = [[FlutterTextInputView alloc] init]; + _inputHider = [[FlutterTextInputViewAccessibilityHider alloc] init]; } return self; @@ -555,10 +557,15 @@ - (instancetype)init { - (void)dealloc { [self hideTextInput]; [_view release]; + [_inputHider release]; [super dealloc]; } +- (UIView*)textInputView { + return _view; +} + - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { NSString* method = call.method; id args = call.arguments; @@ -587,13 +594,15 @@ - (void)showTextInput { @"The application must have a key window since the keyboard client " @"must be part of the responder chain to function"); _view.textInputDelegate = _textInputDelegate; - [[UIApplication sharedApplication].keyWindow addSubview:_view]; + [_inputHider addSubview:_view]; + [[UIApplication sharedApplication].keyWindow addSubview:_inputHider]; [_view becomeFirstResponder]; } - (void)hideTextInput { [_view resignFirstResponder]; [_view removeFromSuperview]; + [_inputHider removeFromSuperview]; } - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configuration { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 065a17fb28177..461696896aaec 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -197,6 +197,7 @@ - (void)performCommonViewControllerInitialization { [_textInputChannel.get() setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { [_textInputPlugin.get() handleMethodCall:call result:result]; }]; + _platformView->SetTextInputPlugin(_textInputPlugin); [self setupNotificationCenterObservers]; } diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h index 43a695a826f03..84e9f732fe355 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h @@ -10,9 +10,12 @@ #include #include +#import + #include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/lib/ui/semantics/semantics_node.h" #include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterChannels.h" +#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #include "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" #include "lib/fxl/macros.h" #include "third_party/skia/include/core/SkMatrix44.h" @@ -22,6 +25,9 @@ namespace shell { class AccessibilityBridge; } // namespace shell +/** + * A node in the iOS semantics tree. + */ @interface SemanticsObject : NSObject /** @@ -35,12 +41,57 @@ class AccessibilityBridge; */ @property(nonatomic, strong) SemanticsObject* parent; +/** + * The accessibility bridge that this semantics object is attached to. This + * object may use the bridge to access contextual application information. + */ +@property(nonatomic, readonly) shell::AccessibilityBridge* bridge; + +/** + * The semantics node used to produce this semantics object. + */ +@property(nonatomic, readonly) blink::SemanticsNode node; + +/** + * Updates this semantics object using data from the `node` argument. + */ +- (void)setSemanticsNode:(const blink::SemanticsNode*)node NS_REQUIRES_SUPER; + +/** + * Whether this semantics object has child semantics objects. + */ +@property(nonatomic, readonly) BOOL hasChildren; + +/** + * Direct children of this semantics object. Each child's `parent` property must + * be equal to this object. + */ +@property(nonatomic, readonly) std::vector* children; + +- (BOOL)nodeWillCauseLayoutChange:(const blink::SemanticsNode*)node; + +#pragma mark - Designated initializers + - (instancetype)init __attribute__((unavailable("Use initWithBridge instead"))); - (instancetype)initWithBridge:(shell::AccessibilityBridge*)bridge uid:(int32_t)uid NS_DESIGNATED_INITIALIZER; @end +/** + * The default implementation of `SemanticsObject` for most accessibility elements + * in the iOS accessibility tree. + * + * Use this implementation for nodes that do not need to be expressed via UIKit-specific + * protocols (it only implements NSObject). + * + * See also: + * * TextInputSemanticsObject, which implements `UITextInput` protocol to expose + * editable text widgets to a11y. + */ +@interface FlutterSemanticsObject : SemanticsObject +@end + namespace shell { class PlatformViewIOS; @@ -49,13 +100,14 @@ class AccessibilityBridge final { AccessibilityBridge(UIView* view, PlatformViewIOS* platform_view); ~AccessibilityBridge(); - void UpdateSemantics(std::vector nodes); + void UpdateSemantics(blink::SemanticsNodeUpdates nodes); void DispatchSemanticsAction(int32_t id, blink::SemanticsAction action); + UIView* textInputView(); UIView* view() const { return view_; } private: - SemanticsObject* GetOrCreateObject(int32_t id); + SemanticsObject* GetOrCreateObject(int32_t id, blink::SemanticsNodeUpdates& updates); void VisitObjectsRecursivelyAndRemove(SemanticsObject* object, NSMutableArray* doomed_uids); void ReleaseObjects(std::unordered_map& objects); diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm index 40be1899df8c9..9779a7a9d593a 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h" +#include "flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h" #include #include @@ -93,10 +94,8 @@ - (instancetype)initWithSemanticsObject:(SemanticsObject*)semanticsObject @end @implementation SemanticsObject { - shell::AccessibilityBridge* _bridge; - blink::SemanticsNode _node; - std::vector _children; SemanticsObjectContainer* _container; + std::vector _children; } #pragma mark - Override base class designated initializers @@ -123,6 +122,14 @@ - (instancetype)initWithBridge:(shell::AccessibilityBridge*)bridge uid:(int32_t) return self; } +- (void)dealloc { + _bridge = nullptr; + _children.clear(); + [_parent release]; + [_container release]; + [super dealloc]; +} + #pragma mark - Semantic object methods - (void)setSemanticsNode:(const blink::SemanticsNode*)node { @@ -132,8 +139,8 @@ - (void)setSemanticsNode:(const blink::SemanticsNode*)node { /** * Whether calling `setSemanticsNode:` with `node` would cause a layout change. */ -- (BOOL)willCauseLayoutChange:(const blink::SemanticsNode*)node { - return _node.rect != node->rect || _node.transform != node->transform; +- (BOOL)nodeWillCauseLayoutChange:(const blink::SemanticsNode*)node { + return [self node].rect != node->rect || [self node].transform != node->transform; } - (std::vector*)children { @@ -144,71 +151,43 @@ - (BOOL)hasChildren { return _children.size() != 0; } -- (void)dealloc { - _bridge = nullptr; - _children.clear(); - [_parent release]; - if (_container != nil) - [_container release]; - [super dealloc]; -} - #pragma mark - UIAccessibility overrides - (BOOL)isAccessibilityElement { // Note: hit detection will only apply to elements that report // -isAccessibilityElement of YES. The framework will continue scanning the // entire element tree looking for such a hit. - return _node.flags != 0 || !_node.label.empty() || !_node.value.empty() || !_node.hint.empty() || - (_node.actions & ~blink::kScrollableSemanticsActions) != 0; + return [self node].flags != 0 || ![self node].label.empty() || ![self node].value.empty() || + ![self node].hint.empty() || + ([self node].actions & ~blink::kScrollableSemanticsActions) != 0; } - (NSString*)accessibilityLabel { - if (_node.label.empty()) + if ([self node].label.empty()) return nil; - return @(_node.label.data()); + return @([self node].label.data()); } - (NSString*)accessibilityHint { - if (_node.hint.empty()) + if ([self node].hint.empty()) return nil; - return @(_node.hint.data()); + return @([self node].hint.data()); } - (NSString*)accessibilityValue { - if (_node.value.empty()) + if ([self node].value.empty()) return nil; - return @(_node.value.data()); -} - -- (UIAccessibilityTraits)accessibilityTraits { - UIAccessibilityTraits traits = UIAccessibilityTraitNone; - if (_node.HasAction(blink::SemanticsAction::kIncrease) || - _node.HasAction(blink::SemanticsAction::kDecrease)) { - traits |= UIAccessibilityTraitAdjustable; - } - if (_node.HasFlag(blink::SemanticsFlags::kIsSelected) || - _node.HasFlag(blink::SemanticsFlags::kIsChecked)) { - traits |= UIAccessibilityTraitSelected; - } - if (_node.HasFlag(blink::SemanticsFlags::kIsButton)) { - traits |= UIAccessibilityTraitButton; - } - if (_node.HasFlag(blink::SemanticsFlags::kHasEnabledState) && - !_node.HasFlag(blink::SemanticsFlags::kIsEnabled)) { - traits |= UIAccessibilityTraitNotEnabled; - } - return traits; + return @([self node].value.data()); } - (CGRect)accessibilityFrame { - SkMatrix44 globalTransform = _node.transform; - for (SemanticsObject* parent = _parent; parent; parent = parent.parent) { - globalTransform = parent->_node.transform * globalTransform; + SkMatrix44 globalTransform = [self node].transform; + for (SemanticsObject* parent = [self parent]; parent; parent = parent.parent) { + globalTransform = parent.node.transform * globalTransform; } SkPoint quad[4]; - _node.rect.toQuad(quad); + [self node].rect.toQuad(quad); for (auto& point : quad) { SkScalar vector[4] = {point.x(), point.y(), 0, 1}; globalTransform.mapScalars(vector); @@ -220,57 +199,101 @@ - (CGRect)accessibilityFrame { // `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in // the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to // convert. - CGFloat scale = [[_bridge->view() window] screen].scale; + CGFloat scale = [[[self bridge] -> view() window] screen].scale; auto result = CGRectMake(rect.x() / scale, rect.y() / scale, rect.width() / scale, rect.height() / scale); - return UIAccessibilityConvertFrameToScreenCoordinates(result, _bridge->view()); + return UIAccessibilityConvertFrameToScreenCoordinates(result, [self bridge] -> view()); } #pragma mark - UIAccessibilityElement protocol - (id)accessibilityContainer { - if ([self hasChildren] || _uid == kRootNodeId) { + if ([self hasChildren] || [self uid] == kRootNodeId) { if (_container == nil) - _container = [[SemanticsObjectContainer alloc] initWithSemanticsObject:self bridge:_bridge]; + _container = + [[SemanticsObjectContainer alloc] initWithSemanticsObject:self bridge:[self bridge]]; return _container; } - NSAssert(_parent != nil, @"Illegal access to non-existent parent of root semantics node"); - return [_parent accessibilityContainer]; + NSAssert([self parent] != nil, @"Illegal access to non-existent parent of root semantics node"); + return [[self parent] accessibilityContainer]; } #pragma mark - UIAccessibilityAction overrides - (BOOL)accessibilityActivate { - if (!_node.HasAction(blink::SemanticsAction::kTap)) + if (![self node].HasAction(blink::SemanticsAction::kTap)) return NO; - _bridge->DispatchSemanticsAction(_uid, blink::SemanticsAction::kTap); + [self bridge] -> DispatchSemanticsAction([self uid], blink::SemanticsAction::kTap); return YES; } - (void)accessibilityIncrement { - if (_node.HasAction(blink::SemanticsAction::kIncrease)) { - _node.value = _node.increasedValue; - _bridge->DispatchSemanticsAction(_uid, blink::SemanticsAction::kIncrease); + if ([self node].HasAction(blink::SemanticsAction::kIncrease)) { + [self node].value = [self node].increasedValue; + [self bridge] -> DispatchSemanticsAction([self uid], blink::SemanticsAction::kIncrease); } } - (void)accessibilityDecrement { - if (_node.HasAction(blink::SemanticsAction::kDecrease)) { - _node.value = _node.decreasedValue; - _bridge->DispatchSemanticsAction(_uid, blink::SemanticsAction::kDecrease); + if ([self node].HasAction(blink::SemanticsAction::kDecrease)) { + [self node].value = [self node].decreasedValue; + [self bridge] -> DispatchSemanticsAction([self uid], blink::SemanticsAction::kDecrease); } } - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction { blink::SemanticsAction action = GetSemanticsActionForScrollDirection(direction); - if (!_node.HasAction(action)) + if (![self node].HasAction(action)) return NO; - _bridge->DispatchSemanticsAction(_uid, action); + [self bridge] -> DispatchSemanticsAction([self uid], action); return YES; } @end +@implementation FlutterSemanticsObject { +} + +#pragma mark - Override base class designated initializers + +// Method declared as unavailable in the interface +- (instancetype)init { + [self release]; + [super doesNotRecognizeSelector:_cmd]; + return nil; +} + +#pragma mark - Designated initializers + +- (instancetype)initWithBridge:(shell::AccessibilityBridge*)bridge uid:(int32_t)uid { + self = [super initWithBridge:bridge uid:uid]; + return self; +} + +#pragma mark - UIAccessibility overrides + +- (UIAccessibilityTraits)accessibilityTraits { + UIAccessibilityTraits traits = UIAccessibilityTraitNone; + if ([self node].HasAction(blink::SemanticsAction::kIncrease) || + [self node].HasAction(blink::SemanticsAction::kDecrease)) { + traits |= UIAccessibilityTraitAdjustable; + } + if ([self node].HasFlag(blink::SemanticsFlags::kIsSelected) || + [self node].HasFlag(blink::SemanticsFlags::kIsChecked)) { + traits |= UIAccessibilityTraitSelected; + } + if ([self node].HasFlag(blink::SemanticsFlags::kIsButton)) { + traits |= UIAccessibilityTraitButton; + } + if ([self node].HasFlag(blink::SemanticsFlags::kHasEnabledState) && + ![self node].HasFlag(blink::SemanticsFlags::kIsEnabled)) { + traits |= UIAccessibilityTraitNotEnabled; + } + return traits; +} + +@end + @implementation SemanticsObjectContainer { SemanticsObject* _semanticsObject; shell::AccessibilityBridge* _bridge; @@ -372,21 +395,26 @@ - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction { [accessibility_channel_.get() setMessageHandler:nil]; } -void AccessibilityBridge::UpdateSemantics(std::vector nodes) { +UIView* AccessibilityBridge::textInputView() { + return [platform_view_->text_input_plugin() textInputView]; +} + +void AccessibilityBridge::UpdateSemantics(blink::SemanticsNodeUpdates nodes) { // Children are received in paint order (inverse hit testing order). We need to bring them into // traversal order (top left to bottom right, with hit testing order as tie breaker). NSMutableSet* childOrdersToUpdate = [[[NSMutableSet alloc] init] autorelease]; BOOL layoutChanged = NO; - for (const blink::SemanticsNode& node : nodes) { - SemanticsObject* object = GetOrCreateObject(node.id); - layoutChanged = layoutChanged || [object willCauseLayoutChange:&node]; + for (const auto& entry : nodes) { + const blink::SemanticsNode& node = entry.second; + SemanticsObject* object = GetOrCreateObject(node.id, nodes); + layoutChanged = layoutChanged || [object nodeWillCauseLayoutChange:&node]; [object setSemanticsNode:&node]; const size_t childrenCount = node.children.size(); auto& children = *[object children]; children.resize(childrenCount); for (size_t i = 0; i < childrenCount; ++i) { - SemanticsObject* child = GetOrCreateObject(node.children[i]); + SemanticsObject* child = GetOrCreateObject(node.children[i], nodes); child.parent = object; // Reverting to get hit testing order (as tie breaker for sorting below). children[childrenCount - i - 1] = child; @@ -431,11 +459,45 @@ - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction { platform_view_->DispatchSemanticsAction(uid, action, args); } -SemanticsObject* AccessibilityBridge::GetOrCreateObject(int32_t uid) { +SemanticsObject* AccessibilityBridge::GetOrCreateObject(int32_t uid, + blink::SemanticsNodeUpdates& updates) { SemanticsObject* object = objects_.get()[@(uid)]; if (!object) { - object = [[[SemanticsObject alloc] initWithBridge:this uid:uid] autorelease]; + // New node case: simply create a new SemanticsObject. + blink::SemanticsNode node = updates[uid]; + if (node.HasFlag(blink::SemanticsFlags::kIsTextField)) { + // Text fields are backed by objects that implement UITextInput. + object = [[[TextInputSemanticsObject alloc] initWithBridge:this uid:uid] autorelease]; + } else { + object = [[[FlutterSemanticsObject alloc] initWithBridge:this uid:uid] autorelease]; + } + objects_.get()[@(uid)] = object; + } else { + // Existing node case + auto nodeEntry = updates.find(object.node.id); + if (nodeEntry != updates.end()) { + // There's an update for this node + blink::SemanticsNode node = nodeEntry->second; + BOOL isTextField = node.HasFlag(blink::SemanticsFlags::kIsTextField); + BOOL wasTextField = object.node.HasFlag(blink::SemanticsFlags::kIsTextField); + if (wasTextField != isTextField) { + // The node changed its type from text field to something else, or vice versa. In this + // case, we cannot reuse the existing SemanticsObject implementation. Instead, we replace + // it with a new instance. + auto positionInChildlist = + std::find(object.parent.children->begin(), object.parent.children->end(), object); + [objects_ removeObjectForKey:@(node.id)]; + if (isTextField) { + // Text fields are backed by objects that implement UITextInput. + object = [[[TextInputSemanticsObject alloc] initWithBridge:this uid:uid] autorelease]; + } else { + object = [[[FlutterSemanticsObject alloc] initWithBridge:this uid:uid] autorelease]; + } + *positionInChildlist = object; + objects_.get()[@(node.id)] = object; + } + } } return object; } diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h b/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h new file mode 100644 index 0000000000000..bcdebfc689deb --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h @@ -0,0 +1,36 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_ACCESSIBILITY_TEXT_ENTRY_H_ +#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_ACCESSIBILITY_TEXT_ENTRY_H_ + +/** + * An implementation of `UITextInput` used for text fields that do not currently + * have input focus. + * + * This class is used by `TextInputSemanticsObject`. + */ +@interface FlutterInactiveTextInput : UIView + +@property(nonatomic, copy) NSString* text; +@property(nonatomic, readonly) NSMutableString* markedText; +@property(readwrite, copy) UITextRange* selectedTextRange; +@property(nonatomic, strong) UITextRange* markedTextRange; +@property(nonatomic, copy) NSDictionary* markedTextStyle; +@property(nonatomic, assign) id inputDelegate; + +@end + +/** + * An implementation of `SemanticsObject` specialized for expressing text + * fields. + * + * Delegates to `FlutterTextInputView` when the object corresponds to a text + * field that currently owns input focus. Delegates to + * `FlutterInactiveTextInput` otherwise. + */ +@interface TextInputSemanticsObject : SemanticsObject +@end + +#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_ACCESSIBILITY_TEXT_ENTRY_H_ diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm b/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm new file mode 100644 index 0000000000000..6d786ed59d696 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm @@ -0,0 +1,409 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +#include "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h" +#include "flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h" + +@implementation FlutterInactiveTextInput { +} + +@synthesize tokenizer = _tokenizer; +@synthesize beginningOfDocument = _beginningOfDocument; +@synthesize endOfDocument = _endOfDocument; + +- (instancetype)init { + return [super init]; +} + +- (BOOL)hasText { + return self.text.length > 0; +} + +- (NSString*)textInRange:(UITextRange*)range { + NSRange textRange = ((FlutterTextRange*)range).range; + return [self.text substringWithRange:textRange]; +} + +- (NSString*)accessibilityLabel { + return self.text; +} + +- (void)replaceRange:(UITextRange*)range withText:(NSString*)text { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. +} + +- (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelectedRange { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. +} + +- (void)unmarkText { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. +} + +- (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition + toPosition:(UITextPosition*)toPosition { + NSUInteger fromIndex = ((FlutterTextPosition*)fromPosition).index; + NSUInteger toIndex = ((FlutterTextPosition*)toPosition).index; + return [FlutterTextRange rangeWithNSRange:NSMakeRange(fromIndex, toIndex - fromIndex)]; +} + +- (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return nil; +} + +- (UITextPosition*)positionFromPosition:(UITextPosition*)position + inDirection:(UITextLayoutDirection)direction + offset:(NSInteger)offset { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return nil; +} + +- (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return NSOrderedSame; +} + +- (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return 0; +} + +- (UITextPosition*)positionWithinRange:(UITextRange*)range + farthestInDirection:(UITextLayoutDirection)direction { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return nil; +} + +- (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position + inDirection:(UITextLayoutDirection)direction { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return nil; +} + +- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position + inDirection:(UITextStorageDirection)direction { + // Not editable. Does not apply. + return UITextWritingDirectionNatural; +} + +- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection + forRange:(UITextRange*)range { + // Not editable. Does not apply. +} + +- (CGRect)firstRectForRange:(UITextRange*)range { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return CGRectZero; +} + +- (CGRect)caretRectForPosition:(UITextPosition*)position { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return CGRectZero; +} + +- (UITextPosition*)closestPositionToPoint:(CGPoint)point { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return nil; +} + +- (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return nil; +} + +- (NSArray*)selectionRectsForRange:(UITextRange*)range { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return @[]; +} + +- (UITextRange*)characterRangeAtPoint:(CGPoint)point { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. + return nil; +} + +- (void)insertText:(NSString*)text { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. +} + +- (void)deleteBackward { + // This method is required but not called by accessibility API for + // features we are using it for. It may need to be implemented if + // requirements change. +} + +@end + +@implementation TextInputSemanticsObject { + FlutterInactiveTextInput* _inactive_text_input; +} + +- (instancetype)initWithBridge:(shell::AccessibilityBridge*)bridge uid:(int32_t)uid { + self = [super initWithBridge:bridge uid:uid]; + + if (self) { + _inactive_text_input = [[FlutterInactiveTextInput alloc] init]; + } + + return self; +} + +- (void)dealloc { + [_inactive_text_input release]; + [super dealloc]; +} + +#pragma mark - SemanticsObject overrides + +- (void)setSemanticsNode:(const blink::SemanticsNode*)node { + [super setSemanticsNode:node]; + _inactive_text_input.text = @(node->value.data()); + if ([self node].HasFlag(blink::SemanticsFlags::kIsFocused)) { + // The text input view must have a non-trivial size for the accessibility + // system to send text editing events. + [self bridge]->textInputView().frame = CGRectMake(0.0, 0.0, 1.0, 1.0); + } +} + +#pragma mark - UIAccessibility overrides + +/** + * The UITextInput whose accessibility properties we present to UIKit as + * substitutes for Flutter's text field properties. + * + * When the field is currently focused (i.e. it is being edited), we use + * the FlutterTextInputView used by FlutterTextInputPlugin. Otherwise, + * we use an FlutterInactiveTextInput. + */ +- (UIView*)textInputSurrogate { + if ([self node].HasFlag(blink::SemanticsFlags::kIsFocused)) { + return [self bridge]->textInputView(); + } else { + return _inactive_text_input; + } +} + +- (UIView*)textInputView { + return [self textInputSurrogate]; +} + +- (void)accessibilityElementDidBecomeFocused { + [[self textInputSurrogate] accessibilityElementDidBecomeFocused]; +} + +- (void)accessibilityElementDidLoseFocus { + [[self textInputSurrogate] accessibilityElementDidLoseFocus]; +} + +- (BOOL)accessibilityElementIsFocused { + return [self node].HasFlag(blink::SemanticsFlags::kIsFocused); +} + +- (BOOL)accessibilityActivate { + return [[self textInputSurrogate] accessibilityActivate]; +} + +- (NSString*)accessibilityLabel { + return [self textInputSurrogate].accessibilityLabel; +} + +- (NSString*)accessibilityHint { + return [self textInputSurrogate].accessibilityHint; +} + +- (NSString*)accessibilityValue { + return [self textInputSurrogate].accessibilityValue; +} + +- (UIAccessibilityTraits)accessibilityTraits { + // Adding UIAccessibilityTraitKeyboardKey to the trait list so that iOS treats it like + // a keyboard entry control, thus adding support for text editing features, such as + // pinch to select text, and up/down fling to move cursor. + return [self textInputSurrogate].accessibilityTraits | UIAccessibilityTraitKeyboardKey; +} + +#pragma mark - UITextInput overrides + +- (NSString*)textInRange:(UITextRange*)range { + return [[self textInputSurrogate] textInRange:range]; +} + +- (void)replaceRange:(UITextRange*)range withText:(NSString*)text { + return [[self textInputSurrogate] replaceRange:range withText:text]; +} + +- (BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)text { + return [[self textInputSurrogate] shouldChangeTextInRange:range replacementText:text]; +} + +- (UITextRange*)selectedTextRange { + return [[self textInputSurrogate] selectedTextRange]; +} + +- (void)setSelectedTextRange:(UITextRange*)range { + [[self textInputSurrogate] setSelectedTextRange:range]; +} + +- (UITextRange*)markedTextRange { + return [[self textInputSurrogate] markedTextRange]; +} + +- (NSDictionary*)markedTextStyle { + return [[self textInputSurrogate] markedTextStyle]; +} + +- (void)setMarkedTextStyle:(NSDictionary*)style { + [[self textInputSurrogate] setMarkedTextStyle:style]; +} + +- (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)selectedRange { + [[self textInputSurrogate] setMarkedText:markedText selectedRange:selectedRange]; +} + +- (void)unmarkText { + [[self textInputSurrogate] unmarkText]; +} + +- (UITextStorageDirection)selectionAffinity { + return [[self textInputSurrogate] selectionAffinity]; +} + +- (UITextPosition*)beginningOfDocument { + return [[self textInputSurrogate] beginningOfDocument]; +} + +- (UITextPosition*)endOfDocument { + return [[self textInputSurrogate] endOfDocument]; +} + +- (id)inputDelegate { + return [[self textInputSurrogate] inputDelegate]; +} + +- (void)setInputDelegate:(id)delegate { + [[self textInputSurrogate] setInputDelegate:delegate]; +} + +- (id)tokenizer { + return [[self textInputSurrogate] tokenizer]; +} + +- (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition + toPosition:(UITextPosition*)toPosition { + return [[self textInputSurrogate] textRangeFromPosition:fromPosition toPosition:toPosition]; +} + +- (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset { + return [[self textInputSurrogate] positionFromPosition:position offset:offset]; +} + +- (UITextPosition*)positionFromPosition:(UITextPosition*)position + inDirection:(UITextLayoutDirection)direction + offset:(NSInteger)offset { + return + [[self textInputSurrogate] positionFromPosition:position inDirection:direction offset:offset]; +} + +- (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other { + return [[self textInputSurrogate] comparePosition:position toPosition:other]; +} + +- (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition { + return [[self textInputSurrogate] offsetFromPosition:from toPosition:toPosition]; +} + +- (UITextPosition*)positionWithinRange:(UITextRange*)range + farthestInDirection:(UITextLayoutDirection)direction { + return [[self textInputSurrogate] positionWithinRange:range farthestInDirection:direction]; +} + +- (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position + inDirection:(UITextLayoutDirection)direction { + return + [[self textInputSurrogate] characterRangeByExtendingPosition:position inDirection:direction]; +} + +- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position + inDirection:(UITextStorageDirection)direction { + return [[self textInputSurrogate] baseWritingDirectionForPosition:position inDirection:direction]; +} + +- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection + forRange:(UITextRange*)range { + [[self textInputSurrogate] setBaseWritingDirection:writingDirection forRange:range]; +} + +- (CGRect)firstRectForRange:(UITextRange*)range { + return [[self textInputSurrogate] firstRectForRange:range]; +} + +- (CGRect)caretRectForPosition:(UITextPosition*)position { + return [[self textInputSurrogate] caretRectForPosition:position]; +} + +- (UITextPosition*)closestPositionToPoint:(CGPoint)point { + return [[self textInputSurrogate] closestPositionToPoint:point]; +} + +- (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range { + return [[self textInputSurrogate] closestPositionToPoint:point withinRange:range]; +} + +- (NSArray*)selectionRectsForRange:(UITextRange*)range { + return [[self textInputSurrogate] selectionRectsForRange:range]; +} + +- (UITextRange*)characterRangeAtPoint:(CGPoint)point { + return [[self textInputSurrogate] characterRangeAtPoint:point]; +} + +- (void)insertText:(NSString*)text { + [[self textInputSurrogate] insertText:text]; +} + +- (void)deleteBackward { + [[self textInputSurrogate] deleteBackward]; +} + +#pragma mark - UIKeyInput overrides + +- (BOOL)hasText { + return [[self textInputSurrogate] hasText]; +} + +@end diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index d2daade5b1057..dab19a0f9ce1a 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -10,6 +10,7 @@ #include "flutter/fml/memory/weak_ptr.h" #include "flutter/shell/common/platform_view.h" #include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterTexture.h" +#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #include "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h" #include "flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.h" #include "flutter/shell/platform/darwin/ios/ios_surface.h" @@ -53,7 +54,7 @@ class PlatformViewIOS : public PlatformView { void RegisterExternalTexture(int64_t id, NSObject* texture); - void UpdateSemantics(std::vector update) override; + void UpdateSemantics(blink::SemanticsNodeUpdates update) override; void RunFromSource(const std::string& assets_directory, const std::string& main, @@ -61,6 +62,23 @@ class PlatformViewIOS : public PlatformView { void SetAssetBundlePath(const std::string& assets_directory) override; + /** + * Exposes the `FlutterTextInputPlugin` singleton for the + * `AccessibilityBridge` to be able to interact with the text entry system. + */ + fml::scoped_nsprotocol text_input_plugin() { + return text_input_plugin_; + } + + /** + * Sets the `FlutterTextInputPlugin` singleton returned by + * `text_input_plugin`. + */ + void SetTextInputPlugin( + fml::scoped_nsprotocol textInputPlugin) { + text_input_plugin_ = textInputPlugin; + } + NSObject* binary_messenger() const { return binary_messenger_; } @@ -72,6 +90,7 @@ class PlatformViewIOS : public PlatformView { fxl::Closure firstFrameCallback_; fml::WeakPtrFactory weak_factory_; NSObject* binary_messenger_; + fml::scoped_nsprotocol text_input_plugin_; void SetupAndLoadFromSource(const std::string& assets_directory, const std::string& main, diff --git a/shell/platform/darwin/ios/platform_view_ios.mm b/shell/platform/darwin/ios/platform_view_ios.mm index d763d0715eb7d..19c5dd4e663a1 100644 --- a/shell/platform/darwin/ios/platform_view_ios.mm +++ b/shell/platform/darwin/ios/platform_view_ios.mm @@ -99,7 +99,7 @@ return ios_surface_ != nullptr ? ios_surface_->ResourceContextMakeCurrent() : false; } -void PlatformViewIOS::UpdateSemantics(std::vector update) { +void PlatformViewIOS::UpdateSemantics(blink::SemanticsNodeUpdates update) { if (accessibility_bridge_) accessibility_bridge_->UpdateSemantics(std::move(update)); } diff --git a/tools/licenses/pubspec.lock b/tools/licenses/pubspec.lock index 8730d46d07740..75d0f144fde64 100644 --- a/tools/licenses/pubspec.lock +++ b/tools/licenses/pubspec.lock @@ -2,60 +2,52 @@ # See http://pub.dartlang.org/doc/glossary.html#lockfile packages: archive: - dependency: "direct main" description: name: archive url: "https://pub.dartlang.org" source: hosted version: "1.0.33" args: - dependency: "direct main" description: name: args url: "https://pub.dartlang.org" source: hosted version: "0.13.7" charcode: - dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted version: "1.1.1" collection: - dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted version: "1.14.5" convert: - dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted version: "2.0.1" crypto: - dependency: "direct main" description: name: crypto url: "https://pub.dartlang.org" source: hosted version: "2.0.2+1" path: - dependency: "direct main" description: name: path url: "https://pub.dartlang.org" source: hosted version: "1.5.1" typed_data: - dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted version: "1.1.5" sdks: - dart: ">=1.21.0 <=2.0.0-dev.16.0" + dart: ">=1.21.0 <2.0.0" diff --git a/travis/licenses_golden/licenses_flutter b/travis/licenses_golden/licenses_flutter index b0113933aaa7b..87fe877ffbbc3 100644 --- a/travis/licenses_golden/licenses_flutter +++ b/travis/licenses_golden/licenses_flutter @@ -1957,6 +1957,42 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== +==================================================================================================== +LIBRARY: engine +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h + ../../../LICENSE +TYPE: LicenseType.bsd +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm +---------------------------------------------------------------------------------------------------- +Copyright 2018 The Chromium Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: engine ORIGIN: ../../../flutter/sky/engine/core/editing/CompositionUnderline.h @@ -9960,4 +9996,4 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== -Total license count: 215 +Total license count: 216