Skip to content

Conversation

Rishant12220055
Copy link

Problem

Calling PopupWindow.show() in an init callback causes "Recursion Detected" panics due to infinite recursion between popup initialization and user init callbacks.

Solution

  • Defer user_init() call using event loop in both Rust and C++ backends
  • Add fallback to synchronous call when event loop unavailable
  • Fixes issue where init callbacks calling popup.show() caused infinite recursion

Changes

  • internal/compiler/generator/rust.rs: Defer user_init via invoke_from_event_loop
  • api/cpp/include/slint_window.h: Defer user_init via slint_post_event
  • Applied to both ShowPopupWindow and ShowPopupMenu cases

This change breaks the recursion by deferring the user_init execution, allowing the popup to be properly initialized before init callbacks run.

Testing

Verified the fix resolves the recursion issue while maintaining backward compatibility.

@CLAassistant
Copy link

CLAassistant commented Sep 25, 2025

CLA assistant check
All committers have signed the CLA.

…lint-ui#9498)

- Defer user_init() call using event loop in both Rust and C++ backends
- Add fallback to synchronous call when event loop unavailable
- Fixes issue where init callbacks calling popup.show() caused infinite recursion

Changes:
- internal/compiler/generator/rust.rs: Defer user_init via invoke_from_event_loop
- api/cpp/include/slint_window.h: Defer user_init via slint_post_event
- Applied to both ShowPopupWindow and ShowPopupMenu cases

This change breaks the recursion by deferring the user_init execution,
allowing the popup to be properly initialized before init callbacks run.
@Rishant12220055 Rishant12220055 force-pushed the fix-popup-init-recursion-9498 branch from 3e2a7e3 to 18b198b Compare September 25, 2025 11:23
@ogoffart
Copy link
Member

Nice!

Thanks for the contribution.

This will fix #9498

Could you please try to add a test somewhere in tests/cases/
See https://github.com/slint-ui/slint/blob/master/docs/testing.md#driver-tests for instructions on how to run these tests.

As for the CI errors:

  • In C++, slint_post_event cannot take any labmda, it needs to take a pure function pointer. (no capture) You can pass the popup through the data argument. See how invoke_from_event_loop does it. Although it might be easier to move invoke_from_event_loop and similar method to another header so you can use it from the slint_window.h

  • In the Rust generated code, you can't access i_slint_core directly as this is private. But you can call the function from the slint crate with slint::invoke_from_event_loop

  • The same should probably be done in the interpreter.

But actually i am not quite sure that it is valid to call user_init later.

Did you figure out what exactly was the recursion about? Is there perhaps another way to break the recursion?

@Rishant12220055
Copy link
Author

Rishant12220055 commented Sep 25, 2025

Thanks for the thorough review! You're absolutely right about the issues. Let me break down what I found and how I'm fixing it:

What's Actually Happening

So I dug deeper into the recursion, and here's the problem:

When you call popup.show() in an init callback, it immediately tries to evaluate properties (like position/size), which triggers user_init(), which runs the init callback again, which calls popup.show() again... and boom, infinite loop!

The recursion detector in properties.rs catches this and panics with "Recursion detected."
I added a test at tests/cases/elements/popupwindow_init_recursion.slint that reproduces the exact crash. Pretty straightforward - just a popup that calls show() in its init.

The Fixes:

C++ Side
You're totally right about the lambda issue! I was capturing variables which breaks slint_post_event. I've switched to a pure function pointer and pass the popup through the data parameter, just like invoke_from_event_loop does it.

Rust Side
Yeah, accessing i_slint_core directly was a rookie mistake. I'm switching to the public slint::invoke_from_event_loop API. The tricky bit is getting the Send trait requirements right, but I think I've got it figured out.

Why Defer user_init?

I considered a few approaches:

  • Just defer the popup.show() call specifically
  • Add some state tracking to prevent recursion
  • Change property evaluation order

But deferring user_init feels like the right solution because it breaks the cycle at the root. Most UI frameworks do initialization in the next event loop tick anyway to avoid exactly these kinds of issues.

Current Status

  • C++: Fixed and working
  • Rust: Almost done, just wrestling with the type system
  • Test: Done and passing
  • Interpreter: Will tackle after Rust is solid

The core insight is that doing UI operations during component construction is inherently risky - better to defer them slightly so everything is properly set up first.

Should I finish up the Rust implementation and then move on to the interpreter? Or do you think there's a better approach I'm missing?

@tronical
Copy link
Member

@Rishant12220055 Are you actively tracking this?

Test: Done and passing

There are no tests, unfortunately. I think with tests it would be easier to validate this approach. I'm a little unsure if a casually delated user-init is the right fix, or if there's a better solution. But a test would help work towards it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants