Net-Base Ajakiri

14.06.2026

Delphi WebView2 FMX-is: korrektselt initsialiseerida, JS-Bridge üles ehitada, allalaadimised ja silumine kontrolli all

WebView2 FireMonkey'is kõlab nagu „lihtsalt brauseri manustamine“, kuid praktikas ebaõnnestub see initsialiseerimisel, navigeerimissündmustel, JS↔Delphi-silla, allalaadimishalduse ja silumisel. See lähtekoodilõik näitab robustset mustrit selgete vastutusaladega...

14.06.2026

Ajakirjateemast projektipraktikasse

Sobivad teenuse- ja tehnilised lehed postituse jaoks

Kellelegi, kes olemasolevas ärirakenduses tahab „natuke“ kaasaegset veebisisu sisse integreerida, satub Windows puhul WebView2 juurde. In Delphi WebView2 FMX on põhiprobleem harva pelgalt URL-i kuvamine, vaid puhas integreerimine FireMonkey-liidesesse (FMX), usaldusväärne initsialiseerimine (asünkroonne ja COM-põhine) ning Edge’i lõkse User-Data-kaustade, allalaadimiste, silumise ja robustse JS↔Delphi-kommunikatsiooni osas.

See source-piirang näitab musterlahendust, mida ma hooldatavate rakenduste puhul eelistan: kapseldatud „Host“-objekt, mis kontrollib WebView2 elutsüklit, ning määratletud sild WebMessage (JSON) kaudu, mitte meelevaldne „ExecuteScript igalpool“. Eesmärk ei ole demo-kood, vaid moodul, mis talub kasvavaid kliente.

Miks WebView2 FMX-is erineb „Browser-Component drop“-ist

WebView2 on COM-/WinRT-lähedane API asünkroonse initsialiseerimisega. FireMonkey abstrakteerib Windows-handleid, ent WebView2 jaoks vajate lõpuks tõelist vanemaknast (HWND) ja kontrollitud suuruse-/fookuse-edastust. Samal ajal ei pruugi sündmused alati käia seal, kus FMX-is eeldatakse. Kui alustate siin „quick and dirty“, tekivad tüüpiliselt:

  • sporadilised AV-d vormi sulgemisel (Callbacks tabavad pärast Destroyi)
  • navigatsiooni-sündmused valest lõimekontekstist
  • ebapüsiv salvestus/vahemälu probleemid seetõttu, et UserDataFolder-strateegia on ebaselge
  • puuduvad allalaadimised või „kinni jäänud“ allalaadimisdialoogid
  • silumine ainult juhuse tahtel, mitte sihipärase Remote-Debug-Konfiguratsiooni kaudu

Vastumürk on selge elutsükkel: Create → InitializeAsync → Attach → Navigate → Detach/Dispose – ja määratletud piir UI ja brauseri-mootori vahel.

Source-Schnipsel: WebView2Host für Delphi WebView2 FMX

Järgmine koodisektsioon visandab kapseldatud host-klass, mis (1) loob WebView2-Environment-konfiguratsiooni, (2) seob Controller-objekti HWND-ga, (3) juhtmestikustab navigatsiooni- ja allalaadimissündmused ning (4) pakub JSON-põhist JS-silda läbi WebMessageReceived. Kood on teadlikult „arhitektuurivalmis“: see kapseldab COM-viiteid, väldib callbackide järellainekuid pärast Destroyi ning lubab operatsioonipiire nagu eraldi UserDataFolder „pro User“ või „pro Maschine“.unit WebView2Host;

interface

uses
System.SysUtils, System.Classes, System.IOUtils, System.JSON,
Winapi.Windows, Winapi.ActiveX,
FMX.Types,
WebView2, WebView2_TLB; // sõltuvalt seadistusest: WebView2.pas või Import-TLB

type
TWebView2JsonMessage = record
Name: string;
CorrelationId: string;
Payload: TJSONObject;
class function TryParse(const Json: string; out Msg: TWebView2JsonMessage): Boolean; static;
end;

TOnWebMessage = reference to procedure(const Msg: TWebView2JsonMessage);
TOnDownload = reference to procedure(const FileName, MimeType: string; TotalBytes: Int64);

TWebView2Host = class
private
FParentHwnd: HWND;
FUserDataFolder: string;
FEnvironment: ICoreWebView2Environment;
FController: ICoreWebView2Controller;
FWebView: ICoreWebView2;
FDestroyed: Boolean;
FOnWebMessage: TOnWebMessage;
FOnDownload: TOnDownload;

procedure EnsureNotDestroyed;
function MakeUserDataFolder: string;

// Event handler
procedure HookEvents;
procedure UnhookEvents;

procedure OnWebMessageReceived(
const sender: ICoreWebView2;
const args: ICoreWebView2WebMessageReceivedEventArgs);

procedure OnDownloadStarting(
const sender: ICoreWebView2;
const args: ICoreWebView2DownloadStartingEventArgs);

public
constructor Create(AParentHwnd: HWND; const AUserDataFolder: string = “);
destructor Destroy; override;

procedure InitializeAsync;
procedure Navigate(const Url: string);
procedure Resize(const Bounds: TRect);

procedure PostJsonToWeb(const Obj: TJSONObject);
procedure SetDevToolsEnabled(const Enabled: Boolean);

property WebView: ICoreWebView2 read FWebView;
property OnWebMessage: TOnWebMessage read FOnWebMessage write FOnWebMessage;
property OnDownload: TOnDownload read FOnDownload write FOnDownload;
end;

implementation

{ TWebView2JsonMessage }

class function TWebView2JsonMessage.TryParse(const Json: string; out Msg: TWebView2JsonMessage): Boolean;
var
V: TJSONValue;
O: TJSONObject;
begin
Result := False;
Msg.Name := “;
Msg.CorrelationId := “;
Msg.Payload := nil;

V := TJSONObject.ParseJSONValue(Json);
try
if not (V is TJSONObject) then Exit;
O := TJSONObject(V);

Msg.Name := O.GetValue(’name‘, “);
Msg.CorrelationId := O.GetValue(‚cid‘, “);

// Payload võib puududa või olla null
if O.TryGetValue(‚payload‘, Msg.Payload) then
Msg.Payload := TJSONObject(Msg.Payload.Clone)
else
Msg.Payload := TJSONObject.Create;

Result := Msg.Name <> “;
finally
V.Free;
end;
end;

{ TWebView2Host }

constructor TWebView2Host.Create(AParentHwnd: HWND; const AUserDataFolder: string);
begin
inherited Create;
FParentHwnd := AParentHwnd;
FUserDataFolder := AUserDataFolder;
FDestroyed := False;
end;

destructor TWebView2Host.Destroy;
begin
FDestroyed := True;

// Vabasta sündmused enne COM-objektide vabastamist
UnhookEvents;

FWebView := nil;
FController := nil;
FEnvironment := nil;

inherited;
end;

procedure TWebView2Host.EnsureNotDestroyed;
begin
if FDestroyed then
raise EInvalidOperation.Create(‚WebView2Host on juba hävitatud.‘);
end;

function TWebView2Host.MakeUserDataFolder: string;
begin
if FUserDataFolder <> “ then
Exit(FUserDataFolder);

// Tavapraktika: per rakendus + per Windows-kasutaja, mitte programmikausta
Result := TPath.Combine(TPath.GetHomePath, ‚AppDataLocalMyCompanyMyAppWebView2‘);
ForceDirectories(Result);
end;

procedure TWebView2Host.InitializeAsync;
var
UserData: string;
Opt: ICoreWebView2EnvironmentOptions;
begin
EnsureNotDestroyed;

UserData := MakeUserDataFolder;

// Valikud: siia saab lisada täiendavaid brauseriargumente, nt Remote-Debug
Opt := TCoreWebView2EnvironmentOptions.Create;

// Async CreateEnvironment
OleCheck(CreateCoreWebView2EnvironmentWithOptions(
nil, PWideChar(UserData), Opt,
TCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler.Create(
procedure (errorCode: HRESULT; const createdEnvironment: ICoreWebView2Environment)
begin
if FDestroyed then Exit;
OleCheck(errorCode);

FEnvironment := createdEnvironment;

// Controller an Parent HWND binden
OleCheck(FEnvironment.CreateCoreWebView2Controller(
FParentHwnd,
TCoreWebView2CreateCoreWebView2ControllerCompletedHandler.Create(
procedure (errorCode2: HRESULT; const createdController: ICoreWebView2Controller)
begin
if FDestroyed then Exit;
OleCheck(errorCode2);

FController := createdController;
OleCheck(FController.get_CoreWebView2(FWebView));

HookEvents;

// Initial sichtbar machen
FController.put_IsVisible(1);
end)));
end)));
end;

procedure TWebView2Host.HookEvents;
var
TokenMsg, TokenDl: EventRegistrationToken;
begin
if (FWebView = nil) then Exit;

// WebMessageReceived (JS->Delphi)
TokenMsg.value := 0;
OleCheck(FWebView.add_WebMessageReceived(
TCoreWebView2WebMessageReceivedEventHandler.Create(
procedure(const sender: ICoreWebView2; const args: ICoreWebView2WebMessageReceivedEventArgs)
begin
if FDestroyed then Exit;
OnWebMessageReceived(sender, args);
end), TokenMsg));

// DownloadStarting
TokenDl.value := 0;
OleCheck(FWebView.add_DownloadStarting(
TCoreWebView2DownloadStartingEventHandler.Create(
procedure(const sender: ICoreWebView2; const args: ICoreWebView2DownloadStartingEventArgs)
begin
if FDestroyed then Exit;
OnDownloadStarting(sender, args);
end), TokenDl));

// Märkus: usaldusväärseks Unhookimiseks peaksite tokenid salvestama.
// Paljudes projektides on see sageli piisav, kui host eksisteerib vaid vormi elutsükli jooksul.
end;

procedure TWebView2Host.UnhookEvents;
begin
// Robustne variant: tokenid meelde jätta ja kutsuda remove_*.
// Siin kommentaarina, sest import-unit-i seadistus ja tokenite haldus varieeruvad sõltuvalt wrapperist.
end;

procedure TWebView2Host.OnWebMessageReceived(
const sender: ICoreWebView2;
const args: ICoreWebView2WebMessageReceivedEventArgs);
var
Json: PWideChar;
S: string;
Msg: TWebView2JsonMessage;
begin
Json := nil;
OleCheck(args.TryGetWebMessageAsString(Json));
try
S := Json;
finally
CoTaskMemFree(Json);
end;

if Assigned(FOnWebMessage) and TWebView2JsonMessage.TryParse(S, Msg) then
begin
try
FOnWebMessage(Msg);
finally
Msg.Payload.Free;
end;
end;
end;

procedure TWebView2Host.OnDownloadStarting(
const sender: ICoreWebView2;
const args: ICoreWebView2DownloadStartingEventArgs);
var
Dl: ICoreWebView2DownloadOperation;
Uri, Mime, ResultFile: PWideChar;
Total: Int64;
FileName: string;
begin
Uri := nil;
Mime := nil;
ResultFile := nil;

OleCheck(args.get_DownloadOperation(Dl));
OleCheck(Dl.get_TotalBytesToReceive(Total));

// Praktikas: ResultFileName võib algselt olla tühi, sõltuvalt allikast.
OleCheck(Dl.get_ResultFilePath(ResultFile));
OleCheck(Dl.get_MimeType(Mime));
OleCheck(Dl.get_Uri(Uri));

try
FileName := ExtractFileName(string(ResultFile));
if FileName = “ then
FileName := ‚download.bin‘;

if Assigned(FOnDownload) then
FOnDownload(FileName, string(Mime), Total);

// Valikuline: kohandatud allalaadimiskasutajaliides — sel juhul seadke Handled
// args.put_Handled(1);
finally
CoTaskMemFree(Uri);
CoTaskMemFree(Mime);
CoTaskMemFree(ResultFile);
end;
end;

procedure TWebView2Host.Navigate(const Url: string);
begin
EnsureNotDestroyed;
if FWebView = nil then
raise EInvalidOperation.Create(‚WebView2 ei ole veel initsialiseeritud.‘);

OleCheck(FWebView.Navigate(PWideChar(Url)));
end;

procedure TWebView2Host.Resize(const Bounds: TRect);
var
R: tagRECT;
begin
if FController = nil then Exit;
R.Left := Bounds.Left;
R.Top := Bounds.Top;
R.Right := Bounds.Right;
R.Bottom := Bounds.Bottom;
OleCheck(FController.put_Bounds(R));
end;

procedure TWebView2Host.PostJsonToWeb(const Obj: TJSONObject);
var
S: string;
begin
EnsureNotDestroyed;
if FWebView = nil then Exit;

S := Obj.ToJSON;
OleCheck(FWebView.PostWebMessageAsString(PWideChar(S)));
end;

procedure TWebView2Host.SetDevToolsEnabled(const Enabled: Boolean);
var
Settings: ICoreWebView2Settings;
begin
if (FWebView = nil) then Exit;
OleCheck(FWebView.get_Settings(Settings));
OleCheck(Settings.put_AreDevToolsEnabled(Ord(Enabled)));
end;

end.

Lähenemise eesmärk

  • Elutsükli kapseldamine: FMX-vorm tunneb ainult „Initialize/Navigate/Resize“, mitte COM-i detaile.
  • Lepingu-põhine sild: JSON-sõnumid väljadega name, valikuline cid (Correlation-ID) ja payload on hooldatavad ja testitavad.
  • Töökindel püsivus: kontrollitud UserDataFolder väldib vahemälu kokkupõrkeid, õiguste probleeme ja olukordi, kus „see töötab arendaja masinal, mitte tootmises“.

JS↔Delphi-sild: miks WebMessage on stabiilsem kui ExecuteScript

WebView2 pakub mitmeid suhtlusviise. Praktikas on ExecuteScript ahvatlev, kuid kehvasti versioonitav: te surute stringe interpreteerijasse ilma selgete vastusekanaliteta ja ilma robusetse veakaardistamiseta. PostWebMessageAsString / WebMessageReceived on seevastu määratletud kanal.

Servajuhtum, mis ettevõttekeskkondades sageli esineb: peate web-frontendist (nt sisemine portaal) käivitama Delphi-töövoo (printimine, seadme juurdepääs, pärandintegreerimine). Siis vajate:

  • sõnuminimede valget nimekirja
  • Correlation-ID-sid asünkroonsete vastuste jaoks
  • tsentraliseeritud kohta, mis valideerib payloade (nt kohustuslikud väljad, suuruspiirangud)

Hostis on see koht OnWebMessageReceived. Tegelik valideerimine kuulub ülemisele kihile (nt Application-Service), et hoida UI-/WebView2-tehnika ja äriloogika eraldi (klassikaline kihtarhitektuur: UI → Application → Domain → Infrastruktur).

Allalaadimised ja failide hoidmine: mis tootmises sageli üllatab

WebView2 puhul käib allalaadimine läbi ICoreWebView2DownloadOperation. Olenevalt allikast võib ResultFilePath alguses tühi olla või määratakse see alles hiljem. Lisaks ei soovi paljud ettevõtted, et lõppkasutajad salvestaksid kontrollimatutesse kaustadesse.

Tõestatud mustrid:

  • DownloadStarting sündmuse kinni püüda ja args.put_Handled(1) abil UI ise üle võtta (oma rada, nimekonventsioon, karantiinikaust).
  • Failisuuruse piirangud ja MIME-tüübi kontrollid, et vältida „juhuslikku 4 GB logifaili“.
  • Auditeerimine: kirjutage allalaadimise metaandmed (URI, MIME, baitide arv) oma logisse, mitte sisu.

Kui teil on reguleeritud protsessid (nt heakskiidud, jälgitavus), on sündmuste kaudu töötlemine ainus koht, kus saate brauserimaailma oma operatiivreeglitesse integreerida.

Silumine: DevTools, kaugsilumise port ja reprodutseeritavad olekud

WebView2 silumine nurjub sageli selle taha, et olekud ei ole reprodutseeritavad. Kaks reguleerimisvõtit aitavad:

  • DevTools lubamine/keelamine läbi ICoreWebView2Settings (koodis: SetDevToolsEnabled) – vabastuses sageli välja, tugijuhtumil sihipäraselt sisse.
  • Stabiilne UserDataFolder: kui teie tugi peab vea taasesitama, on määratletud rada kuldaväärt. Saate kausta varundada/zip’ida (Tähelepanu: andmekaitse/PII) ja olekuid sihipäraselt võrrelda.

Võimalusel (sõltuvalt wrapperist) saate EnvironmentOptions’ile lisada täiendavaid brauseriargumente, nt kaugsilumise pordi. See on asjakohane, kui peate analüüsima rakendust testisüsteemil ilma kohalike arendajatööriistadeta. Piirang: tootmiskeskkonnas peab see olema korrektselt lubatud ja dokumenteeritud, vastasel juhul loote tarbetu ründepinna.

Põrkekivid Delphi WebView2 FMX-is: COM, lõimed ja vormi elutsükkel

1) Tagasikutsed pärast sulgemist

Asünkroonsed CompletedHandlerid võivad saabuda pärast seda, kui vorm on juba sulgemisel. Snippetis verhindert FDestroyed juurdepääsu vabastatud objektidele. Veelgi robustsem on lisaks:

  • Säilitage sündmuste tokenid ja kutsuge Destroy-is korrektselt remove_*.
  • Lubage InitializeAsync ainult üks kord (olekumasin: Created/Initializing/Ready/Disposed).

2) Thread-Kontext

Paljud handlerid tulevad küll „UI‑lähedaselt“, kuid ärge tuginege sellele, et saate otse FMX‑kontrollidesse kirjutada. Kui värskite UI‑d OnWebMessage-s, on turvaline variant TThread.Queue(nil, ...). Ma eraldan meelsasti vastutused: host kogub sündmuse, rakendusteenus otsustab, UI värskendatakse üksnes Queue kaudu.

3) DPI/Resize und FMX-Layouts

FMX töötab loogiliste ühikutega, WebView2 ootab pikslirekte. Praktikas vajate selget kohta, kus teisendate FMX‑kontrollide bounds päris piksliteks. Snippet eeldab TRect-i; teie vormis peaksite sellest tuletama WinAPI‑koordinaadid (nt FMX.Platform.Win ja handle‑APIde kaudu). Kui rakendus skaleerub monitori DPI järgi, testige monitorite vahetust: WebView2 on siin tundlikum kui puhtad FMX‑kontrollid.

Millal WebView2 FMX‑is tasub – ja millal mitte

WebView2 tasub, kui soovite olemasolevas Delphi‑klientrakenduses veebitehnoloogiat sihipäraselt kasutada: manustatud admin‑vaated, OAuth/OIDC‑sisselogimisvood, HTML‑aruanded, sisemised portaalid või kontrollitud „Micro‑Frontends“. Ka moderniseerimise sildana on see praktiline, kui vastutused on selgelt eraldatud ja sild ei muutu äriloogika kontrollimatuks tagaukseks.

Piirid lähenemisel:

  • Platvorm: Muster on Windows‑keskne. FMX on mitmeplatvormiline, WebView2 seda ei ole. macOS/iOS/Android jaoks vajate teisi WebView‑tehnoloogiaid või abstraktsioonikihi.
  • Turvalisus/Kõvendamine: Kui laaditakse väliseid sisu, peate navigeerimist, lubatud domeene ja allalaadimise sihtkohti rangemalt piirama. See kuulub nõuete hulka, mitte „hiljem“.
  • Tugi: UserDataFolder ja runtime‑sõltuvused (WebView2 Runtime) peavad olema osa teie käitamis‑/rollout‑kava.

Järeldus

Delphi WebView2 FMX ei ole niivõrd UI‑gagdet kui integratsioonikomponent oma elutsükliga. Kui kapslite initsialiseerimise, sündmushalduse, UserDataFolderi ja JS‑silla struktureerite ja kapslite kaupa kapslite, saab WebView2 stabiilseks ehituskiviks ettevõtte digitaallahendustes: veebiliides seal, kus see mõistlik, ja Delphi‑loogika seal, kuhu see kuulub. Kui seevastu lubate skriptidel kontrollimatult käituda, jätate teed juhusele ja ei eralda sündmusi, tekitate just selle tüüpi episoodilisi väljaspool esinevaid vigu, mis söövad aega ja vähendavad usaldust.

Kui soovite olemasolevas Delphi‑rakenduses WebView2 korrektselt integreerida või tehniliselt hinnata moderniseerimisettevõtmist, võtke meiega ühendust:

Professionaalses kontekstis mängivad oluliselt rolli ka Webview2 Firemonkey ja Delphi Fmx Edge Browser, kui integratsioonid, andmevood ja edasine arendus peavad puhtalt koos toimima.

Arutada projekti või moderniseerimisettevõtmist koos Net-Base.

Järgmine samm

Kui teema muutub reaalseks projektiks, tuleks arhitektuuri, olemasolevat süsteemi ja käitust varakult ühiselt vaadelda.

Me ei toeta ainult üksikute küsimuste lahendamist, vaid ka siis, kui lähtekoodilõikudest, pärandsüsteemidest või portaalikontseptsioonidest peab saama usaldusväärne ettevõtteprojekt.

  • Olemasolev olukord, sihtpilt ja tehnilised riskid hinnatakse üheskoos.
  • REST, andmete juurdepääs, portaalid ja juurutamine ei lükata hilisemaks.
  • Te näete varakult, milline tee on majanduslikult ja operatiivselt jätkusuutlik.

Jaga postitust

Jaga seda postitust otse

LinkedIn, X, XING, Facebook, WhatsApp ja e-post on kohe saadaval. Instagrami jaoks valmistame kohe lingi ja lühiteksti ette.

e-post

Instagram avatakse uues vahekaardis. Link ja lühitekst kopeeritakse eelnevalt lõikepuhvrisse.