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

Conversation

@dkwingsmt
Copy link
Contributor

@dkwingsmt dkwingsmt commented Oct 3, 2022

This PR changes the accessibility bridge and its delegate classes, so that they no longer assume that the embedding engine only has one view or one bridge, but instead receive their view and bridge through their constructors.

With this PR, all uses of FlutterWindowsEngine::accessibility_bridge() and FlutterEngine.accessibilityBridge in production code have been removed. They're now only referred in unit tests.

Why is this needed?

Part of the multiwindow project (design doc): In the multi-view world, FlutterEngine will not only have multiple view controllers, but also multiple accessiblity bridges, each corresponding to a view. This requires that we get rid of the GetView and GetBridge methods.

The decision of multiplying accessibility bridges is made because there's nothing in the bridge that keeps us from doing so, and the bridge works directly with the view it was given.

Design details

The hardest part of this change is a circular dependency between the bridge and the delegate. The bridge used to take the delegate in a constructor, while the delegate now also wants to refer to the bridge. To resolve this, either make the bridge take the delegate later, or make the delegate aware of the bridge later. I chose the first option, because the bridge already has an UpdateDelegate method.

I still kept AccessibilityBridge's one-argument constructor because many tests are still using it. Let me know if you'd like to all of the tests updated.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on
    writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

///
/// The bridge is not ready for use until a delegate has been
/// assigned. See AccessibilityBridge::UpdateDelegate.
AccessibilityBridge();
Copy link
Member

Choose a reason for hiding this comment

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

From my understanding, this trick is necessary due to a circular dependency between the accessibility bridge and the delegate. The drawback is that it's now possible for the accessibility bridge to be in an invalid state. Would it be possible to avoid this circular dependency to avoid this drawback?

For example, perhaps the accessibility bridge could provide its instance when it calls the delegate's methods?

Copy link
Contributor Author

@dkwingsmt dkwingsmt Oct 3, 2022

Choose a reason for hiding this comment

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

You're absolutely correct about the drawback.

There's another approach: The bridge is initialized with the viewId, fetches the view controller from the engine on demand, and passes this ID to the delegate and the nodes. However I'm really hoping to limit the knowledge of the view IDs, and instead let the bridge talk directly with assigned objects.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another approach is to let AccessibilityBridge to be able to host multiple AXTrees, and I think that is what chromium does, too. The AXTreeData can set the id for different AXTree, and you can also derive axtree id from a AXNode. You can let the FlutterEngine to create a wrapper delegate that wires up action and event to different ViewController and view.

Copy link
Contributor Author

@dkwingsmt dkwingsmt Oct 3, 2022

Choose a reason for hiding this comment

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

We can, but is it making a big difference? In the current approach, the engine will manage a map of view controllers and bridges. In your approach, the engine will manage a map of view controllers and one bridge, and the bridge will manage a map of delegates and trees (which is likely another class). So you're basically moving what's in the engine into the bridge singleton, which isn't much code (just map addition and map removal).

Copy link
Contributor Author

@dkwingsmt dkwingsmt Oct 4, 2022

Choose a reason for hiding this comment

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

Actually I'm wondering, why can't we merge the delegate and the bridge? We'll still use virtual methods to allow platform-specific code, and reassign views when the view is being swapped. The engine will hold a map of bridges, and there will be no circular reference.

Copy link
Contributor

Choose a reason for hiding this comment

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

My original thought about the multiple AXTree in one bridge vs Multiple bridges was on how easy to use the bridge in different platforms. I am not sure how the embedding code will be like, so I will leave the decision to you.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another approach is to let AccessibilityBridge to be able to host multiple AXTrees, and I think that is what chromium does, too.

We can, but is it making a big difference?

I think that doing the same thing that chromium does will help us in the short term. We will definitely encounter mistakes in the short term as we add this new functionality. The more help we can get, the better.

Copy link
Contributor Author

@dkwingsmt dkwingsmt Oct 4, 2022

Choose a reason for hiding this comment

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

For the circular dependency part, I think I kinda prefer the other way around, let the delegate aware of the bridge later. If one of them has to be an unusable state during initializing, let the delegate be unusable would probably be better. The bridge is a public class that will be shared between embedding, it is better for it to be more stable

My initial thought was also the 2nd option, but I gave up because it's still very hard to initialize the bridge. There are two ways:

If the outside code assign the bridge to the delegate manually, it'd be hard to get the delegate since it's been moved in, and has to use a dangerous raw pointer:

void productionCode() {
  auto delegate = new FlutterPlatformNodeDelegateMac();
  // The delegate will be moved away before it can be assigned a bridge.
  auto bridge = new AccessibilityBridge(std::unique_ptr<FlutterPlatformNodeDelegateMac>(delegate));
  delegate->SetBridge(bridge);
}

Or the bridge can also assign the delegate automatically, but then the bridge needs to access its own weak_ptr in constructor, which somehow crashes (it seems that there's some restriction with shared_from_this and weak_from_this, which I'm not sure.)

AccessibilityBridge::AccessibilityBridge(std::unique_ptr<AccessibilityBridgeDelegate> delegate) {
  delegate_ = std::move(delegate);
  delegate_->SetBridge(weak_from_this()); // crashes
}

Copy link
Contributor Author

@dkwingsmt dkwingsmt Oct 4, 2022

Choose a reason for hiding this comment

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

I think that doing the same thing that chromium does will help us in the short term. We will definitely encounter mistakes in the short term as we add this new functionality. The more help we can get, the better.

What kind of help and from whom are we expecting? The code is going to evolve and who other than the Flutter contributors will read our code?

I think the best kind of help is that we choose the best structure that suits us with the best maintainability and cleanest flow, instead of trying to fit our feet in others' shoes.

Copy link
Contributor

@chunhtai chunhtai Oct 4, 2022

Choose a reason for hiding this comment

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

Or the bridge can also assign the delegate automatically, but then the bridge needs to access its own weak_ptr in constructor, which somehow crashes (it seems that there's some restriction with shared_from_this and weak_from_this, which I'm not sure.)

shared_from_this and weak_from_this requires the AccessibilityBridge to be created with shared_pointer. It should not crash though since the flutterengine in macos already holds it as shared_ptr, and it has already used shared_from_this in UpdateDelegate. Anyway, I don't think this is the right way though, not every platform's delegate needs the reference to bridge.

If the outside code assign the bridge to the delegate manually, it'd be hard to get the delegate since it's been moved in, and has to use a dangerous raw pointer

I am not sure what moved away means, can you use shared_ptr + weak_ptr? the unqiue ptr doesn't really make sense in this ownership model if multiple objects want references from it. it would also be good to draw out the ownership model, right now there seems to be a lot of moving parts, engine, bridge, delegate, viewcontroller.

@dkwingsmt
Copy link
Contributor Author

An alternative approach has been posted in #36597.

@dkwingsmt
Copy link
Contributor Author

Closing this for now unless #36597 is denied.

@dkwingsmt dkwingsmt closed this Oct 21, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants