Guest Posted November 11, 2023 (edited) 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 November 11, 2023 by Guest Share this post Link to post
Dalija Prasnikar 1396 Posted November 11, 2023 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 Posted November 11, 2023 (edited) 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 November 11, 2023 by Guest Share this post Link to post
Guest Posted November 11, 2023 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
PeterBelow 238 Posted November 12, 2023 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