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 0a85a093fb3b4a6661d0cfd8fac92f115c65e069
Author:     Miklos Vajna <[email protected]>
AuthorDate: Thu Mar 5 13:56:00 2026 +0100
Commit:     Miklos Vajna <[email protected]>
CommitDate: Thu Mar 5 14:32:08 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.
    
    Change-Id: I3b7d422dd4a6c95e6fcedbdecb8a98bca6130002
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/201028
    Reviewed-by: Michael Meeks <[email protected]>
    Tested-by: Jenkins CollaboraOffice <[email protected]>

diff --git a/comphelper/source/misc/lok.cxx b/comphelper/source/misc/lok.cxx
index 406df02c8ff6..59822588ae4b 100644
--- a/comphelper/source/misc/lok.cxx
+++ b/comphelper/source/misc/lok.cxx
@@ -18,6 +18,7 @@
 #ifdef _WIN32
 #include <tools/UnixWrappers.h>
 #endif
+#include <vcl/task.hxx>
 
 #include <iostream>
 
@@ -34,6 +35,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);
@@ -156,6 +159,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 906f49f30ccf..2577ee4de6a3 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 a527eea4242d..af13cd38c934 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 db88c12ddbef..e4243d768e08 100644
--- a/sw/source/core/layout/layact.cxx
+++ b/sw/source/core/layout/layact.cxx
@@ -2445,9 +2445,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);

Reply via email to