using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; using Lskj.Util; using Xilium.CefGlue; namespace Lskj.Control { /// /// Keeps an application-owned root HWND alive for the complete CEF /// lifecycle. The root is created independently of recreatable WinForms /// control handles, then embedded after CEF reports OnAfterCreated. /// internal sealed class CefBrowserLifetimeCoordinator { // CEF 生命周期诊断默认关闭,避免正常创建和销毁浏览器时持续写入日志。 internal static readonly bool LifecycleTracingEnabled = false; private static readonly CefBrowserSettings _sharedBrowserSettings = new CefBrowserSettings(); private readonly object _hostWindowSyncRoot = new object(); private CefBrowser _browser; private CefBrowserHostWindow _hostWindow; private int _browserCreationPending; private int _browserCloseRequested; private int _browserCloseApproved; private int _browserBeforeCloseReceived; private int _disposeRequested; private int _ownerHandleDestroying; public CefBrowser Browser { get { return Interlocked.CompareExchange(ref _browser, null, null); } } public static CefBrowserSettings SharedBrowserSettings { get { return _sharedBrowserSettings; } } public bool IsDisposing { get { return Thread.VolatileRead(ref _disposeRequested) != 0; } } public bool IsUnavailable { get { return IsDisposing || Thread.VolatileRead(ref _ownerHandleDestroying) != 0; } } public bool HasBrowserOrPendingCreation { get { return Browser != null || Thread.VolatileRead(ref _browserCreationPending) != 0; } } public IntPtr GetCreationParentHandle( IntPtr ownerHandle, int width, int height) { if (ownerHandle == IntPtr.Zero || !CefBrowserNativeMethods.IsWindow(ownerHandle)) throw new ArgumentException( "A valid WinForms owner handle is required.", "ownerHandle"); CefBrowserHostWindow hostWindow; lock (_hostWindowSyncRoot) { if (_hostWindow == null || _hostWindow.IsDestroyed) { _hostWindow = new CefBrowserHostWindow( ownerHandle, width, height, HostWindowCloseRequested, HostWindowDestroyed); } hostWindow = _hostWindow; } IntPtr hostHandle = hostWindow.Handle; bool hostValid = hostHandle != IntPtr.Zero && CefBrowserNativeMethods.IsWindow(hostHandle); bool ownerValid = CefBrowserNativeMethods.IsWindow(ownerHandle); TraceClose( "creation-parent hostHwnd=" + hostHandle + " hostValid=" + hostValid + " hostParent=" + CefBrowserNativeMethods.GetParent(hostHandle) + " ownerHwnd=" + ownerHandle + " ownerValid=" + ownerValid + " hostThread=" + hostWindow.OwnerThreadId + " ownerThread=" + CefBrowserNativeMethods.GetWindowThread(ownerHandle), null, null); if (!hostValid || !ownerValid) throw new InvalidOperationException( "CEF browser creation requires live host and owner HWNDs."); return hostHandle; } public bool TryBeginBrowserCreation() { if (IsUnavailable || Browser != null) return false; if (Interlocked.CompareExchange(ref _browserCreationPending, 1, 0) != 0) return false; if (!CefBrowserRuntimeLifetime.TryRegister(this)) { Interlocked.Exchange(ref _browserCreationPending, 0); return false; } Interlocked.Exchange(ref _browserCloseApproved, 0); Interlocked.Exchange(ref _browserBeforeCloseReceived, 0); if (!IsUnavailable) return true; BrowserCreationFailed(); return false; } public void BrowserCreationFailed() { Interlocked.Exchange(ref _browserCreationPending, 0); RequestHostWindowDestroy("creation-failed"); TryReleaseHostWindow(); CefBrowserRuntimeLifetime.Unregister(this); } public bool BrowserCreated(CefBrowser browser) { if (browser == null) throw new ArgumentNullException("browser"); Interlocked.Exchange(ref _browserCreationPending, 0); var currentBrowser = Interlocked.CompareExchange( ref _browser, browser, null); TraceClose("created", browser, currentBrowser); if (currentBrowser != null && !IsSameBrowser(currentBrowser, browser)) { CloseBrowser(browser, true); return false; } if (IsUnavailable) { RequestClose(IsDisposing); return false; } return true; } public bool BrowserClosed(CefBrowser browser, Action clearOwnerBrowser) { var currentBrowser = Browser; bool sameBrowser = currentBrowser != null && IsSameBrowser(currentBrowser, browser); TraceClose( "on-before-close same=" + sameBrowser, browser, currentBrowser); if (!sameBrowser) return false; if (clearOwnerBrowser != null) clearOwnerBrowser(); if (currentBrowser != null) Interlocked.CompareExchange(ref _browser, null, currentBrowser); Interlocked.Exchange(ref _browserBeforeCloseReceived, 1); Interlocked.Exchange(ref _browserCloseRequested, 0); RequestHostWindowDestroy("on-before-close"); TryReleaseHostWindow(); DisposeBrowserReference(currentBrowser); CefBrowserRuntimeLifetime.Unregister(this); return true; } public bool BrowserClosing(CefBrowser browser) { var currentBrowser = Browser; bool sameBrowser = currentBrowser != null && IsSameBrowser(currentBrowser, browser); TraceClose( "do-close same=" + sameBrowser, browser, currentBrowser); if (sameBrowser) { // DoClose runs after onbeforeunload has been accepted. Make the // stable host top-level before returning so CEF's WM_CLOSE is // never sent to the WPF root window. bool parked = ParkBrowser(browser, "do-close"); if (!parked) { // Suppress CEF's OS close event when the stable host could // not be detached. Destroying the application-owned host // is the non-standard-window equivalent of cefclient's // DestroyRender path and avoids targeting the WPF root. RequestHostWindowDestroy("do-close-park-failed"); return true; } Interlocked.Exchange(ref _browserCloseApproved, 1); } // Match CEF's documented non-standard close flow. Returning false // makes CEF send WM_CLOSE after this callback returns. The parked // application-owned host handles that message and destroys itself. return false; } public void RequestClose() { RequestClose(false); } private void RequestClose(bool forceClose) { var browser = Browser; if (browser == null) return; // Suppress only duplicate dispatches. The flag is reset as soon as // CloseBrowser is delivered so a canceled onbeforeunload can be // retried. A forced dispose is always allowed to upgrade a pending // non-forced close. if (!forceClose && Interlocked.Exchange(ref _browserCloseRequested, 1) != 0) return; Interlocked.Exchange(ref _browserCloseRequested, 1); if (forceClose) ParkBrowser(browser, "forced-close-request"); CloseBrowser(browser, forceClose); } public void BeginDispose() { if (Interlocked.Exchange(ref _disposeRequested, 1) != 0) return; Interlocked.Exchange(ref _ownerHandleDestroying, 1); TraceOwnerAction("owner-dispose"); RequestClose(true); TryReleaseHostWindow(); } public void BeginOwnerHandleDestruction() { if (IsDisposing || Interlocked.Exchange(ref _ownerHandleDestroying, 1) != 0) return; // WinForms can recreate a control HWND for DPI, parenting or style // changes. Preserve the browser and only detach its stable host. TraceOwnerAction("owner-handle-destroy"); ParkBrowser(Browser, "owner-handle-destroy"); } public void OwnerHandleCreated( IntPtr ownerHandle, int width, int height) { if (IsDisposing || ownerHandle == IntPtr.Zero) return; Interlocked.Exchange(ref _ownerHandleDestroying, 0); var hostWindow = GetHostWindow(); var browser = Browser; if (hostWindow != null && !hostWindow.IsDestroyed && browser != null) hostWindow.AttachTo(ownerHandle, width, height, true); TraceClose( "owner-handle-created ownerHwnd=" + ownerHandle + " ownerThread=" + CefBrowserNativeMethods.GetWindowThread(ownerHandle), Browser, null); } public void AttachBrowserToOwner( CefBrowser browser, IntPtr ownerHandle, int width, int height) { if (browser == null || ownerHandle == IntPtr.Zero || !CefBrowserNativeMethods.IsWindow(ownerHandle) || IsUnavailable) return; var currentBrowser = Browser; if (currentBrowser == null || !IsSameBrowser(currentBrowser, browser)) return; try { var hostWindow = GetHostWindow(); if (hostWindow == null || hostWindow.IsDestroyed) return; IntPtr browserHandle; using (var browserHost = browser.GetHost()) { browserHandle = browserHost.GetWindowHandle(); } IntPtr hostHandle = hostWindow.Handle; if (browserHandle == IntPtr.Zero || hostHandle == IntPtr.Zero) return; IntPtr browserParent = CefBrowserNativeMethods.GetParent(browserHandle); if (browserParent != hostHandle) { TraceClose( "attach-invalid-parent browserHwnd=" + browserHandle + " browserParent=" + browserParent + " hostHwnd=" + hostHandle, browser, currentBrowser); return; } hostWindow.AttachTo(ownerHandle, width, height, true); ResizeBrowserWindow(browserHandle, width, height, true); TraceClose( "attach browserHwnd=" + browserHandle + " hostHwnd=" + hostHandle + " hostParent=" + CefBrowserNativeMethods.GetParent(hostHandle) + " hostThread=" + hostWindow.OwnerThreadId + " ownerThread=" + CefBrowserNativeMethods.GetWindowThread(ownerHandle), browser, currentBrowser); } catch (Exception ex) { TraceClose( "attach-failed " + ex.GetType().FullName, browser, currentBrowser); } } public void ResizeBrowser( IntPtr browserHandle, int width, int height) { if (browserHandle == IntPtr.Zero || IsUnavailable) return; try { var hostWindow = GetHostWindow(); if (hostWindow == null || hostWindow.IsDestroyed) return; hostWindow.Resize(width, height); ResizeBrowserWindow(browserHandle, width, height, false); } catch (Exception) { } } private bool ParkBrowser(CefBrowser browser, string reason) { var hostWindow = GetHostWindow(); if (hostWindow == null || hostWindow.IsDestroyed) return false; try { bool parked = hostWindow.Park(); TraceClose( "park reason=" + reason + " parked=" + parked + " hostHwnd=" + hostWindow.Handle + " hostParent=" + CefBrowserNativeMethods.GetParent(hostWindow.Handle), browser, Browser); return parked; } catch (Exception ex) { TraceClose( "park-failed " + ex.GetType().FullName, browser, Browser); return false; } } private static void ResizeBrowserWindow( IntPtr browserHandle, int width, int height, bool show) { uint flags = CefBrowserNativeMethods.SwpNoZOrder | CefBrowserNativeMethods.SwpNoActivate; if (show) flags |= CefBrowserNativeMethods.SwpShowWindow; CefBrowserNativeMethods.SetWindowPos( browserHandle, IntPtr.Zero, 0, 0, Math.Max(1, width), Math.Max(1, height), flags); } private CefBrowserHostWindow GetHostWindow() { lock (_hostWindowSyncRoot) { return _hostWindow; } } private bool HostWindowCloseRequested() { bool approved = Thread.VolatileRead(ref _browserCloseApproved) != 0; TraceClose( "host-wm-close approved=" + approved, Browser, null); if (!approved) RequestClose(IsDisposing); return approved; } private void HostWindowDestroyed(CefBrowserHostWindow hostWindow) { TraceClose( "host-destroyed hostHwnd=" + hostWindow.LastHandle, Browser, null); TryReleaseHostWindow(); } private void RequestHostWindowDestroy(string reason) { var hostWindow = GetHostWindow(); if (hostWindow == null || hostWindow.IsDestroyed) return; TraceClose( "host-destroy-request reason=" + reason + " hostHwnd=" + hostWindow.Handle + " valid=" + CefBrowserNativeMethods.IsWindow(hostWindow.Handle), Browser, null); hostWindow.RequestDestroy(); } private void TryReleaseHostWindow() { var hostWindow = GetHostWindow(); if (hostWindow == null) return; bool browserFinished = Browser == null && Thread.VolatileRead(ref _browserCreationPending) == 0; if (!browserFinished) return; bool closeCompleted = Thread.VolatileRead(ref _browserBeforeCloseReceived) != 0; if (!hostWindow.IsDestroyed) { if (IsDisposing || closeCompleted) RequestHostWindowDestroy("release-ready"); return; } lock (_hostWindowSyncRoot) { if (ReferenceEquals(_hostWindow, hostWindow) && Browser == null && Thread.VolatileRead(ref _browserCreationPending) == 0) { _hostWindow = null; } } TraceClose( "release-complete hostHwnd=" + hostWindow.LastHandle + " browserClosed=" + closeCompleted, null, null); } private static bool IsSameBrowser(CefBrowser left, CefBrowser right) { if (ReferenceEquals(left, right)) return true; if (left == null || right == null) return false; try { return left.IsSame(right); } catch (Exception) { try { return left.Identifier == right.Identifier; } catch (Exception) { return false; } } } private void CloseBrowser(CefBrowser browser, bool forceClose) { try { TraceClose( "close-request force=" + forceClose, browser, null); if (CefRuntime.CurrentlyOn(CefThreadId.UI)) { ExecuteCloseBrowser(browser, forceClose); return; } bool posted = CefRuntime.PostTask( CefThreadId.UI, new CefActionTask(delegate { ExecuteCloseBrowser(browser, forceClose); })); TraceClose("close-post-ui accepted=" + posted, browser, null); if (!posted) ExecuteCloseBrowser(browser, forceClose); } catch (Exception ex) { BrowserCloseFailed(browser, ex); } } private void ExecuteCloseBrowser(CefBrowser browser, bool forceClose) { try { TraceClose( "close-execute-ui force=" + forceClose, browser, null); using (var browserHost = browser.GetHost()) { browserHost.CloseBrowser(forceClose); } } catch (Exception ex) { BrowserCloseFailed(browser, ex); } finally { Interlocked.Exchange(ref _browserCloseRequested, 0); } } private static void DisposeBrowserReference(CefBrowser browser) { if (browser == null) return; try { browser.Dispose(); } catch (Exception ex) { TraceClose( "browser-reference-dispose-failed " + ex.GetType().FullName, null, null); } } private void BrowserCloseFailed(CefBrowser browser, Exception exception) { TraceClose( "close-failed " + exception.GetType().FullName, browser, Browser); Interlocked.Exchange(ref _browserCloseRequested, 0); if (IsDisposing) RequestHostWindowDestroy("close-failed"); TryReleaseHostWindow(); } private void TraceOwnerAction(string action) { if (!LifecycleTracingEnabled) return; string stack = Environment.StackTrace .Replace("\r", string.Empty) .Replace("\n", " | "); if (stack.Length > 1800) stack = stack.Substring(0, 1800); TraceClose(action + " stack=" + stack, Browser, null); } private static void TraceClose( string action, CefBrowser browser, CefBrowser currentBrowser) { if (!LifecycleTracingEnabled) return; try { LogHelper.Instance.WriteLog( "[CEF_LIFECYCLE] " + action + "; browser=" + GetBrowserIdentifier(browser) + "; current=" + GetBrowserIdentifier(currentBrowser) + "; thread=" + Thread.CurrentThread.ManagedThreadId + "; cefUi=" + CefRuntime.CurrentlyOn(CefThreadId.UI)); } catch (Exception) { } } private static string GetBrowserIdentifier(CefBrowser browser) { if (browser == null) return "null"; try { return browser.Identifier.ToString(); } catch (Exception) { return "unavailable"; } } private sealed class CefBrowserHostWindow : NativeWindow { private const int WmClose = 0x0010; private const int WmReleaseHandle = 0x8001; private const int WmPark = 0x8002; private const int WmNcDestroy = 0x0082; private readonly uint _ownerThreadId; private Func _onCloseRequested; private Action _onDestroyed; private IntPtr _lastHandle; private int _destroyRequested; private int _destroyed; private int _parked; private int _parking; public uint OwnerThreadId { get { return _ownerThreadId; } } public bool IsDestroyed { get { return Thread.VolatileRead(ref _destroyed) != 0; } } public bool IsParked { get { return Thread.VolatileRead(ref _parked) != 0; } } public IntPtr LastHandle { get { return _lastHandle; } } public CefBrowserHostWindow( IntPtr ownerHandle, int width, int height, Func onCloseRequested, Action onDestroyed) { _onCloseRequested = onCloseRequested; _onDestroyed = onDestroyed; _ownerThreadId = CefBrowserNativeMethods.GetCurrentThreadId(); uint ownerThread = CefBrowserNativeMethods.GetWindowThread(ownerHandle); if (ownerThread != _ownerThreadId) { throw new InvalidOperationException( "CEF host HWND must be created on the WinForms owner thread."); } CreateHandle(new CreateParams { Caption = "Lskj.CefBrowserHostWindow", Parent = IntPtr.Zero, X = -32000, Y = -32000, Width = 1, Height = 1, Style = CefBrowserNativeMethods.WsPopup | CefBrowserNativeMethods.WsClipChildren | CefBrowserNativeMethods.WsClipSiblings }); _lastHandle = Handle; Interlocked.Exchange(ref _parked, 1); if (_lastHandle == IntPtr.Zero || !CefBrowserNativeMethods.IsWindow(_lastHandle)) { throw new InvalidOperationException( "Failed to create the stable CEF root HWND."); } } public void AttachTo( IntPtr ownerHandle, int width, int height, bool show) { EnsureOwnerThread(ownerHandle); if (IsDestroyed || Handle == IntPtr.Zero || !CefBrowserNativeMethods.IsWindow(Handle)) return; int style = CefBrowserNativeMethods.GetWindowLong( Handle, CefBrowserNativeMethods.GwlStyle); CefBrowserNativeMethods.SetWindowLong( Handle, CefBrowserNativeMethods.GwlStyle, (style & ~CefBrowserNativeMethods.WsPopup) | CefBrowserNativeMethods.WsChild | CefBrowserNativeMethods.WsClipChildren | CefBrowserNativeMethods.WsClipSiblings); CefBrowserNativeMethods.SetParent(Handle, ownerHandle); if (!CefBrowserNativeMethods.IsWindow(Handle) || CefBrowserNativeMethods.GetParent(Handle) != ownerHandle) { throw new InvalidOperationException( "Failed to attach the CEF host HWND to its owner."); } uint flags = CefBrowserNativeMethods.SwpNoZOrder | CefBrowserNativeMethods.SwpNoActivate | CefBrowserNativeMethods.SwpFrameChanged; if (show) flags |= CefBrowserNativeMethods.SwpShowWindow; CefBrowserNativeMethods.SetWindowPos( Handle, IntPtr.Zero, 0, 0, Math.Max(1, width), Math.Max(1, height), flags); Interlocked.Exchange(ref _parked, 0); } public void Resize(int width, int height) { if (IsDestroyed || Handle == IntPtr.Zero || !CefBrowserNativeMethods.IsWindow(Handle)) return; CefBrowserNativeMethods.SetWindowPos( Handle, IntPtr.Zero, 0, 0, Math.Max(1, width), Math.Max(1, height), CefBrowserNativeMethods.SwpNoZOrder | CefBrowserNativeMethods.SwpNoActivate); } public bool Park() { if (IsDestroyed || Handle == IntPtr.Zero || !CefBrowserNativeMethods.IsWindow(Handle)) return false; if (IsParked) return true; if (CefBrowserNativeMethods.GetCurrentThreadId() != _ownerThreadId) { CefBrowserNativeMethods.SendMessage( Handle, WmPark, IntPtr.Zero, IntPtr.Zero); return IsParked && CefBrowserNativeMethods.IsWindow(Handle) && CefBrowserNativeMethods.GetParent(Handle) == IntPtr.Zero; } return ParkNow(); } private bool ParkNow() { if (IsParked) return true; if (Interlocked.Exchange(ref _parking, 1) != 0) return false; try { IntPtr handle = Handle; if (IsDestroyed || handle == IntPtr.Zero || !CefBrowserNativeMethods.IsWindow(handle)) return false; CefBrowserNativeMethods.ShowWindow( handle, CefBrowserNativeMethods.SwHide); int style = CefBrowserNativeMethods.GetWindowLong( handle, CefBrowserNativeMethods.GwlStyle); // Win32 requires WS_CHILD to be cleared before setting a // child window's parent to NULL. Reversing this order can // leave the host attached to the WPF root while appearing // parked in managed state. CefBrowserNativeMethods.SetWindowLong( handle, CefBrowserNativeMethods.GwlStyle, (style & ~CefBrowserNativeMethods.WsChild) | CefBrowserNativeMethods.WsPopup | CefBrowserNativeMethods.WsClipChildren | CefBrowserNativeMethods.WsClipSiblings); CefBrowserNativeMethods.SetParent(handle, IntPtr.Zero); if (!CefBrowserNativeMethods.IsWindow(handle) || CefBrowserNativeMethods.GetParent(handle) != IntPtr.Zero) return false; CefBrowserNativeMethods.SetWindowPos( handle, IntPtr.Zero, -32000, -32000, 1, 1, CefBrowserNativeMethods.SwpNoZOrder | CefBrowserNativeMethods.SwpNoActivate | CefBrowserNativeMethods.SwpFrameChanged); Interlocked.Exchange(ref _parked, 1); return true; } finally { Interlocked.Exchange(ref _parking, 0); } } public void RequestDestroy() { if (Interlocked.Exchange(ref _destroyRequested, 1) != 0) return; IntPtr handle = Handle; if (handle == IntPtr.Zero || !CefBrowserNativeMethods.IsWindow(handle)) { NotifyDestroyed(); return; } if (CefBrowserNativeMethods.GetCurrentThreadId() == _ownerThreadId) { DestroyNow(); return; } if (!CefBrowserNativeMethods.PostMessage( handle, WmReleaseHandle, IntPtr.Zero, IntPtr.Zero)) { Interlocked.Exchange(ref _destroyRequested, 0); TraceClose( "host-destroy-post-failed hostHwnd=" + handle, null, null); } } protected override void WndProc(ref Message message) { if (message.Msg == WmClose) { var onCloseRequested = _onCloseRequested; bool allowClose = onCloseRequested == null || onCloseRequested(); if (allowClose) base.WndProc(ref message); return; } if (message.Msg == WmReleaseHandle) { DestroyNow(); return; } if (message.Msg == WmPark) { ParkNow(); return; } if (message.Msg == WmNcDestroy) { _lastHandle = message.HWnd; base.WndProc(ref message); NotifyDestroyed(); return; } base.WndProc(ref message); } private void DestroyNow() { IntPtr handle = Handle; if (handle == IntPtr.Zero || !CefBrowserNativeMethods.IsWindow(handle)) { NotifyDestroyed(); return; } TraceClose( "host-destroying hostHwnd=" + handle, null, null); DestroyHandle(); } private void NotifyDestroyed() { if (Interlocked.Exchange(ref _destroyed, 1) != 0) return; _onCloseRequested = null; var onDestroyed = Interlocked.Exchange( ref _onDestroyed, null); if (onDestroyed != null) onDestroyed(this); } private void EnsureOwnerThread(IntPtr ownerHandle) { EnsureCurrentThread(); if (CefBrowserNativeMethods.GetWindowThread(ownerHandle) != _ownerThreadId) { throw new InvalidOperationException( "CEF host and owner HWND must belong to the same thread."); } } private void EnsureCurrentThread() { if (CefBrowserNativeMethods.GetCurrentThreadId() != _ownerThreadId) { throw new InvalidOperationException( "CEF host HWND may only be changed on its owner thread."); } } } } /// /// Mirrors the browser list used by CEF's cefsimple application. CEF may /// only be shut down after every asynchronous OnBeforeClose callback has /// completed. /// public static class CefBrowserRuntimeLifetime { private static readonly object _syncRoot = new object(); private static readonly HashSet _activeBrowsers = new HashSet(); private static readonly ManualResetEvent _allBrowsersClosed = new ManualResetEvent(true); private static bool _shutdownRequested; internal static bool TryRegister( CefBrowserLifetimeCoordinator coordinator) { if (coordinator == null) throw new ArgumentNullException("coordinator"); lock (_syncRoot) { if (_shutdownRequested) return false; if (_activeBrowsers.Add(coordinator)) _allBrowsersClosed.Reset(); return true; } } internal static void Unregister( CefBrowserLifetimeCoordinator coordinator) { if (coordinator == null) return; lock (_syncRoot) { _activeBrowsers.Remove(coordinator); if (_activeBrowsers.Count == 0) _allBrowsersClosed.Set(); } } public static bool CloseAllBrowsersAndWait(int timeoutMilliseconds) { CefBrowserLifetimeCoordinator[] browsers; lock (_syncRoot) { _shutdownRequested = true; browsers = new CefBrowserLifetimeCoordinator[ _activeBrowsers.Count]; _activeBrowsers.CopyTo(browsers); if (browsers.Length == 0) return true; } foreach (var browser in browsers) browser.BeginDispose(); bool closed = _allBrowsersClosed.WaitOne( Math.Max(0, timeoutMilliseconds)); TraceShutdown(closed, timeoutMilliseconds); return closed; } private static void TraceShutdown( bool closed, int timeoutMilliseconds) { if (!CefBrowserLifetimeCoordinator.LifecycleTracingEnabled) return; try { int remaining; lock (_syncRoot) { remaining = _activeBrowsers.Count; } LogHelper.Instance.WriteLog( "[CEF_LIFECYCLE] shutdown-wait closed=" + closed + "; remaining=" + remaining + "; timeoutMs=" + timeoutMilliseconds); } catch (Exception) { } } } internal static class CefBrowserNativeMethods { internal const int GwlStyle = -16; internal const int SwHide = 0; internal const int WsChild = 0x40000000; internal const int WsClipChildren = 0x02000000; internal const int WsClipSiblings = 0x04000000; internal const int WsPopup = unchecked((int)0x80000000); internal const uint SwpNoZOrder = 0x0004; internal const uint SwpNoActivate = 0x0010; internal const uint SwpFrameChanged = 0x0020; internal const uint SwpShowWindow = 0x0040; [DllImport("user32.dll", SetLastError = true)] internal static extern IntPtr SetParent( IntPtr childWindow, IntPtr newParentWindow); [DllImport("user32.dll")] internal static extern IntPtr GetParent(IntPtr window); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool IsWindow(IntPtr window); [DllImport("user32.dll", SetLastError = true)] internal static extern int GetWindowLong(IntPtr window, int index); [DllImport("user32.dll", SetLastError = true)] internal static extern int SetWindowLong( IntPtr window, int index, int newValue); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool ShowWindow(IntPtr window, int command); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetWindowPos( IntPtr window, IntPtr insertAfter, int x, int y, int width, int height, uint flags); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool PostMessage( IntPtr window, int message, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] internal static extern IntPtr SendMessage( IntPtr window, int message, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] internal static extern uint GetWindowThreadProcessId( IntPtr window, IntPtr processId); [DllImport("kernel32.dll")] internal static extern uint GetCurrentThreadId(); internal static uint GetWindowThread(IntPtr window) { return window == IntPtr.Zero ? 0 : GetWindowThreadProcessId(window, IntPtr.Zero); } } internal sealed class CefActionTask : CefTask { private Action _action; public CefActionTask(Action action) { if (action == null) throw new ArgumentNullException("action"); _action = action; } protected override void Execute() { var action = Interlocked.Exchange(ref _action, null); if (action == null) return; try { action(); } catch (Exception) { // Never propagate managed exceptions into a native CEF task. } } } }