tdf#152703 Force relayout during live resizing of window

Merge the following commits from the master branch:

3f7406bc4df3c7d6cc618312607bff1ec36a12f7
2d1a0d86d2d0c00fcfee61c39f2221e786e4245b
fed429e4f6f437997aa6a88e2d071f58aa00ee34
24eabdbebcbc3b9189bbb9809205ead9f903a0cb
d56d76a4204aad18f75463b0c9aa6130d558e0ef
eefc323cd592c7958b13062e3f08e105d24055b1
9cc91e17172709c65742c092d3f312bce48ac6d9

Change-Id: Id0f001b3a1e09a6187bf5c08611e7eaa06385743
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/145918
Tested-by: Jenkins
Reviewed-by: Patrick Luby <plubius@neooffice.org>
diff --git a/vcl/inc/osx/salframeview.h b/vcl/inc/osx/salframeview.h
index 2fcff0d..6242f3d 100644
--- a/vcl/inc/osx/salframeview.h
+++ b/vcl/inc/osx/salframeview.h
@@ -28,8 +28,13 @@ enum class SalEvent;
{
    AquaSalFrame*       mpFrame;
    id mDraggingDestinationHandler;
    BOOL                mbInLiveResize;
    BOOL                mbInWindowDidResize;
    NSTimer*            mpLiveResizeTimer;
}
-(id)initWithSalFrame: (AquaSalFrame*)pFrame;
-(void)clearLiveResizeTimer;
-(void)dealloc;
-(BOOL)canBecomeKeyWindow;
-(void)displayIfNeeded;
-(void)windowDidBecomeKey: (NSNotification*)pNotification;
@@ -62,6 +67,8 @@ enum class SalEvent;

-(void)endExtTextInput;
-(void)endExtTextInput:(EndExtTextInputFlags)nFlags;

-(void)windowDidResizeWithTimer:(NSTimer *)pTimer;
@end

@interface SalFrameView : AquaA11yWrapper <NSTextInputClient>
diff --git a/vcl/inc/osx/salinst.h b/vcl/inc/osx/salinst.h
index 1e6fce7..8811fa3 100644
--- a/vcl/inc/osx/salinst.h
+++ b/vcl/inc/osx/salinst.h
@@ -89,7 +89,6 @@ public:
    int                                     mnActivePrintJobs;
    osl::Mutex                              maUserEventListMutex;
    osl::Condition                          maWaitingYieldCond;
    bool                                    mbIsLiveResize;
    bool                                    mbNoYieldLock;
    bool                                    mbTimerProcessed;

diff --git a/vcl/inc/svdata.hxx b/vcl/inc/svdata.hxx
index 3651eb3..06d0aeb9 100644
--- a/vcl/inc/svdata.hxx
+++ b/vcl/inc/svdata.hxx
@@ -269,6 +269,7 @@ struct ImplSVWinData
    StartAutoScrollFlags    mnAutoScrollFlags = StartAutoScrollFlags::NONE; // auto scroll flags
    bool                    mbNoDeactivate = false;         // true: do not execute Deactivate
    bool                    mbNoSaveFocus = false;          // true: menus must not save/restore focus
    bool                    mbIsLiveResize = false;         // true: skip waiting for events and low priority timers
};

typedef std::vector< std::pair< OUString, FieldUnit > > FieldUnitStringList;
diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm
index 09643b5..8445a6c 100644
--- a/vcl/osx/salframeview.mm
+++ b/vcl/osx/salframeview.mm
@@ -169,6 +169,9 @@ static AquaSalFrame* getMouseContainerFrame()
-(id)initWithSalFrame: (AquaSalFrame*)pFrame
{
    mDraggingDestinationHandler = nil;
    mbInLiveResize = NO;
    mbInWindowDidResize = NO;
    mpLiveResizeTimer = nil;
    mpFrame = pFrame;
    NSRect aRect = { { static_cast<CGFloat>(pFrame->maGeometry.x()), static_cast<CGFloat>(pFrame->maGeometry.y()) },
                     { static_cast<CGFloat>(pFrame->maGeometry.width()), static_cast<CGFloat>(pFrame->maGeometry.height()) } };
@@ -209,6 +212,22 @@ static AquaSalFrame* getMouseContainerFrame()
    return static_cast<SalFrameWindow *>(pNSWindow);
}

-(void)clearLiveResizeTimer
{
    if ( mpLiveResizeTimer )
    {
        [mpLiveResizeTimer invalidate];
        [mpLiveResizeTimer release];
        mpLiveResizeTimer = nil;
    }
}

-(void)dealloc
{
    [self clearLiveResizeTimer];
    [super dealloc];
}

-(AquaSalFrame*)getSalFrame
{
    return mpFrame;
@@ -219,21 +238,6 @@ static AquaSalFrame* getMouseContainerFrame()
    if( GetSalData() && GetSalData()->mpInstance )
    {
        SolarMutexGuard aGuard;

#if HAVE_FEATURE_SKIA
        // Related: tdf#152703 Eliminate empty window with Skia/Metal while resizing
        // The window will clear its background so when Skia/Metal is enabled,
        // explicitly flush the Skia graphics to the window during live
        // resizing or else nothing will be drawn until after live resizing
        // has ended.
        if ( [self inLiveResize] && SkiaHelper::isVCLSkiaEnabled() && mpFrame && AquaSalFrame::isAlive( mpFrame ) )
        {
            AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
            if ( pGraphics )
                pGraphics->Flush();
        }
#endif

        [super displayIfNeeded];
    }
}
@@ -332,22 +336,100 @@ static AquaSalFrame* getMouseContainerFrame()
    (void)pNotification;
    SolarMutexGuard aGuard;

    if ( mbInWindowDidResize )
        return;

    mbInWindowDidResize = YES;

    if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
    {
        mpFrame->UpdateFrameGeometry();
        mpFrame->CallCallback( SalEvent::Resize, nullptr );

        // Related: tdf#152703 Stop flicker with Skia/Metal while resizing
        // When Skia/Metal is enabled, rapidly resizing a window has a
        // noticeable amount of flicker so don't send any paint events during
        // live resizing.
        // Also, it appears that most of the LibreOffice layouts do not change
        // their layout much during live resizing so apply this change when
        // Skia is not enabled to ensure consistent behavior whether Skia is
        // enabled or not.
        if ( ![self inLiveResize] )
        bool bInLiveResize = [self inLiveResize];
        ImplSVData* pSVData = ImplGetSVData();
        assert( pSVData );
        if ( pSVData )
        {
            const bool bWasLiveResize = pSVData->mpWinData->mbIsLiveResize;
            if ( bWasLiveResize != bInLiveResize )
            {
                pSVData->mpWinData->mbIsLiveResize = bInLiveResize;
                Scheduler::Wakeup();
            }
        }

        if ( bInLiveResize || mbInLiveResize )
        {
            mbInLiveResize = bInLiveResize;

#if HAVE_FEATURE_SKIA
            // Related: tdf#152703 Eliminate empty window with Skia/Metal while resizing
            // The window will clear its background so when Skia/Metal is
            // enabled, explicitly flush the Skia graphics to the window
            // during live resizing or else nothing will be drawn until after
            // live resizing has ended.
            // Also, flushing during [self windowDidResize:] eliminates flicker
            // by forcing this window's SkSurface to recreate its underlying
            // CAMetalLayer with the new size. Flushing in
            // [self displayIfNeeded] does not eliminate flicker so apparently
            // [self windowDidResize:] is called earlier.
            if ( SkiaHelper::isVCLSkiaEnabled() )
            {
                AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
                if ( pGraphics )
                    pGraphics->Flush();
            }
#endif

            // tdf#152703 Force relayout during live resizing of window
            // During a live resize, macOS floods the application with
            // windowDidResize: notifications so sending a paint event does
            // not trigger redrawing with the new size.
            // Instead, force relayout by dispatching all pending internal
            // events and firing any pending timers.
            // Also, Application::Reschedule() can potentially display a
            // modal dialog which will cause a hang so temporarily disable
            // live resize by clamping the window's minimum and maximum sizes
            // to the current frame size which in Application::Reschedule().
            NSRect aFrame = [self frame];
            NSSize aMinSize = [self minSize];
            NSSize aMaxSize = [self maxSize];
            [self setMinSize:aFrame.size];
            [self setMaxSize:aFrame.size];
            Application::Reschedule( true );
            [self setMinSize:aMinSize];
            [self setMaxSize:aMaxSize];

            if ( mbInLiveResize )
            {
                // tdf#152703 Force repaint after live resizing ends
                // Repost this notification so that this selector will be called
                // at least once after live resizing ends
                if ( !mpLiveResizeTimer )
                {
                    mpLiveResizeTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(windowDidResizeWithTimer:) userInfo:pNotification repeats:YES];
                    if ( mpLiveResizeTimer )
                    {
                        [mpLiveResizeTimer retain];

                        // The timer won't fire without a call to
                        // Application::Reschedule() unless we copy the fix for
                        // #i84055# from vcl/osx/saltimer.cxx and add the timer
                        // to the NSEventTrackingRunLoopMode run loop mode
                        [[NSRunLoop currentRunLoop] addTimer:mpLiveResizeTimer forMode:NSEventTrackingRunLoopMode];
                    }
                }
            }
        }
        else
        {
            [self clearLiveResizeTimer];
            mpFrame->SendPaintEvent();
        }
    }

    mbInWindowDidResize = NO;
}

-(void)windowDidMiniaturize: (NSNotification*)pNotification
@@ -486,6 +568,12 @@ static AquaSalFrame* getMouseContainerFrame()
        [pView endExtTextInput:nFlags];
}

-(void)windowDidResizeWithTimer:(NSTimer *)pTimer
{
    if ( pTimer )
        [self windowDidResize:[pTimer userInfo]];
}

@end

@implementation SalFrameView
@@ -565,9 +653,9 @@ static AquaSalFrame* getMouseContainerFrame()

-(void)drawRect: (NSRect)aRect
{
    AquaSalInstance *pInstance = GetSalData()->mpInstance;
    assert(pInstance);
    if (!pInstance)
    ImplSVData* pSVData = ImplGetSVData();
    assert( pSVData );
    if ( !pSVData )
        return;

    SolarMutexGuard aGuard;
@@ -575,10 +663,10 @@ static AquaSalFrame* getMouseContainerFrame()
        return;

    const bool bIsLiveResize = [self inLiveResize];
    const bool bWasLiveResize = pInstance->mbIsLiveResize;
    const bool bWasLiveResize = pSVData->mpWinData->mbIsLiveResize;
    if (bWasLiveResize != bIsLiveResize)
    {
        pInstance->mbIsLiveResize = bIsLiveResize;
        pSVData->mpWinData->mbIsLiveResize = bIsLiveResize;
        Scheduler::Wakeup();
    }

@@ -1705,7 +1793,7 @@ static AquaSalFrame* getMouseContainerFrame()
        else
             mSelectedRange = NSMakeRange( selRange.location, selRange.location + selRange.length > mMarkedRange.length ? mMarkedRange.length - selRange.location : selRange.length );

        // If we are going to post uncommitted text, cache the string paramater
        // If we are going to post uncommitted text, cache the string parameter
        // as is needed in both [self endExtTextInput] and
        // [self attributedSubstringForProposedRange:actualRange:]
        mpLastMarkedText = [aString retain];
@@ -1851,7 +1939,7 @@ static AquaSalFrame* getMouseContainerFrame()
    // the returned position won't be anywhere near the text cursor. So,
    // dispatch an empty SalEvent::ExtTextInput event, fetch the position,
    // and then dispatch a SalEvent::EndExtTextInput event.
    BOOL bNeedsExtTextInput = ( mbInKeyInput && !mpLastMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] );
    bool bNeedsExtTextInput = ( mbInKeyInput && !mpLastMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] );
    if ( bNeedsExtTextInput )
    {
        SalExtTextInputEvent aInputEvent;
@@ -2018,7 +2106,7 @@ static AquaSalFrame* getMouseContainerFrame()
            mbInCommitMarkedText = YES;
            if (nFlags & EndExtTextInputFlags::Complete)
            {
                // Retain the last marked text as it will be releasd in
                // Retain the last marked text as it will be released in
                // [self insertText:replacementText:]
                NSAttributedString *pText = [mpLastMarkedText retain];
                [self insertText:pText replacementRange:NSMakeRange(0, [mpLastMarkedText length])];
diff --git a/vcl/osx/salinst.cxx b/vcl/osx/salinst.cxx
index e8a4a94e..ee5a1b2 100644
--- a/vcl/osx/salinst.cxx
+++ b/vcl/osx/salinst.cxx
@@ -368,7 +368,6 @@ VCLPLUG_OSX_PUBLIC SalInstance* create_SalInstance()
AquaSalInstance::AquaSalInstance()
    : SalInstance(std::make_unique<SalYieldMutex>())
    , mnActivePrintJobs( 0 )
    , mbIsLiveResize( false )
    , mbNoYieldLock( false )
    , mbTimerProcessed( false )
{
@@ -556,6 +555,13 @@ static bool isWakeupEvent( NSEvent *pEvent )

bool AquaSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
{
    // Related: tdf#152703 Eliminate potential blocking during live resize
    // Some events and timers call Application::Reschedule() or
    // Application::Yield() so don't block and wait for events when a
    // window is in live resize
    if ( ImplGetSVData()->mpWinData->mbIsLiveResize )
        bWait = false;

    // ensure that the per thread autorelease pool is top level and
    // will therefore not be destroyed by cocoa implicitly
    SalData::ensureThreadAutoreleasePool();
@@ -565,7 +571,11 @@ bool AquaSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
    ReleasePoolHolder aReleasePool;

    // first, process current user events
    bool bHadEvent = DispatchUserEvents( bHandleAllCurrentEvents );
    // Related: tdf#152703 Eliminate potential blocking during live resize
    // Only native events and timers need to be dispatched to redraw
    // the window so skip dispatching user events when a window is in
    // live resize
    bool bHadEvent = ( !ImplGetSVData()->mpWinData->mbIsLiveResize && DispatchUserEvents( bHandleAllCurrentEvents ) );
    if ( !bHandleAllCurrentEvents && bHadEvent )
        return true;

diff --git a/vcl/osx/saltimer.cxx b/vcl/osx/saltimer.cxx
index 4c33c32..8af7de2 100644
--- a/vcl/osx/saltimer.cxx
+++ b/vcl/osx/saltimer.cxx
@@ -83,7 +83,7 @@ void AquaSalTimer::Start( sal_uInt64 nMS )
        return;
    }

    m_bDirectTimeout = (0 == nMS) && !pSalData->mpInstance->mbIsLiveResize;
    m_bDirectTimeout = (0 == nMS) && !ImplGetSVData()->mpWinData->mbIsLiveResize;
    if ( m_bDirectTimeout )
        Stop();
    else
@@ -142,7 +142,7 @@ void AquaSalTimer::callTimerCallback()

void AquaSalTimer::handleTimerElapsed()
{
    if ( m_bDirectTimeout || GetSalData()->mpInstance->mbIsLiveResize )
    if ( m_bDirectTimeout || ImplGetSVData()->mpWinData->mbIsLiveResize )
    {
        // Stop the timer, as it is just invalidated after the firing function
        Stop();
diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx
index 896849b..c218d5d 100644
--- a/vcl/skia/gdiimpl.cxx
+++ b/vcl/skia/gdiimpl.cxx
@@ -372,6 +372,11 @@ void SkiaSalGraphicsImpl::performFlush()
{
    SkiaZone zone;
    flushDrawing();
    // Related: tdf#152703 Eliminate flickering during live resizing of a window
    // When in live resize, the SkiaSalGraphicsImpl class does not detect that
    // the window size has changed until after the flush has been called so
    // call checkSurface() to recreate the SkSurface if needed before flushing.
    checkSurface();
    if (mSurface)
    {
        if (mDirtyRect.intersect(SkIRect::MakeWH(GetWidth(), GetHeight())))
diff --git a/vcl/source/app/scheduler.cxx b/vcl/source/app/scheduler.cxx
index 251b972..f83a4bb 100644
--- a/vcl/source/app/scheduler.cxx
+++ b/vcl/source/app/scheduler.cxx
@@ -359,6 +359,13 @@ void Scheduler::CallbackTaskScheduling()

    for (; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
    {
        // Related: tdf#152703 Eliminate potential blocking during live resize
        // Only higher priority tasks need to be fired to redraw the window
        // so skip firing potentially long-running tasks, such as the Writer
        // idle layout timer, when a window is in live resize
        if ( ImplGetSVData()->mpWinData->mbIsLiveResize && nTaskPriority == static_cast<int>(TaskPriority::LOWEST) )
            continue;

        pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
        pPrevSchedulerData = nullptr;
        while (pSchedulerData)