2019-10-09 12:24:47 +02:00
unit uCEFSentinel;
{$IFDEF FPC}
{$MODE OBJFPC} {$H+}
{$ENDIF}
{$I cef.inc}
2022-02-19 18:56:41 +01:00
{$IFNDEF TARGET_64BITS} {$ALIGN ON} {$ENDIF}
{$MINENUMSIZE 4}
2019-10-09 12:24:47 +02:00
interface
uses
{$IFDEF DELPHI16_UP}
{$IFDEF MSWINDOWS} WinApi . Windows, WinApi . Messages, {$ENDIF}
System. Classes, Vcl. Controls, Vcl. ExtCtrls, System. SysUtils, System. SyncObjs, System. Math,
{$ELSE}
{$IFDEF MSWINDOWS} Windows, {$ENDIF} Classes, Controls, ExtCtrls, SysUtils, SyncObjs, Math,
{$IFDEF FPC}
2020-02-04 11:04:29 +01:00
LCLIntf, LResources, Forms,
2019-10-09 12:24:47 +02:00
{$ELSE}
Messages,
{$ENDIF}
{$ENDIF}
2022-10-14 16:35:50 +02:00
uCEFTypes, uCEFInterfaces, uCEFConstants;
2019-10-09 12:24:47 +02:00
const
CEFSENTINEL_DEFAULT_DELAYPERPROCMS = 2 0 0 ;
CEFSENTINEL_DEFAULT_MININITDELAYMS = 1 5 0 0 ;
CEFSENTINEL_DEFAULT_FINALDELAYMS = 1 0 0 ;
CEFSENTINEL_DEFAULT_MINCHILDPROCS = 2 ;
CEFSENTINEL_DEFAULT_MAXCHECKCOUNTS = 1 0 ;
type
TSentinelStatus = ( ssIdle, ssInitialDelay, ssCheckingChildren, ssClosing) ;
2022-10-14 16:35:50 +02:00
{$IFNDEF FPC} {$IFDEF DELPHI16_UP} [ ComponentPlatformsAttribute( pfidWindows or pfidOSX or pfidLinux) ] {$ENDIF} {$ENDIF}
2023-08-08 20:19:19 +02:00
/// <summary>
/// TCEFSentinel is used as a timer that checks the number of running
/// CEF processes when you close all browsers before shutdown.
/// This component is only used as a last resort when there's an unresolved
/// shutdown issue in CEF or CEF4Delphi that generates exceptions when the
/// application is closed.
/// </summary>
2019-10-09 12:24:47 +02:00
TCEFSentinel = class( TComponent)
protected
2019-11-10 18:23:39 +01:00
{$IFDEF MSWINDOWS}
2019-10-09 12:24:47 +02:00
FCompHandle : HWND;
2019-11-10 18:23:39 +01:00
{$ENDIF}
2019-10-09 12:24:47 +02:00
FStatus : TSentinelStatus;
FStatusCS : TCriticalSection;
FDelayPerProcMs : cardinal ;
FMinInitDelayMs : cardinal ;
FFinalDelayMs : cardinal ;
FMinChildProcs : integer ;
FMaxCheckCount : integer ;
FCheckCount : integer ;
FOnClose : TNotifyEvent;
FTimer : TTimer;
function GetStatus : TSentinelStatus;
function GetChildProcCount : integer ;
{$IFDEF MSWINDOWS}
procedure WndProc( var aMessage: TMessage) ;
2019-11-10 18:23:39 +01:00
{$ENDIF}
2019-12-18 15:10:30 +01:00
procedure doStartMsg( {$IFDEF MSWINDOWS} var aMessage : TMessage{$ELSE IFDEF FPC} Data: PtrInt{$ENDIF} ) ; virtual ;
procedure doCloseMsg( {$IFDEF MSWINDOWS} var aMessage : TMessage{$ELSE IFDEF FPC} Data: PtrInt{$ENDIF} ) ; virtual ;
function SendCompMessage( aMsg : cardinal ) : boolean ;
2019-10-09 12:24:47 +02:00
function CanClose : boolean ; virtual ;
procedure Timer_OnTimer( Sender: TObject) ; virtual ;
public
constructor Create( AOwner: TComponent) ; override ;
destructor Destroy; override ;
procedure AfterConstruction; override ;
2023-09-24 11:21:05 +02:00
/// <summary>
/// Start checking all the CEF subprocesses.
/// </summary>
2019-10-09 12:24:47 +02:00
procedure Start; virtual ;
2023-09-24 11:21:05 +02:00
/// <summary>
/// Status of this component.
/// </summary>
2019-10-09 12:24:47 +02:00
property Status : TSentinelStatus read GetStatus;
2023-09-24 11:21:05 +02:00
/// <summary>
/// Number of CEF subprocesses.
/// </summary>
2019-10-09 12:24:47 +02:00
property ChildProcCount : integer read GetChildProcCount;
published
2023-09-24 11:21:05 +02:00
/// <summary>
/// Delay per subprocess in milliseconds. This delay is used to calculate how much time to wait until this component checks the CEF subprocesses again.
/// </summary>
2019-10-09 12:24:47 +02:00
property DelayPerProcMs : cardinal read FDelayPerProcMs write FDelayPerProcMs default CEFSENTINEL_DEFAULT_DELAYPERPROCMS;
2023-09-24 11:21:05 +02:00
/// <summary>
/// Minimum initial delay in milliseconds. This is the minimum time to wait until this component checks the CEF subprocesses again.
/// </summary>
2019-10-09 12:24:47 +02:00
property MinInitDelayMs : cardinal read FMinInitDelayMs write FMinInitDelayMs default CEFSENTINEL_DEFAULT_MININITDELAYMS;
2023-09-24 11:21:05 +02:00
/// <summary>
/// Final delay in milliseconds. This is an extra delay to wait after enough CEF subprocesses are closed.
/// </summary>
2019-10-09 12:24:47 +02:00
property FinalDelayMs : cardinal read FFinalDelayMs write FFinalDelayMs default CEFSENTINEL_DEFAULT_FINALDELAYMS;
2023-09-24 11:21:05 +02:00
/// <summary>
/// Minimum number of CEF subprocesses. When ChildProcCount reaches this value it's considered safe to trigger OnClose.
/// </summary>
2019-10-09 12:24:47 +02:00
property MinChildProcs : integer read FMinChildProcs write FMinChildProcs default CEFSENTINEL_DEFAULT_MINCHILDPROCS;
2023-09-24 11:21:05 +02:00
/// <summary>
/// Maximum number of times this component will check the CEF subprocesses.
/// </summary>
2019-10-09 12:24:47 +02:00
property MaxCheckCount : integer read FMaxCheckCount write FMaxCheckCount default CEFSENTINEL_DEFAULT_MAXCHECKCOUNTS;
2023-09-24 11:21:05 +02:00
/// <summary>
/// Event triggered when enought CEF subprocesses are closed.
/// </summary>
2019-10-09 12:24:47 +02:00
property OnClose : TNotifyEvent read FOnClose write FOnClose;
end ;
{$IFDEF FPC}
procedure Register ;
{$ENDIF}
implementation
uses
2022-10-14 16:35:50 +02:00
uCEFLibFunctions, uCEFApplicationCore, uCEFMiscFunctions;
2019-10-09 12:24:47 +02:00
2023-08-09 19:38:57 +02:00
// Attribution :
// TCEFSentinel icon made by Everaldo Coelho
// https://www.iconfinder.com/icons/17914/castle_fortress_tower_war_icon
// http://www.everaldo.com/
2019-10-09 12:24:47 +02:00
constructor TCEFSentinel. Create( AOwner: TComponent) ;
begin
inherited Create( aOwner) ;
2019-11-10 18:23:39 +01:00
{$IFDEF MSWINDOWS}
2019-10-09 12:24:47 +02:00
FCompHandle : = 0 ;
2019-11-10 18:23:39 +01:00
{$ENDIF}
2019-10-09 12:24:47 +02:00
FDelayPerProcMs : = CEFSENTINEL_DEFAULT_DELAYPERPROCMS;
FMinInitDelayMs : = CEFSENTINEL_DEFAULT_MININITDELAYMS;
FFinalDelayMs : = CEFSENTINEL_DEFAULT_FINALDELAYMS;
FMinChildProcs : = CEFSENTINEL_DEFAULT_MINCHILDPROCS;
FMaxCheckCount : = CEFSENTINEL_DEFAULT_MAXCHECKCOUNTS;
FOnClose : = nil ;
FTimer : = nil ;
FStatusCS : = nil ;
FStatus : = ssIdle;
FCheckCount : = 0 ;
end ;
procedure TCEFSentinel. AfterConstruction;
begin
inherited AfterConstruction;
if not( csDesigning in ComponentState) then
begin
{$IFDEF MSWINDOWS}
2019-11-10 18:23:39 +01:00
FCompHandle : = AllocateHWnd( {$IFDEF FPC} @ {$ENDIF} WndProc) ;
2019-10-09 12:24:47 +02:00
{$ENDIF}
FStatusCS : = TCriticalSection. Create;
FTimer : = TTimer. Create( nil ) ;
FTimer. Enabled : = False ;
FTimer. OnTimer : = {$IFDEF FPC} @ {$ENDIF} Timer_OnTimer;
end ;
end ;
destructor TCEFSentinel. Destroy;
begin
try
{$IFDEF MSWINDOWS}
if ( FCompHandle < > 0 ) then
begin
DeallocateHWnd( FCompHandle) ;
FCompHandle : = 0 ;
end ;
{$ENDIF}
if ( FTimer < > nil ) then FreeAndNil( FTimer) ;
if ( FStatusCS < > nil ) then FreeAndNil( FStatusCS) ;
finally
inherited Destroy;
end ;
end ;
{$IFDEF MSWINDOWS}
procedure TCEFSentinel. WndProc( var aMessage: TMessage) ;
begin
case aMessage. Msg of
CEF_SENTINEL_START : doStartMsg( aMessage) ;
CEF_SENTINEL_DOCLOSE : doCloseMsg( aMessage) ;
else aMessage. Result : = DefWindowProc( FCompHandle, aMessage. Msg, aMessage. WParam, aMessage. LParam) ;
end ;
end ;
2019-12-18 15:10:30 +01:00
{$ENDIF}
2019-10-09 12:24:47 +02:00
2019-12-18 15:10:30 +01:00
procedure TCEFSentinel. doStartMsg( {$IFDEF MSWINDOWS} var aMessage : TMessage{$ELSE IFDEF FPC} Data: PtrInt{$ENDIF} ) ;
2019-10-09 12:24:47 +02:00
begin
if ( FTimer < > nil ) then
begin
2023-09-24 11:21:05 +02:00
FTimer. Interval : = max( cardinal( ChildProcCount) * FDelayPerProcMs, FMinInitDelayMs) ;
2019-10-09 12:24:47 +02:00
FTimer. Enabled : = True ;
end ;
end ;
2019-12-18 15:10:30 +01:00
procedure TCEFSentinel. doCloseMsg( {$IFDEF MSWINDOWS} var aMessage : TMessage{$ELSE IFDEF FPC} Data: PtrInt{$ENDIF} ) ;
2019-10-09 12:24:47 +02:00
begin
if assigned( FOnClose) then FOnClose( self) ;
end ;
function TCEFSentinel. SendCompMessage( aMsg : cardinal ) : boolean ;
begin
2019-12-18 15:10:30 +01:00
{$IFDEF MSWINDOWS}
2019-10-09 12:24:47 +02:00
Result : = ( FCompHandle < > 0 ) and PostMessage( FCompHandle, aMsg, 0 , 0 ) ;
2019-12-18 15:10:30 +01:00
{$ELSE IFDEF FPC}
case aMsg of
CEF_SENTINEL_START : Application. QueueAsyncCall( @ doStartMsg, 0 ) ;
CEF_SENTINEL_DOCLOSE : Application. QueueAsyncCall( @ doCloseMsg, 0 ) ;
end ;
{$ENDIF}
2019-10-09 12:24:47 +02:00
end ;
procedure TCEFSentinel. Start;
begin
try
if ( FStatusCS < > nil ) then FStatusCS. Acquire;
if ( FStatus = ssIdle) then
begin
FStatus : = ssInitialDelay;
SendCompMessage( CEF_SENTINEL_START) ;
end ;
finally
if ( FStatusCS < > nil ) then FStatusCS. Release;
end ;
end ;
function TCEFSentinel. GetStatus : TSentinelStatus;
begin
Result : = ssIdle;
if ( FStatusCS < > nil ) then
try
FStatusCS. Acquire;
Result : = FStatus;
finally
FStatusCS. Release;
end ;
end ;
function TCEFSentinel. GetChildProcCount : integer ;
begin
if ( GlobalCEFApp < > nil ) then
Result : = GlobalCEFApp. ChildProcessesCount
else
Result : = 0 ;
end ;
function TCEFSentinel. CanClose : boolean ;
begin
Result : = ( FCheckCount > = FMaxCheckCount) or
( GlobalCEFApp = nil ) or
( ChildProcCount < = FMinChildProcs) ;
end ;
procedure TCEFSentinel. Timer_OnTimer( Sender: TObject) ;
begin
FTimer. Enabled : = False ;
try
if ( FStatusCS < > nil ) then FStatusCS. Acquire;
case FStatus of
ssInitialDelay :
if CanClose then
begin
FStatus : = ssClosing;
SendCompMessage( CEF_SENTINEL_DOCLOSE) ;
end
else
begin
FStatus : = ssCheckingChildren;
FCheckCount : = 0 ;
FTimer. Interval : = FFinalDelayMs;
FTimer. Enabled : = True ;
end ;
ssCheckingChildren :
if CanClose then
begin
FStatus : = ssClosing;
SendCompMessage( CEF_SENTINEL_DOCLOSE) ;
end
else
begin
inc( FCheckCount) ;
FTimer. Enabled : = True ;
end ;
end ;
finally
if ( FStatusCS < > nil ) then FStatusCS. Release;
end ;
end ;
{$IFDEF FPC}
procedure Register ;
begin
{$I res/tcefsentinel.lrs}
RegisterComponents( 'Chromium' , [ TCEFSentinel] ) ;
end ;
{$ENDIF}
end .