@@ -134,12 +134,21 @@ public class ChromiumWebBrowser : Control, IRenderWebBrowser, IWpfWebBrowser
134134 /// </summary>
135135 private static bool DesignMode ;
136136
137+ private bool resizeHackForIssue2779Enabled ;
138+ private CefSharp . Structs . Size ? resizeHackForIssue2779Size ;
139+
137140 /// <summary>
138141 /// The value for disposal, if it's 1 (one) then this instance is either disposed
139142 /// or in the process of getting disposed
140143 /// </summary>
141144 private int disposeSignaled ;
142145
146+ /// <summary>
147+ /// Hack to work around issue https://github.com/cefsharp/CefSharp/issues/2779
148+ /// Enabled by default
149+ /// </summary>
150+ public bool EnableResizeHackForIssue2779 { get ; set ; }
151+
143152 /// <summary>
144153 /// Gets a value indicating whether this instance is disposed.
145154 /// </summary>
@@ -557,6 +566,8 @@ public ChromiumWebBrowser(string initialAddress)
557566 [ MethodImpl ( MethodImplOptions . NoInlining ) ]
558567 private void NoInliningConstructor ( )
559568 {
569+ EnableResizeHackForIssue2779 = true ;
570+
560571 //Initialize CEF if it hasn't already been initialized
561572 if ( ! Cef . IsInitialized )
562573 {
@@ -822,7 +833,18 @@ Rect IRenderWebBrowser.GetViewRect()
822833 /// <returns>View Rectangle</returns>
823834 protected virtual Rect GetViewRect ( )
824835 {
825- return viewRect ;
836+ //Take a local copy as the value is set on a different thread,
837+ //Its possible the struct is set to null after our initial check.
838+ var resizeRect = resizeHackForIssue2779Size ;
839+
840+ if ( resizeRect == null )
841+ {
842+ return viewRect ;
843+ }
844+
845+ var size = resizeRect . Value ;
846+
847+ return new Rect ( 0 , 0 , size . Width , size . Height ) ;
826848 }
827849
828850 bool IRenderWebBrowser . GetScreenPoint ( int viewX , int viewY , out int screenX , out int screenY )
@@ -967,6 +989,11 @@ void IRenderWebBrowser.OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buf
967989 /// <param name="height">height</param>
968990 protected virtual void OnPaint ( bool isPopup , Rect dirtyRect , IntPtr buffer , int width , int height )
969991 {
992+ if ( resizeHackForIssue2779Enabled )
993+ {
994+ return ;
995+ }
996+
970997 var paint = Paint ;
971998 if ( paint != null )
972999 {
@@ -1759,7 +1786,7 @@ private void PresentationSourceChangedHandler(object sender, SourceChangedEventA
17591786 }
17601787 }
17611788
1762- private void OnWindowStateChanged ( object sender , EventArgs e )
1789+ private async void OnWindowStateChanged ( object sender , EventArgs e )
17631790 {
17641791 var window = ( Window ) sender ;
17651792
@@ -1770,16 +1797,54 @@ private void OnWindowStateChanged(object sender, EventArgs e)
17701797 {
17711798 if ( previousWindowState == WindowState . Minimized && browser != null )
17721799 {
1773- browser . GetHost ( ) . WasHidden ( false ) ;
1800+ await CefUiThreadRunAsync ( async ( ) =>
1801+ {
1802+ var host = browser ? . GetHost ( ) ;
1803+ if ( host != null && ! host . IsDisposed )
1804+ {
1805+ try
1806+ {
1807+ host . WasHidden ( false ) ;
1808+
1809+ await ResizeHackFor2779 ( ) ;
1810+ }
1811+ catch ( ObjectDisposedException )
1812+ {
1813+ // Because Dispose runs in another thread there's a race condition between
1814+ // that and this code running on the CEF UI thread, so the host could be disposed
1815+ // between the check and using it. We can either synchronize access using locking
1816+ // (potentially blocking the UI thread in Dispose) or catch the extremely rare
1817+ // exception, which is what we do here
1818+ }
1819+ }
1820+ } ) ;
17741821 }
1822+
17751823 break ;
17761824 }
17771825 case WindowState . Minimized :
17781826 {
1779- if ( browser != null )
1827+ await CefUiThreadRunAsync ( ( ) =>
17801828 {
1781- browser . GetHost ( ) . WasHidden ( true ) ;
1782- }
1829+ var host = browser ? . GetHost ( ) ;
1830+ if ( host != null && ! host . IsDisposed )
1831+ {
1832+ if ( EnableResizeHackForIssue2779 )
1833+ {
1834+ resizeHackForIssue2779Enabled = true ;
1835+ }
1836+
1837+ try
1838+ {
1839+ host . WasHidden ( true ) ;
1840+ }
1841+ catch ( ObjectDisposedException )
1842+ {
1843+ // See comment in catch in OnWindowStateChanged
1844+ }
1845+ }
1846+ } ) ;
1847+
17831848 break ;
17841849 }
17851850 }
@@ -1916,6 +1981,24 @@ internal void UiThreadRunAsync(Action action, DispatcherPriority priority = Disp
19161981 }
19171982 }
19181983
1984+ protected async Task CefUiThreadRunAsync ( Action action )
1985+ {
1986+ if ( ! IsDisposed && InternalIsBrowserInitialized ( ) )
1987+ {
1988+ if ( Cef . CurrentlyOnThread ( CefThreadIds . TID_UI ) )
1989+ {
1990+ action ( ) ;
1991+ }
1992+ else
1993+ {
1994+ await Cef . UIThreadTaskFactory . StartNew ( delegate
1995+ {
1996+ action ( ) ;
1997+ } ) ;
1998+ }
1999+ }
2000+ }
2001+
19192002 /// <summary>
19202003 /// Runs the specific Action on the Dispatcher in an sync fashion
19212004 /// </summary>
@@ -1938,52 +2021,97 @@ private void UiThreadRunSync(Action action, DispatcherPriority priority = Dispat
19382021 /// </summary>
19392022 /// <param name="sender">The sender.</param>
19402023 /// <param name="e">The <see cref="SizeChangedEventArgs"/> instance containing the event data.</param>
1941- private void OnActualSizeChanged ( object sender , SizeChangedEventArgs e )
2024+ private async void OnActualSizeChanged ( object sender , SizeChangedEventArgs e )
19422025 {
19432026 // Initialize RenderClientAdapter when WPF has calculated the actual size of current content.
19442027 CreateOffscreenBrowser ( e . NewSize ) ;
19452028
2029+ if ( InternalIsBrowserInitialized ( ) )
2030+ {
2031+ await CefUiThreadRunAsync ( ( ) =>
2032+ {
2033+ SetViewRect ( e ) ;
2034+
2035+ var host = browser ? . GetHost ( ) ;
2036+ if ( host != null && ! host . IsDisposed )
2037+ {
2038+ try
2039+ {
2040+ host . WasResized ( ) ;
2041+ }
2042+ catch ( ObjectDisposedException )
2043+ {
2044+ // See comment in catch in OnWindowStateChanged
2045+ }
2046+ }
2047+ } ) ;
2048+ }
2049+ else
2050+ {
2051+ //If the browser hasn't been created yet then directly update the viewRect
2052+ SetViewRect ( e ) ;
2053+ }
2054+ }
2055+
2056+ private void SetViewRect ( SizeChangedEventArgs e )
2057+ {
19462058 //NOTE: Previous we used Math.Ceiling to round the sizing up, we
19472059 //now set UseLayoutRounding = true; on the control so the sizes are
19482060 //already rounded to a whole number for us.
19492061 viewRect = new Rect ( 0 , 0 , ( int ) e . NewSize . Width , ( int ) e . NewSize . Height ) ;
1950-
1951- if ( browser != null )
1952- {
1953- browser . GetHost ( ) . WasResized ( ) ;
1954- }
19552062 }
19562063
19572064 /// <summary>
19582065 /// Handles the <see cref="E:IsVisibleChanged" /> event.
19592066 /// </summary>
19602067 /// <param name="sender">The sender.</param>
19612068 /// <param name="args">The <see cref="DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
1962- private void OnIsVisibleChanged ( object sender , DependencyPropertyChangedEventArgs args )
2069+ private async void OnIsVisibleChanged ( object sender , DependencyPropertyChangedEventArgs args )
19632070 {
19642071 var isVisible = ( bool ) args . NewValue ;
19652072
19662073 if ( browser != null )
19672074 {
1968- var host = browser . GetHost ( ) ;
1969- host . WasHidden ( ! isVisible ) ;
2075+ await CefUiThreadRunAsync ( async ( ) =>
2076+ {
2077+ var host = browser ? . GetHost ( ) ;
2078+ if ( host != null && ! host . IsDisposed )
2079+ {
2080+ try
2081+ {
2082+ host . WasHidden ( ! isVisible ) ;
19702083
1971- if ( isVisible )
2084+ if ( isVisible )
2085+ {
2086+ await ResizeHackFor2779 ( ) ;
2087+ }
2088+ else if ( EnableResizeHackForIssue2779 )
2089+ {
2090+ resizeHackForIssue2779Enabled = true ;
2091+ }
2092+ }
2093+ catch ( ObjectDisposedException )
2094+ {
2095+ // See comment in catch in OnWindowStateChanged
2096+ }
2097+ }
2098+ } ) ;
2099+
2100+ if ( browser != null )
19722101 {
19732102 //Fix for #1778 - When browser becomes visible we update the zoom level
19742103 //browsers of the same origin will share the same zoomlevel and
19752104 //we need to track the update, so our ZoomLevelProperty works
19762105 //properly
1977- host . GetZoomLevelAsync ( ) . ContinueWith ( t =>
2106+ var zoomLevel = await browser . GetHost ( ) . GetZoomLevelAsync ( ) ;
2107+
2108+ UiThreadRunAsync ( ( ) =>
19782109 {
19792110 if ( ! IsDisposed )
19802111 {
1981- SetCurrentValue ( ZoomLevelProperty , t . Result ) ;
2112+ SetCurrentValue ( ZoomLevelProperty , zoomLevel ) ;
19822113 }
1983- } ,
1984- CancellationToken . None ,
1985- TaskContinuationOptions . OnlyOnRanToCompletion ,
1986- TaskScheduler . FromCurrentSynchronizationContext ( ) ) ;
2114+ } ) ;
19872115 }
19882116 }
19892117 }
@@ -2625,5 +2753,32 @@ private bool InternalIsBrowserInitialized()
26252753 // Volatile.Read would likely use a memory barrier which I believe is unnecessary in this scenario
26262754 return Interlocked . CompareExchange ( ref browserInitialized , 0 , 0 ) == 1 ;
26272755 }
2756+
2757+ private async Task ResizeHackFor2779 ( )
2758+ {
2759+ if ( EnableResizeHackForIssue2779 )
2760+ {
2761+ const int delayInMs = 50 ;
2762+
2763+ var host = browser ? . GetHost ( ) ;
2764+ if ( host != null && ! host . IsDisposed )
2765+ {
2766+ resizeHackForIssue2779Size = new Structs . Size ( viewRect . Width - 1 , viewRect . Height - 1 ) ;
2767+ host . WasResized ( ) ;
2768+
2769+ await Task . Delay ( delayInMs ) ;
2770+
2771+ if ( ! host . IsDisposed )
2772+ {
2773+ resizeHackForIssue2779Size = null ;
2774+ host . WasResized ( ) ;
2775+
2776+ resizeHackForIssue2779Enabled = false ;
2777+
2778+ host . Invalidate ( PaintElementType . View ) ;
2779+ }
2780+ }
2781+ }
2782+ }
26282783 }
26292784}
0 commit comments