Skip to content

Conversation

@micieslak
Copy link
Member

@micieslak micieslak commented Nov 5, 2025

What does the PR do

  • removes StatusWindow as none of the custom functionality defined there is used
  • replaces it with regular Window
  • adapts to https://bugreports.qt.io/browse/QTBUG-141829, all asynchronous: true for Loaders has been removed (see the comment below)

This is potentially helpful to simplify SafeArea handling as custom window is problematic in some cases, e.g. https://bugreports.qt.io/browse/QTBUG-141733

Important

This PR should be tested on Windows to verify performance. Last attempt to remove StatusWindow was abandoned because surprisingly bad performance on Windows (but then ApplicationWindow was used)

Closes: #19218

Affected areas

main.qml

Architecture compliance

@micieslak micieslak requested review from a team, alexjba, caybro and noeliaSD as code owners November 5, 2025 15:50
@status-im-auto
Copy link
Member

status-im-auto commented Nov 5, 2025

Jenkins Builds

Click to see older builds (57)
Commit #️⃣ Finished (UTC) Duration Platform Result
✔️ 40f47d7 #1 2025-11-05 15:59:01 ~8 min tests/nim 📄log
✔️ 40f47d7 #1 2025-11-05 15:59:44 ~9 min android/arm64 🤖apk 📲
40f47d7 #1 2025-11-05 16:02:02 ~11 min ios/aarch64 📄log
✔️ 40f47d7 #1 2025-11-05 16:03:01 ~12 min macos/aarch64 🍎dmg
40f47d7 #1 2025-11-05 16:03:24 ~12 min macos/aarch64-nwaku 📄log
✔️ 40f47d7 #1 2025-11-05 16:05:26 ~14 min tests/ui 📄log
✔️ 40f47d7 #1 2025-11-05 16:07:52 ~17 min linux/x86_64 📦tgz
✔️ 40f47d7 #1 2025-11-05 16:12:15 ~21 min linux/x86_64-nwaku 📦tgz
✔️ 40f47d7 #1 2025-11-05 16:17:16 ~26 min windows/x86_64 💿exe
✖️ 40f47d7 PR19228 2025-11-05 16:31:05 ~13 min tests/e2e-windows 📊rpt
✖️ 40f47d7 pr19228 2025-11-05 16:31:14 ~23 min tests/e2e 📊rpt
05b64dc #2 2025-11-06 09:04:41 ~9 min ios/aarch64 📄log
✔️ 05b64dc #2 2025-11-06 09:04:57 ~9 min android/arm64 🤖apk 📲
✔️ 05b64dc #2 2025-11-06 09:10:22 ~14 min tests/nim 📄log
✔️ 05b64dc #2 2025-11-06 09:10:53 ~15 min macos/aarch64 🍎dmg
✔️ 05b64dc #2 2025-11-06 09:12:43 ~16 min macos/aarch64-nwaku 🍎dmg
✔️ 05b64dc #2 2025-11-06 09:16:56 ~21 min windows/x86_64 💿exe
✔️ 05b64dc #2 2025-11-06 09:18:28 ~22 min tests/ui 📄log
✔️ 05b64dc #2 2025-11-06 09:19:31 ~23 min linux/x86_64 📦tgz
✔️ 05b64dc #2 2025-11-06 09:21:44 ~25 min linux/x86_64-nwaku 📦tgz
✔️ 05b64dc PR19228 2025-11-06 09:32:05 ~15 min tests/e2e-windows 📊rpt
✖️ 05b64dc pr19228 2025-11-06 09:39:05 ~19 min tests/e2e 📊rpt
ccfeb2f #3 2025-11-06 10:31:34 ~7 min macos/aarch64-nwaku 📄log
✔️ ccfeb2f #3 2025-11-06 10:31:59 ~7 min tests/nim 📄log
ccfeb2f #3 2025-11-06 10:32:15 ~8 min ios/aarch64 📄log
✔️ ccfeb2f #3 2025-11-06 10:37:21 ~13 min macos/aarch64 🍎dmg
✔️ ccfeb2f #3 2025-11-06 10:37:55 ~13 min tests/ui 📄log
✔️ ccfeb2f #3 2025-11-06 10:41:19 ~17 min linux/x86_64 📦tgz
✔️ ccfeb2f #3 2025-11-06 10:43:51 ~19 min windows/x86_64 💿exe
✔️ ccfeb2f #3 2025-11-06 10:46:34 ~22 min linux/x86_64-nwaku 📦tgz
✖️ ccfeb2f pr19228 2025-11-06 10:56:38 ~15 min tests/e2e 📊rpt
✖️ ccfeb2f PR19228 2025-11-06 10:59:00 ~15 min tests/e2e-windows 📊rpt
✔️ 4c35542c #3 2025-11-06 10:34:04 ~10 min android/arm64 🤖apk 📲
4632223 #4 2025-11-06 14:40:15 ~5 min macos/aarch64-nwaku 📄log
✔️ 4632223 #4 2025-11-06 14:40:57 ~6 min tests/nim 📄log
✔️ 4632223 #4 2025-11-06 14:47:16 ~12 min macos/aarch64 🍎dmg
✔️ 4632223 #4 2025-11-06 14:47:49 ~13 min ios/aarch64 📱ipa
✔️ 4632223 #4 2025-11-06 14:48:11 ~13 min tests/ui 📄log
✔️ 4632223 #4 2025-11-06 14:50:58 ~16 min linux/x86_64-nwaku 📦tgz
✔️ 4632223 #4 2025-11-06 14:51:43 ~17 min linux/x86_64 📦tgz
✔️ 4632223 #4 2025-11-06 15:00:13 ~25 min windows/x86_64 💿exe
✖️ 4632223 pr19228 2025-11-06 15:13:12 ~21 min tests/e2e 📊rpt
✖️ 4632223 PR19228 2025-11-06 15:16:48 ~16 min tests/e2e-windows 📊rpt
✔️ 7372f752 #4 2025-11-06 14:44:12 ~9 min android/arm64 🤖apk 📲
✔️ 7935e1e4 #5 2025-11-06 17:25:49 ~10 min android/arm64 🤖apk 📲
df56328 #5 2025-11-07 16:57:52 ~6 min macos/aarch64-nwaku 📄log
✔️ df56328 #5 2025-11-07 17:05:32 ~13 min macos/aarch64 🍎dmg
✔️ df56328 #5 2025-11-07 17:05:35 ~13 min ios/aarch64 📱ipa
✔️ df56328 #5 2025-11-07 17:07:20 ~15 min tests/nim 📄log
✔️ df56328 #5 2025-11-07 17:14:19 ~22 min linux/x86_64-nwaku 📦tgz
df56328 #5 2025-11-07 17:17:19 ~25 min tests/ui 📄log
✖️ df56328 #5 2025-11-07 17:17:43 ~26 min linux/x86_64 📦tgz
✖️ df56328 #5 2025-11-07 17:18:55 ~27 min windows/x86_64 💿exe
✔️ 8b6ae6b5 #6 2025-11-07 17:03:27 ~11 min android/arm64 🤖apk 📲
✔️ 6ecc2885 #7 2025-11-08 17:27:24 ~11 min android/arm64 🤖apk 📲
✔️ e5a91e40 #8 2025-11-10 17:27:37 ~12 min android/arm64 🤖apk 📲
✔️ c74dc3bc #9 2025-11-11 17:29:00 ~13 min android/arm64 🤖apk 📲
Commit #️⃣ Finished (UTC) Duration Platform Result
7da3cd6 #6 2025-11-12 09:19:47 ~6 min macos/aarch64-nwaku 📄log
✔️ 7da3cd6 #6 2025-11-12 09:20:25 ~7 min tests/nim 📄log
✔️ 7da3cd6 #10 2025-11-12 09:22:53 ~10 min android/arm64 🤖apk 📲
7da3cd6 #6 2025-11-12 09:25:48 ~12 min tests/ui 📄log
✔️ 7da3cd6 #6 2025-11-12 09:26:53 ~13 min macos/aarch64 🍎dmg
✔️ 7da3cd6 #6 2025-11-12 09:27:11 ~14 min ios/aarch64 📱ipa
✔️ 7da3cd6 #6 2025-11-12 09:28:28 ~15 min linux/x86_64 📦tgz
✔️ 7da3cd6 #6 2025-11-12 09:29:50 ~17 min linux/x86_64-nwaku 📦tgz
✔️ 7da3cd6 #6 2025-11-12 09:38:11 ~25 min windows/x86_64 💿exe
✔️ 7da3cd6 pr19228 2025-11-12 09:45:50 ~17 min tests/e2e 📊rpt
✖️ 7da3cd6 PR19228 2025-11-12 09:51:44 ~13 min tests/e2e-windows 📊rpt
7da3cd6 #7 2025-11-12 10:57:05 ~12 min tests/ui 📄log
6360f11 #7 2025-11-12 11:56:54 ~5 min macos/aarch64-nwaku 📄log
✔️ 6360f11 #7 2025-11-12 11:57:36 ~6 min tests/nim 📄log
✔️ 6360f11 #11 2025-11-12 12:00:42 ~9 min android/arm64 🤖apk 📲
✔️ 6360f11 #7 2025-11-12 12:03:13 ~12 min macos/aarch64 🍎dmg
✔️ 6360f11 #7 2025-11-12 12:03:46 ~12 min ios/aarch64 📱ipa
✔️ 6360f11 #8 2025-11-12 12:04:45 ~13 min tests/ui 📄log
✔️ 6360f11 #7 2025-11-12 12:06:50 ~15 min linux/x86_64-nwaku 📦tgz
✔️ 6360f11 #7 2025-11-12 12:07:18 ~16 min linux/x86_64 📦tgz
✔️ 6360f11 #7 2025-11-12 12:13:29 ~22 min windows/x86_64 💿exe
✖️ 6360f11 pr19228 2025-11-12 12:26:04 ~18 min tests/e2e 📊rpt
✖️ 6360f11 PR19228 2025-11-12 12:28:03 ~14 min tests/e2e-windows 📊rpt

@micieslak micieslak force-pushed the chore/issue-19218 branch 2 times, most recently from 05b64dc to ccfeb2f Compare November 6, 2025 10:23
@micieslak micieslak requested a review from a team as a code owner November 6, 2025 10:23
@micieslak micieslak requested review from glitchminer and removed request for a team November 6, 2025 10:23
Copy link
Member

@caybro caybro left a comment

Choose a reason for hiding this comment

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

Great if it works everywhere :)

Worth checking if the event listener to quit on close still works as expected; or if we still need it at all

@anastasiyaig
Copy link
Contributor

i will fix the tests

@anastasiyaig
Copy link
Contributor

i tried to fix it and the "fix" works for the tests with 1 app instance
but it does not work with the tests having 2 apps at the same time, tests are failing with Lookup Error that window can't be found

what Squish gives me in inspector is: {"name": "mainWindow", "type": "QQuickWindowQmlImpl", "visible": True} , which looks very wrong
there is no object with type Window (your initial change @micieslak) at all in the object map

image

any ideas?

@anastasiyaig
Copy link
Contributor

based on Squish docs, TopLevel Window should work with Main Window type

ToplevelWindow.byObject(object)
These methods return a ToplevelWindow object for the given objectname or object. The first is a convenience method for calling the second, with the result of [Object waitForObject(objectOrName)](https://doc.qt.io/squish/waitforobject-function.html#waitforobject-function). These methods accept different argument types depending on the toolkit, and should work with most top-level windows such as Dialogs, Main Windows, and Browser Tabs.

I tried to hack that locally, but sadly no luck yet

@micieslak
Copy link
Member Author

So, summarizing the StatusWindow -> Window transition:

  • StatusWindow is derived from QQuickWindow
  • According to docs Window instantiates QQuickWindow, and it's true. But it's not end of story, because QQuickWindow is just a base class for the class that's actually used, namely QQuickWindowQmlImpl. This middleman does additional setup in-between.

This switch led to some behavioral changes in the app:

  • Nastya noticed that there is no placeholder in chat input, it's also enabled when it should be disabled
  • I noticed that when app is closed with community as the last section, after restart after login there is blank screen. Logs unveils that this piece of code fails (AppMain.qml), printing error:
case Constants.appSection.community:
    for (let i = this.children.length - 1; i >= 0; i--) {
        var obj = this.children[i]
        if (obj && obj.sectionId && obj.sectionId === appMain.rootStore.activeSectionId) {
            return i
        }
    }
    // Should never be here, correct index must be returned from the for loop above
    console.error("Wrong section type:", d.activeSectionType,
                  "or section id: ", appMain.rootStore.activeSectionId)
    return Constants.appViewStackIndex.community

How it's possible that that change caused that butterfly effect?

The other interesting observation was that after the change, sections started loading in visually more "progressive" way, a bit like web pages in ancient times. And it's the key to understanding the situation.

Let's add sth like that e.g. to loader in our main.qml:

    Loader {
        id: loader

        onStatusChanged: {
            console.log("STATUS CHANGED ------------------------------> ", status)
        }

        asynchronous: true

        ...

On master it always prints only "STATUS CHANGED ------------------------------> 1", where 1 is Loader.Ready (!!!)
The loader never enters Loader.Loading state. The loader actually works in fully synchronous mode, even though we have asynchronous: true set.

On chore/issue-19218 the logs are more as they should be:

  • first STATUS CHANGED ------------------------------> 2 (Loader.Loading) is printed
  • tons of other logs is printed
  • then STATUS CHANGED ------------------------------> 1 (Loader.Ready) is printed

It means that in master ALL laoader we have are always synchronous. StatusWindow -> Window transition actually made them really asynchronous finally...

Ok, but why, especially knowing that both Window and StatusWindow are both in the end derived from QQuickWindow and basically should behave similarly?

The answer seems to be in the aforementioned middle-ware code, QQuickWindowQmlImpl, in classBegin method (https://codebrowser.dev/qt5/qtdeclarative/src/quick/items/qquickwindowmodule.cpp.html#_ZN19QQuickWindowQmlImpl10classBeginEv).

In that place, the Window's incubation controller is set as an incubation controller of the entire QQmlEngine. By default Qml Engine has no incubation controller. Normally it's set by QQuickWindowQmlImpl, but we were "skipping" that class by deriving directly from QQuickWindow. What's the only option anyways as QQuickWindowQmlImpl is private ofc.

void QQuickWindowQmlImpl::classBegin()
{
    Q_D(QQuickWindowQmlImpl);
    QQmlEngine* e = qmlEngine(this);
 
    QQmlEngine::setContextForObject(contentItem(), e->rootContext());
 
    //Give QQuickView behavior when created from QML with QQmlApplicationEngine
    if (QCoreApplication::instance()->property("__qml_using_qqmlapplicationengine") == QVariant(true)) {
        if (e && !e->incubationController())
            e->setIncubationController(incubationController());
    }
    {
        // The content item has CppOwnership policy (set in QQuickWindow). Ensure the presence of a JS
        // wrapper so that the garbage collector can see the policy.
        QV4::ExecutionEngine *v4 = e->handle();
        QV4::QObjectWrapper::wrap(v4, d->contentItem);
    }
}

This discovery seems to be a key for explaining multiple puzzles:

  • The issues observed in chore/issue-19218. Those are effect of poorly designed initialization, working only in synchronous mode but vulnerable for race conditions in async mode. Attempt to set current inded by traversing children for (let i = this.children.length - 1; i >= 0; i--) { that are instantiated by Repeater is probably not good idea. Working in sync mode, but failing in async. Repeater's delegates are not ready when that code is executed, leading to error.
  • Slowness of application. It was initially observed on first attempt of removing StatusWindow. On Windows os app was very slow, things were loading progressively on user's eyes. This behavior is quite understandable, because Loader in Loading state should not be visible. During loading loader should be invisible itself, and some progress indication placeholder should be displayed to the user. Otherwise framework tries to render "on the fly" whatever is ready, triggering a lot unnecessary intermediate (and bad looking) rendering passes. It may be system-dependent, various backend may handle it differently. Maybe that's the reason why Windows was extremely slow and it's barely visible on Linux.
  • When we were adding asynchronous: true whenever we could without any additional handling in form of controlling Loader's status and e.g. using placeholders when loading I coudn't understand why it just works wihtout any negative side effects. Now it's cleare - it was not working at all.
  • This discovery to some extent also explains why running app on QML server results in buggy behaviors even though we are using async loading anyways. So... we were actually not using async loading and the app is not prepared for that.
  • This also explains why loading components in Storybook using asynchronous mode was always looking differently in comparison to loading the same component in the app (asking that question many times to myself: why it looks as regular synced loading?).

Final conclusions:

  • We were not aware of fully sync loading by Loaders in the app
  • This discovery opens way improve the app regarding initialization and then taking full advantage of asynchronous loading. But this process must be done carefully, with proper control of the loading process, hiding loaders when loading and providing proper placeholders.

micieslak added a commit that referenced this pull request Nov 7, 2025
All Loaders restored to default synchronous mode because with
StatusWindow they all were synchronous even with asynchronous property
set to true. Along with migration from StatusWindow to Window, those
loaders become truly asynchronous causing various subtle issues in the
app. Asynchronous behavior will be introduced step by step, with proper
handling of loading state.

Needed for: #19218
Related to https://bugreports.qt.io/browse/QTBUG-141829
Detailed explanation: #19228 (comment)
@micieslak micieslak requested a review from a team as a code owner November 7, 2025 16:51
@micieslak micieslak requested review from friofry and removed request for a team November 7, 2025 16:51
micieslak added a commit that referenced this pull request Nov 12, 2025
All Loaders restored to default synchronous mode because with
StatusWindow they all were synchronous even with asynchronous property
set to true. Along with migration from StatusWindow to Window, those
loaders become truly asynchronous causing various subtle issues in the
app. Asynchronous behavior will be introduced step by step, with proper
handling of loading state.

Needed for: #19218
Related to https://bugreports.qt.io/browse/QTBUG-141829
Detailed explanation: #19228 (comment)
micieslak and others added 3 commits November 12, 2025 12:41
All Loaders restored to default synchronous mode because with
StatusWindow they all were synchronous even with asynchronous property
set to true. Along with migration from StatusWindow to Window, those
loaders become truly asynchronous causing various subtle issues in the
app. Asynchronous behavior will be introduced step by step, with proper
handling of loading state.

The only exception is StatusRoundedMedia where async behavior is assumed
in existing tests.

Needed for: #19218
Related to https://bugreports.qt.io/browse/QTBUG-141829
Detailed explanation: #19228 (comment)
@anastasiyaig
Copy link
Contributor

ran full suite here just in case, it looks okay , 1 test failed but thats not related to the changes in this PR https://ci.status.im/job/status-desktop/job/e2e/job/manual/2851/allure/#

@micieslak micieslak merged commit 68a4f4b into master Nov 12, 2025
11 of 13 checks passed
@micieslak micieslak deleted the chore/issue-19218 branch November 12, 2025 17:08
micieslak added a commit that referenced this pull request Nov 12, 2025
All Loaders restored to default synchronous mode because with
StatusWindow they all were synchronous even with asynchronous property
set to true. Along with migration from StatusWindow to Window, those
loaders become truly asynchronous causing various subtle issues in the
app. Asynchronous behavior will be introduced step by step, with proper
handling of loading state.

The only exception is StatusRoundedMedia where async behavior is assumed
in existing tests.

Needed for: #19218
Related to https://bugreports.qt.io/browse/QTBUG-141829
Detailed explanation: #19228 (comment)
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.

Drop custom StatusWindow, use regular QML Window instead

5 participants