Net-Base Maġazin

14.06.2026

Delphi WebView2 f'FMX: inizjalizzazzjoni nadifa, kostruzzjoni ta' JS-Bridge, immaniġġjar ta' downloads u debugging

WebView2 f'FireMonkey jidher bħal 'sempliċement tħaddan browser', imma fil-prattika jfalli fl-inizjalizzazzjoni, fl-avvenimenti tan-navigazzjoni, fil-JS↔Delphi-Bridge, fil-ġestjoni tat-tniżżil u fid-debugging. Dan il-framment tas-sors juri mudell robust bi responsabbiltajiet ċari...

14.06.2026

Minn suġġett tar-rivista għall-prattika tal-proġett

Paġni ta' servizz u paġni tekniċi relevanti għall-artiklu

Min jixtieq jdaħħal b’mod „mal-ewwel“ kontenut modern tal-web f’software tan-negozju eżistenti, jispiċċa fuq Windows ma‘ WebView2. F‘ Delphi WebView2 FMX il-problema bażika rari tkun il-wiri ta‘ URL, iżda l-integrazzjoni nadifa f’interfaċċa FireMonkey (FMX), l-inizjalizzazzjoni affidabbli (asimkrona u bbażata fuq COM), kif ukoll il-pitfalls ta‘ Edge madwar il-fowlders tal-User-Data, downloads, debugging u komunikazzjoni robusta JS↔Delphi.

Dan il-fragment tal-kodiċi juri mudell li nippreferi għall-applikazzjonijiet faċli għall-manutenzjoni: oġġett “Host” kapsulat li jikkontrolla l-lifecycle ta‘ WebView2, u bridge definit permezz ta‘ WebMessage (JSON), minflok “ExecuteScript” arbitrju kullimkien. L-għan mhuwiex kodiċi ta‘ demo, imma komponent li jiflaħ fil-clients żviluppati.

Għaliex WebView2 f’FMX huwa differenti minn „Browser-Component drop“

WebView2 huwa API viċin COM/WinRT b’inizjalizzazzjoni asinkrona. FireMonkey jastratti l-handles ta‘ Windows, madankollu għall-WebView2 finalment ikollok bżonn parent-window reali (HWND) u inoltrazzjoni kontrollata ta‘ resize/focus. Fl-istess ħin, l-avvenimenti mhux dejjem jonqsu fejn wieħed jistenna f’FMX. Jekk tibda hawn „quick and dirty“, tipikament tikseb:

  • AVs sporadiċi meta tagħlaq il-form (il-callbacks jaslu wara Destroy)
  • avvenimenti ta‘ navigazzjoni f’kuntest ta‘ thread żbaljat
  • problemi ta‘ persistenza/cache mhux affidabbli minħabba strategija mhux ċara tal-fowlders tal-User-Data
  • l-ebda downloads jew dialoghi ta‘ download „imxekkla“
  • debugging biss fuq każ minflok konfigurazzjoni mirata ta‘ remote-debug

Ir-rimedju huwa lifecycle ċar: Create → InitializeAsync → Attach → Navigate → Detach/Dispose – u limit definit bejn l-UI u l-browser-engine.

Source-Schnipsel: WebView2Host für Delphi WebView2 FMX

Il-kodiċi li ġej jispeċifika klassi Host kapsulata li (1) toħloq konfigurazzjoni tal-WebView2-Environment, (2) tgħaqqad l-objett controller ma‘ HWND, (3) tintegra l-avvenimenti ta‘ navigazzjoni u download u (4) tipprovdi bridge JS ibbażata fuq JSON permezz ta‘ WebMessageReceived. Il-kodiċi hu maħsub b’mod „arkitetturali“: jikkapsula referenzi COM, jipprevjeni callbacks li jaslu wara Destroy, u jippermetti kantunieri operattivi bħal fowlders tal-User-Data separati per-user jew per-machine.

Delphi
unit WebView2Host;

interface

uses
  System.SysUtils, System.Classes, System.IOUtils, System.JSON,
  Winapi.Windows, Winapi.ActiveX,
  FMX.Types,
  WebView2, WebView2_TLB; // skond il-konfigurazzjoni: WebView2.pas jew 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', '');

    // Il-payload jista' jonqos jew ikun 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;

  // Neħħi l-event handlers qabel ma' jiġu rilaxxati l-oġġetti COM
  UnhookEvents;

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

  inherited;
end;

procedure TWebView2Host.EnsureNotDestroyed;
begin
  if FDestroyed then
    raise EInvalidOperation.Create('WebView2Host diġà ġie mħassar.');
end;

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

  // Prattika: per-app u għal kull user ta' Windows, mhux fil-katalgu tal-programm
  Result := TPath.Combine(TPath.GetHomePath, 'AppDataLocalMyCompanyMyAppWebView2');
  ForceDirectories(Result);
end;

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

  UserData := MakeUserDataFolder;

  // Options: hawnhekk jistgħu jiġu inklużi argumenti addizzjonali tal-browser, pereżempju 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;

        // Rabta tal-controller ma' HWND parent
        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;

              // Agħmilha viżibbli b'mod inizjali
              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));

  // Nota: Għal unhooking robust, għandek iżżomm il-valuri tal-token.
  // F'ħafna proġetti dan ikun biżżejjed jekk il-host jgħix biss flimkien mal-form.
end;

procedure TWebView2Host.UnhookEvents;
begin
  // Varjant robust: ħażżen it-tokens u sejjaħ remove_*.
  // Hawnhekk bħala kumment, għax is-setup tal-import-unit u l-immaniġġjar tal-tokens jvarjaw skont il-wrapper.
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));

  // Fil-prattika: ResultFileName inizjalment vojt, skont is-sors.
  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);

    // Opsjonali: UI ta' download proprja, imbagħad issettja 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 għadu ma ġiex inizjalizzat.');

  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.

Skop tal-approċċ

  • Kapsulazzjoni tal-ciklu tal-ħajja: Il-forma FMX taf biss „Initialize/Navigate/Resize“, mhux id-dettalji tal-COM.
  • Bridge bi-kuntratt: Messaġġi JSON bi name, opzjonalment cid (Correlation-ID) u payload huma mmanutenibbli u jistgħu jittestjaw.
  • Persistenza affidabbli għall-operat: folder kontrollat UserDataFolder jipprevjeni kolliżjonijiet tal-cache, problemi ta‘ permessi u l-kwistjoni li s-servizz „jagħmel fuq il-magni tal-iżviluppatur, mhux fil-produzzjoni“.

JS↔Delphi-Bridge: għaliex WebMessage huwa aktar stabbli minn ExecuteScript

WebView2 joffri diversi kanali għall-komunikazzjoni. Fil-prattika ExecuteScript jista‘ jkun attraenti, imma huwa diffiċli biex jiġu verżjonati: qed tgħaddi strings f’interpreter mingħajr kanali ta‘ tweġiba ċari u mingħajr mappjar robust ta‘ żbalji. PostWebMessageAsString / WebMessageReceived min-naħa l-oħra huwa kanal definit.

Każ limiti, li spiss joħroġ f’ambjenti korporattivi: trid tibda workflow Delphi minn frontend web (eż. portali interni) — eż. stampa, aċċess għal apparat, integrawjoni legacy. F’dak il-każ għandek bżonn:

  • whitelist ta‘ ismijiet tal-messaġġi
  • Correlation-IDs għal risposti asincroni
  • punt ċentrali li jivvalida l-payloads (eż. kampi obbligatorji, limiti ta‘ daqs)

Fil-host dan hu l-post OnWebMessageReceived. Il-verifika vera għandha tiġi f’saff ogħla (eż. Application-Service), sabiex iżżomm it-teknoloġija UI/WebView2 u l-loġika tan-negozju separati (arkitettura ta‘ saff klassika: UI → Application → Domain → Infrastruktur).

Downloads u ħażna tal-fajls: dak li spiss jassustaw fil-produzzjoni

Downloads f’WebView2 imorru permezz ta‘ ICoreWebView2DownloadOperation. Skont is-sors il-ResultFilePath jista‘ jibqa‘ vojt bikri jew issettjat biss aktar tard. Aktar minn hekk, ħafna kumpaniji ma jridx li utenti finali jaħżnu f’folders mhux kontrollati.

Prattiki stabbiliti:

  • Interċetta DownloadStarting u billi tuża args.put_Handled(1) tieħu kontroll tal-UI (path proprju, konvenzjoni ta‘ isim, folder ta‘ kwarantina).
  • Limiti fuq id-daqs tal-fajl u verifiki tal-MIME-Type, biex tevita sitwazzjonijiet bħal fajl ta‘ log ta‘ 4 GB b’żball.
  • Auditing: ikteb il-metadata tal-download (URI, MIME, bytes) fil-log tiegħek, mhux il-kontenut stess.

Jekk għandek proċessi regolati (eż. approvazzjonijiet, traċċabbiltà), il-manipulazzjoni permezz tal-events hi l-unika post fejn tista‘ tintegra d-dinja tal-browser fir-regoli operattivi tiegħek.

Debugging: DevTools, Remote Debug Port u stati riproduċibbli

Id-debugging ta‘ WebView2 spiss jaqa‘ meta s-stati ma jkunux riproduċibbli. Żewġ aġġustamenti jgħinu:

  • Attiva/deattiva DevTools permezz ta‘ ICoreWebView2Settings (fil-kodiċi: SetDevToolsEnabled) – fil-release spiss mitfi, fil-każ ta‘ support jiġi miżbugħ b’mod mirat.
  • UserDataFolder stabbli: jekk il-support tiegħek irid jirriproduċi żball, path definit huwa ta‘ valur kbir. Tista‘ issalva/zipja l-folder (attenzjoni: protezzjoni tad-dejta/PII) u tqabbel l-istati b’mod mirat.

Bħala għażla (skont il-wrapper) tista‘ tipprovdi EnvironmentOptions b’argumenti addizzjonali għall-browser, pereżempju Remote-Debug-Port. Dan għandu sens meta tkun qed tanalizza applikazzjoni fuq sistema tat-test mingħajr għodod tal-iżviluppatur lokali. Limitazzjonijiet: f’ambjenti produttivi dan irid jiġi abilitati u dokumentati b’mod ċar, inkella toħloq vulerabbiltà mhux meħtieġa.

Snagli f’Delphi WebView2 FMX: COM, Threads u Form-Lifecycle

1) Callbacks wara l-għeluq

Is-CompletedHandler asinkroni jistgħu jaslu wara li l-forma tkun diġà għalqet. Fil-snippet FDestroyed jipprevjeni aċċess għal oġġetti rilaxxati. Aktar robust huwa wkoll:

  • Żomm tokens għall-avvenimenti u fi Destroy sejjaħ b’mod nadif remove_*
  • Permetti InitializeAsync darba biss (State-Machine: Created/Initializing/Ready/Disposed)

2) Kuntest tat-Thread

Ħafna handler jiġu «qrib tal-UI», imma tgħaqqadx fuqhekk li tista‘ tikteb direttament f’kontrolli FMX. Jekk taġġorna l-UI f‘OnWebMessage, TThread.Queue(nil, ...) hija l-verżjoni sigura. Jiena nissepara: il-host jikkollezjona l-avveniment, is-servizz tal-applikazzjoni jiddeċiedi, u l-UI tiġi aġġornata esklussivament permezz ta‘ Queue.

3) DPI/Resize und FMX-Layouts

FMX jaħseb f’unitajiet loġiċi, WebView2 jistenna Pixel-Rects. Fil-prattika għandek bżonn punt ċar fejn tittraduċi l-bounds mill-kontrolli FMX għal pixels reali. Is-snippet jaċċetta TRect; fil-form tiegħek għandek estratti l-koordinati WinAPI minn dan (per eż., permezz ta‘ FMX.Platform.Win u l-Handle-APIs). Jekk l-app tħaddem scaling skond il-DPI tal-monitor, ittestja l-bidla bejn monitors: WebView2 huwa hawn aktar sensittiv minn kontrolli FMX puri.

Meta WebView2 f’FMX jagħmel sens – u meta le

WebView2 jagħmel sens meta trid tuża teknoloġija web f’applikazzjoni klijent Delphi li evolviet: view amministrattivi imdaħħla, flussi ta‘ login OAuth/OIDC, rapporti HTML, portali interni jew «Micro-Frontends» ikkontrollati. Kif ukoll bħala bridge ta‘ modernizzazzjoni huwa prattiku, sakemm tistabbilixxi responsabbiltajiet b’mod ċar u l-bridge ma jsirx bibien ta‘ wara mhux kkontrollati għall-logika tan-negozju.

Limiti tal-approċċ:

  • Pjattaforma: Il-mudell huwa ċentriku fuq Windows. FMX huwa multi-pjattaforma, WebView2 mhux. Għall-macOS/iOS/Android għandek bżonn WebViews oħra jew saff ta‘ astrazzjoni.
  • Security/Hardening: Ladarba jittellgħu kontenuti esterni, trid tillimita b’mod aktar strett in-navigazzjoni, id-domini permessi u l-miri tal-download. Dan għandu jkun parti mir-rekwiżiti, mhux «wara».
  • Support: UserDataFolder u d-dipendenzi runtime (WebView2 Runtime) għandhom ikunu parti mill-kunċett tal-operazzjoni/rollout tiegħek.

Konklużjoni

Delphi WebView2 FMX mhuwiex biss gadget tal-UI, imma komponent ta‘ integrazjoni b’lifecycle proprju. Jekk tikkapsula b’mod strutturat l-inizjalizzazzjoni, l-eventing, il-UserDataFolder u l-JS-Bridge, WebView2 jsir element stabbli għal soluzzjonijiet korporattivi diġitali: Web-UI fejn għandu sens, u loġika Delphi fejn tidħol. Jekk min-naħa l-oħra tnaddaf skripts mingħajr kontroll, tagħti r-rotot lil-paths skont ix-xorti u ma tiddikoppjax l-events, tikseb eżatt it-tip ta‘ żbalji sporadiċi fil-produzzjoni li jieħdu żmien u jnaqqsu l-fiduċja.

Jekk trid tintegra WebView2 b’mod nadif f’applikazzjoni Delphi eżistenti jew tivvaluta teknikanament punt ta‘ modernizzazzjoni, tkellem magħna:

Fil-qasam professjonali, Webview2 Firemonkey u Delphi Fmx Edge Browser għandhom rwol importanti wkoll, meta l-integrazzjonijiet, il-flussi tad-dejta u l-iżvilupp aktar għandhom jaħdmu flimkien b’mod nadif.

Diskuti proġett jew inizjattiva ta‘ modernizzazzjoni ma‘ Net-Base.

Pass li jmiss

Meta suġġett jissarraf f’proġett reali, l-arkitettura, is-sistema eżistenti u l-operazzjoni għandhom jiġu kkunsidrati flimkien kmien.

Aħna nappoġġjaw mhux biss f'kwistjonijiet puntwali, iżda wkoll meta biċċiet ta' kodiċi sors, temi legacy jew ideat għal portali jridu jsiru proġett korporattiv stabbli u affidabbli.

  • L-istat attwali, l-istat tal-mira u r-riskji tekniċi jiġu vvalutati flimkien.
  • REST, aċċess għad-dejta, portali u Rollout mhux se jiġu posposti bħala konsegwenzi tardivi.
  • Tara kmieni liema triq hija sostenibbli kemm mill‑punt ta’ vista ekonomiku kif ukoll mill‑punt ta’ vista operattiv.

Aqsam il-post

Aqsam dan il-post direttament

LinkedIn, X, XING, Facebook, WhatsApp u E-Mail huma immedjatament disponibbli. Għal Instagram nippreparaw il-link u t-test qasir direttament.

Imejl

Instagram jiftaħ f'tab ġdid. Il-link u t-test qasir jiġu kkopjati qabel fil-clipboard.