Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make scrollable unfocusable when voiceover is running #27118

Merged
merged 1 commit into from Jul 1, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -1228,7 +1228,8 @@ - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
platformView->SetSemanticsEnabled(true);
platformView->SetAccessibilityFeatures(flags);
#else
bool enabled = UIAccessibilityIsVoiceOverRunning() || UIAccessibilityIsSwitchControlRunning();
_isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
bool enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
if (enabled)
flags |= static_cast<int32_t>(flutter::AccessibilityFeatureFlag::kAccessibleNavigation);
platformView->SetSemanticsEnabled(enabled || UIAccessibilityIsSpeakScreenEnabled());
Expand Down
Expand Up @@ -26,6 +26,7 @@ extern NSNotificationName const FlutterViewControllerShowHomeIndicator;
@interface FlutterViewController ()

@property(nonatomic, readonly) BOOL isPresentingViewController;
@property(nonatomic, readonly) BOOL isVoiceOverRunning;
- (fml::WeakPtr<FlutterViewController>)getWeakPtr;
- (std::shared_ptr<flutter::FlutterPlatformViewsController>&)platformViewsController;
- (FlutterRestorationPlugin*)restorationPlugin;
Expand Down
18 changes: 12 additions & 6 deletions shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
Expand Up @@ -193,12 +193,6 @@ - (void)accessibilityBridgeDidFinishUpdate {
[self setFrame:[_semanticsObject accessibilityFrame]];
[self setContentSize:[self contentSizeInternal]];
[self setContentOffset:[self contentOffsetInternal] animated:NO];
if (self.contentSize.width > self.frame.size.width ||
self.contentSize.height > self.frame.size.height) {
self.isAccessibilityElement = YES;
} else {
self.isAccessibilityElement = NO;
}
}

- (void)setChildren:(NSArray<SemanticsObject*>*)children {
Expand All @@ -219,6 +213,18 @@ - (id)accessibilityContainer {
return _container.get();
}

- (BOOL)isAccessibilityElement {
if (![_semanticsObject isAccessibilityBridgeAlive]) {
return NO;
}
if (self.contentSize.width > self.frame.size.width ||
self.contentSize.height > self.frame.size.height) {
return !_semanticsObject.bridge->isVoiceOverRunning();
} else {
return NO;
}
}

// private methods

- (float)scrollExtentMax {
Expand Down
26 changes: 26 additions & 0 deletions shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm
Expand Up @@ -31,6 +31,7 @@
window_ = [[UIWindow alloc] initWithFrame:kScreenSize];
[window_ addSubview:view_];
}
bool isVoiceOverRunning() const override { return isVoiceOverRunningValue; }
UIView* view() const override { return view_; }
UIView<UITextInput>* textInputView() override { return nil; }
void DispatchSemanticsAction(int32_t id, SemanticsAction action) override {
Expand All @@ -49,6 +50,7 @@ void AccessibilityObjectDidLoseFocus(int32_t id) override {}
return nil;
}
std::vector<SemanticsActionObservation> observations;
bool isVoiceOverRunningValue;

private:
UIView* view_;
Expand Down Expand Up @@ -344,6 +346,30 @@ - (void)testFlutterScrollableSemanticsObjectIsNotHittestable {
XCTAssertEqual([scrollable hitTest:CGPointMake(10, 10) withEvent:nil], nil);
}

- (void)testFlutterScrollableSemanticsObjectIsHiddenWhenVoiceOverIsRunning {
flutter::MockAccessibilityBridge* mock = new flutter::MockAccessibilityBridge();
mock->isVoiceOverRunningValue = false;
fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(mock);
fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();

flutter::SemanticsNode node;
node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
node.actions = flutter::kHorizontalScrollSemanticsActions;
node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
node.scrollExtentMax = 100.0;
node.scrollPosition = 0.0;

FlutterSemanticsObject* delegate = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0];
FlutterScrollableSemanticsObject* scrollable =
[[FlutterScrollableSemanticsObject alloc] initWithSemanticsObject:delegate];
SemanticsObject* scrollable_object = static_cast<SemanticsObject*>(scrollable);
[scrollable_object setSemanticsNode:&node];
[scrollable_object accessibilityBridgeDidFinishUpdate];
XCTAssertTrue(scrollable_object.isAccessibilityElement);
mock->isVoiceOverRunningValue = true;
XCTAssertFalse(scrollable_object.isAccessibilityElement);
}

- (void)testSemanticsObjectBuildsAttributedString {
fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
new flutter::MockAccessibilityBridge());
Expand Down
Expand Up @@ -18,9 +18,9 @@
#include "flutter/lib/ui/semantics/custom_accessibility_action.h"
#include "flutter/lib/ui/semantics/semantics_node.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h"
#include "third_party/skia/include/core/SkRect.h"
Expand Down Expand Up @@ -68,6 +68,8 @@ class AccessibilityBridge final : public AccessibilityBridgeIos {

UIView* view() const override { return view_controller_.view; }

bool isVoiceOverRunning() const override { return view_controller_.isVoiceOverRunning; }

fml::WeakPtr<AccessibilityBridge> GetWeakPtr();

std::shared_ptr<FlutterPlatformViewsController> GetPlatformViewsController() const override {
Expand Down
Expand Up @@ -21,6 +21,7 @@ class AccessibilityBridgeIos {
public:
virtual ~AccessibilityBridgeIos() = default;
virtual UIView* view() const = 0;
virtual bool isVoiceOverRunning() const = 0;
virtual UIView<UITextInput>* textInputView() = 0;
virtual void DispatchSemanticsAction(int32_t id, flutter::SemanticsAction action) = 0;
virtual void DispatchSemanticsAction(int32_t id,
Expand Down
Expand Up @@ -224,6 +224,32 @@ - (void)testUpdateSemanticsOneNode {
OCMVerifyAll(mockFlutterView);
}

- (void)testIsVoiceOverRunning {
flutter::MockDelegate mock_delegate;
auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/thread_task_runner,
/*raster=*/thread_task_runner,
/*ui=*/thread_task_runner,
/*io=*/thread_task_runner);
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
/*platform_views_controller=*/nil,
/*task_runners=*/runners);
id mockFlutterView = OCMClassMock([FlutterView class]);
id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
OCMStub([mockFlutterViewController isVoiceOverRunning]).andReturn(YES);

__block auto bridge =
std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
/*platform_view=*/platform_view.get(),
/*platform_views_controller=*/nil);

XCTAssertTrue(bridge->isVoiceOverRunning());
}

- (void)testSemanticsDeallocated {
@autoreleasepool {
flutter::MockDelegate mock_delegate;
Expand Down