comphelper/source/misc/lok.cxx | 21 ++++++++++ include/comphelper/lok.hxx | 2 + sw/qa/core/layout/layact.cxx | 75 +++++++++++++++++++++++++++++++++++++++ sw/source/core/layout/layact.cxx | 9 ++++ 4 files changed, 106 insertions(+), 1 deletion(-)
New commits: commit 7254545661994ba9a6ab14598045af87a550cbcf Author: Miklos Vajna <[email protected]> AuthorDate: Thu Mar 5 13:56:00 2026 +0100 Commit: Caolán McNamara <[email protected]> CommitDate: Fri Mar 6 12:14:31 2026 +0100 tdf#170595 sw lok, idle layout: fast render of the 2nd page, too Open a large document, the first page (inside the visible area) shows up fast, then the idle layout starts calculating later pages. Now scroll down to page 2, and tiles for that second page only arrive after the full layout finished. What happens is that idle layout unfortunately creates high priority tasks as a side effect of completing its work, so the COOL any input callback will not interrupt (assuming core has high priority tasks), even when it should, so idle layout runs to completion and only then we start rendering tiles. An option would be to fix the priority of these tasks, but there are a lot of them: 'vcl SystemDependentDataBuffer', 'svx::SdrPaintView aComeBackIdle', 'svx::svdraw::SdrPageWindow mpObjectContact', 'sw::SwView m_aTimer', then I stopped looking further. At this point it looks better to have a higher level mechanism. So introduce a flag in comphelper/, and using that ignore reporting high priority tasks from core to COOL till idle layout is in progress. Note that recursive layouts are not supported, so no need to query the current value before the first comphelper::LibreOfficeKit::setIdleLayouting() call. With this, my 839 pages long document shows content on e.g. pages 2 & 3 just fine, even if the full layout is not yet complete. (cherry picked from commit 0a85a093fb3b4a6661d0cfd8fac92f115c65e069) Change-Id: I3b7d422dd4a6c95e6fcedbdecb8a98bca6130002 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201092 Tested-by: Jenkins CollaboraOffice <[email protected]> Tested-by: Caolán McNamara <[email protected]> Reviewed-by: Caolán McNamara <[email protected]> diff --git a/comphelper/source/misc/lok.cxx b/comphelper/source/misc/lok.cxx index 903ba9ffec8a..372f134c7eb8 100644 --- a/comphelper/source/misc/lok.cxx +++ b/comphelper/source/misc/lok.cxx @@ -17,6 +17,7 @@ #ifdef _WIN32 #include <tools/UnixWrappers.h> #endif +#include <vcl/task.hxx> #include <iostream> @@ -33,6 +34,8 @@ static bool g_bPartInInvalidation(false); static bool g_bTiledPainting(false); +static bool g_bIdleLayouting(false); + static bool g_bDialogPainting(false); static bool g_bTiledAnnotations(true); @@ -155,6 +158,11 @@ bool isTiledPainting() return g_bTiledPainting; } +void setIdleLayouting(bool bIdleLayouting) +{ + g_bIdleLayouting = bIdleLayouting; +} + void setDialogPainting(bool bDialogPainting) { g_bDialogPainting = bDialogPainting; @@ -371,7 +379,18 @@ bool anyInput() // Ignore input events during background save. if (!g_bForkedChild && g_pAnyInputCallback && g_pAnyInputCallbackData) { - int nMostUrgentPriority = g_pMostUrgentPriorityGetter(); + int nMostUrgentPriority; + if (g_bIdleLayouting) + { + // Report idle priority instead of querying the scheduler. Various unrelated tasks + // (timers, paint idles) may be queued at high priority, which would cause the LOK + // client to not interrupt, preventing the idle layout from stopping. + nMostUrgentPriority = static_cast<int>(TaskPriority::DEFAULT_IDLE); + } + else + { + nMostUrgentPriority = g_pMostUrgentPriorityGetter(); + } bRet = g_pAnyInputCallback(g_pAnyInputCallbackData, nMostUrgentPriority); } diff --git a/include/comphelper/lok.hxx b/include/comphelper/lok.hxx index 6e301a200743..74e96162bd59 100644 --- a/include/comphelper/lok.hxx +++ b/include/comphelper/lok.hxx @@ -86,6 +86,8 @@ COMPHELPER_DLLPUBLIC void setPartInInvalidation(bool bPartInInvalidation); COMPHELPER_DLLPUBLIC bool isTiledPainting(); /// Set if we are doing tiled painting. COMPHELPER_DLLPUBLIC void setTiledPainting(bool bTiledPainting); +/// Set if we are doing idle layout. +COMPHELPER_DLLPUBLIC void setIdleLayouting(bool bIdleLayouting); /// Check if we are painting the dialog. COMPHELPER_DLLPUBLIC bool isDialogPainting(); /// Set if we are painting the dialog. diff --git a/sw/qa/core/layout/layact.cxx b/sw/qa/core/layout/layact.cxx index fa36c1599786..3f3d8d8a32da 100644 --- a/sw/qa/core/layout/layact.cxx +++ b/sw/qa/core/layout/layact.cxx @@ -10,8 +10,11 @@ #include <swmodeltestbase.hxx> #include <vcl/scheduler.hxx> +#include <vcl/idle.hxx> +#include <comphelper/lok.hxx> #include <IDocumentLayoutAccess.hxx> +#include <unotxdoc.hxx> #include <anchoredobject.hxx> #include <docsh.hxx> #include <flyfrm.hxx> @@ -130,6 +133,78 @@ CPPUNIT_TEST_FIXTURE(Test, testBadSplitSection) // 2. CPPUNIT_ASSERT(!pSection->GetFollow()); } + +/// anyInput callback that doesn't interrupt for high-priority tasks. +class PriorityAwareAnyInputCallback final +{ +public: + static bool callback(void* /*pData*/, int nPriority) + { + // Only interrupt if all ready tasks are low priority. + return nPriority < 0 || nPriority > static_cast<int>(TaskPriority::REPAINT); + } + + PriorityAwareAnyInputCallback() + { + comphelper::LibreOfficeKit::setAnyInputCallback(&callback, this, + Scheduler::GetMostUrgentTaskPriority); + } + + ~PriorityAwareAnyInputCallback() + { + comphelper::LibreOfficeKit::setAnyInputCallback(nullptr, nullptr, + []() -> int { return -1; }); + } +}; + +CPPUNIT_TEST_FIXTURE(Test, testIdleLayoutingAnyInput) +{ + // Set up LOK: + comphelper::LibreOfficeKit::setActive(true); + + // Given a document with 3 pages, the first page is visible: + createSwDoc(); + getSwTextDoc()->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->InsertPageBreak(); + pWrtShell->InsertPageBreak(); + SwRootFrame* pLayout = pWrtShell->GetLayout(); + SwPageFrame* pPage1 = pLayout->GetLower()->DynCastPageFrame(); + pWrtShell->setLOKVisibleArea(pPage1->getFrameArea().SVRect()); + // Visible page is calculated, the rest is not: + pWrtShell->StartAllAction(); + pPage1->InvalidateContent(); + SwPageFrame* pPage2 = pPage1->GetNext()->DynCastPageFrame(); + pPage2->InvalidateContent(); + SwPageFrame* pPage3 = pPage2->GetNext()->DynCastPageFrame(); + pPage3->InvalidateContent(); + pWrtShell->EndAllAction(); + CPPUNIT_ASSERT(!pPage1->IsInvalidContent()); + CPPUNIT_ASSERT(pPage2->IsInvalidContent()); + CPPUNIT_ASSERT(pPage3->IsInvalidContent()); + + // When idle layout runs and we have a scheduled high priority task together with an any input + // callback: + Idle aHighPrioTask("test task"); + aHighPrioTask.SetPriority(TaskPriority::DEFAULT); + aHighPrioTask.Start(); + PriorityAwareAnyInputCallback aAnyInput; + pWrtShell->LayoutIdle(); + + // Then make sure async layout calculates page 2 but stops before page 3: + CPPUNIT_ASSERT(!pPage1->IsInvalidContent()); + CPPUNIT_ASSERT(!pPage2->IsInvalidContent()); + // Without the fix in place, the idle layout would calculate all pages because the + // high-priority task caused the any input callback not to interrupt. + CPPUNIT_ASSERT(pPage3->IsInvalidContent()); + + // Tear down LOK: + aHighPrioTask.Stop(); + Scheduler::ProcessEventsToIdle(); + mxComponent->dispose(); + mxComponent.clear(); + comphelper::LibreOfficeKit::setActive(false); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/layact.cxx b/sw/source/core/layout/layact.cxx index d907f87f68ba..57690dea1e02 100644 --- a/sw/source/core/layout/layact.cxx +++ b/sw/source/core/layout/layact.cxx @@ -2422,9 +2422,18 @@ SwLayIdle::SwLayIdle( SwRootFrame *pRt, SwViewShellImp *pI ) : bSdrModelIdle = pSdrModel->IsWriterIdle(); pSdrModel->SetWriterIdle(true); } + if (comphelper::LibreOfficeKit::isActive()) + { + // Let the LOK anyInput() mechanism know that we're inside the idle layout. + comphelper::LibreOfficeKit::setIdleLayouting(true); + } aAction.Action(m_pImp->GetShell()->GetOut()); + if (comphelper::LibreOfficeKit::isActive()) + { + comphelper::LibreOfficeKit::setIdleLayouting(false); + } if (pSdrModel) { pSdrModel->SetWriterIdle(bSdrModelIdle);
