CEF4Delphi/source/uCEFTimerWorkScheduler.pas

442 lines
11 KiB
ObjectPascal

// ************************************************************************
// ***************************** CEF4Delphi *******************************
// ************************************************************************
//
// CEF4Delphi is based on DCEF3 which uses CEF to embed a chromium-based
// browser in Delphi applications.
//
// The original license of DCEF3 still applies to CEF4Delphi.
//
// For more information about CEF4Delphi visit :
// https://www.briskbard.com/index.php?lang=en&pageid=cef
//
// Copyright © 2022 Salvador Diaz Fau. All rights reserved.
//
// ************************************************************************
// ************ vvvv Original license and comments below vvvv *************
// ************************************************************************
(*
* Delphi Chromium Embedded 3
*
* Usage allowed under the restrictions of the Lesser GNU General Public License
* or alternatively the restrictions of the Mozilla Public License 1.1
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
*
* Unit owner : Henri Gourvest <hgourvest@gmail.com>
* Web site : http://www.progdigy.com
* Repository : http://code.google.com/p/delphichromiumembedded/
* Group : http://groups.google.com/group/delphichromiumembedded
*
* Embarcadero Technologies, Inc is not permitted to use or redistribute
* this source code without explicit permission.
*
*)
unit uCEFTimerWorkScheduler;
{$IFDEF FPC}
{$MODE OBJFPC}{$H+}
{$ENDIF}
{$I cef.inc}
{.$DEFINE USEEVENTPIPE}
{$IFNDEF TARGET_64BITS}{$ALIGN ON}{$ENDIF}
{$MINENUMSIZE 4}
interface
uses
{$IFDEF DELPHI16_UP}
System.Classes, System.SyncObjs, {$IFDEF MSWINDOWS}WinApi.Windows, WinApi.Messages,{$ENDIF}
{$IFDEF FMX}
FMX.Types, uCEFMacOSCustomCocoaTimer,
{$ELSE}
Vcl.ExtCtrls,
{$ENDIF}
{$ELSE}
Classes, SyncObjs, {$IFDEF MSWINDOWS}Windows,{$ENDIF} ExtCtrls,
{$IFDEF FPC}
LMessages, Forms,
{$ELSE}
Messages,
{$ENDIF}
{$ENDIF}
{$IFDEF USEEVENTPIPE}uCEFLinuxEventPipe,{$ENDIF} uCEFTypes, uCEFConstants,
uCEFApplicationCore;
type
TOnAllowEvent = procedure(Sender: TObject; var allow : boolean) of object;
TCEFTimerWorkScheduler = class
protected
FTimer : {$IFDEF MACOS}TCustomCocoaTimer{$ELSE}TTimer{$ENDIF};
FDepleteWorkCycles : cardinal;
FDepleteWorkDelay : cardinal;
FStopped : boolean;
FIsActive : boolean;
FReentrancyDetected : boolean;
FOnAllowDoWork : TOnAllowEvent;
{$IFDEF USEEVENTPIPE}
FEventPipe : TCEFLinuxEventPipe;
FDelayedWorkTime : TCefTime;
{$ENDIF}
{$IFDEF MSWINDOWS}
FCompHandle : HWND;
{$ENDIF}
function GetIsTimerPending : boolean;
procedure Timer_OnTimer(Sender: TObject);
{$IFDEF USEEVENTPIPE}
procedure FEventPipe_OnPrepare(Sender: TObject; var aTimeout: integer);
procedure FEventPipe_OnCheck(Sender: TObject; var aMustDispatch: boolean);
procedure FEventPipe_OnDispatch(Sender: TObject);
{$ENDIF}
procedure Initialize;
procedure CreateTimer;
procedure DestroyTimer;
procedure KillTimer;
procedure SetTimer(aInterval : integer);
procedure DoWork;
function PerformMessageLoopWork : boolean;
procedure DoMessageLoopWork;
procedure OnScheduleWork(delay_ms : integer);
procedure DepleteWork;
{$IFDEF MSWINDOWS}
procedure WndProc(var aMessage: TMessage);
procedure AllocateWindowHandle;
procedure DeallocateWindowHandle;
{$ELSE}
{$IFDEF FPC}
procedure OnScheduleWorkAsync(Data: PtrInt);
{$ENDIF}
{$ENDIF}
public
constructor Create;
destructor Destroy; override;
procedure StopScheduler;
procedure ScheduleMessagePumpWork(const delay_ms : int64);
property DepleteWorkCycles : cardinal read FDepleteWorkCycles write FDepleteWorkCycles;
property DepleteWorkDelay : cardinal read FDepleteWorkDelay write FDepleteWorkDelay;
property IsTimerPending : boolean read GetIsTimerPending;
property OnAllowDoWork : TOnAllowEvent read FOnAllowDoWork write FOnAllowDoWork;
end;
var
GlobalCEFTimerWorkScheduler : TCEFTimerWorkScheduler = nil;
procedure DestroyGlobalCEFTimerWorkScheduler;
implementation
uses
{$IFDEF DELPHI16_UP}
System.SysUtils, System.Math, {$IFDEF MACOS}System.RTTI, FMX.Forms, FMX.Platform,{$ENDIF}
{$ELSE}
SysUtils, Math,
{$ENDIF}
uCEFMiscFunctions;
procedure DestroyGlobalCEFTimerWorkScheduler;
begin
if (GlobalCEFTimerWorkScheduler <> nil) then
FreeAndNil(GlobalCEFTimerWorkScheduler);
end;
constructor TCEFTimerWorkScheduler.Create;
begin
inherited Create;
Initialize;
{$IFDEF MSWINDOWS}
AllocateWindowHandle;
{$ENDIF}
{$IFDEF USEEVENTPIPE}
FEventPipe := TCEFLinuxEventPipe.Create;
FEventPipe.OnPrepare := {$IFDEF FPC}@{$ENDIF}FEventPipe_OnPrepare;
FEventPipe.OnCheck := {$IFDEF FPC}@{$ENDIF}FEventPipe_OnCheck;
FEventPipe.OnDispatch := {$IFDEF FPC}@{$ENDIF}FEventPipe_OnDispatch;
FEventPipe.InitializePipe;
{$ENDIF}
end;
destructor TCEFTimerWorkScheduler.Destroy;
begin
DestroyTimer;
{$IFDEF MSWINDOWS}
DeallocateWindowHandle;
{$ENDIF}
{$IFDEF USEEVENTPIPE}
if assigned(FEventPipe) then
FreeAndNil(FEventPipe);
{$ENDIF}
inherited Destroy;
end;
procedure TCEFTimerWorkScheduler.Initialize;
begin
{$IFDEF MSWINDOWS}
FCompHandle := 0;
{$ENDIF}
{$IFDEF USEEVENTPIPE}
FEventPipe := nil;
InitializeCefTime(FDelayedWorkTime);
{$ENDIF}
FOnAllowDoWork := nil;
FTimer := nil;
FStopped := False;
FIsActive := False;
FReentrancyDetected := False;
FDepleteWorkCycles := CEF_TIMER_DEPLETEWORK_CYCLES;
FDepleteWorkDelay := CEF_TIMER_DEPLETEWORK_DELAY;
end;
{$IFDEF MSWINDOWS}
procedure TCEFTimerWorkScheduler.WndProc(var aMessage: TMessage);
begin
if (aMessage.Msg = CEF_PUMPHAVEWORK) then
OnScheduleWork(aMessage.lParam)
else
aMessage.Result := DefWindowProc(FCompHandle, aMessage.Msg, aMessage.WParam, aMessage.LParam);
end;
procedure TCEFTimerWorkScheduler.AllocateWindowHandle;
begin
if (FCompHandle = 0) and (GlobalCEFApp <> nil) and
((GlobalCEFApp.ProcessType = ptBrowser) or GlobalCEFApp.SingleProcess) then
FCompHandle := AllocateHWnd({$IFDEF FPC}@{$ENDIF}WndProc);
end;
procedure TCEFTimerWorkScheduler.DeallocateWindowHandle;
begin
if (FCompHandle <> 0) then
begin
DeallocateHWnd(FCompHandle);
FCompHandle := 0;
end;
end;
{$ENDIF}
{$IFDEF USEEVENTPIPE}
procedure TCEFTimerWorkScheduler.FEventPipe_OnPrepare(Sender: TObject; var aTimeout: integer);
begin
aTimeout := GetTimeIntervalMilliseconds(FDelayedWorkTime);
end;
procedure TCEFTimerWorkScheduler.FEventPipe_OnCheck(Sender: TObject; var aMustDispatch: boolean);
var
TempValue : integer;
begin
if FEventPipe.HasData or FEventPipe.HasPendingData then
begin
TempValue := 0;
FEventPipe.Read(TempValue);
OnScheduleWork(TempValue);
end;
aMustDispatch := GetTimeIntervalMilliseconds(FDelayedWorkTime) = 0;
end;
procedure TCEFTimerWorkScheduler.FEventPipe_OnDispatch(Sender: TObject);
begin
KillTimer;
DoWork;
end;
{$ENDIF}
procedure TCEFTimerWorkScheduler.StopScheduler;
begin
FStopped := True;
KillTimer;
DepleteWork;
end;
procedure TCEFTimerWorkScheduler.DepleteWork;
var
i : cardinal;
begin
i := FDepleteWorkCycles;
while (i > 0) do
begin
DoMessageLoopWork;
Sleep(FDepleteWorkDelay);
dec(i);
end;
end;
{$IFNDEF MSWINDOWS}{$IFDEF FPC}
procedure TCEFTimerWorkScheduler.OnScheduleWorkAsync(Data: PtrInt);
begin
OnScheduleWork(integer(Data));
end;
{$ENDIF}{$ENDIF}
procedure TCEFTimerWorkScheduler.CreateTimer;
begin
if (FTimer = nil) then
begin
{$IFDEF MACOS}
FTimer := TCustomCocoaTimer.Create;
{$ELSE}
FTimer := TTimer.Create(nil);
{$ENDIF}
FTimer.OnTimer := {$IFDEF FPC}@{$ENDIF}Timer_OnTimer;
FTimer.Enabled := False;
end;
end;
procedure TCEFTimerWorkScheduler.DestroyTimer;
begin
if (FTimer <> nil) then
FreeAndNil(FTimer);
end;
procedure TCEFTimerWorkScheduler.KillTimer;
begin
{$IFDEF USEEVENTPIPE}
InitializeCefTime(FDelayedWorkTime);
{$ELSE}
if (FTimer <> nil) then
FTimer.Enabled := False;
{$ENDIF}
end;
procedure TCEFTimerWorkScheduler.SetTimer(aInterval : integer);
begin
{$IFDEF USEEVENTPIPE}
FDelayedWorkTime := DoubleToCefTime((DoubleTimeNow + aInterval) / 1000);
{$ELSE}
if (FTimer = nil) then
CreateTimer;
FTimer.Interval := aInterval;
FTimer.Enabled := True;
{$ENDIF}
end;
function TCEFTimerWorkScheduler.GetIsTimerPending : boolean;
begin
{$IFDEF USEEVENTPIPE}
Result := GetTimeIntervalMilliseconds(FDelayedWorkTime) > 0;
{$ELSE}
Result := (FTimer <> nil) and FTimer.Enabled;
{$ENDIF}
end;
procedure TCEFTimerWorkScheduler.OnScheduleWork(delay_ms : integer);
begin
if FStopped or
((delay_ms = high(integer)) and IsTimerPending) then
exit;
KillTimer;
if (delay_ms <= 0) then
DoWork
else
SetTimer(min(delay_ms, CEF_TIMER_MAXDELAY));
end;
procedure TCEFTimerWorkScheduler.Timer_OnTimer(Sender: TObject);
begin
KillTimer;
DoWork;
end;
procedure TCEFTimerWorkScheduler.DoWork;
begin
if PerformMessageLoopWork then
ScheduleMessagePumpWork(0)
else
if not(IsTimerPending) then
ScheduleMessagePumpWork(high(integer));
end;
function TCEFTimerWorkScheduler.PerformMessageLoopWork : boolean;
begin
Result := False;
if FIsActive then
begin
FReentrancyDetected := True;
exit;
end;
FReentrancyDetected := False;
DoMessageLoopWork;
Result := FReentrancyDetected;
end;
procedure TCEFTimerWorkScheduler.DoMessageLoopWork;
var
TempAllow : boolean;
begin
TempAllow := True;
if assigned(FOnAllowDoWork) then
FOnAllowDoWork(self, TempAllow);
if TempAllow and (GlobalCEFApp <> nil) then
try
FIsActive := True;
GlobalCEFApp.DoMessageLoopWork;
finally
FIsActive := False;
end;
end;
procedure TCEFTimerWorkScheduler.ScheduleMessagePumpWork(const delay_ms : int64);
begin
if FStopped then exit;
{$IFDEF MSWINDOWS}
if (FCompHandle <> 0) then
PostMessage(FCompHandle, CEF_PUMPHAVEWORK, 0, LPARAM(delay_ms));
{$ENDIF}
{$IFDEF LINUX}
{$IFDEF FPC}
{$IFDEF USEEVENTPIPE}
if assigned(FEventPipe) then
FEventPipe.Write(integer(delay_ms));
{$ELSE}
Application.QueueAsyncCall(@OnScheduleWorkAsync, integer(delay_ms));
{$ENDIF}
{$ELSE}
TThread.ForceQueue(nil, procedure
begin
OnScheduleWork(integer(delay_ms));
end);
{$ENDIF}
{$ENDIF}
{$IFDEF MACOS}
{$IFDEF FPC}
Application.QueueAsyncCall(@OnScheduleWorkAsync, integer(delay_ms));
{$ELSE}
TThread.ForceQueue(nil, procedure
begin
OnScheduleWork(integer(delay_ms));
end);
{$ENDIF}
{$ENDIF}
end;
end.