flypig.co.uk

List items

Items from the current list are shown below.

Gecko

30 Dec 2023 : Day 123 #
I finished yesterday in high spirits, having figured out what was causing the hang on switching to private mode although, to be clear, that's not the same thing as having a solution. Nevertheless this puts us in a good position with the printing situation, because it means the window hiding code isn't causing the hang and it should now be pretty straightforward to get it to a state where it's ready to be merged in.

I concluded my post yesterday by highlighting four tasks which I now need to focus on to get this over the line.
  1. Check whether the flicker can be removed by skipping the activation of the hidden window.
  2. Tidy up the QML implementation of the new proxy filter model.
  3. Move the DownloadPDFSaver class from DownloadCore.js to EmbedliteDownloadManager.js.
  4. Record an issue for the setBrowserCover() hang.
I'm going to spend a bit of time on the first of these today. Unfortunately I don't have as much time to spend on this today as I do usually, so this will be a terse investigation. And if it doesn't pan out then my plan is to simply drop the idea rather than spend any more time trying to figure something more complex out. So this may not work out and in case it doesn't, well, that's just fine.

The bits I'm interested in are the following, which are part of qtmozembed and can be found in the qopenglwebpage.cpp file:
void QOpenGLWebPage::suspendView()
{
    if (!d || !d->mViewInitialized) {
        return;
    }
    setActive(false);
    d->mView->SuspendTimeouts();
}

void QOpenGLWebPage::resumeView()
{
    if (!d || !d->mViewInitialized) {
        return;
    }
    setActive(true);

    // Setting view as active, will reset RefreshDriver()->SetThrottled at
    // PresShell::SetIsActive (nsPresShell). Thus, keep on throttling
    // if should keep on throttling.
    if (mThrottlePainting) {
        d->setThrottlePainting(true);
    }

    d->mView->ResumeTimeouts();
}
I'm interested in these because of the flicker that happens when the hidden page is initially created. If we can arrange things so that the page is in a suspended state when it's created and never resumed, then rendering may never take place and it's possible the flicker can be avoided. The aim here is to suspend the rendering but not execution of the page itself: we want the page to continue running JavaScript and doing whatever else it needs to do in the background. It's only the rendering we want to avoid.

These methods are called from a couple of places both of which appear to be in the webpages.cpp file of sailfish-browser. Here's one of them:
WebPageActivationData WebPages::page(const Tab& tab)
{
    const int tabId = tab.tabId();

    if (m_activePages.active(tabId)) {
        DeclarativeWebPage *activePage = m_activePages.activeWebPage();
        activePage->resumeView();
        return WebPageActivationData(activePage, false);
    }

    DeclarativeWebPage *webPage = 0;
    DeclarativeWebPage *oldActiveWebPage = m_activePages.activeWebPage();
    if (!m_activePages.alive(tabId)) {
        webPage = m_pageFactory->createWebPage(m_webContainer, tab);
        if (webPage) {
            m_activePages.prepend(tabId, webPage);
        } else {
            return WebPageActivationData(nullptr, false);
        }
    }

    DeclarativeWebPage *newActiveWebPage = m_activePages.activate(tabId);
    updateStates(oldActiveWebPage, newActiveWebPage);

    if (m_memoryLevel == MemCritical) {
        handleMemNotify(m_memoryLevel);
    }

    return WebPageActivationData(newActiveWebPage, true);
}
And here's the other (notice that this gets called in the code above):
void WebPages::updateStates(DeclarativeWebPage *oldActivePage,
    DeclarativeWebPage *newActivePage)
{
    if (oldActivePage) {
        // Allow suspending only the current active page if it is not the
        // creator (parent).
        if (newActivePage->parentId() != (int)oldActivePage->uniqueId()) {
            if (oldActivePage->loading()) {
                oldActivePage->stop();
            }
            oldActivePage->suspendView();
        } else {
            // Sets parent to inactive and suspends rendering keeping
            // timeouts running.
            oldActivePage->setActive(false);
        }
    }

    if (newActivePage) {
        newActivePage->resumeView();
        newActivePage->update();
    }
}
The second of these we've seen before (I was playing around with this block of code on Day 118). In order to try to get the page to stay inactive I've amended the WebPages::page() method so that rather than calling updateStates() in all circumstances, it now does something slightly different if the tab is hidden:
    DeclarativeWebPage *newActiveWebPage = m_activePages.activate(tabId);
    if (tab.hidden()) {
        newActiveWebPage->setActive(false);
        newActiveWebPage->suspendView();
    } else {
        updateStates(oldActiveWebPage, newActiveWebPage);
    }
The idea here is that if the tab is hidden it will be set to inactive and suspended rather than being activated through the call to updateStates().

The theory looks plausible, but the practice shows otherwise: after making this change there's still a visible flicker when the hidden page is created and then immediately hidden.

However, while investigating this I also notice that the WebView.qml has this readyToPaint value that also controls whether rendering occurs:
    readyToPaint: resourceController.videoActive ? webView.visible
        && !resourceController.displayOff : webView.visible
        && webView.contentItem
        && (webView.contentItem.domContentLoaded || webView.contentItem.painted)
If I comment out this line, so that readyToPaint is never set to true then I find none of the pages render at all. That's good, because it means that using this flag it may be possible to avoid the rendering of the page that's causing the flicker.

I've added some extra variables into the class so that when a hidden page is created the readyToPaint value is skipped on the next occasion it's set. This is a hack and if this works I'll need to figure out a more satisfactory approach, but for testing this might be enough.

Unfortunately my attempts to control it fail: it has precisely the opposite effect, so that the page turns white and then rendering never starts up again. I'm left with a blank screen and no page being rendered at all.

I give it one more go, this time with a little more sophistication in my approach. Essentially I'm recording the hidden state, skipping any readyToPaint updates while it's set, then restoring the flag as soon as the page has reverted back to the non-hidden page.

Now the rendering state is at least restored afterwards, but there's still a flicker on screen as the page appears and then is hidden. And when I check the debug output it's clear that there's no change of readyToPaint state occurring between the time the new page is created and the old page is reinstated. During this time the rendering state is set to false.

So I don't think there's anything more to test here. Suspending the page appears to still render to the screen as a white page, rather than simply leaving behind the page that was there before. This shouldn't be such a surprise; the texture used for rending is almost certainly getting cleared somewhere, even when rendering is suspended.

But this task simply isn't worth spending more time on. Maybe at some point in the future the path to avoiding the new page render for a frame will become clearer, but in the meantime the impact on the user is minimal.

So, tomorrow I'll get started on cleaning up the QML and JavaScript code so that this can all be finalised.

If you'd like to read any of my other gecko diary entries, they're all available on my Gecko-dev Diary page.

Comments

Uncover Disqus comments