Skip to content

Conversation

@robintown
Copy link
Member

@robintown robintown commented Nov 25, 2025

Depends on #31354

We allow widgets to remain "always on screen" by moving between a PiP view and an AppTile view as you switch rooms. But apparently the lifecycle of a StopGapWidget object was such that it would be disposed of and reconstructed every time the AppTile component was remounted, even while presenting the very same widget. This meant that whenever you switched rooms, causing a widget to move into a PiP, the client would forget some of the widget's state (like which iframe it was associated with, and whether the postmessage API had been started).

As far as I can tell, EW's widget code has always been brittle in this way, but the forgotten state didn't cause any visible impact until the upgrade to React 19 and some related fixes that I made. After those changes, switching rooms would cause the client to forget state that would prevent it from stopping widgets properly, causing issues for when you later try to launch the same widget again.

I believe the right way to fix this situation is to make StopGapWidget persist between React components, just as we persist the iframe element. In other words, put it in a store. But it felt really redundant to create a StopGapWidgetStore on top of all the other widget stores that we have knocking around…

So, the proposed solution: Rename the StopGapWidget class to WidgetMessaging, and store these objects in the WidgetMessagingStore. (Today that store holds ClientWidgetApi objects, but if WidgetMessaging objects are persistent, then they can just hold their own ClientWidgetApi without any help.) I used this renaming opportunity to also give the driver class a more proper name and add some much-needed doc comments to things.

Closes #30838
Closes #31065

@robintown
Copy link
Member Author

robintown commented Nov 25, 2025

This is failing the test coverage check at the moment because SonarCloud thinks the renamed files are entirely new code. This probably could benefit from a new test or two anyway targeting the actual regression, but I'm out of time until next week and would like this to at least gather review feedback in the meantime, please :)

Edit: regression test added in #31354

Copy link
Member

@BillCarsonFr BillCarsonFr left a comment

Choose a reason for hiding this comment

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

Great work catching this!
I find the renaming great, and thanks for documenting existing code 🙏
I just have a few comments.

// we register listeners for both cases. Note that due to React strict
// mode, the messaging could even be aborted and replaced by an entirely
// new messaging while we are waiting here!
while (!messaging?.widgetApi) {
Copy link
Member

Choose a reason for hiding this comment

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

I am a bit lost, if messaging is undefined this is then a while(true) what is breaking the loop?

Copy link
Member

Choose a reason for hiding this comment

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

...nothing, I think this needs a different check.

Copy link
Member Author

Choose a reason for hiding this comment

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

There were two things that could break the loop in this case:

  1. A new messaging object is emitted by the store. The onStoreMessaging callback runs the messaging = m; statement which would cause the next iteration of the loop to check the newly stored messaging object.
  2. The timeout is hit. This rejects the promise and causes the whole Call.start method to throw.

By breaking it up into two loops this method became vulnerable to races seen in React strict mode, which in my testing today were causing the app to not know that the call was ever started & therefore also not be able to close on hangup. So I added a test demonstrating the race in 12bda78 and fixed it in 20fc397 by restoring the single loop approach (with more exhaustive comments this time). Sound good?

Copy link
Contributor

@toger5 toger5 left a comment

Choose a reason for hiding this comment

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

Thanks Robin!

* set of callbacks.
*/
// TODO: Consider alternative designs for matrix-widget-api?
// Replace with matrix-rust-sdk?
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add another comment that we would need to add thread support to the rust sdk for this but it would highly reduce maintenance burden.

Copy link
Member

Choose a reason for hiding this comment

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

Imo, we should probably be putting these thoughts into an issue rather than in here. Code comments don't really allow for discussion and just rot :/

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I just wanted to leave something small here in the code as a reminder though, and to replace the previously less helpful "TODO: Destroy all this code".

I don't see how threads are related.

}

/**
* A running instance of a widget, associated with an iframe and a messaging transport.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this get a bit more verbose. Especially the realtion between the widgetDriver and the WidgetMessaging is sth that might be hard to grasp for ppl trying to learn this code?

I feel like a complete list of the resposibilites of this class would be very nice.

  • generate url (via template)
  • allocate driver (delegate class required for the matrix-widget-api class to do cs api and client navigation tasks. (for widget-> element client communication)
  • feed information into the widget (manually do all element client -> widget communication) (updateTheme,setViewedRoomId, ...)

^ this summary also documents that the two classes WidgetDriver WidgetMessaging are doing very similar things just split by direction. It makes it easier to reason about unifying this seperation.

It would make a lot of sense, that the driver is a bidirectional interface (containing functions that do things on behave of the widget AND set up listeners on the client and call functions on the widget api (or emit) on specific actions.)

Copy link
Member Author

Choose a reason for hiding this comment

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

8127687 Better?

If there's still anything you feel could be added, feel free to commit it.

@CLAassistant
Copy link

CLAassistant commented Dec 1, 2025

CLA assistant check
All committers have signed the CLA.

@robintown robintown assigned robintown and unassigned Half-Shot Dec 1, 2025
@robintown
Copy link
Member Author

FYI I rebased your commits @Half-Shot to avoid there being duplicate rebased develop commits on the branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rejoining a minimised Jitsi call doesn't work Widget fails to load when rejoining call after switching rooms

5 participants