desktop/inc/lib/init.hxx                       |   23 +++
 desktop/source/lib/init.cxx                    |  148 +++++++++++++++++++++++++
 include/editeng/outliner.hxx                   |    2 
 include/sfx2/lokcallback.hxx                   |   11 +
 include/sfx2/lokhelper.hxx                     |   18 ++-
 include/sfx2/viewsh.hxx                        |    5 
 include/test/lokcallback.hxx                   |   24 +++-
 sc/qa/unit/tiledrendering/tiledrendering.cxx   |   15 ++
 sd/qa/unit/tiledrendering/tiledrendering.cxx   |   15 ++
 sfx2/source/view/lokhelper.cxx                 |   69 +++++++++--
 sfx2/source/view/viewsh.cxx                    |   33 +++++
 sw/inc/view.hxx                                |    1 
 sw/inc/viscrs.hxx                              |    3 
 sw/qa/core/txtnode/txtnode.cxx                 |    2 
 sw/qa/extras/tiledrendering/tiledrendering.cxx |   72 ++++++++----
 sw/source/core/crsr/viscrs.cxx                 |   85 +++++++++-----
 sw/source/uibase/inc/wrtsh.hxx                 |    2 
 sw/source/uibase/uiview/view.cxx               |    7 +
 sw/source/uibase/wrtsh/wrtsh4.cxx              |   13 ++
 test/source/lokcallback.cxx                    |  121 +++++++++++++++++++-
 20 files changed, 588 insertions(+), 81 deletions(-)

New commits:
commit d18d53da7cd554ed887ae53f5fb7c6ac1f7e4a98
Author:     Luboš Luňák <l.lu...@collabora.com>
AuthorDate: Fri Oct 15 08:43:23 2021 +0200
Commit:     Luboš Luňák <l.lu...@collabora.com>
CommitDate: Sun Oct 24 01:41:00 2021 +0200

    change some LOK internal updates to be pull model instead of push
    
    Some LOK messages may get called very often, such as updates about
    cursor position. And since only the last one matters, they get
    generated every time, which costs some time, and then later except
    for one they get all discard again from CallbackFlushHandler queue,
    which again costs time.
    
    Change the model to instead only set an 'updated' flag, and
    CallbackFlushHandler will request the actual message payload only
    before flushing.
    
    This commit changes LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR and
    LOK_CALLBACK_INVALIDATE_VIEW_CURSOR to work this way.
    
    Change-Id: I376be63176c0b4b5cb492fbf529c21ed01b35481
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/124084
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Luboš Luňák <l.lu...@collabora.com>

diff --git a/desktop/inc/lib/init.hxx b/desktop/inc/lib/init.hxx
index c43e82fdd430..2035226bc059 100644
--- a/desktop/inc/lib/init.hxx
+++ b/desktop/inc/lib/init.hxx
@@ -103,6 +103,8 @@ namespace desktop {
         virtual void libreOfficeKitViewCallback(int nType, const char* 
pPayload) override;
         virtual void libreOfficeKitViewCallbackWithViewId(int nType, const 
char* pPayload, int nViewId) override;
         virtual void libreOfficeKitViewInvalidateTilesCallback(const 
tools::Rectangle* pRect, int nPart) override;
+        virtual void libreOfficeKitViewUpdatedCallback(int nType) override;
+        virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int 
nViewId, int nSourceViewId) override;
 
     private:
         struct CallbackData
@@ -171,6 +173,8 @@ namespace desktop {
         queue_type2::iterator toQueue2(queue_type1::iterator);
         queue_type2::reverse_iterator toQueue2(queue_type1::reverse_iterator);
         void queue(const int type, CallbackData& data);
+        void enqueueUpdatedTypes();
+        void enqueueUpdatedType( int type, SfxViewShell* sourceViewShell, int 
viewId );
 
         /** we frequently want to scan the queue, and mostly when we do so, we 
only care about the element type
             so we split the queue in 2 to make the scanning cache friendly. */
@@ -178,6 +182,25 @@ namespace desktop {
         queue_type2 m_queue2;
         std::map<int, std::string> m_states;
         std::unordered_map<int, std::unordered_map<int, std::string>> 
m_viewStates;
+
+        // For some types only the last message matters (see isUpdatedType()) 
or only the last message
+        // per each viewId value matters (see isUpdatedTypePerViewId()), so 
instead of using push model
+        // where we'd get flooded by repeated messages (which might be costly 
to generate and process),
+        // the preferred way is that libreOfficeKitViewUpdatedCallback()
+        // or libreOfficeKitViewUpdatedCallbackPerViewId() get called to 
notify about such a message being
+        // needed, and we'll set a flag here to fetch the actual message 
before flushing.
+        void setUpdatedType( int nType, bool value );
+        void setUpdatedTypePerViewId( int nType, int nViewId, int 
nSourceViewId, bool value );
+        void resetUpdatedType( int nType);
+        void resetUpdatedTypePerViewId( int nType, int nViewId );
+        std::vector<bool> m_updatedTypes; // index is type, value is if set
+        struct PerViewIdData
+        {
+            bool set; // value is if set
+            int sourceViewId;
+        };
+        std::unordered_map<int, std::vector<PerViewIdData>> 
m_updatedTypesPerViewId; // key is view, index is type
+
         LibreOfficeKitDocument* m_pDocument;
         int m_viewId = -1; // view id of the associated SfxViewShell
         LibreOfficeKitCallback m_pCallback;
diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx
index 9e1eeb78d184..b7bd3ca28a32 100644
--- a/desktop/source/lib/init.cxx
+++ b/desktop/source/lib/init.cxx
@@ -697,6 +697,27 @@ static bool lcl_isViewCallbackType(const int type)
     }
 }
 
+static bool isUpdatedType(int type)
+{
+    switch (type)
+    {
+        default:
+            return false;
+    }
+}
+
+static bool isUpdatedTypePerViewId(int type)
+{
+    switch (type)
+    {
+        case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+        case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
+            return true;
+        default:
+            return false;
+    }
+}
+
 static int lcl_getViewId(const std::string& payload)
 {
     // this is a cheap way how to get the viewId from a JSON message; proper
@@ -1461,6 +1482,48 @@ CallbackFlushHandler::queue_type2::reverse_iterator 
CallbackFlushHandler::toQueu
     return m_queue2.rbegin() + delta;
 }
 
+void CallbackFlushHandler::setUpdatedType( int nType, bool value )
+{
+    assert(isUpdatedType(nType));
+    if( m_updatedTypes.size() <= o3tl::make_unsigned( nType ))
+        m_updatedTypes.resize( nType + 1 ); // new are default-constructed, 
i.e. false
+    m_updatedTypes[ nType ] = value;
+}
+
+void CallbackFlushHandler::resetUpdatedType( int nType )
+{
+    setUpdatedType( nType, false );
+}
+
+void CallbackFlushHandler::setUpdatedTypePerViewId( int nType, int nViewId, 
int nSourceViewId, bool value )
+{
+    assert(isUpdatedTypePerViewId(nType));
+    std::vector<PerViewIdData>& types = m_updatedTypesPerViewId[ nViewId ];
+    if( types.size() <= o3tl::make_unsigned( nType ))
+        types.resize( nType + 1 ); // new are default-constructed, i.e. false
+    types[ nType ] = PerViewIdData{ value, nSourceViewId };
+}
+
+void CallbackFlushHandler::resetUpdatedTypePerViewId( int nType, int nViewId )
+{
+    assert(isUpdatedTypePerViewId(nType));
+    bool allViewIds = false;
+    // Handle specially messages that do not have viewId for backwards 
compatibility.
+    if( nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR && 
!comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
+        allViewIds = true;
+    if( !allViewIds )
+    {
+        setUpdatedTypePerViewId( nType, nViewId, -1, false );
+        return;
+    }
+    for( auto& it : m_updatedTypesPerViewId )
+    {
+        std::vector<PerViewIdData>& types = it.second;
+        if( types.size() >= o3tl::make_unsigned( nType ))
+            types[ nType ].set = false;
+    }
+}
+
 void CallbackFlushHandler::libreOfficeKitViewCallback(int nType, const char* 
pPayload)
 {
     CallbackData callbackData(pPayload);
@@ -1479,6 +1542,22 @@ void 
CallbackFlushHandler::libreOfficeKitViewInvalidateTilesCallback(const tools
     queue(LOK_CALLBACK_INVALIDATE_TILES, callbackData);
 }
 
+void CallbackFlushHandler::libreOfficeKitViewUpdatedCallback(int nType)
+{
+    assert(isUpdatedType( nType ));
+    std::unique_lock<std::mutex> lock(m_mutex);
+    SAL_INFO("lok", "Updated: [" << nType << "]");
+    setUpdatedType(nType, true);
+}
+
+void CallbackFlushHandler::libreOfficeKitViewUpdatedCallbackPerViewId(int 
nType, int nViewId, int nSourceViewId)
+{
+    assert(isUpdatedTypePerViewId( nType ));
+    std::unique_lock<std::mutex> lock(m_mutex);
+    SAL_INFO("lok", "Updated: [" << nType << "]");
+    setUpdatedTypePerViewId(nType, nViewId, nSourceViewId, true);
+}
+
 void CallbackFlushHandler::queue(const int type, const char* data)
 {
     CallbackData callbackData(data);
@@ -1542,6 +1621,20 @@ void CallbackFlushHandler::queue(const int type, 
CallbackData& aCallbackData)
 
     std::unique_lock<std::mutex> lock(m_mutex);
 
+    // Update types should be received via the updated callbacks for 
performance,
+    // getting them as normal callbacks is technically not wrong, but probably 
should be avoided.
+    // Reset the updated flag if we get a normal message.
+    if(isUpdatedType(type))
+    {
+        SAL_INFO("lok", "Received event with updated type [" << type << "] as 
normal callback");
+        resetUpdatedType(type);
+    }
+    if(isUpdatedTypePerViewId(type))
+    {
+        SAL_INFO("lok", "Received event with updated type [" << type << "] as 
normal callback");
+        resetUpdatedTypePerViewId(type, aCallbackData.getViewId());
+    }
+
     // drop duplicate callbacks for the listed types
     switch (type)
     {
@@ -2034,6 +2127,58 @@ bool CallbackFlushHandler::processWindowEvent(int type, 
CallbackData& aCallbackD
     return false;
 }
 
+void CallbackFlushHandler::enqueueUpdatedTypes()
+{
+    if( m_updatedTypes.empty() && m_updatedTypesPerViewId.empty())
+        return;
+    SfxViewShell* viewShell = SfxViewShell::GetFirst( false,
+        [this](const SfxViewShell* shell) { return 
shell->GetViewShellId().get() == m_viewId; } );
+    assert(viewShell != nullptr);
+    for( size_t type = 0; type < m_updatedTypes.size(); ++type )
+    {
+        if(m_updatedTypes[ type ])
+        {
+            assert(isUpdatedType( type ));
+            enqueueUpdatedType( type, viewShell, m_viewId );
+        }
+    }
+    for( auto it : m_updatedTypesPerViewId )
+    {
+        int viewId = it.first;
+        const std::vector<PerViewIdData>& types = it.second;
+        for( size_t type = 0; type < types.size(); ++type )
+        {
+            if(types[ type ].set)
+            {
+                assert(isUpdatedTypePerViewId( type ));
+                SfxViewShell* sourceViewShell = viewShell;
+                const int sourceViewId = types[ type ].sourceViewId;
+                if( sourceViewId != m_viewId )
+                    sourceViewShell = SfxViewShell::GetFirst( false,
+                    [sourceViewId](const SfxViewShell* shell) { return 
shell->GetViewShellId().get() == sourceViewId; } );
+                if(sourceViewShell == nullptr)
+                {
+                    SAL_INFO("lok", "View #" << sourceViewId << " no longer 
found for updated event [" << type << "]");
+                    continue; // View removed, probably cleaning up.
+                }
+                enqueueUpdatedType( type, sourceViewShell, viewId );
+            }
+        }
+    }
+    m_updatedTypes.clear();
+    m_updatedTypesPerViewId.clear();
+}
+
+void CallbackFlushHandler::enqueueUpdatedType( int type, SfxViewShell* 
viewShell, int viewId )
+{
+    OString payload = viewShell->getLOKPayload( type, viewId );
+    CallbackData callbackData(payload.getStr(), viewId);
+    m_queue1.emplace_back(type);
+    m_queue2.emplace_back(callbackData);
+    SAL_INFO("lok", "Queued updated [" << type << "]: [" << 
callbackData.getPayload()
+        << "] to have " << m_queue1.size() << " entries.");
+}
+
 void CallbackFlushHandler::Invoke()
 {
     comphelper::ProfileZone aZone("CallbackFlushHandler::Invoke");
@@ -2051,6 +2196,9 @@ void CallbackFlushHandler::Invoke()
 
     std::scoped_lock<std::mutex> lock(m_mutex);
 
+    // Append messages for updated types, fetch them only now.
+    enqueueUpdatedTypes();
+
     SAL_INFO("lok", "Flushing " << m_queue1.size() << " elements.");
     auto it1 = m_queue1.begin();
     auto it2 = m_queue2.begin();
diff --git a/include/editeng/outliner.hxx b/include/editeng/outliner.hxx
index 1374a9f26f82..8cc3e8c23247 100644
--- a/include/editeng/outliner.hxx
+++ b/include/editeng/outliner.hxx
@@ -367,6 +367,8 @@ public:
     virtual void libreOfficeKitViewCallback(int nType, const char* pPayload) 
const = 0;
     virtual void libreOfficeKitViewCallbackWithViewId(int nType, const char* 
pPayload, int nViewId) const = 0;
     virtual void libreOfficeKitViewInvalidateTilesCallback(const 
tools::Rectangle* pRect, int nPart) const = 0;
+    virtual void libreOfficeKitViewUpdatedCallback(int nType) const = 0;
+    virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int 
nViewId, int nSourceViewId) const = 0;
     virtual ViewShellId GetViewShellId() const = 0;
     virtual ViewShellDocId GetDocId() const = 0;
     /// Wrapper around SfxLokHelper::notifyOtherViews().
diff --git a/include/sfx2/lokcallback.hxx b/include/sfx2/lokcallback.hxx
index d01e7203205e..6f59402d0cec 100644
--- a/include/sfx2/lokcallback.hxx
+++ b/include/sfx2/lokcallback.hxx
@@ -11,6 +11,8 @@
 
 #include <sal/types.h>
 
+#include <vector>
+
 namespace tools
 {
 class Rectangle;
@@ -37,6 +39,15 @@ public:
     // comphelper::LibreOfficeKit::isPartInInvalidation() is not set
     virtual void libreOfficeKitViewInvalidateTilesCallback(const 
tools::Rectangle* pRect, int nPart)
         = 0;
+    // A message of the given type should be sent, for performance purpose 
only a notification
+    // is given here, details about the message should be queried from 
SfxViewShell when necessary.
+    // This is used for messages that are generated often but only the last 
one is needed.
+    virtual void libreOfficeKitViewUpdatedCallback(int nType) = 0;
+    // Like libreOfficeKitViewUpdatedCallback(), but a last message is needed 
for each nViewId value.
+    // SfxViewShell:getLOKPayload() will be called on nSourceViewId view.
+    virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int 
nViewId,
+                                                            int nSourceViewId)
+        = 0;
 };
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/include/sfx2/lokhelper.hxx b/include/sfx2/lokhelper.hxx
index 060e0b152722..d5c3adbf9d95 100644
--- a/include/sfx2/lokhelper.hxx
+++ b/include/sfx2/lokhelper.hxx
@@ -109,14 +109,28 @@ public:
     static void notifyDocumentSizeChangedAllViews(vcl::ITiledRenderable* pDoc, 
bool bInvalidateAll = true);
     /// Emits a LOK_CALLBACK_INVALIDATE_TILES, but tweaks it according to 
setOptionalFeatures() if needed.
     static void notifyInvalidation(SfxViewShell const* pThisView, 
tools::Rectangle const *);
-    /// Emits a LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, but tweaks it 
according to setOptionalFeatures() if needed.
-    static void notifyVisCursorInvalidation(OutlinerViewShell const* 
pThisView, const OString& rRectangle, bool bMispelledWord = false, const 
OString& rHyperlink = "");
     /// Notifies all views with the given type and payload.
     static void notifyAllViews(int nType, const OString& rPayload);
 
     /// Notify about the editing context change.
     static void notifyContextChange(SfxViewShell const* pViewShell, const 
OUString& aApplication, const OUString& aContext);
 
+    // Notify about the given type needing an update.
+    static void notifyUpdate(SfxViewShell const* pViewShell, int nType);
+    // Notify about the given type needing a per-viewid update.
+    static void notifyUpdatePerViewId(SfxViewShell const* pViewShell, int 
nType);
+    /// Same as notifyUpdatePerViewId(), pTargetShell will be notified, 
relevant viewId in pViewShell,
+    /// pSourceView->getLOKPayload() will be called to get the data.
+    static void notifyUpdatePerViewId(SfxViewShell const* pTargetShell, 
SfxViewShell const* pViewShell,
+        SfxViewShell const* pSourceShell, int nType);
+    // Notify other views about the given type needing a per-viewid update.
+    static void notifyOtherViewsUpdatePerViewId(SfxViewShell const* 
pViewShell, int nType);
+
+    static OString makePayloadJSON(const SfxViewShell* pThisView, int nViewId, 
const OString& rKey, const OString& rPayload);
+    /// Makes a LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR payload, but tweaks it 
according to setOptionalFeatures() if needed.
+    static OString makeVisCursorInvalidation(int nViewId, const OString& 
rRectangle,
+                                             bool bMispelledWord = false, 
const OString& rHyperlink = "");
+
     /// Helper for posting async key event
     static void postKeyEventAsync(const VclPtr<vcl::Window> &xWindow,
                                   int nType, int nCharCode, int nKeyCode, int 
nRepeat = 0);
diff --git a/include/sfx2/viewsh.hxx b/include/sfx2/viewsh.hxx
index ee11c77a418d..f0d71bc542e6 100644
--- a/include/sfx2/viewsh.hxx
+++ b/include/sfx2/viewsh.hxx
@@ -342,6 +342,11 @@ public:
     virtual void libreOfficeKitViewInvalidateTilesCallback(const 
tools::Rectangle* pRect, int nPart) const override;
     // Performs any pending calls to 
libreOfficeKitViewInvalidateTilesCallback() as necessary.
     virtual void flushPendingLOKInvalidateTiles();
+    virtual void libreOfficeKitViewUpdatedCallback(int nType) const override;
+    virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int 
nViewId, int nSourceViewId) const override;
+    // Returns current payload for nType, after 
libreOfficeKitViewUpdatedCallback() or
+    // libreOfficeKitViewUpdatedCallbackPerViewId() were called.
+    virtual OString getLOKPayload(int nType, int nViewId) const;
 
     /// Set if we are doing tiled searching.
     void setTiledSearching(bool bTiledSearching);
diff --git a/include/test/lokcallback.hxx b/include/test/lokcallback.hxx
index f7372bc7ec80..a3f383bcec5e 100644
--- a/include/test/lokcallback.hxx
+++ b/include/test/lokcallback.hxx
@@ -15,28 +15,48 @@
 #include <sfx2/lokcallback.hxx>
 #include <vcl/idle.hxx>
 
+#include <vector>
+
 /**
 A helper to convert SfxLokCallbackInterface to a LIbreOfficeKitCallback for 
tests.
 
 It reimplements the specialized callbacks and converts them to the generic 
type/payload
 callback.
 */
-
 class OOO_DLLPUBLIC_TEST TestLokCallbackWrapper final : public 
SfxLokCallbackInterface, public Idle
 {
 public:
     TestLokCallbackWrapper(LibreOfficeKitCallback callback, void* data);
+    /// Discard all possibly still held events.
+    void clear();
+    /// Set the view id of the associated SfxViewShell.
+    void setLOKViewId(int viewId) { m_viewId = viewId; }
     virtual void libreOfficeKitViewCallback(int nType, const char* pPayload) 
override;
     virtual void libreOfficeKitViewCallbackWithViewId(int nType, const char* 
pPayload,
                                                       int nViewId) override;
     virtual void libreOfficeKitViewInvalidateTilesCallback(const 
tools::Rectangle* pRect,
                                                            int nPart) override;
+    virtual void libreOfficeKitViewUpdatedCallback(int nType) override;
+    virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int 
nViewId,
+                                                            int nSourceViewId) 
override;
     virtual void Invoke() override;
 
 private:
-    void callCallback(int nType, const char* pPayload);
+    void callCallback(int nType, const char* pPayload, int nViewId);
+    void startTimer();
+    void flushLOKData();
+    void discardUpdatedTypes(int nType, int nViewId);
     LibreOfficeKitCallback m_callback;
     void* m_data;
+    int m_viewId = -1; // the associated SfxViewShell
+    std::vector<int> m_updatedTypes; // value is type
+    struct PerViewIdData
+    {
+        int type;
+        int viewId;
+        int sourceViewId;
+    };
+    std::vector<PerViewIdData> m_updatedTypesPerViewId;
 };
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/sc/qa/unit/tiledrendering/tiledrendering.cxx 
b/sc/qa/unit/tiledrendering/tiledrendering.cxx
index 82287bfb2441..341852641b22 100644
--- a/sc/qa/unit/tiledrendering/tiledrendering.cxx
+++ b/sc/qa/unit/tiledrendering/tiledrendering.cxx
@@ -181,6 +181,7 @@ public:
 
 private:
     ScModelObj* createDoc(const char* pName);
+    void setupLibreOfficeKitViewCallback(SfxViewShell* pViewShell);
     static void callback(int nType, const char* pPayload, void* pData);
     void callbackImpl(int nType, const char* pPayload);
 
@@ -222,6 +223,7 @@ void ScTiledRenderingTest::tearDown()
         }
         mxComponent->dispose();
     }
+    m_callbackWrapper.clear();
     comphelper::LibreOfficeKit::setActive(false);
 
     test::BootstrapFixture::tearDown();
@@ -238,6 +240,12 @@ ScModelObj* ScTiledRenderingTest::createDoc(const char* 
pName)
     return pModelObj;
 }
 
+void ScTiledRenderingTest::setupLibreOfficeKitViewCallback(SfxViewShell* 
pViewShell)
+{
+    pViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(pViewShell));
+}
+
 void ScTiledRenderingTest::callback(int nType, const char* pPayload, void* 
pData)
 {
     static_cast<ScTiledRenderingTest*>(pData)->callbackImpl(nType, pPayload);
@@ -400,7 +408,7 @@ void ScTiledRenderingTest::testDocumentSize()
     ScTabViewShell* pViewShell = pDocSh->GetBestViewShell(false);
     CPPUNIT_ASSERT(pViewShell);
 
-    pViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pViewShell);
 
     // check initial document size
     Size aDocSize = pModelObj->getDocumentSize();
@@ -600,6 +608,7 @@ public:
         mpViewShell = SfxViewShell::Current();
         mpViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper);
         mnView = SfxLokHelper::getView();
+        m_callbackWrapper.setLOKViewId( mnView );
         if (!bDeleteListenerOnDestruct)
             mpViewShell = nullptr;
     }
@@ -783,7 +792,7 @@ void ScTiledRenderingTest::testDocumentSizeChanged()
 
     // Load a document that doesn't have much content.
     createDoc("small.ods");
-    SfxViewShell::Current()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(SfxViewShell::Current());
 
     // Go to the A30 cell -- that will extend the document size.
     uno::Sequence<beans::PropertyValue> aPropertyValues =
@@ -885,7 +894,7 @@ void ScTiledRenderingTest::testColRowResize()
     ScTabViewShell* pViewShell = pDocSh->GetBestViewShell(false);
     CPPUNIT_ASSERT(pViewShell);
 
-    pViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pViewShell);
 
     ScDocument& rDoc = pDocSh->GetDocument();
 
diff --git a/sd/qa/unit/tiledrendering/tiledrendering.cxx 
b/sd/qa/unit/tiledrendering/tiledrendering.cxx
index 8d9a23afaa5b..917f2eb04038 100644
--- a/sd/qa/unit/tiledrendering/tiledrendering.cxx
+++ b/sd/qa/unit/tiledrendering/tiledrendering.cxx
@@ -197,6 +197,7 @@ public:
 
 private:
     SdXImpressDocument* createDoc(const char* pName, const 
uno::Sequence<beans::PropertyValue>& rArguments = 
uno::Sequence<beans::PropertyValue>());
+    void setupLibreOfficeKitViewCallback(SfxViewShell& pViewShell);
     static void callback(int nType, const char* pPayload, void* pData);
     void callbackImpl(int nType, const char* pPayload);
     xmlDocUniquePtr parseXmlDump();
@@ -246,6 +247,7 @@ void SdTiledRenderingTest::tearDown()
     if (m_pXmlBuffer)
         xmlBufferFree(m_pXmlBuffer);
 
+    m_callbackWrapper.clear();
     comphelper::LibreOfficeKit::setActive(false);
 
     test::BootstrapFixture::tearDown();
@@ -262,6 +264,12 @@ SdXImpressDocument* SdTiledRenderingTest::createDoc(const 
char* pName, const uno
     return pImpressDocument;
 }
 
+void SdTiledRenderingTest::setupLibreOfficeKitViewCallback(SfxViewShell& 
pViewShell)
+{
+    pViewShell.setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(&pViewShell));
+}
+
 void SdTiledRenderingTest::callback(int nType, const char* pPayload, void* 
pData)
 {
     static_cast<SdTiledRenderingTest*>(pData)->callbackImpl(nType, pPayload);
@@ -398,7 +406,7 @@ void SdTiledRenderingTest::testRegisterCallback()
 {
     SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp");
     sd::ViewShell* pViewShell = 
pXImpressDocument->GetDocShell()->GetViewShell();
-    
pViewShell->GetViewShellBase().setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pViewShell->GetViewShellBase());
 
     // Start text edit of the empty title shape.
     SdPage* pActualPage = pViewShell->GetActualPage();
@@ -628,7 +636,7 @@ void SdTiledRenderingTest::testInsertDeletePage()
 {
     SdXImpressDocument* pXImpressDocument = createDoc("insert-delete.odp");
     sd::ViewShell* pViewShell = 
pXImpressDocument->GetDocShell()->GetViewShell();
-    
pViewShell->GetViewShellBase().setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pViewShell->GetViewShellBase());
 
     SdDrawDocument* pDoc = pXImpressDocument->GetDocShell()->GetDoc();
     CPPUNIT_ASSERT(pDoc);
@@ -915,6 +923,7 @@ public:
         mpViewShell = SfxViewShell::Current();
         mpViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper);
         mnView = SfxLokHelper::getView();
+        m_callbackWrapper.setLOKViewId( mnView );
     }
 
     ~ViewCallback()
@@ -2528,7 +2537,7 @@ void SdTiledRenderingTest::testCutSelectionChange()
     CPPUNIT_ASSERT(pXImpressDocument);
 
     sd::ViewShell* pViewShell = 
pXImpressDocument->GetDocShell()->GetViewShell();
-    
pViewShell->GetViewShellBase().setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pViewShell->GetViewShellBase());
     Scheduler::ProcessEventsToIdle();
 
     // Select first text object
diff --git a/sfx2/source/view/lokhelper.cxx b/sfx2/source/view/lokhelper.cxx
index e2039498ca53..9b65dcec7df7 100644
--- a/sfx2/source/view/lokhelper.cxx
+++ b/sfx2/source/view/lokhelper.cxx
@@ -355,15 +355,21 @@ static OString lcl_generateJSON(const SfxViewShell* 
pView, const boost::property
     return OString(aString.c_str(), aString.size()).trim();
 }
 
-static inline OString lcl_generateJSON(const SfxViewShell* pView, const 
OString& rKey,
+static inline OString lcl_generateJSON(const SfxViewShell* pView, int nViewId, 
const OString& rKey,
                                        const OString& rPayload)
 {
     assert(pView != nullptr && "pView must be valid");
-    return OStringLiteral("{ \"viewId\": \"") + 
OString::number(SfxLokHelper::getView(pView))
+    return OStringLiteral("{ \"viewId\": \"") + OString::number(nViewId)
            + "\", \"part\": \"" + OString::number(pView->getPart()) + "\", \"" 
+ rKey + "\": \""
            + lcl_sanitizeJSONAsValue(rPayload) + "\" }";
 }
 
+static inline OString lcl_generateJSON(const SfxViewShell* pView, const 
OString& rKey,
+                                       const OString& rPayload)
+{
+    return lcl_generateJSON(pView, SfxLokHelper::getView(pView), rKey, 
rPayload);
+}
+
 void SfxLokHelper::notifyOtherView(const SfxViewShell* pThisView, SfxViewShell 
const* pOtherView,
                                    int nType, const OString& rKey, const 
OString& rPayload)
 {
@@ -449,6 +455,11 @@ void SfxLokHelper::notifyOtherViews(const SfxViewShell* 
pThisView, int nType,
     }
 }
 
+OString SfxLokHelper::makePayloadJSON(const SfxViewShell* pThisView, int 
nViewId, const OString& rKey, const OString& rPayload)
+{
+    return lcl_generateJSON(pThisView, nViewId, rKey, rPayload);
+}
+
 namespace {
     OUString lcl_getNameForSlot(const SfxViewShell* pShell, sal_uInt16 nWhich)
     {
@@ -563,25 +574,20 @@ void 
SfxLokHelper::notifyDocumentSizeChangedAllViews(vcl::ITiledRenderable* pDoc
     }
 }
 
-void SfxLokHelper::notifyVisCursorInvalidation(OutlinerViewShell const* 
pThisView, const OString& rRectangle, bool bMispelledWord, const OString& 
rHyperlink)
+OString SfxLokHelper::makeVisCursorInvalidation(int nViewId, const OString& 
rRectangle,
+    bool bMispelledWord, const OString& rHyperlink)
 {
-    if (DisableCallbacks::disabled())
-        return;
-
     if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
     {
         OString sHyperlink = rHyperlink.isEmpty() ? "{}" : rHyperlink;
-        OString sPayload = OStringLiteral("{ \"viewId\": \"") + 
OString::number(SfxLokHelper::getView()) +
+        return OStringLiteral("{ \"viewId\": \"") + OString::number(nViewId) +
             "\", \"rectangle\": \"" + rRectangle +
             "\", \"mispelledWord\": \"" +  OString::number(bMispelledWord ? 1 
: 0) +
             "\", \"hyperlink\": " + sHyperlink + " }";
-        const int viewId = SfxLokHelper::getView();
-        
pThisView->libreOfficeKitViewCallbackWithViewId(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR,
 sPayload.getStr(), viewId);
     }
     else
     {
-        OString sPayload = rRectangle;
-        
pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, 
sPayload.getStr());
+        return rRectangle;
     }
 }
 
@@ -613,6 +619,47 @@ void SfxLokHelper::notifyContextChange(SfxViewShell const* 
pViewShell, const OUS
     pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CONTEXT_CHANGED, 
aBuffer.getStr());
 }
 
+void SfxLokHelper::notifyUpdate(SfxViewShell const* pThisView, int nType)
+{
+    if (DisableCallbacks::disabled())
+        return;
+
+    pThisView->libreOfficeKitViewUpdatedCallback(nType);
+}
+
+void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const* pThisView, int 
nType)
+{
+    notifyUpdatePerViewId(pThisView, pThisView, pThisView, nType);
+}
+
+void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const* pTargetShell, 
SfxViewShell const* pViewShell,
+    SfxViewShell const* pSourceShell, int nType)
+{
+    if (DisableCallbacks::disabled())
+        return;
+
+    int viewId = SfxLokHelper::getView(pViewShell);
+    int sourceViewId = SfxLokHelper::getView(pSourceShell);
+    pTargetShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, 
sourceViewId);
+}
+
+void SfxLokHelper::notifyOtherViewsUpdatePerViewId(SfxViewShell const* 
pThisView, int nType)
+{
+    assert(pThisView != nullptr && "pThisView must be valid");
+    if (DisableCallbacks::disabled())
+        return;
+
+    int viewId = SfxLokHelper::getView(pThisView);
+    const ViewShellDocId nCurrentDocId = pThisView->GetDocId();
+    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
+    while (pViewShell)
+    {
+        if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId())
+            pViewShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, 
viewId, viewId);
+
+        pViewShell = SfxViewShell::GetNext(*pViewShell);
+    }
+}
 
 namespace
 {
diff --git a/sfx2/source/view/viewsh.cxx b/sfx2/source/view/viewsh.cxx
index 419b0284de2e..81757258805a 100644
--- a/sfx2/source/view/viewsh.cxx
+++ b/sfx2/source/view/viewsh.cxx
@@ -1532,6 +1532,32 @@ void SfxViewShell::libreOfficeKitViewCallback(int nType, 
const char* pPayload) c
             << lokCallbackTypeToString(nType) << ": [" << pPayload << ']');
 }
 
+void SfxViewShell::libreOfficeKitViewUpdatedCallback(int nType) const
+{
+    if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get()))
+        return;
+    if (pImpl->m_pLibreOfficeKitViewCallback)
+        
pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewUpdatedCallback(nType);
+    else
+        SAL_INFO(
+            "sfx.view",
+            "SfxViewShell::libreOfficeKitViewUpdatedCallback no callback set! 
Dropped payload of type "
+            << lokCallbackTypeToString(nType));
+}
+
+void SfxViewShell::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int 
nViewId, int nSourceViewId) const
+{
+    if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get()))
+        return;
+    if (pImpl->m_pLibreOfficeKitViewCallback)
+        
pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewUpdatedCallbackPerViewId(nType,
 nViewId, nSourceViewId);
+    else
+        SAL_INFO(
+            "sfx.view",
+            "SfxViewShell::libreOfficeKitViewUpdatedCallbackPerViewId no 
callback set! Dropped payload of type "
+            << lokCallbackTypeToString(nType));
+}
+
 void SfxViewShell::afterCallbackRegistered()
 {
 }
@@ -1541,6 +1567,13 @@ void SfxViewShell::flushPendingLOKInvalidateTiles()
     // SfxViewShell itself does not delay any tile invalidations.
 }
 
+OString SfxViewShell::getLOKPayload(int nType, int /*nViewId*/) const
+{
+    // SfxViewShell itself currently doesn't handle any updated-payload types.
+    SAL_WARN("sfx.view", "SfxViewShell::getLOKPayload unhandled type " << 
lokCallbackTypeToString(nType));
+    abort();
+}
+
 vcl::Window* SfxViewShell::GetEditWindowForActiveOLEObj() const
 {
     vcl::Window* pEditWin = nullptr;
diff --git a/sw/inc/view.hxx b/sw/inc/view.hxx
index a2d1a86d1356..b793614f58f7 100644
--- a/sw/inc/view.hxx
+++ b/sw/inc/view.hxx
@@ -680,6 +680,7 @@ public:
 
     virtual tools::Rectangle getLOKVisibleArea() const override;
     virtual void flushPendingLOKInvalidateTiles() override;
+    virtual OString getLOKPayload(int nType, int nViewId) const override;
 };
 
 inline tools::Long SwView::GetXScroll() const
diff --git a/sw/inc/viscrs.hxx b/sw/inc/viscrs.hxx
index 95c013b233ca..3bd001bce074 100644
--- a/sw/inc/viscrs.hxx
+++ b/sw/inc/viscrs.hxx
@@ -47,6 +47,7 @@ class SW_DLLPUBLIC SwVisibleCursor
 
     /// For LibreOfficeKit only - remember what page we were at the last time.
     sal_uInt16 m_nPageLastTime;
+    SwRect m_aLastLOKRect;
 
 public:
     SwVisibleCursor( const SwCursorShell * pCShell );
@@ -59,6 +60,8 @@ public:
     void SetDragCursor( bool bFlag = true ) { m_bIsDragCursor = bFlag; }
     void SetPosAndShow(SfxViewShell const * pViewShell);
     const vcl::Cursor& GetTextCursor() const;
+
+    OString getLOKPayload(int nType, int nViewId) const;
 };
 
 // From here classes/methods for selections.
diff --git a/sw/qa/core/txtnode/txtnode.cxx b/sw/qa/core/txtnode/txtnode.cxx
index 78eecb14a631..9d3c798dc0fa 100644
--- a/sw/qa/core/txtnode/txtnode.cxx
+++ b/sw/qa/core/txtnode/txtnode.cxx
@@ -14,6 +14,7 @@
 #include <sfx2/viewsh.hxx>
 #include <vcl/gdimtf.hxx>
 #include <vcl/scheduler.hxx>
+#include <sfx2/lokhelper.hxx>
 #include <test/lokcallback.hxx>
 
 #include <IDocumentStatistics.hxx>
@@ -134,6 +135,7 @@ CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, 
testTitleFieldInvalidate)
     ViewCallback aCallback;
     TestLokCallbackWrapper aCallbackWrapper(&ViewCallback::callback, 
&aCallback);
     
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&aCallbackWrapper);
+    
aCallbackWrapper.setLOKViewId(SfxLokHelper::getView(pWrtShell->GetSfxViewShell()));
     Scheduler::ProcessEventsToIdle();
     aCallback.m_nInvalidations = 0;
 
diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx 
b/sw/qa/extras/tiledrendering/tiledrendering.cxx
index a9aa0b63cc7c..20a8141584fb 100644
--- a/sw/qa/extras/tiledrendering/tiledrendering.cxx
+++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx
@@ -243,6 +243,7 @@ public:
 
 private:
     SwXTextDocument* createDoc(const char* pName = nullptr);
+    void setupLibreOfficeKitViewCallback(SfxViewShell* pViewShell);
     static void callback(int nType, const char* pPayload, void* pData);
     void callbackImpl(int nType, const char* pPayload);
     // First invalidation.
@@ -302,6 +303,7 @@ void SwTiledRenderingTest::tearDown()
         mxComponent->dispose();
         mxComponent.clear();
     }
+    m_callbackWrapper.clear();
     comphelper::LibreOfficeKit::setActive(false);
 
     test::BootstrapFixture::tearDown();
@@ -320,6 +322,12 @@ SwXTextDocument* SwTiledRenderingTest::createDoc(const 
char* pName)
     return pTextDocument;
 }
 
+void SwTiledRenderingTest::setupLibreOfficeKitViewCallback(SfxViewShell* 
pViewShell)
+{
+    pViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(pViewShell));
+}
+
 void SwTiledRenderingTest::callback(int nType, const char* pPayload, void* 
pData)
 {
     static_cast<SwTiledRenderingTest*>(pData)->callbackImpl(nType, pPayload);
@@ -438,7 +446,7 @@ void SwTiledRenderingTest::testRegisterCallback()
 {
     SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     // Insert a character at the beginning of the document.
     pWrtShell->Insert("x");
     Scheduler::ProcessEventsToIdle();
@@ -621,7 +629,7 @@ void SwTiledRenderingTest::testSearch()
 {
     SwXTextDocument* pXTextDocument = createDoc("search.odt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     std::size_t nNode = 
pWrtShell->getShellCursor(false)->Start()->nNode.GetNode().GetIndex();
 
     // First hit, in the second paragraph, before the shape.
@@ -686,7 +694,7 @@ void SwTiledRenderingTest::testSearchTextFrame()
 {
     SwXTextDocument* pXTextDocument = createDoc("search.odt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     uno::Sequence<beans::PropertyValue> 
aPropertyValues(comphelper::InitPropertySequence(
     {
         {"SearchItem.SearchString", uno::makeAny(OUString("TextFrame"))},
@@ -701,7 +709,7 @@ void SwTiledRenderingTest::testSearchTextFrameWrapAround()
 {
     SwXTextDocument* pXTextDocument = createDoc("search.odt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     uno::Sequence<beans::PropertyValue> 
aPropertyValues(comphelper::InitPropertySequence(
     {
         {"SearchItem.SearchString", uno::makeAny(OUString("TextFrame"))},
@@ -719,7 +727,7 @@ void SwTiledRenderingTest::testDocumentSizeChanged()
     // Get the current document size.
     SwXTextDocument* pXTextDocument = createDoc("2-pages.odt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     Size aSize = pXTextDocument->getDocumentSize();
 
     // Delete the second page and see how the size changes.
@@ -735,7 +743,7 @@ void SwTiledRenderingTest::testSearchAll()
 {
     SwXTextDocument* pXTextDocument = createDoc("search.odt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     uno::Sequence<beans::PropertyValue> 
aPropertyValues(comphelper::InitPropertySequence(
     {
         {"SearchItem.SearchString", uno::makeAny(OUString("shape"))},
@@ -753,7 +761,7 @@ void SwTiledRenderingTest::testSearchAllNotifications()
 {
     SwXTextDocument* pXTextDocument = createDoc("search.odt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     // Reset notification counter before search.
     m_nSelectionBeforeSearchResult = 0;
     uno::Sequence<beans::PropertyValue> 
aPropertyValues(comphelper::InitPropertySequence(
@@ -780,7 +788,7 @@ void SwTiledRenderingTest::testPageDownInvalidation()
     }));
     pXTextDocument->initializeForTiledRendering(aPropertyValues);
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     comphelper::dispatchCommand(".uno:PageDown", 
uno::Sequence<beans::PropertyValue>());
 
     // This was 2.
@@ -851,6 +859,7 @@ public:
         mpViewShell = pViewShell ? pViewShell : SfxViewShell::Current();
         mpViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper);
         mnView = SfxLokHelper::getView();
+        m_callbackWrapper.setLOKViewId( mnView );
     }
 
     ~ViewCallback()
@@ -1052,6 +1061,7 @@ void SwTiledRenderingTest::testViewCursors()
     SfxLokHelper::createView();
     ViewCallback aView2;
 
+    Scheduler::ProcessEventsToIdle();
     CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated);
     CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated);
     CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated);
@@ -1070,6 +1080,7 @@ void SwTiledRenderingTest::testViewCursors()
     pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 5, 
/*bBasicCall=*/false);
     // Create a selection on the word.
     pWrtShell->SelWrd();
+    Scheduler::ProcessEventsToIdle();
     SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
     // Did we indeed manage to select the second word?
     CPPUNIT_ASSERT_EQUAL(OUString("bbb"), pShellCursor->GetText());
@@ -1529,7 +1540,7 @@ void SwTiledRenderingTest::testTrackChangesCallback()
     // Load a document.
     SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
 
     // Turn on track changes and type "x".
     uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, 
uno::UNO_QUERY);
@@ -1556,7 +1567,7 @@ void SwTiledRenderingTest::testRedlineUpdateCallback()
     // Load a document.
     SwXTextDocument* pXTextDocument = createDoc("dummy.fodt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
 
     // Turn on track changes, type "xx" and delete the second one.
     uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, 
uno::UNO_QUERY);
@@ -2346,7 +2357,7 @@ void SwTiledRenderingTest::testSplitNodeRedlineCallback()
     // Load a document.
     SwXTextDocument* pXTextDocument = 
createDoc("splitnode_redline_callback.fodt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
 
     // 1. test case
     // Move cursor between the two tracked changes
@@ -2404,7 +2415,7 @@ void SwTiledRenderingTest::testDeleteNodeRedlineCallback()
     // Load a document.
     SwXTextDocument* pXTextDocument = 
createDoc("removenode_redline_callback.fodt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
 
     // 1. test case
     // Move cursor between the two tracked changes
@@ -2469,7 +2480,6 @@ void SwTiledRenderingTest::testVisCursorInvalidation()
     ViewCallback aView2;
     Scheduler::ProcessEventsToIdle();
 
-
     // Move visible cursor in the first view
     SfxLokHelper::setView(nView1);
     Scheduler::ProcessEventsToIdle();
@@ -2491,6 +2501,7 @@ void SwTiledRenderingTest::testVisCursorInvalidation()
     // Insert text in the second view which moves the other view's cursor too
     SfxLokHelper::setView(nView2);
 
+    Scheduler::ProcessEventsToIdle();
     aView1.m_bOwnCursorInvalidated = false;
     aView1.m_bViewCursorInvalidated = false;
     aView2.m_bOwnCursorInvalidated = false;
@@ -2504,12 +2515,19 @@ void SwTiledRenderingTest::testVisCursorInvalidation()
     CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated);
     CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated);
     CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated);
+    // Check that views have correct location for the other's cursor.
+    CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor, aView2.m_aViewCursor);
+    CPPUNIT_ASSERT_EQUAL(aView2.m_aOwnCursor, aView1.m_aViewCursor);
+    // Their cursors should be on the same line, first view's more to the 
right.
+    CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor.getY(), 
aView2.m_aOwnCursor.getY());
+    CPPUNIT_ASSERT_GREATER(aView2.m_aOwnCursor.getX(), 
aView1.m_aOwnCursor.getX());
 
     // Do the same as before, but set the related compatibility flag first
     SfxLokHelper::setView(nView2);
 
     comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true);
 
+    Scheduler::ProcessEventsToIdle();
     aView1.m_bOwnCursorInvalidated = false;
     aView1.m_bViewCursorInvalidated = false;
     aView2.m_bOwnCursorInvalidated = false;
@@ -2525,6 +2543,11 @@ void SwTiledRenderingTest::testVisCursorInvalidation()
     CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated);
     CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated);
     CPPUNIT_ASSERT_EQUAL(nView2, aView2.m_nOwnCursorInvalidatedBy);
+    CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor, aView2.m_aViewCursor);
+    CPPUNIT_ASSERT_EQUAL(aView2.m_aOwnCursor, aView1.m_aViewCursor);
+    // Their cursors should be on the same line, first view's more to the 
right.
+    CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor.getY(), 
aView2.m_aOwnCursor.getY());
+    CPPUNIT_ASSERT_GREATER(aView2.m_aOwnCursor.getX(), 
aView1.m_aOwnCursor.getX());
 
     comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(false);
 }
@@ -2756,7 +2779,7 @@ void 
SwTiledRenderingTest::testRedlineNotificationDuringSave()
     // It's an empty document, just settings.xml and content.xml are custom.
     SwXTextDocument* pXTextDocument = 
createDoc("redline-notification-during-save.odt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
 
     // Save the document.
     utl::MediaDescriptor aMediaDescriptor;
@@ -2772,7 +2795,8 @@ void SwTiledRenderingTest::testHyperlink()
     comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true);
     SwXTextDocument* pXTextDocument = createDoc("hyperlink.odt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
+    
m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(pWrtShell->GetSfxViewShell()));
     SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false);
 
     Point aStart = pShellCursor->GetSttPos();
@@ -2799,7 +2823,7 @@ void SwTiledRenderingTest::testDropDownFormFieldButton()
     pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000));
 
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
 
     // Move the cursor to trigger displaying of the field button.
     pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, 
/*bBasicCall=*/false);
@@ -2872,7 +2896,7 @@ void 
SwTiledRenderingTest::testDropDownFormFieldButtonEditing()
     pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000));
 
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
 
     // Move the cursor to trigger displaying of the field button.
     pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, 
/*bBasicCall=*/false);
@@ -2929,7 +2953,7 @@ void 
SwTiledRenderingTest::testDropDownFormFieldButtonNoSelection()
     pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000));
 
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
 
     // Move the cursor to trigger displaying of the field button.
     pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, 
/*bBasicCall=*/false);
@@ -2982,7 +3006,7 @@ void SwTiledRenderingTest::testMoveShapeHandle()
     SwXTextDocument* pXTextDocument = createDoc("shape.fodt");
 
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     SdrPage* pPage = 
pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0);
     SdrObject* pObject = pPage->GetObj(0);
     pWrtShell->SelectObj(Point(), 0, pObject);
@@ -3015,7 +3039,7 @@ void 
SwTiledRenderingTest::testDropDownFormFieldButtonNoItem()
     pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000));
 
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
 
     // Move the cursor to trigger displaying of the field button.
     pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, 
/*bBasicCall=*/false);
@@ -3052,7 +3076,7 @@ void SwTiledRenderingTest::testTablePaintInvalidate()
     // Load a document with a table in it.
     SwXTextDocument* pXTextDocument = createDoc("table-paint-invalidate.odt");
     SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     // Enter the table.
     pWrtShell->Down(/*bSelect=*/false);
     Scheduler::ProcessEventsToIdle();
@@ -3150,7 +3174,7 @@ void SwTiledRenderingTest::testBulletDeleteInvalidation()
     pWrtShell->GetLayout()->PaintSwFrame(*pWrtShell->GetOut(),
                                          
pWrtShell->GetLayout()->getFrameArea());
     Scheduler::ProcessEventsToIdle();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     m_aInvalidations = tools::Rectangle();
 
     // When pressing backspace in the last paragraph.
@@ -3180,7 +3204,7 @@ void SwTiledRenderingTest::testBulletNoNumInvalidation()
     pWrtShell->GetLayout()->PaintSwFrame(*pWrtShell->GetOut(),
                                          
pWrtShell->GetLayout()->getFrameArea());
     Scheduler::ProcessEventsToIdle();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     m_aInvalidations = tools::Rectangle();
 
     // When pressing backspace in the last paragraph to turn bullets off.
@@ -3217,7 +3241,7 @@ void 
SwTiledRenderingTest::testBulletMultiDeleteInvalidation()
     pWrtShell->GetLayout()->PaintSwFrame(*pWrtShell->GetOut(),
                                          
pWrtShell->GetLayout()->getFrameArea());
     Scheduler::ProcessEventsToIdle();
-    
pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+    setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell());
     m_aInvalidations = tools::Rectangle();
 
     // When selecting and deleting several bullets: select till the end of the 
2nd para and delete.
diff --git a/sw/source/core/crsr/viscrs.cxx b/sw/source/core/crsr/viscrs.cxx
index b664e5f6c80b..26e3a4d1d30e 100644
--- a/sw/source/core/crsr/viscrs.cxx
+++ b/sw/source/core/crsr/viscrs.cxx
@@ -224,12 +224,64 @@ void SwVisibleCursor::SetPosAndShow(SfxViewShell const * 
pViewShell)
             
m_pCursorShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART,
 aPayload.getStr());
         }
 
+        // This may get called often, so instead of sending data on each 
update, just notify
+        // that there's been an update, and the other side will pull the data 
using
+        // getLOKPayload() when it decides to.
+        m_aLastLOKRect = aRect;
+        if (pViewShell)
+        {
+            if (pViewShell == m_pCursorShell->GetSfxViewShell())
+            {
+                SfxLokHelper::notifyUpdatePerViewId(pViewShell, 
LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR);
+            }
+            else
+            {
+                SfxLokHelper::notifyUpdatePerViewId(pViewShell, 
m_pCursorShell->GetSfxViewShell(), pViewShell,
+                    LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
+            }
+        }
+        else
+        {
+            
SfxLokHelper::notifyUpdatePerViewId(m_pCursorShell->GetSfxViewShell(), 
SfxViewShell::Current(),
+                m_pCursorShell->GetSfxViewShell(), 
LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR);
+            
SfxLokHelper::notifyOtherViewsUpdatePerViewId(m_pCursorShell->GetSfxViewShell(),
 LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
+        }
+    }
+
+    if ( m_pCursorShell->IsCursorReadonly() && 
!m_pCursorShell->GetViewOptions()->IsSelectionInReadonly() )
+        return;
+
+    if ( m_pCursorShell->GetDrawView() )
+        const_cast<SwDrawView*>(static_cast<const 
SwDrawView*>(m_pCursorShell->GetDrawView()))->SetAnimationEnabled(
+                !m_pCursorShell->IsSelection() );
+
+    sal_uInt16 nStyle = m_bIsDragCursor ? CURSOR_SHADOW : 0;
+    if( nStyle != m_aTextCursor.GetStyle() )
+    {
+        m_aTextCursor.SetStyle( nStyle );
+        m_aTextCursor.SetWindow( m_bIsDragCursor ? m_pCursorShell->GetWin() : 
nullptr );
+    }
+
+    m_aTextCursor.Show();
+}
+
+OString SwVisibleCursor::getLOKPayload(int nType, int nViewId) const
+{
+    assert(nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR || nType == 
LOK_CALLBACK_INVALIDATE_VIEW_CURSOR);
+    if (comphelper::LibreOfficeKit::isActive())
+    {
+        SwRect aRect = m_aLastLOKRect;
+
         // notify about the cursor position & size
         tools::Rectangle aSVRect(aRect.Pos().getX(), aRect.Pos().getY(), 
aRect.Pos().getX() + aRect.SSize().Width(), aRect.Pos().getY() + 
aRect.SSize().Height());
         OString sRect = aSVRect.toString();
 
+        if(nType == LOK_CALLBACK_INVALIDATE_VIEW_CURSOR)
+            return 
SfxLokHelper::makePayloadJSON(m_pCursorShell->GetSfxViewShell(), nViewId, 
"rectangle", sRect);
+
         // is cursor at a misspelled word ?
         bool bIsWrong = false;
+        SwView* pView = 
dynamic_cast<SwView*>(m_pCursorShell->GetSfxViewShell());
         if (pView && pView->GetWrtShellPtr())
         {
             const SwViewOption* pVOpt = pView->GetWrtShell().GetViewOptions();
@@ -284,37 +336,10 @@ void SwVisibleCursor::SetPosAndShow(SfxViewShell const * 
pViewShell)
             }
         }
 
-        if (pViewShell)
-        {
-            if (pViewShell == m_pCursorShell->GetSfxViewShell())
-            {
-                SfxLokHelper::notifyVisCursorInvalidation(pViewShell, sRect, 
bIsWrong, sHyperlink);
-            }
-            else
-                
SfxLokHelper::notifyOtherView(m_pCursorShell->GetSfxViewShell(), pViewShell, 
LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", sRect);
-        }
-        else
-        {
-            
SfxLokHelper::notifyVisCursorInvalidation(m_pCursorShell->GetSfxViewShell(), 
sRect, bIsWrong, sHyperlink);
-            SfxLokHelper::notifyOtherViews(m_pCursorShell->GetSfxViewShell(), 
LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", sRect);
-        }
+        return SfxLokHelper::makeVisCursorInvalidation(nViewId, sRect, 
bIsWrong, sHyperlink);
     }
-
-    if ( m_pCursorShell->IsCursorReadonly() && 
!m_pCursorShell->GetViewOptions()->IsSelectionInReadonly() )
-        return;
-
-    if ( m_pCursorShell->GetDrawView() )
-        const_cast<SwDrawView*>(static_cast<const 
SwDrawView*>(m_pCursorShell->GetDrawView()))->SetAnimationEnabled(
-                !m_pCursorShell->IsSelection() );
-
-    sal_uInt16 nStyle = m_bIsDragCursor ? CURSOR_SHADOW : 0;
-    if( nStyle != m_aTextCursor.GetStyle() )
-    {
-        m_aTextCursor.SetStyle( nStyle );
-        m_aTextCursor.SetWindow( m_bIsDragCursor ? m_pCursorShell->GetWin() : 
nullptr );
-    }
-
-    m_aTextCursor.Show();
+    else
+        abort();
 }
 
 const vcl::Cursor& SwVisibleCursor::GetTextCursor() const
diff --git a/sw/source/uibase/inc/wrtsh.hxx b/sw/source/uibase/inc/wrtsh.hxx
index a8cc9a49d6cb..a98cb26bfdfc 100644
--- a/sw/source/uibase/inc/wrtsh.hxx
+++ b/sw/source/uibase/inc/wrtsh.hxx
@@ -493,6 +493,8 @@ typedef bool (SwWrtShell::*FNSimpleMove)();
     void ToggleOutlineContentVisibility(SwNode* pNd, bool bForceFold = false);
     void ToggleOutlineContentVisibility(const size_t nPos, bool bForceFold = 
false);
 
+    OString getLOKPayload(int nType, int nViewId) const;
+
 private:
 
     SAL_DLLPRIVATE void  OpenMark();
diff --git a/sw/source/uibase/uiview/view.cxx b/sw/source/uibase/uiview/view.cxx
index 96f53fda5923..ebf2a31fc08c 100644
--- a/sw/source/uibase/uiview/view.cxx
+++ b/sw/source/uibase/uiview/view.cxx
@@ -1892,6 +1892,13 @@ void SwView::flushPendingLOKInvalidateTiles()
     pSh->FlushPendingLOKInvalidateTiles();
 }
 
+OString SwView::getLOKPayload(int nType, int nViewId) const
+{
+    SwWrtShell* pSh = GetWrtShellPtr();
+    assert(pSh);
+    return pSh->getLOKPayload(nType, nViewId);
+}
+
 OUString SwView::GetDataSourceName() const
 {
     uno::Reference<lang::XMultiServiceFactory> 
xFactory(GetDocShell()->GetModel(), uno::UNO_QUERY);
diff --git a/sw/source/uibase/wrtsh/wrtsh4.cxx 
b/sw/source/uibase/wrtsh/wrtsh4.cxx
index 8009ce98037b..b80b41b41e54 100644
--- a/sw/source/uibase/wrtsh/wrtsh4.cxx
+++ b/sw/source/uibase/wrtsh/wrtsh4.cxx
@@ -19,6 +19,8 @@
 
 #include <wrtsh.hxx>
 
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
 // Private methods, which move the cursor over search.
 // The removal of the selection must be made on the level above.
 
@@ -232,4 +234,15 @@ bool SwWrtShell::BwdPara_()
     return bRet;
 }
 
+OString SwWrtShell::getLOKPayload(int nType, int nViewId) const
+{
+    switch(nType)
+    {
+        case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+        case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
+            return GetVisibleCursor()->getLOKPayload(nType, nViewId);
+    }
+    abort();
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/test/source/lokcallback.cxx b/test/source/lokcallback.cxx
index 13d381f0b46a..8cda54424961 100644
--- a/test/source/lokcallback.cxx
+++ b/test/source/lokcallback.cxx
@@ -25,22 +25,37 @@ 
TestLokCallbackWrapper::TestLokCallbackWrapper(LibreOfficeKitCallback callback,
     SetPriority(TaskPriority::LOWEST);
 }
 
-inline void TestLokCallbackWrapper::callCallback(int nType, const char* 
pPayload)
+void TestLokCallbackWrapper::clear()
+{
+    m_viewId = -1;
+    m_updatedTypes.clear();
+    m_updatedTypesPerViewId.clear();
+}
+
+inline void TestLokCallbackWrapper::startTimer()
 {
-    m_callback(nType, pPayload, m_data);
     if (!IsActive())
         Start();
 }
 
+static constexpr int NO_VIEWID = -1;
+
+inline void TestLokCallbackWrapper::callCallback(int nType, const char* 
pPayload, int nViewId)
+{
+    discardUpdatedTypes(nType, nViewId);
+    m_callback(nType, pPayload, m_data);
+    startTimer();
+}
+
 void TestLokCallbackWrapper::libreOfficeKitViewCallback(int nType, const char* 
pPayload)
 {
-    callCallback(nType, pPayload);
+    callCallback(nType, pPayload, NO_VIEWID);
 }
 
 void TestLokCallbackWrapper::libreOfficeKitViewCallbackWithViewId(int nType, 
const char* pPayload,
-                                                                  int 
/*nViewId*/)
+                                                                  int nViewId)
 {
-    callCallback(nType, pPayload); // the view id is also included in payload
+    callCallback(nType, pPayload, nViewId);
 }
 
 void TestLokCallbackWrapper::libreOfficeKitViewInvalidateTilesCallback(
@@ -56,7 +71,100 @@ void 
TestLokCallbackWrapper::libreOfficeKitViewInvalidateTilesCallback(
         buf.append(", ");
         buf.append(static_cast<sal_Int32>(nPart));
     }
-    callCallback(LOK_CALLBACK_INVALIDATE_TILES, 
buf.makeStringAndClear().getStr());
+    callCallback(LOK_CALLBACK_INVALIDATE_TILES, 
buf.makeStringAndClear().getStr(), NO_VIEWID);
+}
+
+// TODO This is probably a pointless code duplication with 
CallbackFlushHandler,
+// and using this in unittests also means that CallbackFlushHandler does not 
get
+// tested as thoroughly as it could. On the other hand, this class is simpler,
+// so debugging those unittests should also be simpler. The proper solution
+// is presumably this class using CallbackFlushHandler internally by default,
+// but having an option to use this simpler code when needed.
+
+void TestLokCallbackWrapper::libreOfficeKitViewUpdatedCallback(int nType)
+{
+    if (std::find(m_updatedTypes.begin(), m_updatedTypes.end(), nType) == 
m_updatedTypes.end())
+    {
+        m_updatedTypes.push_back(nType);
+        startTimer();
+    }
+}
+
+void TestLokCallbackWrapper::libreOfficeKitViewUpdatedCallbackPerViewId(int 
nType, int nViewId,
+                                                                        int 
nSourceViewId)
+{
+    const PerViewIdData data{ nType, nViewId, nSourceViewId };
+    auto& l = m_updatedTypesPerViewId;
+    // The source view doesn't matter for uniqueness, just keep the latest one.
+    auto it = std::find_if(l.begin(), l.end(), [data](const PerViewIdData& 
other) {
+        return data.type == other.type && data.viewId == other.viewId;
+    });
+    if (it != l.end())
+        *it = data;
+    else
+        l.push_back(data);
+    startTimer();
+}
+
+void TestLokCallbackWrapper::discardUpdatedTypes(int nType, int nViewId)
+{
+    // If a callback is called directly with an event, drop the updated flag 
for it, since
+    // the direct event replaces it.
+    for (auto it = m_updatedTypes.begin(); it != m_updatedTypes.end();)
+    {
+        if (*it == nType)
+            it = m_updatedTypes.erase(it);
+        else
+            ++it;
+    }
+    // If we do not have a specific view id, drop flag for all views.
+    bool allViewIds = false;
+    if (nViewId < 0)
+        allViewIds = true;
+    if (nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR
+        && !comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
+        allViewIds = true;
+    for (auto it = m_updatedTypesPerViewId.begin(); it != 
m_updatedTypesPerViewId.end();)
+    {
+        if (it->type == nType && (allViewIds || it->viewId == nViewId))
+            it = m_updatedTypesPerViewId.erase(it);
+        else
+            ++it;
+    }
+}
+
+void TestLokCallbackWrapper::flushLOKData()
+{
+    if (m_updatedTypes.empty() && m_updatedTypesPerViewId.empty())
+        return;
+    // Ask for payloads of all the pending types that need updating, and call 
the generic callback with that data.
+    assert(m_viewId >= 0);
+    SfxViewShell* viewShell = SfxViewShell::GetFirst(false, [this](const 
SfxViewShell* shell) {
+        return shell->GetViewShellId().get() == m_viewId;
+    });
+    assert(viewShell != nullptr);
+    // First move data to local structures, so that notifyFromLOKCallback() 
doesn't modify it.
+    std::vector<int> updatedTypes;
+    std::swap(updatedTypes, m_updatedTypes);
+    std::vector<PerViewIdData> updatedTypesPerViewId;
+    std::swap(updatedTypesPerViewId, m_updatedTypesPerViewId);
+
+    for (int type : updatedTypes)
+    {
+        OString payload = viewShell->getLOKPayload(type, m_viewId);
+        if (!payload.isEmpty())
+            libreOfficeKitViewCallback(type, payload.getStr());
+    }
+    for (const PerViewIdData& data : updatedTypesPerViewId)
+    {
+        viewShell = SfxViewShell::GetFirst(false, [data](const SfxViewShell* 
shell) {
+            return shell->GetViewShellId().get() == data.sourceViewId;
+        });
+        assert(viewShell != nullptr);
+        OString payload = viewShell->getLOKPayload(data.type, data.viewId);
+        if (!payload.isEmpty())
+            libreOfficeKitViewCallbackWithViewId(data.type, payload.getStr(), 
data.viewId);
+    }
 }
 
 void TestLokCallbackWrapper::Invoke()
@@ -67,6 +175,7 @@ void TestLokCallbackWrapper::Invoke()
     {
         viewShell->flushPendingLOKInvalidateTiles();
     }
+    flushLOKData();
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to