I am beginner in Delphi. Presently using Delphi Berlin version.
I am trying to invoke Delphi function/method from JavaScript. For example I want to open a new Delphi form on clicking on html button with additional data attribute.
HTML CODE
<input type="button" name="btn" value="Button" id="edit" data-prop="24"></button>
<input type="button" name="btnAnother" value="Button2" id="edit2" data-prop="1"></button>
When button will be clicked a new Delphi [second form] will be opened that will display data-prop of button on a TLabel.
[UPDATE - 12-10-2020]
I have tried to create application with the help of JSExtension demo. I have tried to add javascript click event but click event on html is not firing and second form is not loading. Here is some of the code
HTML [jsExtensionClickEvent.html]
<!DOCTYPE html>
<html>
<body>
<form method="POST">
<input type="button" name="btnEx" value="Button" id="edit" data-prop="1"></button>
<input type="button" name="anotherBtn" value="Another Button" id="edit2" data-prop="24"></button>
</form>
</body>
</html>
DELPHI
Extension handler class [uExtensionHandler.pas]
unit uExtensionHandler;
{$I cef.inc}
interface
uses
{$IFDEF DELPHI16_UP}
Winapi.Windows,
{$ELSE}
Windows,
{$ENDIF}
uCEFRenderProcessHandler, uCEFBrowserProcessHandler, uCEFInterfaces,
uCEFProcessMessage,
uCEFv8Context, uCEFTypes, uCEFv8Handler;
const
MOUSECLICK_MESSAGE_NAME = 'mouseclick';
type
TExtensionHelper = class(TCefv8HandlerOwn)
protected
function Execute(const name: ustring; const object_: ICefv8Value;
const arguments: TCefv8ValueArray; var retval: ICefv8Value;
var exception: ustring): Boolean; override;
end;
implementation
{ TExtensionHelper }
function TExtensionHelper.Execute(const name: ustring;
const object_: ICefv8Value; const arguments: TCefv8ValueArray;
var retval: ICefv8Value; var exception: ustring): Boolean;
var
TempMessage: ICefProcessMessage;
TempFrame: ICefFrame;
begin
Result := False;
try
if (name = 'mouseclick') then
begin
if (length(arguments) > 1) and arguments[0].IsString and arguments[1].IsString
then
begin
TempMessage := TCefProcessMessageRef.New(arguments[1].GetStringValue);
TempMessage.ArgumentList.SetString(0, arguments[0].GetStringValue);
TempFrame := TCefv8ContextRef.Current.Browser.MainFrame;
if (TempFrame <> nil) and TempFrame.IsValid then
TempFrame.SendProcessMessage(PID_BROWSER, TempMessage);
end;
Result := True;
end;
finally
TempMessage := nil;
end;
end;
end.
Main Form [uMainForm.pas]
unit uMainForm;
interface
uses
{$IFDEF DELPHI16_UP}
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
Vcl.ComCtrls, System.IOUtils,
{$ELSE}
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls, IOUtils,
{$ENDIF}
uCEFChromium, uCEFWindowParent, uCEFInterfaces, uCEFApplication, uCEFTypes,
uCEFConstants,
uCEFWinControl, uCEFSentinel, uCEFChromiumCore;
const
MINIBROWSER_SHOWSECONDFORM = WM_APP + $100;
type
TForm1 = class(TForm)
CEFWindowParent1: TCEFWindowParent;
Chromium1: TChromium;
Timer1: TTimer;
procedure FormShow(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure Timer1Timer(Sender: TObject);
procedure Chromium1ProcessMessageReceived(Sender: TObject;
const browser: ICefBrowser; const frame: ICefFrame;
sourceProcess: TCefProcessId; const message: ICefProcessMessage;
out Result: Boolean);
procedure Chromium1AfterCreated(Sender: TObject;
const browser: ICefBrowser);
procedure Chromium1BeforePopup(Sender: TObject; const browser: ICefBrowser;
const frame: ICefFrame; const targetUrl, targetFrameName: ustring;
targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean;
const popupFeatures: TCefPopupFeatures; var windowInfo: TCefWindowInfo;
var client: ICefClient; var settings: TCefBrowserSettings;
var extra_info: ICefDictionaryValue;
var noJavascriptAccess, Result: Boolean);
procedure Chromium1Close(Sender: TObject; const browser: ICefBrowser;
var aAction: TCefCloseBrowserAction);
procedure Chromium1BeforeClose(Sender: TObject; const browser: ICefBrowser);
procedure Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser;
const frame: ICefFrame; httpStatusCode: Integer);
private
{ Private declarations }
public
{ Public declarations }
protected
Fid: string;
// Variables to control when can we destroy the form safely
FCanClose: Boolean; // Set to True in TChromium.OnBeforeClose
FClosing: Boolean; // Set to True in the CloseQuery event.
procedure BrowserCreatedMsg(var aMessage: TMessage);
message CEF_AFTERCREATED;
procedure BrowserDestroyMsg(var aMessage: TMessage); message CEF_DESTROY;
procedure ShowSecondForm(var aMessage: TMessage);
message MINIBROWSER_SHOWSECONDFORM;
procedure WMMove(var aMessage: TWMMove); message WM_MOVE;
procedure WMMoving(var aMessage: TMessage); message WM_MOVING;
procedure WMEnterMenuLoop(var aMessage: TMessage); message WM_ENTERMENULOOP;
procedure WMExitMenuLoop(var aMessage: TMessage); message WM_EXITMENULOOP;
end;
var
Form1: TForm1;
procedure CreateGlobalCEFApp;
implementation
uses
uSecondForm, uCEFMiscFunctions, uCEFDictionaryValue, uExtensionHandler;
procedure GlobalCEFApp_OnWebKitInitialized;
var
TempExtensionCode: string;
TempHandler: ICefv8Handler;
begin
try
TempExtensionCode := 'var myextension;' + 'if (!myextension)' +
' myextension = {};' + '(function() {' +
' myextension.mouseclick = function(b,c) {' +
' native function mouseclick();' + ' mouseclick(b,c);' + ' };'
+ '})();';
TempHandler := TExtensionHelper.Create;
if CefRegisterExtension('myextension', TempExtensionCode, TempHandler) then
{$IFDEF DEBUG}CefDebugLog('JavaScript extension registered successfully!'){$ENDIF}
else
{$IFDEF DEBUG}CefDebugLog('There was an error registering the JavaScript extension!'){$ENDIF};
finally
TempHandler := nil;
end;
end;
procedure CreateGlobalCEFApp;
begin
GlobalCEFApp := TCefApplication.Create;
GlobalCEFApp.OnWebKitInitialized := GlobalCEFApp_OnWebKitInitialized;
{$IFDEF DEBUG}
GlobalCEFApp.LogFile := 'debug.log';
GlobalCEFApp.LogSeverity := LOGSEVERITY_INFO;
{$ENDIF}
end;
{$R *.dfm}
{ TForm1 }
procedure TForm1.BrowserCreatedMsg(var aMessage: TMessage);
begin
CEFWindowParent1.UpdateSize;
end;
procedure TForm1.BrowserDestroyMsg(var aMessage: TMessage);
begin
CEFWindowParent1.Free;
end;
procedure TForm1.Chromium1AfterCreated(Sender: TObject;
const browser: ICefBrowser);
begin
PostMessage(Handle, CEF_AFTERCREATED, 0, 0);
end;
procedure TForm1.Chromium1BeforeClose(Sender: TObject;
const browser: ICefBrowser);
begin
FCanClose := True;
PostMessage(Handle, WM_CLOSE, 0, 0);
end;
procedure TForm1.Chromium1BeforePopup(Sender: TObject;
const browser: ICefBrowser; const frame: ICefFrame;
const targetUrl, targetFrameName: ustring;
targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean;
const popupFeatures: TCefPopupFeatures; var windowInfo: TCefWindowInfo;
var client: ICefClient; var settings: TCefBrowserSettings;
var extra_info: ICefDictionaryValue; var noJavascriptAccess, Result: Boolean);
begin
Result := (targetDisposition in [WOD_NEW_FOREGROUND_TAB,
WOD_NEW_BACKGROUND_TAB, WOD_NEW_POPUP, WOD_NEW_WINDOW]);
end;
procedure TForm1.Chromium1Close(Sender: TObject; const browser: ICefBrowser;
var aAction: TCefCloseBrowserAction);
begin
PostMessage(Handle, CEF_DESTROY, 0, 0);
aAction := cbaDelay;
end;
procedure TForm1.Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser;
const frame: ICefFrame; httpStatusCode: Integer);
var
TempJSCode: string;
begin
Chromium1.LoadURL('file:///jsExtensionClickEvent.html');
TempJSCode := 'document.body.addEventListener("click", function (evt) { ' +
' function getpath(n) {' +
' var result = document.getElementById(n.id).getAttribute("data-prop"); ' +
' return result; ' + ' } '
+' myextension.mouseclick(getpath(evt.target), ' +
quotedstr(MOUSECLICK_MESSAGE_NAME) + ');});';
frame.ExecuteJavaScript(TempJSCode, 'about:blank', 0);
end;
procedure TForm1.Chromium1ProcessMessageReceived(Sender: TObject;
const browser: ICefBrowser; const frame: ICefFrame;
sourceProcess: TCefProcessId; const message: ICefProcessMessage;
out Result: Boolean);
begin
Result := False;
if (message = nil) or (message.ArgumentList = nil) then
exit;
// This function receives the messages with the JavaScript results
if (message.Name = MOUSECLICK_MESSAGE_NAME) then
begin
Fid := message.ArgumentList.GetString(0);
PostMessage(Handle, MINIBROWSER_SHOWSECONDFORM, 0, 0);
// this doesn't create/destroy components
Result := True;
end;
end;
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
CanClose := FCanClose;
if not(FClosing) then
begin
FClosing := True;
Visible := False;
Chromium1.CloseBrowser(True);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FCanClose := False;
FClosing := False;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
// GlobalCEFApp.GlobalContextInitialized has to be TRUE before creating any browser
// If it's not initialized yet, we use a simple timer to create the browser later.
if not(Chromium1.CreateBrowser(CEFWindowParent1, '')) then
Timer1.Enabled := True;
end;
procedure TForm1.ShowSecondForm(var aMessage: TMessage);
begin
Form2.Label1.Caption := Fid;
Form2.ShowModal;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
if not(Chromium1.CreateBrowser(CEFWindowParent1, '')) and
not(Chromium1.Initialized) then
Timer1.Enabled := True;
end;
procedure TForm1.WMEnterMenuLoop(var aMessage: TMessage);
begin
inherited;
if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then
GlobalCEFApp.OsmodalLoop := True;
end;
procedure TForm1.WMExitMenuLoop(var aMessage: TMessage);
begin
inherited;
if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then
GlobalCEFApp.OsmodalLoop := False;
end;
procedure TForm1.WMMove(var aMessage: TWMMove);
begin
inherited;
if (Chromium1 <> nil) then
Chromium1.NotifyMoveOrResizeStarted;
end;
procedure TForm1.WMMoving(var aMessage: TMessage);
begin
inherited;
if (Chromium1 <> nil) then
Chromium1.NotifyMoveOrResizeStarted;
end;
end.
Second Form [uSecondForm.pas]
unit uSecondForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm2 = class(TForm)
Label1: TLabel;
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
end.
Content of the log file
[1012/202103.335:ERROR:CEF4Delphi(1)] PID: 6708, TID: 2872, PT: Renderer - JavaScript extension registered successfully!
[1012/203832.621:ERROR:CEF4Delphi(1)] PID: 6660, TID: 6748, PT: Renderer - JavaScript extension registered successfully!
[1012/203832.688:ERROR:CEF4Delphi(1)] PID: 5436, TID: 6016, PT: Renderer - JavaScript extension registered successfully!
When clicking on button getting below Debug Event Log. Cef4DelphiJsExtension.exe is application name.
Thread Start: Thread ID: 1732. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 1732. Process Cef4DelphiJsExtension.exe (7156)
Thread Start: Thread ID: 1180. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 2076. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 6592. Process Cef4DelphiJsExtension.exe (7156)
Thread Start: Thread ID: 7200. Process Cef4DelphiJsExtension.exe (7156)
Thread Start: Thread ID: 7220. Process Cef4DelphiJsExtension.exe (7156)
Thread Start: Thread ID: 7276. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 7276. Process Cef4DelphiJsExtension.exe (7156)
Thread Start: Thread ID: 7376. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 7376. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 7220. Process Cef4DelphiJsExtension.exe (7156)
Thank you.
You can do that in two ways:
The full explanation for the JavaScript extension is a bit long but you can read it here : https://github.com/salvadordf/CEF4Delphi/blob/d44db3bf2a3ead0654ca90178161b09bfbe33602/demos/Delphi_VCL/JavaScript/JSExtension/uJSExtension.pas#L122
Please remember that all TChromium and GlobalCEFApp events are executed in a CEF thread that it's different than the main application thread. The VCL is not thread safe and you may have problems if you create, destroy or modify Windows controls inside those events. The CEF4Delphi demos are oversimplified and you should always move the VCL code outside the those events. The first solution sends a Windows message to the main form for this reason.