Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Conversation

@xster
Copy link
Member

@xster xster commented Apr 18, 2020

Fixes flutter/flutter#53065

I hope #17493 had good tests since I had to merge :)

The nature of the bug is that despite vague documentation, accessibilityElementsHidden on UIView doesn't seem to also hide its subviews from accessibility. When we first focus on a text field from the Flutter side, we create and add a FlutterTextInputView onto the view hierarchy above everything else. We intended to hide it but it never worked and since the accessibility bridge also sends various UIAccessibilityPostNotification, the new, higher, invisible FlutterTextInputView takes the focus.

I really wanted to have an integration test for this but unfortunately concluded that we can't have an exact test for this because:

  1. VoiceOver (or any assistive technologies with accessibility focus) isn't implemented on the simulator.
  2. Xcode has an accessibility debugger but unfortunately there are no APIs. The undocumented UIAccessibilityPostNotification(UIAccessibilityResumeAssistiveTechnologyNotification, UIAccessibilityNotificationVoiceOverIdentifier) crashes on simulator and UIAccessibilityNotificationSwitchControlIdentifier is input only (doesn't have focus highlighting).
  3. The accessibility debugger also doesn't work the same way as the VoiceOver. The original issue doesn't actually reproduce using accessibility focus via the simulator accessibility debugger.

I added some integration tests for the general accessibility bridge semantics node translation and focusing functionalities, but it doesn't test this issue specifically.

@xster xster requested review from gaaclarke and goderbauer April 18, 2020 07:16
@auto-assign auto-assign bot requested a review from GaryQian April 18, 2020 07:16
@xster xster removed the request for review from GaryQian April 18, 2020 07:16
_nonAutofillInputView = [[FlutterTextInputView alloc] init];
_nonAutofillInputView.secureTextEntry = NO;
_nonAutofillInputView = [[FlutterTextInputView alloc] init];
_nonAutofillSecureInputView = [[FlutterTextInputView alloc] init];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be a bug

Copy link
Member

@gaaclarke gaaclarke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks fine. Did you see it's breaking a unit test? Man, these scenario tests are a mess. There is so many moving pieces it is going to be a nightmare to maintain this if it breaks since the linkage in the code isn't apparent. I wonder if there is some better way we can start organizing them so the dart code and the platform code are in the same folder so it's a bit more clear.


if (![_activeView isDescendantOfView:_inputHider]) {
[_inputHider addSubview:_activeView];
if (!_activeView.window) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (_activeView.window != keyWindow) would be a bit safer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah right, thanks

flutterViewController = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
return [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you throw a FLUTTER_ASSERT_ARC or FLUTTER_ASSERT_NOT_ARC at the top of this file?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none of the files in testing/ has it. Do you think it's something we'll run into?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's hard to review files if you don't know if they are ARC or not. You need to import FlutterMacros.h. The line I've highlighted is correct for ARC, a leak for not ARC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just meant whether we've had cases where we built the engine locally or on LUCI without ARC in the past? I would imagine everything failing spectacularly everywhere if we did rather than having to gate on a file-per-file basis. But I don't really have much context, I'm just guessing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARC can be turned on and off on a file-by-file basis. The Engine is built without ARC, this file is part of a whole different binary though and looks as if it is being compiled with ARC, which is fine. In a world where some objective-c code is ARC and some is not; it's hard to do code reviews. The macro is helpful until that magical day that will never come where all of our code is ARC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. Thanks for the context. Added

XCUIElement* textInputSemanticsObject =
[[[self.application textFields] matchingIdentifier:@"flutter textfield"] element];
XCTAssertTrue([textInputSemanticsObject waitForExistenceWithTimeout:5]);
XCTAssertEqual([textInputSemanticsObject valueForKey:@"hasKeyboardFocus"], @(NO));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XCTAssertFalse, you are doing pointer comparisons on objects.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI alternatively you could use, xctassertequalobjects.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @NO/__objc_no is a singleton and is a true-y (!= nil) value 🤣
But XCTAssertEqualObjects does show a nicer error when it doesn't match. Changed.

textInputSemanticsObject =
[[[self.application textFields] matchingIdentifier:@"focused flutter textfield"] element];
XCTAssertTrue([textInputSemanticsObject waitForExistenceWithTimeout:5]);
XCTAssertEqual([textInputSemanticsObject valueForKey:@"hasKeyboardFocus"], @(YES));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XCTAssertTrue

// real text entry events to the framework.
delegateTextInput = [[self.application textViews] element];
XCTAssertTrue([delegateTextInput waitForExistenceWithTimeout:5]);
XCTAssertEqual([delegateTextInput valueForKey:@"hasKeyboardFocus"], @(YES));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XCTAssertTrue

).buffer.asByteData(),
callback,
);
} No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no newline

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


/// Util method to replicate the behavior of a `MethodChannel` in the Flutter
/// framework.
void sendMethodCall({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the json codec typically not the standard message codec? maybe the name should reflect json or take in an optional argument of the codec?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh ya good call. Only our system channels are being weird with json codecs. Good point. Name changed.

currentValueLength: 0,
scrollChildren: 0,
scrollIndex: 0,
transform: Float64List.fromList(<double>[1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 366.5, 0.0, 1.0]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't there a function somewhere where you can make a translation matrix? For readabilities sake you should at least have a local one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just copy pasted it from a real run but it doesn't actually matter for the purpose of this test. Removed.

@xster
Copy link
Member Author

xster commented Apr 21, 2020

oops, I missed a lint.

ya I agree, it's a pile on right now. There were so many more things I wanted to do as I was looking at it :)

I want to clean up a bit more next

@xster xster force-pushed the fix-accessibility-focus-loss branch from 34688d7 to 636285d Compare April 22, 2020 07:11
// Find the inactive autofillable input field.
// Find all the FlutterTextInputViews we created.
NSArray<FlutterTextInputView*>* inputFields = [[[[textInputPlugin textInputView] superview]
subviews]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't believe this is what the autoformatter wants...

Copy link
Member

@gaaclarke gaaclarke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm modulo the presubmit failures.

gaaclarke added a commit to flutter/flutter that referenced this pull request Apr 27, 2020
flutter/engine@2b94311...4f888d6

git log 2b94311..4f888d6 --first-parent --oneline
2020-04-25 [email protected] Change the repo fetch script used in integration tests (flutter/engine#17943)
2020-04-24 [email protected] Roll src/third_party/skia 1e21d14f2b8b..c12aad9485a9 (20 commits) (flutter/engine#17942)
2020-04-24 [email protected] Roll src/third_party/dart a69cb6d700f5..216e3df4526c (16 commits) (flutter/engine#17945)
2020-04-24 [email protected] Fix accessibility focus loss when first focusing on text field (flutter/engine#17803)
2020-04-24 [email protected] Roll fuchsia/sdk/core/linux-amd64 from _dAFU... to G4HpJ... (flutter/engine#17938)
2020-04-24 [email protected] [web] Fix exception when getting boxes for rich text range (flutter/engine#17933)
2020-04-24 [email protected] [web] Batch systemFontChange messages (flutter/engine#17885)
2020-04-24 [email protected] Roll fuchsia/sdk/core/linux-amd64 from kpECk... to _dAFU... (flutter/engine#17929)
2020-04-24 [email protected] Roll src/third_party/skia b965ff597315..1e21d14f2b8b (25 commits) (flutter/engine#17928)
2020-04-24 [email protected] Roll src/third_party/dart 94178e920ee8..a69cb6d700f5 (22 commits) (flutter/engine#17926)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to shift focus from TextField, stuck in editing mode

3 participants