Jump to content
Guest

how can I effectively destroy a form that contains a TEdgeBrowser while it is loading?

Recommended Posts

Guest

Hello,

I have a use case of TEdgeBrowser in Delphi 11 which completely screws up.

I'm hovering the mouse over a button and when a certain amount of time passes, it displays a dynamically created window which has a TEdgeBrowser. The latter will display the content with the call of a Navigate. If I move the mouse away from my button area, I stop navigation and destroy the window. Sometimes I have an access violation of ntdll.dll and it stops on TEdgeBrowser is finalizing the creation of its environment.

I understood that the EdgeBrowser is returning control to the window except that it's too late.

Looking a little, it's at a critical section where he can no longer enter and I suppose he can no longer find the Handle of the window that hosts it since it is destroyed.

I try to delay loading the browser as much as possible by avoiding process messages and sleep but once in 10, and especially when I hover the mouse quickly over my button to trigger the described navigation process and access violation, ntdll.dll, etc etc ... That's the first problem but it is less harmful than the second which is very serious: once I have this blockage, I can no longer load any TEdgeBrowser browser in my application knowing that I use it everywhere and each time, I have empty content of the page, pdf, or file to load by calling Navigate.

I feel like something is broken in my program or something like a special engine or loader in Delphi that means I can only use the TEdgeBrowser again after restarting my application.

Can you help me please, because I can't find any serious solutions for this problem which is starting to persist. Thanks for your helps

Edited by Guest

Share this post


Link to post

Can you please show the minimal working example of your code that you have problem with. It is very hard to follow explanations of the code. We need to see exact code, to be able to help and propose solution.

Share this post


Link to post
Guest

Thank you for your answer, I tried to create a mini project to extract the only part that is creating the problem for me.

I tried to reproduce the problem in a separate example
Here is the code for the main form

unit MainForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons, FormHelp;

type
  TMain = class(TForm)
    BitBtnHelp1: TBitBtn;
    BitBtnHelp2: TBitBtn;
    procedure FormCreate(Sender: TObject);
  private
    FHelpManager: THelpManager;
  public
  end;

var
  Main: TMain;

implementation

{$R *.dfm}

procedure TMain.FormCreate(Sender: TObject);
var
  i: integer;
begin
  FHelpManager := THelpManager.Create;
  For i:= ComponentCount-1 Downto 0 Do
  begin
    if (Components[i] Is TBitBtn) then
    begin
      TBitBtn(Components[i]).OnMouseLeave := FHelpManager.OnBitBtnMouseLeave;
      TBitBtn(Components[i]).OnMouseEnter := FHelpManager.OnBitBtnMouseEnter;
    end;
  end;
end;

end.

and in this unit the code to manage the display

unit FormHelp;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Winapi.WebView2, Winapi.ActiveX,
  Vcl.Edge, VCL.ExtCtrls, VCL.Buttons;

const
  CM_HTMLNAVIGATE = WM_USER + $1001;

type
  THelpForm = class;

  THelpManager = class
  private
    FHelpForm: THelpForm;
    FTimer: TTimer;
    FHelpFileName: String;
    procedure FireTimerTreatment();
  public
    procedure   OnBitBtnMouseLeave(Sender: TObject);
    procedure   OnBitBtnMouseEnter(Sender: TObject);
    procedure   OnTimer(Sender: TObject);
    constructor Create();
  end;

  THelpForm = class(TForm)
    Browser: TEdgeBrowser;
    procedure BrowserCreateWebViewCompleted(Sender: TCustomEdgeBrowser;
      AResult: HRESULT);
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
  protected
    FHelpFileNameToLoad:string;
    FIsWebViewOfBrowserCreated: Boolean;
    procedure DoNavigation(var Message: TMessage);  message CM_HTMLNAVIGATE;
    procedure SetIsWebViewOfBrowserCreated(AValue: Boolean);
    function  GetIsWebViewOfBrowserCreated(): Boolean;
  public
    property  IsWebViewOfBrowserCreated: Boolean read GetIsWebViewOfBrowserCreated write SetIsWebViewOfBrowserCreated default False;
    procedure Show(AFileName: String); reintroduce;
  end;

var
  HelpForm: THelpForm;

implementation

{$R *.dfm}

Uses System.UITypes;

{ THandleHelp }

constructor THelpManager.Create();
begin
  FHelpForm := Nil;
  FTimer := Nil;
  FHelpFileName := '';
end;

procedure THelpManager.OnBitBtnMouseEnter(Sender: TObject);
begin
  if not(Sender is TBitBtn) then
    Exit
  else begin
    If (TBitBtn(Sender).Tag=1) Then
      FHelpFileName := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName))+'Aide\MyFile1.html'
    else if (TBitBtn(Sender).Tag=2) Then
      FHelpFileName := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName))+'Aide\MyFile2.pdf';
  end;

  if (FTimer<>Nil) then
  begin
    FTimer.Enabled := True;
    Exit;
  end else begin
    FTimer := TTimer.Create(nil);
    FTimer.Interval := 800;
    FTimer.Enabled := True;
    FTimer.OnTimer := OnTimer;
  end;
end;

procedure THelpManager.OnBitBtnMouseLeave(Sender: TObject);
begin
  if not (Sender is TBitBtn) then
    Exit;

  if FHelpForm<>nil then
  Begin
    var counter: Integer := 0;
    While ((not FHelpForm.IsWebViewOfBrowserCreated) or
          (FHelpForm.Browser.BrowserControlState = TCustomEdgeBrowser.TBrowserControlState.Creating) or
          (FHelpForm.Browser.WebViewCreated=False)) and (counter<99) do
    Begin
      Inc(counter);
    End;
  End;

  if FTimer<>nil then
    FreeAndNil(FTimer);

  if FHelpForm<>nil then
  begin
    FHelpForm.Close;
    FHelpForm := Nil;
  end;
end;

procedure THelpManager.FireTimerTreatment;
begin
  if (FHelpForm<>nil) and (FHelpForm.Showing) then
    Exit;

  if (FHelpForm=nil) then
    FHelpForm := THelpForm.Create(Nil);

  if (FileExists(FHelpFileName) = false)  then
  begin
    MessageDlg('No help file was found !', mtWarning, [mbOK], 0);
    Exit;
  end;

  FHelpForm.Show(FHelpFileName);
end;

procedure THelpManager.OnTimer(Sender: TObject);
begin
  FTimer.Enabled := False;
  FireTimerTreatment();
end;

procedure THelpForm.BrowserCreateWebViewCompleted(Sender: TCustomEdgeBrowser;
  AResult: HRESULT);
begin
  FIsWebViewOfBrowserCreated := True;
end;

procedure THelpForm.DoNavigation(var Message: TMessage);
begin
  Browser.Navigate(FHelpFileNameToLoad);
end;

procedure THelpForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

procedure THelpForm.FormShow(Sender: TObject);
begin
  PostMessage(Self.handle, CM_HTMLNAVIGATE, 0, 0);
end;

function THelpForm.GetIsWebViewOfBrowserCreated: Boolean;
begin
  Result := FIsWebViewOfBrowserCreated;
end;

procedure THelpForm.SetIsWebViewOfBrowserCreated(AValue: Boolean);
begin
  FIsWebViewOfBrowserCreated := AValue;
end;

procedure THelpForm.Show(AFileName: String);
begin
  FHelpFileNameToLoad := AFileName;
  Position := TPosition.poMainFormCenter;
  inherited Show;
end;

end.

by hovering the mouse gently over one of the 2 buttons and especially when the HelpForm is being drawn, I have an access violation. Note: if I increase the interval of my counter, I will have less of the problem and if I decrease it, I will have more of the error

the call stack is like this (with madExcept)

exception number   : 1
exception class    : EAccessViolation
exception message  : Violation d'accès à l'adresse 7757F603 dans le module 'ntdll.dll'. Ecriture de l'adresse 00000014.

main thread ($4340):
7757f603 +93 ntdll.dll
7755ff74 +44 ntdll.dll                              RtlEnterCriticalSection
00ac252f +0b Project1.exe System.SyncObjs  1104  +1 TCriticalSection.Acquire
00b863b7 +0f Project1.exe Vcl.Edge         1709  +1 TCustomEdgeBrowser.ProcessHResult
00b8532e +52 Project1.exe Vcl.Edge         1039  +2 TCustomEdgeBrowser.CreateCoreWebView2ControllerCompleted
00b89075 +11 Project1.exe Vcl.Edge          834  +1 {Vcl.Edge}Callback`2.CreateAs[3]$ActRec<System.HRESULT,Winapi.WebView2.ICoreWebView2Controller,Winapi.WebView2.ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>.$0$Body
76915b0b +0b USER32.dll                             DispatchMessageW
00b6d3d7 +f3 Project1.exe Vcl.Forms       11488 +23 TApplication.ProcessMessage
00b6d41a +0a Project1.exe Vcl.Forms       11518  +1 TApplication.HandleMessage
00b6d759 +d1 Project1.exe Vcl.Forms       11657 +27 TApplication.Run
00b93e11 +49 Project1.exe Project1           19  +4 initialization
7717fcc7 +17 KERNEL32.DLL                           BaseThreadInitThunk

When I get this error, if I hover the mouse over any button again, I have the window that appears, the browser container that is there but is empty.
Besides, this is where I wanted to go:
In my application where I have dozens of windows all using the TEdgeBrowser, none will display the loaded content and I am always forced to restart the application to reload my windows.
There is something broken in this component (something that is frozen or still loaded but cannot be unloaded) and is blocking the browser state.
Besides, I think that the TEdgeBrowser is dependent on a global variable or an initialization that I was unable to find in the Delphi sources.
Or maybe it's a Windows problem?
No idea.

Edited by Guest

Share this post


Link to post
Guest

I changed the code a little by hiding the window with a caHide instead of caFree and closing the window which embeds the browser without making a Nil. It works because the window is still there even hidden. But that's not the goal of the game. So the THelpManager class is responsible for the life cycle of the form but that doesn't explain to me why I always have a total crash at the TEdgeBrowser level in the application. Knowing that I sometimes have cases (other than this example) where the TEdgeBrowser (whether created dynamically or directly placed in the Dfm) crashes with an error of this type but I no longer have control over it. reload from any window in my application.

Share this post


Link to post
21 hours ago, moezben said:

Begin var counter: Integer := 0; While ((not FHelpForm.IsWebViewOfBrowserCreated) or (FHelpForm.Browser.BrowserControlState = TCustomEdgeBrowser.TBrowserControlState.Creating) or (FHelpForm.Browser.WebViewCreated=False)) and (counter<99) do Begin Inc(counter); End; End;

It is never a good idea to block message processing in the main thread when a complex control is doing something that may depend on certain messages.

The browser has an OnNavigateComplete event, use that to detect when the browser has hopefully reached a "save" state. When stopping the navigation, do not destroy the help form, hide it and only destroy it after the OnNavigationComplete event has fired. In fact I would not destroy it at all since it may get reused again.

 

Anyway, your UI design is rather user-unfriendly in my opinion. I would not like it at all if I cannot read all of the help text just because the mouse pointer moved off the trigger control by accident. And if you only want to show a small help text explaining what the button is for a complex heavy weight browser is certainly not the most effective (and fastest!) way to do that. VCL controls have the Hint property for this purpose, and if you need decorated text (e.g. simple HTML) there are replacements for the THintwindow class used by default that can display text with formatting, like HTML, RTF, or probably also markdown.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×