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.
}
}
}
}