unit uCEFWorkScheduler; {$IFDEF FPC} {$MODE OBJFPC}{$H+} {$ENDIF} {$I cef.inc} {$IFNDEF TARGET_64BITS}{$ALIGN ON}{$ENDIF} {$MINENUMSIZE 4} interface uses {$IFDEF DELPHI16_UP} {$IFDEF MSWINDOWS}WinApi.Windows, WinApi.Messages,{$ENDIF} System.Classes, {$ELSE} {$IFDEF MSWINDOWS}Windows,{$ENDIF} Classes, {$IFDEF FPC} LCLProc, LCLType, LCLIntf, LResources, LMessages, InterfaceBase, {$IFNDEF MSWINDOWS}forms,{$ENDIF} {$ELSE} Messages, {$ENDIF} {$ENDIF} uCEFConstants, uCEFWorkSchedulerQueueThread, uCEFWorkSchedulerThread; type {$IFNDEF FPC}{$IFDEF DELPHI16_UP}[ComponentPlatformsAttribute(pfidWindows)]{$ENDIF}{$ENDIF} /// /// Implementation of an external message pump for VCL and LCL. /// Read the GlobalCEFApp.OnScheduleMessagePumpWork documentation for all the details. /// TCEFWorkScheduler = class(TComponent) protected FThread : TCEFWorkSchedulerThread; FQueueThread : TCEFWorkSchedulerQueueThread; FDepleteWorkCycles : cardinal; FDepleteWorkDelay : cardinal; FDefaultInterval : integer; FStopped : boolean; FUseQueueThread : boolean; {$IFDEF MSWINDOWS} {$WARN SYMBOL_PLATFORM OFF} FCompHandle : HWND; FPriority : TThreadPriority; {$WARN SYMBOL_PLATFORM ON} {$ENDIF} procedure CreateQueueThread; procedure DestroyQueueThread; procedure QueueThread_OnPulse(Sender : TObject; aDelay : integer); procedure DestroyThread; procedure DepleteWork; {$IFDEF MSWINDOWS} procedure WndProc(var aMessage: TMessage); procedure DeallocateWindowHandle; {$ELSE} {$IFDEF FPC} procedure ScheduleWorkAsync(Data: PtrInt); {$ENDIF} {$ENDIF} procedure NextPulse(aInterval : integer); procedure ScheduleWork(const delay_ms : int64); procedure DoWork; procedure DoMessageLoopWork; procedure Initialize; procedure SetDefaultInterval(aValue : integer); {$IFDEF MSWINDOWS} {$WARN SYMBOL_PLATFORM OFF} procedure SetPriority(aValue : TThreadPriority); {$WARN SYMBOL_PLATFORM ON} {$ENDIF} procedure Thread_OnPulse(Sender : TObject); public /// /// Full constructor of TCEFWorkScheduler. This constructor also creates the internal threads. /// constructor Create(AOwner: TComponent); override; /// /// Partial constructor of TCEFWorkScheduler. This constructor doesn't create any threads. /// Call TCEFWorkScheduler.CreateThread when necessary. /// constructor CreateDelayed; /// /// TCEFWorkScheduler destructor. /// destructor Destroy; override; /// /// Called from GlobalCEFApp.OnScheduleMessagePumpWork to schedule /// a GlobalCEFApp.DoMessageLoopWork call asynchronously to perform a single /// iteration of CEF message loop processing. /// /// Requested delay in milliseconds. procedure ScheduleMessagePumpWork(const delay_ms : int64); /// /// Stop the scheduler. This function must be called after the destruction of all the forms in the application. /// procedure StopScheduler; /// /// Creates all the internal threads used by TCEFWorkScheduler. /// procedure CreateThread; published {$IFDEF MSWINDOWS} {$WARN SYMBOL_PLATFORM OFF} /// /// Priority of TCEFWorkSchedulerThread in Windows. /// property Priority : TThreadPriority read FPriority write SetPriority default tpNormal; {$WARN SYMBOL_PLATFORM ON} {$ENDIF} /// /// Default interval in milliseconds to do the next GlobalCEFApp.DoMessageLoopWork call. /// property DefaultInterval : integer read FDefaultInterval write SetDefaultInterval default CEF_TIMER_MAXDELAY; /// /// Number of cycles used to deplete the remaining messages in the work loop. /// property DepleteWorkCycles : cardinal read FDepleteWorkCycles write FDepleteWorkCycles default CEF_TIMER_DEPLETEWORK_CYCLES; /// /// Delay in milliseconds between the cycles used to deplete the remaining messages in the work loop. /// property DepleteWorkDelay : cardinal read FDepleteWorkDelay write FDepleteWorkDelay default CEF_TIMER_DEPLETEWORK_DELAY; /// /// Use a custom queue thread instead of Windows messages or any other way to schedule the next pump work. /// property UseQueueThread : boolean read FUseQueueThread write FUseQueueThread default False; end; var GlobalCEFWorkScheduler : TCEFWorkScheduler = nil; {$IFDEF FPC} procedure Register; {$ENDIF} procedure DestroyGlobalCEFWorkScheduler; implementation uses {$IFDEF DELPHI16_UP} System.SysUtils, System.Math, {$ELSE} SysUtils, Math, {$ENDIF} uCEFMiscFunctions, uCEFApplicationCore, uCEFTypes; procedure DestroyGlobalCEFWorkScheduler; begin if (GlobalCEFWorkScheduler <> nil) then FreeAndNil(GlobalCEFWorkScheduler); end; constructor TCEFWorkScheduler.Create(AOwner: TComponent); begin inherited Create(AOwner); Initialize; if not(csDesigning in ComponentState) then begin {$IFDEF MSWINDOWS} if (GlobalCEFApp <> nil) and ((GlobalCEFApp.ProcessType = ptBrowser) or GlobalCEFApp.SingleProcess) then FCompHandle := AllocateHWnd({$IFDEF FPC}@{$ENDIF}WndProc); {$ENDIF} CreateThread; end; end; constructor TCEFWorkScheduler.CreateDelayed; begin inherited Create(nil); Initialize; if not(csDesigning in ComponentState) then begin {$IFDEF MSWINDOWS} if (GlobalCEFApp <> nil) and ((GlobalCEFApp.ProcessType = ptBrowser) or GlobalCEFApp.SingleProcess) then FCompHandle := AllocateHWnd({$IFDEF FPC}@{$ENDIF}WndProc); {$ENDIF} end; end; destructor TCEFWorkScheduler.Destroy; begin DestroyThread; DestroyQueueThread; {$IFDEF MSWINDOWS} DeallocateWindowHandle; {$ENDIF} inherited Destroy; end; procedure TCEFWorkScheduler.Initialize; begin FUseQueueThread := False; FThread := nil; FQueueThread := nil; FStopped := False; {$IFDEF MSWINDOWS} {$WARN SYMBOL_PLATFORM OFF} FCompHandle := 0; FPriority := tpNormal; {$WARN SYMBOL_PLATFORM ON} {$ENDIF} FDefaultInterval := CEF_TIMER_MAXDELAY; FDepleteWorkCycles := CEF_TIMER_DEPLETEWORK_CYCLES; FDepleteWorkDelay := CEF_TIMER_DEPLETEWORK_DELAY; end; procedure TCEFWorkScheduler.CreateThread; begin if (FThread <> nil) then exit; FThread := TCEFWorkSchedulerThread.Create; {$IFDEF MSWINDOWS} FThread.Priority := FPriority; {$ENDIF} FThread.DefaultInterval := FDefaultInterval; FThread.OnPulse := {$IFDEF FPC}@{$ENDIF}Thread_OnPulse; {$IFDEF DELPHI14_UP} FThread.Start; {$ELSE} {$IFNDEF FPC} FThread.Resume; {$ELSE} FThread.Start; {$ENDIF} {$ENDIF} if FUseQueueThread then CreateQueueThread; end; procedure TCEFWorkScheduler.CreateQueueThread; begin FQueueThread := TCEFWorkSchedulerQueueThread.Create; FQueueThread.OnPulse := {$IFDEF FPC}@{$ENDIF}QueueThread_OnPulse; {$IFDEF DELPHI14_UP} FQueueThread.Start; {$ELSE} {$IFNDEF FPC} FQueueThread.Resume; {$ELSE} FQueueThread.Start; {$ENDIF} {$ENDIF} end; procedure TCEFWorkScheduler.DestroyQueueThread; begin try if (FQueueThread <> nil) then begin FQueueThread.Terminate; FQueueThread.StopThread; FQueueThread.WaitFor; FreeAndNil(FQueueThread); end; except on e : exception do if CustomExceptionHandler('TCEFWorkScheduler.DestroyQueueThread', e) then raise; end; end; procedure TCEFWorkScheduler.QueueThread_OnPulse(Sender : TObject; aDelay : integer); begin ScheduleWork(aDelay); end; procedure TCEFWorkScheduler.DestroyThread; begin try if (FThread <> nil) then begin FThread.Terminate; FThread.NextPulse(0); FThread.WaitFor; FreeAndNil(FThread); end; except on e : exception do if CustomExceptionHandler('TCEFWorkScheduler.DestroyThread', e) then raise; end; end; {$IFDEF MSWINDOWS} procedure TCEFWorkScheduler.WndProc(var aMessage: TMessage); begin if (aMessage.Msg = CEF_PUMPHAVEWORK) then ScheduleWork(aMessage.lParam) else aMessage.Result := DefWindowProc(FCompHandle, aMessage.Msg, aMessage.WParam, aMessage.LParam); end; procedure TCEFWorkScheduler.DeallocateWindowHandle; begin if (FCompHandle <> 0) then begin DeallocateHWnd(FCompHandle); FCompHandle := 0; end; end; {$WARN SYMBOL_PLATFORM OFF} procedure TCEFWorkScheduler.SetPriority(aValue : TThreadPriority); begin FPriority := aValue; if (FThread <> nil) then FThread.Priority := aValue; end; {$WARN SYMBOL_PLATFORM ON} {$ENDIF} procedure TCEFWorkScheduler.DoMessageLoopWork; begin if (GlobalCEFApp <> nil) then GlobalCEFApp.DoMessageLoopWork; end; procedure TCEFWorkScheduler.SetDefaultInterval(aValue : integer); begin FDefaultInterval := aValue; if (FThread <> nil) then FThread.DefaultInterval := aValue; end; procedure TCEFWorkScheduler.DepleteWork; var i : cardinal; begin i := FDepleteWorkCycles; while (i > 0) do begin DoMessageLoopWork; Sleep(FDepleteWorkDelay); dec(i); end; end; procedure TCEFWorkScheduler.ScheduleMessagePumpWork(const delay_ms : int64); begin if FStopped then exit; if FUseQueueThread and (FQueueThread <> nil) and FQueueThread.Ready then FQueueThread.EnqueueValue(integer(delay_ms)) else begin {$IFDEF MSWINDOWS} if (FCompHandle <> 0) then PostMessage(FCompHandle, CEF_PUMPHAVEWORK, 0, LPARAM(delay_ms)); {$ELSE} {$IFDEF FPC} Application.QueueAsyncCall(@ScheduleWorkAsync, integer(delay_ms)); {$ELSE} TThread.ForceQueue(nil, procedure begin ScheduleWork(delay_ms); end); {$ENDIF} {$ENDIF} end; end; {$IFNDEF MSWINDOWS}{$IFDEF FPC} procedure TCEFWorkScheduler.ScheduleWorkAsync(Data: PtrInt); begin ScheduleWork(integer(Data)); end; {$ENDIF}{$ENDIF} procedure TCEFWorkScheduler.StopScheduler; begin FStopped := True; NextPulse(0); DepleteWork; {$IFDEF MSWINDOWS} DeallocateWindowHandle; {$ENDIF} end; procedure TCEFWorkScheduler.Thread_OnPulse(Sender: TObject); begin if not(FStopped) then DoMessageLoopWork; end; procedure TCEFWorkScheduler.DoWork; begin DoMessageLoopWork; NextPulse(FDefaultInterval); end; procedure TCEFWorkScheduler.ScheduleWork(const delay_ms : int64); begin if not(FStopped) then begin if (delay_ms <= 0) then DoWork else NextPulse(delay_ms); end; end; procedure TCEFWorkScheduler.NextPulse(aInterval : integer); begin if (FThread <> nil) then FThread.NextPulse(aInterval); end; {$IFDEF FPC} procedure Register; begin {$I res/tcefworkscheduler.lrs} RegisterComponents('Chromium', [TCEFWorkScheduler]); end; {$ENDIF} end.