Disclosure Statement: This site contains affiliate links, which means that I may receive a commission if you make a purchase using these links. As an eBay Partner, I earn from qualifying purchases.

Map Delphi object events in V8 handlers

Post Reply
AndreMurta
Posts: 6
Joined: Thu Apr 12, 2018 1:51 am

Map Delphi object events in V8 handlers

Post by AndreMurta »

I've been using CEF4Delphi to develop some desktop applications using the React framework as the application GUI. The integration between my Delphi classes and the V8 engine is working nicely, but I'm facing a situation with Delphi objects events I'm not being able to handle.
I made the following extension to work with ZIP files in my JS scripts:

Code: Select all

unit uV8ObjZipHandler;

{$I cef.inc}

interface

uses
  Winapi.Windows,
  System.Classes, System.Zip, System.SysUtils,
  uCEFTypes, uCEFInterfaces, uCEFv8Value, uCEFv8Handler, uCEFProcessMessage, uCEFv8Context;

type
  TV8ZipFileObjHandler = class(TCefv8HandlerOwn)
    FZip: TZipFile;
    procedure _OnProgress(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);
  protected
    FExtensionCode, FComment: string;
    FUTF8Support: Boolean;

    function Execute(const name: ustring; const obj: ICefv8Value; const arguments: TCefv8ValueArray; var retval: ICefv8Value; var _exception: ustring): Boolean; override;
  public
    constructor Create; override;
    destructor Destroy; override;

    property ExtensionCode: String read FExtensionCode;
  end;

implementation

{ TV8ZipFileObjHandler }

constructor TV8ZipFileObjHandler.Create;
begin
  inherited;

  FZip := TZipFile.Create();
  FZip.OnProgress := _OnProgress;

  FExtensionCode :=
    // Constants
    // Mode
    'const zmClosed = ' + UIntToStr(Cardinal(TZipMode.zmClosed)) + ';' +
    'const zmRead = ' + UIntToStr(Cardinal(TZipMode.zmRead)) + ';' +
    'const zmWrite = ' + UIntToStr(Cardinal(TZipMode.zmWrite)) + ';' +
    'const zmReadWrite = ' + UIntToStr(Cardinal(TZipMode.zmReadWrite)) + ';' +
    // Compression
    'const zcStored = ' + UIntToStr(Cardinal(TZipCompression.zcStored)) + ';' +
    'const zcShrunk = ' + UIntToStr(Cardinal(TZipCompression.zcShrunk)) + ';' +
    'const zcReduce1 = ' + UIntToStr(Cardinal(TZipCompression.zcReduce1)) + ';' +
    'const zcReduce2 = ' + UIntToStr(Cardinal(TZipCompression.zcReduce2)) + ';' +
    'const zcReduce3 = ' + UIntToStr(Cardinal(TZipCompression.zcReduce3)) + ';' +
    'const zcReduce4 = ' + UIntToStr(Cardinal(TZipCompression.zcReduce4)) + ';' +
    'const zcImplode = ' + UIntToStr(Cardinal(TZipCompression.zcImplode)) + ';' +
    'const zcTokenize = ' + UIntToStr(Cardinal(TZipCompression.zcTokenize)) + ';' +
    'const zcDeflate = ' + UIntToStr(Cardinal(TZipCompression.zcDeflate)) + ';' +
    'const zcDeflate64 = ' + UIntToStr(Cardinal(TZipCompression.zcDeflate64)) + ';' +
    'const zcPKImplode = ' + UIntToStr(Cardinal(TZipCompression.zcPKImplode)) + ';' +
    'const zcBZIP2 = ' + UIntToStr(Cardinal(TZipCompression.zcBZIP2)) + ';' +
    'const zcLZMA = ' + UIntToStr(Cardinal(TZipCompression.zcLZMA)) + ';' +
    'const zcTERSE = ' + UIntToStr(Cardinal(TZipCompression.zcTERSE)) + ';' +
    'const zcLZ77 = ' + UIntToStr(Cardinal(TZipCompression.zcLZ77)) + ';' +
    'const zcPPMdI1 = ' + UIntToStr(Cardinal(TZipCompression.zcPPMdI1)) + ';' +
    // Object
    'var DZipFile; ' +
    'if (!DZipFile) ' +
    '  DZipFile = function() {}; ' +
    // Properties
    '  DZipFile.prototype.__defineGetter__("comment", function() { native function dZipGetComment(); return dZipGetComment(); }); ' +
    '  DZipFile.prototype.__defineSetter__("comment", function(a) { native function dZipSetComment(a); if(a) dZipSetComment(a); }); ' +
    '  DZipFile.prototype.__defineGetter__("fileCount", function() { native function dZipGetFileCount(); return dZipGetFileCount(); }); ' +
    '  DZipFile.prototype.__defineGetter__("mode", function() { native function dZipGetMode(); return dZipGetMode(); }); ' +
    '  DZipFile.prototype.__defineGetter__("utf8Support", function() { native function dZipGetUTF8Support(); return dZipGetUTF8Support(); }); ' +
    '  DZipFile.prototype.__defineSetter__("utf8Support", function(a) { native function dZipSetUTF8Support(a); if(a) dZipSetUTF8Support(a); }); ' +
    '(function() { ' +
    // Methods
    '  DZipFile.prototype.fileComment = function(a) { native function dZipGetFileComment(a); return dZipGetFileComment(a); }; '+
    '  DZipFile.prototype.fileComment = function(a,b) { native function dZipSetFileComment(a,b); return dZipSetFileComment(a,b); }; '+
    '  DZipFile.prototype.fileName = function(a) { native function dZipGetFileName(a); return dZipGetFileName(a); }; '+
    '  DZipFile.prototype.add = function(a,b,c) { native function dZipAddFileName(a,b,c); return dZipAddFileName(a,b,c); }; '+
    '  DZipFile.prototype.close = function() { native function dZipClose(); return dZipClose(); }; '+
    '  DZipFile.prototype.extractByName = function(a,b,c) { native function dZipExtractName(a,b,c); return dZipExtractName(a,b,c); }; '+
    '  DZipFile.prototype.extractByIndex = function(a,b,c) { native function dZipExtractIndex(a,b,c); return dZipExtractIndex(a,b,c); }; '+
    '  DZipFile.prototype.extractAll = function(a) { native function dZipExtractAll(a); return dZipExtractAll(a); }; '+
    '  DZipFile.prototype.indexOf = function(a) { native function dZipIndexOf(a); return dZipIndexOf(a); }; '+
    '  DZipFile.prototype.open = function(a,b) { native function dZipOpen(a,b); return dZipOpen(a,b); }; '+
    '})();';
end;

destructor TV8ZipFileObjHandler.Destroy;
begin
  FZip.Free();

  inherited;
end;

function TV8ZipFileObjHandler.Execute(const name: ustring; const obj: ICefv8Value; const arguments: TCefv8ValueArray; var retval: ICefv8Value; var _exception: ustring): Boolean;
begin
  if (name = 'dZipGetComment') then
  begin
    if Assigned(FZip) then
    begin
      retval := TCefv8ValueRef.NewString(FZip.Comment);
      Result := True;
    end
    else Result := false;
  end
  else if (name = 'dZipSetComment') then
  begin
    if Assigned(FZip) and (length(arguments) > 0) and arguments[0].IsString() then
    begin
      FComment := arguments[0].GetStringValue();
      FZip.Comment := FComment;
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipGetFileCount') then
  begin
    if Assigned(FZip) then
    begin
      retval := TCefv8ValueRef.NewInt(FZip.FileCount);
      Result := True;
    end
    else Result := false;
  end
  else if (name = 'dZipGetMode') then
  begin
    if Assigned(FZip) then
    begin
      retval := TCefv8ValueRef.NewUInt(Cardinal(FZip.Mode));
      Result := True;
    end
    else Result := false;
  end
  else if (name = 'dZipGetUTF8Support()') then
  begin
    if Assigned(FZip) then
    begin
      retval := TCefv8ValueRef.NewBool(FZip.UTF8Support);
      Result := True;
    end
    else Result := false;
  end
  else if (name = 'dZipSetUTF8Support()') then
  begin
    if Assigned(FZip) and (length(arguments) > 0) and arguments[0].IsBool() then
    begin
      FUTF8Support := arguments[0].GetBoolValue();
      FZip.UTF8Support := FUTF8Support;
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipGetFileComment') then
  begin
    if Assigned(FZip) and (Length(arguments) > 0) and arguments[0].IsUInt() then
    begin
      retval := TCefv8ValueRef.NewString(FZip.FileComment[arguments[0].GetUIntValue()]);
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipSetFileComment') then
  begin
    if Assigned(FZip) and (Length(arguments) > 1) and arguments[0].IsUInt() and arguments[1].IsString() then
    begin
      FZip.FileComment[arguments[0].GetUIntValue()] := arguments[1].GetStringValue();
      retval := TCefv8ValueRef.NewString(FZip.FileComment[arguments[0].GetUIntValue()]);
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipGetFileName') then
  begin
    if Assigned(FZip) and (Length(arguments) > 0) and arguments[0].IsUInt() then
    begin
      retval := TCefv8ValueRef.NewString(FZip.FileNames[arguments[0].GetUIntValue()]);
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipAddFileName') then
  begin
    if Assigned(FZip) and (Length(arguments) > 2) and arguments[0].IsString() and arguments[1].IsString() and arguments[2].IsUInt() then
    begin
      if Length(arguments[1].GetStringValue()) = 0 then
        FZip.Add(arguments[0].GetStringValue(), ExtractFileName(arguments[0].GetStringValue()),  TZipCompression(arguments[2].GetUIntValue()))
      else
        FZip.Add(arguments[0].GetStringValue(), arguments[1].GetStringValue(), TZipCompression(arguments[2].GetUIntValue()));
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipClose') then
  begin
    if Assigned(FZip) then
    begin
      FZip.Close();
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipExtractName') then
  begin
    if Assigned(FZip) and (Length(arguments) > 2) and arguments[0].IsString() and arguments[1].IsString() and arguments[2].IsBool() then
    begin
      FZip.Extract(arguments[0].GetStringValue(), arguments[1].GetStringValue(), arguments[2].GetBoolValue());
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipExtractIndex') then
  begin
    if Assigned(FZip) and (Length(arguments) > 2) and arguments[0].IsUInt() and arguments[1].IsString() and arguments[2].IsBool() then
    begin
      FZip.Extract(arguments[0].GetUintValue(), arguments[1].GetStringValue(), arguments[2].GetBoolValue());
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipExtractAll') then
  begin
    if Assigned(FZip) and (Length(arguments) > 0) and arguments[0].IsString() then
    begin
      FZip.ExtractAll(arguments[0].GetStringValue());
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipIndexOf') then
  begin
    if Assigned(FZip) and (Length(arguments) > 0) and arguments[0].IsString() then
    begin
      retval := TCefv8ValueRef.NewInt(FZip.IndexOf(arguments[0].GetStringValue()));
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipOpen') then
  begin
    if Assigned(FZip) and (Length(arguments) > 1) and arguments[0].IsString()  and arguments[1].IsUInt() then
    begin
      FZip.Open(arguments[0].GetStringValue(), TZipMode(arguments[1].GetUIntValue()));
      Result := true;
    end
    else Result := false;
  end
  else
  begin
    Result := false;
  end;
end;

procedure TV8ZipFileObjHandler._OnProgress(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);
begin
  //???
end;

end.

Code: Select all

{$IFDEF DELPHI12_UP}procedure{$ELSE}class procedure TJSExtensionFrm.{$ENDIF}GlobalCEFApp_OnWebKitInitializedEvent;
var
  ZipFileObjHandler: ICefv8Handler;
begin
  // This is the JS extension example with a function in the "JavaScript Integration" wiki page at
  // https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md

  ZipFileObjHandler := TV8ZipFileObjHandler.Create();
  CefRegisterExtension('v8/DZipfile', (ZipFileObjHandler as TV8ZipFileObjHandler).ExtensionCode, ZipFileObjHandler);
end;
The exposition of the TZipFile class properties and events is working perfectly. I'm able to work with ZIP files in my JS scripts, but I would like also to have the TZipFile event "OnProgress" as part of my solution, so I could have a JS callback function to follow the progress of file operations. The truth is that I'm stucked because I just did not figure I proper way to integrate the Delphi native object events in my V8 extensions. Does anyone already achieved such functionality? Is there a way to do it? Thanks in advance.
User avatar
salvadordf
Posts: 4016
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Map Delphi object events in V8 handlers

Post by salvadordf »

Hi,

Sorry for the delay.

The JS extensions are executed in the Render process and you need to send the TV8ZipFileObjHandler._OnProgress information to the Browser process

To send that information you can use a process message. You have an example in the JSExtension demo when it sends the mouseover event information.

Here you can see how it sends the information from the extension in the render process :
https://github.com/salvadordf/CEF4Delphi/blob/8d8d498b0fa881a51246fcfe7cfc12119a898e99/demos/Delphi_VCL/JavaScript/JSExtension/uTestExtensionHandler.pas#L78

And here you have the code that receives the information in the browser process :
https://github.com/salvadordf/CEF4Delphi/blob/8d8d498b0fa881a51246fcfe7cfc12119a898e99/demos/Delphi_VCL/JavaScript/JSExtension/uJSExtension.pas#L449
AndreMurta
Posts: 6
Joined: Thu Apr 12, 2018 1:51 am

Re: Map Delphi object events in V8 handlers

Post by AndreMurta »

Thanks Salvador, based on the tips you gave in your last post, I spent some time trying to figure the "most elegant" way to deal with Delphi objects events in my JS extensions. For the moment, that's what I have achieved.

In the injected JS object's "constructor", I created an JS Event labeled "zipProgress".

In the TZipFile OnProgress event, I create the message to send to the main browser with two parameters, at this moment these arguments are respectively the Filename and the Position parameters sent by the Delphi OnProgress event itself.

In the event OnProcessMessageReceived from the Chromium component, I receive the parameters sent in the message and inject them as parameters to the JS event, after that I just dispatch the result to be intercepted by the JS code that now can "listen" to the "zipProgress" event.

Sounds tricky, I do know, but the lines of code in the JS script are the most similar I could achieve if compared to what we normally do when using Delphi objects events.

Following you can find all what I have done. If you think there is a better way to handle Delphi object events in JS code, please, let me know.

Following is the Delphi unit where the DZipFile object is defined:

Code: Select all

unit uV8ObjZipHandler;

{$I cef.inc}

interface

uses
  Winapi.Windows,
  System.Classes, System.Zip, System.SysUtils,
  uCEFTypes, uCEFInterfaces, uCEFv8Value, uCEFv8Handler, uCEFProcessMessage, uCEFv8Context;

type
  TV8ZipFileObjHandler = class(TCefv8HandlerOwn)
    FZip: TZipFile;
    procedure _OnProgress(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);
  protected
    FExtensionCode, FComment, FFileName: string;
    FPosition: Int64;
    FUTF8Support: Boolean;

    function Execute(const name: ustring; const obj: ICefv8Value; const arguments: TCefv8ValueArray; var retval: ICefv8Value; var _exception: ustring): Boolean; override;
  public
    constructor Create; override;
    destructor Destroy; override;

    property ExtensionCode: String read FExtensionCode;
  end;

implementation

uses
  uCEFMiscFunctions, uCEFConstants, uMiniBrowser;

{ TV8ZipFileObjHandler }

constructor TV8ZipFileObjHandler.Create;
begin
  inherited;

  FZip := TZipFile.Create();
  FZip.OnProgress := _OnProgress;

  FExtensionCode :=
    // Constants
    // Mode
    'const zmClosed = ' + UIntToStr(Cardinal(TZipMode.zmClosed)) + ';' +
    'const zmRead = ' + UIntToStr(Cardinal(TZipMode.zmRead)) + ';' +
    'const zmWrite = ' + UIntToStr(Cardinal(TZipMode.zmWrite)) + ';' +
    'const zmReadWrite = ' + UIntToStr(Cardinal(TZipMode.zmReadWrite)) + ';' +
    // Compression
    'const zcStored = ' + UIntToStr(Cardinal(TZipCompression.zcStored)) + ';' +
    'const zcShrunk = ' + UIntToStr(Cardinal(TZipCompression.zcShrunk)) + ';' +
    'const zcReduce1 = ' + UIntToStr(Cardinal(TZipCompression.zcReduce1)) + ';' +
    'const zcReduce2 = ' + UIntToStr(Cardinal(TZipCompression.zcReduce2)) + ';' +
    'const zcReduce3 = ' + UIntToStr(Cardinal(TZipCompression.zcReduce3)) + ';' +
    'const zcReduce4 = ' + UIntToStr(Cardinal(TZipCompression.zcReduce4)) + ';' +
    'const zcImplode = ' + UIntToStr(Cardinal(TZipCompression.zcImplode)) + ';' +
    'const zcTokenize = ' + UIntToStr(Cardinal(TZipCompression.zcTokenize)) + ';' +
    'const zcDeflate = ' + UIntToStr(Cardinal(TZipCompression.zcDeflate)) + ';' +
    'const zcDeflate64 = ' + UIntToStr(Cardinal(TZipCompression.zcDeflate64)) + ';' +
    'const zcPKImplode = ' + UIntToStr(Cardinal(TZipCompression.zcPKImplode)) + ';' +
    'const zcBZIP2 = ' + UIntToStr(Cardinal(TZipCompression.zcBZIP2)) + ';' +
    'const zcLZMA = ' + UIntToStr(Cardinal(TZipCompression.zcLZMA)) + ';' +
    'const zcTERSE = ' + UIntToStr(Cardinal(TZipCompression.zcTERSE)) + ';' +
    'const zcLZ77 = ' + UIntToStr(Cardinal(TZipCompression.zcLZ77)) + ';' +
    'const zcPPMdI1 = ' + UIntToStr(Cardinal(TZipCompression.zcPPMdI1)) + ';' +
    // Object
    'var DZipfile; ' +
    'if (!DZipfile) ' +
    '  DZipfile = function() { dZipProgressEvt = new Event("zipProgress", {bubbles: true, cancelable: true}); }; ' +
    // Properties
    '  DZipfile.prototype.__defineGetter__("comment", function() { native function dZipGetComment(); return dZipGetComment(); }); ' +
    '  DZipfile.prototype.__defineSetter__("comment", function(a) { native function dZipSetComment(a); if(a) dZipSetComment(a); }); ' +
    '  DZipfile.prototype.__defineGetter__("fileCount", function() { native function dZipGetFileCount(); return dZipGetFileCount(); }); ' +
    '  DZipfile.prototype.__defineGetter__("mode", function() { native function dZipGetMode(); return dZipGetMode(); }); ' +
    '  DZipfile.prototype.__defineGetter__("utf8Support", function() { native function dZipGetUTF8Support(); return dZipGetUTF8Support(); }); ' +
    '  DZipfile.prototype.__defineSetter__("utf8Support", function(a) { native function dZipSetUTF8Support(a); if(a) dZipSetUTF8Support(a); }); ' +
    '  DZipfile.prototype.__defineGetter__("currentFileName", function() { native function dCurrentProgressFileName(); return dCurrentProgressFileName(); }); ' +
    '  DZipfile.prototype.__defineGetter__("currentPosition", function() { native function dCurrentProgressPosition(); return dCurrentProgressPosition(); }); ' +
    '(function() { ' +
    // Methods
    '  DZipfile.prototype.fileComment = function(a) { native function dZipGetFileComment(a); return dZipGetFileComment(a); }; '+
    '  DZipfile.prototype.fileComment = function(a,b) { native function dZipSetFileComment(a,b); return dZipSetFileComment(a,b); }; '+
    '  DZipfile.prototype.fileName = function(a) { native function dZipGetFileName(a); return dZipGetFileName(a); }; '+
    '  DZipfile.prototype.add = function(a,b,c) { native function dZipAddFileName(a,b,c); return dZipAddFileName(a,b,c); }; '+
    '  DZipfile.prototype.close = function() { native function dZipClose(); return dZipClose(); }; '+
    '  DZipfile.prototype.extractByName = function(a,b,c) { native function dZipExtractName(a,b,c); return dZipExtractName(a,b,c); }; '+
    '  DZipfile.prototype.extractByIndex = function(a,b,c) { native function dZipExtractIndex(a,b,c); return dZipExtractIndex(a,b,c); }; '+
    '  DZipfile.prototype.extractAll = function(a) { native function dZipExtractAll(a); return dZipExtractAll(a); }; '+
    '  DZipfile.prototype.indexOf = function(a) { native function dZipIndexOf(a); return dZipIndexOf(a); }; '+
    '  DZipfile.prototype.open = function(a,b) { native function dZipOpen(a,b); return dZipOpen(a,b); }; '+
    '})();';
end;

destructor TV8ZipFileObjHandler.Destroy;
begin
  FZip.Free();

  inherited;
end;

function TV8ZipFileObjHandler.Execute(const name: ustring; const obj: ICefv8Value; const arguments: TCefv8ValueArray; var retval: ICefv8Value; var _exception: ustring): Boolean;
begin
  if (name = 'dZipGetComment') then
  begin
    if Assigned(FZip) then
    begin
      retval := TCefv8ValueRef.NewString(FZip.Comment);
      Result := True;
    end
    else Result := false;
  end
  else if (name = 'dZipSetComment') then
  begin
    if Assigned(FZip) and (length(arguments) > 0) and arguments[0].IsString() then
    begin
      FComment := arguments[0].GetStringValue();
      FZip.Comment := FComment;
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipGetFileCount') then
  begin
    if Assigned(FZip) then
    begin
      retval := TCefv8ValueRef.NewInt(FZip.FileCount);
      Result := True;
    end
    else Result := false;
  end
  else if (name = 'dZipGetMode') then
  begin
    if Assigned(FZip) then
    begin
      retval := TCefv8ValueRef.NewUInt(Cardinal(FZip.Mode));
      Result := True;
    end
    else Result := false;
  end
  else if (name = 'dZipGetUTF8Support()') then
  begin
    if Assigned(FZip) then
    begin
      retval := TCefv8ValueRef.NewBool(FZip.UTF8Support);
      Result := True;
    end
    else Result := false;
  end
  else if (name = 'dZipSetUTF8Support()') then
  begin
    if Assigned(FZip) and (length(arguments) > 0) and arguments[0].IsBool() then
    begin
      FUTF8Support := arguments[0].GetBoolValue();
      FZip.UTF8Support := FUTF8Support;
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dCurrentProgressFileName') then
  begin
    if Assigned(FZip) then
    begin
      retval := TCefv8ValueRef.NewString(FFileName);
      Result := True;
    end
    else Result := false;
  end
  else if (name = 'dCurrentProgressPosition') then
  begin
    if Assigned(FZip) then
    begin
      retval := TCefv8ValueRef.NewString(IntToStr(FPosition));
      Result := True;
    end
    else Result := false;
  end
  else if (name = 'dZipGetFileComment') then
  begin
    if Assigned(FZip) and (Length(arguments) > 0) and arguments[0].IsUInt() then
    begin
      retval := TCefv8ValueRef.NewString(FZip.FileComment[arguments[0].GetUIntValue()]); // Retorno da função (método) JS
      Result := true; // Retorno deste método Delphi
    end
    else Result := false;
  end
  else if (name = 'dZipSetFileComment') then
  begin
    if Assigned(FZip) and (Length(arguments) > 1) and arguments[0].IsUInt() and arguments[1].IsString() then
    begin
      FZip.FileComment[arguments[0].GetUIntValue()] := arguments[1].GetStringValue();
      retval := TCefv8ValueRef.NewString(FZip.FileComment[arguments[0].GetUIntValue()]); // Retorno da função (método) JS
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipGetFileName') then
  begin
    if Assigned(FZip) and (Length(arguments) > 0) and arguments[0].IsUInt() then
    begin
      retval := TCefv8ValueRef.NewString(FZip.FileNames[arguments[0].GetUIntValue()]); // Retorno da função (método) JS
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipAddFileName') then
  begin
    if Assigned(FZip) and (Length(arguments) > 2) and arguments[0].IsString() and arguments[1].IsString() and arguments[2].IsUInt() then
    begin
      if Length(arguments[1].GetStringValue()) = 0 then
        FZip.Add(arguments[0].GetStringValue(), ExtractFileName(arguments[0].GetStringValue()),  TZipCompression(arguments[2].GetUIntValue()))
      else
        FZip.Add(arguments[0].GetStringValue(), arguments[1].GetStringValue(), TZipCompression(arguments[2].GetUIntValue()));
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipClose') then
  begin
    if Assigned(FZip) then
    begin
      FZip.Close();
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipExtractName') then
  begin
    if Assigned(FZip) and (Length(arguments) > 2) and arguments[0].IsString() and arguments[1].IsString() and arguments[2].IsBool() then
    begin
      FZip.Extract(arguments[0].GetStringValue(), arguments[1].GetStringValue(), arguments[2].GetBoolValue());
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipExtractIndex') then
  begin
    if Assigned(FZip) and (Length(arguments) > 2) and arguments[0].IsUInt() and arguments[1].IsString() and arguments[2].IsBool() then
    begin
      FZip.Extract(arguments[0].GetUintValue(), arguments[1].GetStringValue(), arguments[2].GetBoolValue());
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipExtractAll') then
  begin
    if Assigned(FZip) and (Length(arguments) > 0) and arguments[0].IsString() then
    begin
      FZip.ExtractAll(arguments[0].GetStringValue());
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipIndexOf') then
  begin
    if Assigned(FZip) and (Length(arguments) > 0) and arguments[0].IsString() then
    begin
      retval := TCefv8ValueRef.NewInt(FZip.IndexOf(arguments[0].GetStringValue()));
      Result := true;
    end
    else Result := false;
  end
  else if (name = 'dZipOpen') then
  begin
    if Assigned(FZip) and (Length(arguments) > 1) and arguments[0].IsString()  and arguments[1].IsUInt() then
    begin
      FZip.Open(arguments[0].GetStringValue(), TZipMode(arguments[1].GetUIntValue()));
      Result := true;
    end
    else Result := false;
  end
  else
  begin
    Result := false;
  end;
end;

procedure TV8ZipFileObjHandler._OnProgress(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);
var
  TheMessage: ICefProcessMessage;
  TheFrame: ICefFrame;
begin
  Self.FFileName := FileName;
  Self.FPosition := Position;
  try
    TheMessage := TCefProcessMessageRef.New(ZIPPROGRESS_MESSAGE_NAME);
    TheMessage.ArgumentList.SetString(0, Self.FFileName);
    TheMessage.ArgumentList.SetString(1, IntToStr(Self.FPosition));

    TheFrame := TCefv8ContextRef.Current.Browser.MainFrame;

    if (TheFrame <> nil) and TheFrame.IsValid then
      TheFrame.SendProcessMessage(PID_BROWSER, TheMessage);
  finally
    TheMessage := nil;
  end;
end;

end.
Following you can find all the code that must be present in the Delphi project main unit, where the Chromium component is located:
First of all, it's necessary to create a global constant to hold the message identification

Code: Select all

const
  ZIPPROGRESS_MESSAGE_NAME = 'dzipfileonprogress';
Following is the Delphi code to register the new extension in Chromium:

Code: Select all

{$IFDEF DELPHI12_UP}procedure{$ELSE}class procedure TJSExtensionFrm.{$ENDIF}GlobalCEFApp_OnWebKitInitializedEvent;
var
  ZipFileObjHandler: ICefv8Handler;
begin
  ZipFileObjHandler := TV8ZipFileObjHandler.Create();
  CefRegisterExtension('v8/DZipfile', (ZipFileObjHandler as TV8ZipFileObjHandler).ExtensionCode, ZipFileObjHandler);
end;
In the Chromium's OnProcessMessageReceived event, I have the following code:

Code: Select all

procedure TMiniBrowserFrm.Chromium1ProcessMessageReceived(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; sourceProcess: TCefProcessId; const _message: ICefProcessMessage; out Result: Boolean);
begin
  if (_message.Name = ZIPPROGRESS_MESSAGE_NAME) then
  begin
    frame.ExecuteJavaScript('dZipProgressEvt.params = { filename: "'+ExtractFileName(_message.ArgumentList.GetString(0))+'", position: '+_message.ArgumentList.GetString(1)+' }; document.dispatchEvent(dZipProgressEvt);', 'about:blank', 0);

    (*this doesn't create/destroy components*)
    Result := True;
  end
end;
That's it in the Delphi project, following is the HTML code I used to test the extension's events handling:

Code: Select all

<!DOCTYPE html>
<html>
<body>

<h1>JS extension with events demo.</h1>

<p>The following button tests the <strong>ZipFile</strong> object, which was registered in the GlobalCEFApp.OnWebKitInitialized event.<br><button onclick="myFunction()">Test ZipFile object</button></p>
<p><button onclick="window.close();">Close the application</button></p>

<script>
function myFunction() {
    document.addEventListener("zipProgress",
      function(event) {
        //console.log("Hello from: "+JSON.stringify(event.target)+", filename: "+event.params.filename+", position: "+event.params.position);
        console.log("filename: "+event.params.filename+", position: "+event.params.position);
      }
    );

    var zip = new DZipfile();
    var zip2 = new DZipfile();

    try {
    zip.open("C:\\Temp\\MyZip.zip", zmWrite);
    zip2.open("C:\\Temp\\MySecondZip.zip", zmWrite);

    zip.add("C:\\Temp\\file1.html", "", zcDeflate);
    zip2.add("C:\\Projects\\temp\\uTestExtensionHandler.cpp", "", zcDeflate);

    zip.add("C:\\Temp\\file2.pdf", "", zcDeflate);
    zip2.add("C:\\Projects\\temp\\uTestExtensionHandler.h", "", zcDeflate);

    zip.add("C:\\Temp\\file3.xlsx", "", zcDeflate);
    zip.add("C:\\Temp\\file4.xlsx", "", zcDeflate);
    zip.add("C:\\Temp\\file5.log", "", zcDeflate);
    zip.add("C:\\Temp\\file6.txt", "", zcDeflate);
    } catch(e) {
      console.log("Exception: "+e.message);
    } finally {
      zip.close();
      zip2.close();
      delete zip;
      delete zip2;
    }
}
</script>

</body>
</html>
After I loaded this HTML in the Chromium component and pressed the "Test ZipFile object" button, I had the result you can see in the image below:
Untitled3.png
Final comments
  • I've just finished to test all what is described above, so I cannot make sure it is bug free;
  • I had to eliminate the full path of the files received through the message in the OnProcessMessageReceived event (using Delphi's ExtractFileName function), because the windows path separator must be "sanitized" before it can be used in the JS script. I think this problem could be solved through Delphi code or through JS code;
  • As you can see in the image above, the events were handled properly even with more than one instance of a DZipFile "object" allocated in the JS function myFunction(), but I'm really suspicious about this code;
  • It's necessary to improve the IO operations in the extension. At the moment if we try to add a file that does not exist to the zip object, the browser does not react very well, even if the JS code is inside a try...catch.
You do not have the required permissions to view the files attached to this post.
AndreMurta
Posts: 6
Joined: Thu Apr 12, 2018 1:51 am

Re: Map Delphi object events in V8 handlers

Post by AndreMurta »

Just corrected a small typo in the post above, specifically at the "OnProcessMessageReceived" event. Now it should be fully functional.

Code: Select all

procedure TMiniBrowserFrm.Chromium1ProcessMessageReceived(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; sourceProcess: TCefProcessId; const _message: ICefProcessMessage; out Result: Boolean);
begin
  if (_message.Name = ZIPPROGRESS_MESSAGE_NAME) then
  begin
    frame.ExecuteJavaScript('dZipProgressEvt.params = { filename: "'+ExtractFileName(_message.ArgumentList.GetString(0))+'", position: '+_message.ArgumentList.GetString(1)+' }; document.dispatchEvent(dZipProgressEvt);', 'about:blank', 0);

    (*this doesn't create/destroy components*)
    Result := True;
  end
end;
User avatar
salvadordf
Posts: 4016
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Map Delphi object events in V8 handlers

Post by salvadordf »

I read your code and it seems ok.

Testing extension is always tricky but you can use the debug methods described here :
https://www.briskbard.com/index.php?lang=en&pageid=cef#debugging

According to the documentation TZipFile methods may raise exceptions :
http://docwiki.embarcadero.com/Libraries/Sydney/en/System.Zip.TZipFile

I would add a try..except block in the Delphi code when you call those methods to avoid issues in the render process.
Post Reply