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

Conversation

@jonahwilliams
Copy link
Contributor

@jonahwilliams jonahwilliams commented Mar 29, 2018

Engine support for flutter/flutter#13704, still requires another PR which adds support to the semantics object.

FIXED: Currently the iOS version loses device connection on the simulator during some transitions

@jonahwilliams
Copy link
Contributor Author

CC @yjbanov and @cbracken for expert level objective c++ readability.

} else {
NSString* latestRoute = routes[[routes count] - 1];
if (![latestRoute isEqualToString:_previous_route_]) {
_previous_route_ = latestRoute;
Copy link
Contributor

Choose a reason for hiding this comment

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

@cbracken to the rescue! I think here you should explicitly retain the object as you are assigning to a C++ class field, which won't do any ref counting for you. Similarly, when you nil it out, you should explicitly release it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This fixed the crash!

([self node].actions & ~blink::kScrollableSemanticsActions) != 0;
}

- (void)latestRoutes:(NSMutableArray*)routes {
Copy link
Contributor

Choose a reason for hiding this comment

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

The name of this method does not convey the fact that it populates the argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed to collectRoutes

[accessibility_channel_.get() setMessageHandler:^(id message, FlutterReply reply) {
HandleEvent((NSDictionary*)message);
}];
_previous_route_ = nil;
Copy link
Contributor

Choose a reason for hiding this comment

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

Release before setting to nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is actually just wrong, moved to destructor

[root collectRoutes:routes];
if ([routes count] == 0) {
NSString* oldRoute = _previous_route_;
[oldRoute release];
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the two lines above are the same as [_previous_route_ release];.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

fml::scoped_nsobject<NSMutableDictionary<NSNumber*, SemanticsObject*>> objects_;
fml::scoped_nsprotocol<FlutterBasicMessageChannel*> accessibility_channel_;
fml::WeakPtrFactory<AccessibilityBridge> weak_factory_;
NSString* _previous_route_;
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe we only use the postfix _

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

[accessibility_channel_.get() setMessageHandler:^(id message, FlutterReply reply) {
HandleEvent((NSDictionary*)message);
}];
_previous_route_ = nil;
Copy link
Contributor

Choose a reason for hiding this comment

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

You should probably still initialize it to nil so it does not contain garbage (C++ is this way), but for better readability, do so in the initializer list.


AccessibilityBridge::~AccessibilityBridge() {
view_.accessibilityElements = nil;
[previous_route_ release];
Copy link
Contributor

Choose a reason for hiding this comment

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

If you just release and not nil it out, it will continue holding onto a dangling pointer (or worse, a live pointer with a random lifecycle).

NSString* oldRoute = _previous_route_;
[oldRoute release];
_previous_route_ = nil;
[previous_route_ release];
Copy link
Contributor

Choose a reason for hiding this comment

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

And here. Setting to nil is likely still necessary.

assert globalRect != null;

if (routeName != null) {
routeNames.add(routeName);
Copy link
Contributor

Choose a reason for hiding this comment

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

since you're only reading the last node in the list, you only need to remember the last string you've seen, not all of them. so you should be able to do away with the list entirely.

ditto on the iOS side

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because of my original lack of retain and release the list version lasted slightly longer before crashing on the iOS side - I'll clean that up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@jonahwilliams jonahwilliams changed the title (WIP) AccessibilityBridge support for edge triggered semantics (iOS + Android) AccessibilityBridge support for edge triggered semantics (iOS + Android) Mar 29, 2018
///
/// This is used by certain widgets like Drawers and Dialogs, to indicate
/// that the node's semantic value can be used for an edge triggered
/// semantic update.
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe mention that changing the value is meaningless

Copy link
Member

Choose a reason for hiding this comment

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

Maybe slightly more specific: ... changing the value of the active edge is meaningless....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

static const int _kIsInMutuallyExclusiveGroupIndex = 1 << 8;
static const int _kIsHeaderIndex = 1 << 9;
static const int _kIsObscuredIndex = 1 << 10;
static const int _kIsEdgeIndex = 1 << 11;
Copy link
Member

Choose a reason for hiding this comment

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

Why not isRoute and isRouteName? Makes it clearer that the two belong together and I have an immediate idea what they mean. I don't think we use the term "edge" anywhere else in the framework with this meaning?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

///
/// This is used by certain widgets like Drawers and Dialogs, to indicate
/// that the node's semantic value can be used for an edge triggered
/// semantic update.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe slightly more specific: ... changing the value of the active edge is meaningless....

}

private void createWindowChangeEvent(SemanticsObject route) {
// TYPE_WINDOW_STATE_CHANGED events should always be sent from the root node.
Copy link
Member

Choose a reason for hiding this comment

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

Looks like this comment is out of date? You are using the specific node id now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

boolean isFocusable() {
int scrollableActions = Action.SCROLL_RIGHT.value | Action.SCROLL_LEFT.value
| Action.SCROLL_UP.value | Action.SCROLL_DOWN.value;
if (hasFlag(Flag.IS_EDGE) && value == null) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we enforce on the framework side that no other relevant information get merged into the IS_EDGE node?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not yet, but I can make sure the framework created semantics nodes have explicitChildNodes set

String getRouteName() {
String name = null;
if (children != null) {
for (int i = children.size() - 1; i >= 0; --i) {
Copy link
Member

Choose a reason for hiding this comment

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

Remind me why we want the last name and not the first one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do want the first one if it is non null & non-empty, cleaned up and added comment

String name = null;
if (children != null) {
for (int i = children.size() - 1; i >= 0; --i) {
String newName = children.get(i).getRouteName();
Copy link
Member

Choose a reason for hiding this comment

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

Should we stop the recursion if we find another route/edge nested in the current one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I've cleaned up these traversal algorithms to not be ... backwards

}
}
}
if ([self node].HasFlag(blink::SemanticsFlags::kIsRoute)) {
Copy link
Member

Choose a reason for hiding this comment

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

Check this first before doing the loop above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

@jonahwilliams
Copy link
Contributor Author

Updated the android accessibility bridge to use label + value + hint instead of just value.

///
/// When a semantics node with a node flag is added to the tree, the
/// framework will search for the first child in inverse hit-test order with
/// a routeName flag and non-empty semantic value and announce it as an edge
Copy link
Member

Choose a reason for hiding this comment

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

[isRouteName] instead of routeName?

/// Whether the semantics node value is the name of a visually distinct route.
///
/// This is used by certain widgets like Drawers and Dialogs, to indicate
/// that the node's semantic value can be used for an edge triggered
Copy link
Member

Choose a reason for hiding this comment

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

.. can be used to announce an edge triggered ...

rootObject.collectRoutes(newRoutes);
}

// Dispatch a TYPE_WINDOW_STATE_CHANGED event if the most recent route id changed from the
Copy link
Member

Choose a reason for hiding this comment

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

This comment ends pretty abruptly :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

// Returns the first non-null and non-empty semantic value of a child
// with an isRouteName flag. Otherwise returns null.
if (hasFlag(Flag.IS_ROUTE_NAME)) {
String routeName = getValueLabelHint();
Copy link
Member

Choose a reason for hiding this comment

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

On iOS we are just using the accessibilityValue, why use all three on Android?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See below - we should probably just use label for both it sounds like

/// Whether the semantics node value is the name of a visually distinct route.
///
/// This is used by certain widgets like Drawers and Dialogs, to indicate
/// that the node's semantic value can be used for an edge triggered
Copy link
Member

Choose a reason for hiding this comment

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

And is it value or value label hint?

Also, should it be label instead of value? I think the app bar title is transfered as a label, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The app bar title is a label and not a value - I decided to change it since I noticed that android accessibility bridge sometimes combined the three?

I'm not 100% on the semantic differences between label and value. If label is a better fit then I can do a quick update

Copy link
Member

Choose a reason for hiding this comment

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

I believe label is the more correct one.

Label is meant for static things (like the title of an app bar). Value is for dynamic, often changeable things like the content of a text fields.

Android doesn't have a way to separate between the two, that's why we often munch them together. However, in this case it sounds like only label is the right choice for both platforms.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it, that makes more sense given that we don't make additional announcements if the route name changes too

@jonahwilliams
Copy link
Contributor Author

I've updated the PR to explicitly claim depth-first paint order - this is only correct on Android. There are two PRs pending by @yjbanov to remove the iOS specific geometric order and to change the child order. This could be submitted first and then updated as a part of the future work - or wait til afterwards and update.

I'm not sure how much of a difference it will make, since in most cases there will be only one scopesRoute/namesRoute per level.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

LGTM

/// order for the first node with a [namesRoute] flag and a non-null,
/// non-empty label. The [namesRoute] and [scopesRoute] flags may be on the
/// same node. The label of the found node will be announced as an edge
/// transition. if no non-empty, non-null label is found then:
Copy link
Member

Choose a reason for hiding this comment

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

Capitalize "if"?

/// is a password or contains other sensitive information.
static const SemanticsFlag isObscured = const SemanticsFlag._(_kIsObscuredIndex);

/// Whether the semantics node is the root of a subtree for which values
Copy link
Member

Choose a reason for hiding this comment

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

instead of values say " a route name"? Since we actually announce the label?

/// semantics update.
///
/// Updating this label within the same active route subtree will not cause
/// additional semantic label updates.
Copy link
Member

Choose a reason for hiding this comment

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

not cause additional announcements? The label itself does get updated, right?

boolean isFocusable() {
int scrollableActions = Action.SCROLL_RIGHT.value | Action.SCROLL_LEFT.value
| Action.SCROLL_UP.value | Action.SCROLL_DOWN.value;
if (hasFlag(Flag.SCOPES_ROUTE)) {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add a comment here that we enforce in the framework that no other useful semantics are added to this node?

Also, maybe makes this the first line of the method? The variable scrollableActions above this kinda belongs to the statement below this.

///
/// This is used in widgets such as Routes, Drawers, and Dialogs to
/// communicate significant changes in the visible screen.
static const SemanticsFlag scopesRoute = const SemanticsFlag._(_kScopesRouteIndex);
Copy link
Member

Choose a reason for hiding this comment

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

Should this document that nodes annotated with this flag generally are not a11y focusable?

///
/// Updating this label within the same active route subtree will not cause
/// additional semantic label updates.
static const SemanticsFlag namesRoute = const SemanticsFlag._(_kNamesRouteIndex);
Copy link
Member

Choose a reason for hiding this comment

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

Should this document that nodes with this flag are still going to receive a11y focus?

@jonahwilliams
Copy link
Contributor Author

Holding off on submitting until the engine culprit is found, per @tvolkert

}
NSMutableArray<SemanticsObject*>* newRoutes = [[[NSMutableArray alloc] init] autorelease];
[root collectRoutes:newRoutes];
for (SemanticsObject* route in newRoutes) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hi @jonahwilliams, do you remember why we check any new route in the previous_routes_ here? This will make the it to not announce route change when any new route is in the previous routes

for example [Route1] -> [Route1, Route2] will not announce any route.

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.

6 participants