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.

Does anyone have a simple demo on how to return a value to Delphi?

Post Reply
User avatar
salvadordf
Posts: 4057
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Does anyone have a simple demo on how to return a value to Delphi?

Post by salvadordf »

Hi,

I'll add more code comments to those demos as soon as I can.

The process messages can have multiple arguments of many data types in the ArgumentList property.
You can add values to the ArgumentList property with functions like "SetString", "SetDouble", "SetInt", etc. and each of those function has an "index" parameter to add that piece of data at that position in the argument list.

A simple way to add several "value-description" pairs would be to concatenate them with an "=" sign and add them as a single string argument. If you prefer to keep them separated, you can assign the "value" to the even list positions and the "description" to the odd list positions.
If you prefer to use JSON or XML you can also create a custom JSON or XML document with all the "value-description" pairs and send it as the first argument with the "SetString" function.
User avatar
salvadordf
Posts: 4057
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Does anyone have a simple demo on how to return a value to Delphi?

Post by salvadordf »

stwizard wrote: Thu Aug 29, 2019 7:27 pm I'm not sure I understand what you are attempting to tell me.
I was describing how to send a list of all available "value, item description" pairs back to Delphi.

The code in the JavaScript extension uses a process message to send information to Delphi and you need to use the ArgumentList property to send that information.
User avatar
salvadordf
Posts: 4057
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Does anyone have a simple demo on how to return a value to Delphi?

Post by salvadordf »

Hi,

I just uploaded a new version of CEF4Delphi and now the code comments in the JSExtension demo are a lot longer.

Here you have a copy with some text formatting :

BASIC CONCEPTS

Chromium uses several processes to carry out all the tasks needed to handle a web page :
  • 1. The main application process is called "BROWSER PROCESS" and it runs the UI.
  • 2. The layout and interpretation of HTML is done in the "RENDERER PROCESS".
Read this for more details about Chromium's architecture :
http://www.chromium.org/developers/desi ... chitecture

Each process is isolated from the rest and you need to use some kind of inter-process communication (IPC) to send information between them. This isolation and protection is guaranteed by the operating system and it's the main reason Chromium uses several processes.

In many cases, you need to use JavaScript or visit the DOM to return some results to Delphi. The DOM and JavaScript live in the RENDERER PROCESS, while the Delphi code of your application lives in the BROWSER PROCESS.

As commented before, the operating system isolates each process and this means that you can't access anything declared in one process like variables, fields, classes, controls, etc. from a different process.

However, CEF has several ways to send information between processes and you can also use your own inter-process communication methods.

If you need to execute some JavaScript code all you need is to call TChromium.ExecuteJavaScript from your application's code in the BROWSER PROCESS and CEF will take care of executing that code in the RENDERER PROCESS.

If you need to send a message to the RENDERER PROCESS from the BROWSER PROCESS you can use TChromium.SendProcessMessage.

To send messages to the BROWSER PROCESS from the RENDERER PROCESS you can use ICefFrame.SendProcessMessage

Code: Select all

     --------------   TChromium.SendProcessMessage  --------------
     |            | ------------------------------> |            |
     |  BROWSER   |                                 |  RENDERER  |
     |            |                                 |            |
     |  PROCESS   |   ICefFrame.SendProcessMessage  |  PROCESS   |
     |            | <------------------------------ |            |
     --------------                                 --------------
To receive the messages sent from the RENDERER PROCESS you need to use the TChromium.OnProcessMessageReceived event. This event is executed in a CEF thread that belongs to the BROWSER PROCESS.

To receive the messages sent from the BROWSER PROCESS you need to use the TCefApplication.OnProcessMessageReceived event (GlobalCEFApp.OnProcessMessageReceived). This event is executed in a CEF thread that belongs to the RENDERER PROCESS.


JAVASCRIPT EXTENSIONS

CEF exposes a large number of JS features for integration in client applications. You can use JS types, arrays, functions, extensions, objects, etc.

All of those features are described in detail here :
https://bitbucket.org/chromiumembedded/ ... gration.md

One of the most interesting JS features available in CEF are the JavaScript extensions because they can be used to execute custom Delphi code from JavaScript.

If you put all you know so far together you can get any result or information in Delphi from JavaScript following these steps :
  • 1. Use TChromium.ExecuteJavaScript to execute your custom JavaScript code.
  • 2. That custom JavaScript code is executed in the RENDERER PROCESS and it can call functions in your custom JavaScript extension, which executes Delphi code. This Delphi code is also executed in the RENDERER PROCESS.
  • 3. The Delphi code in the JavaScript extension can use ICefFrame.SendProcessMessage to send information back to the BROWSER PROCESS.
  • 4. The BROWSER PROCESS receives the information in the TChromium.OnProcessMessageReceived event.
To create a JavaScript extension in CEF you have to create a new class that inherits from TCefv8HandlerOwn and it has to override the "execute" function. Open uTestExtensionHandler.pas to see an example and read this for more details about the "execute" parameters :
https://magpcss.org/ceforum/apidocs3/pr ... ndler.html

In order to use that extension, you must reister it in the GlobalCEFApp.OnWebKitInitialized event as you can see in the GlobalCEFApp_OnWebKitInitialized procedure on this PAS unit.

You have to call the CefRegisterExtension function with 3 parameters :
  • 1. name : The extension name.
  • 2. code : Any valid JS code but in this case it includes 2 "native function" forward declarations.
  • 3. Handler : An instance of your TCefv8HandlerOwn subclass.
Notice that the code used with the CefRegisterExtension function in this demo is declaring "myextension.mouseover" as a function that calls the "mouseover" native function, and the "myextension.sendresulttobrowser" function that calls the "sendresulttobrowser" native function.

The "execute" function in the custom TCefv8HandlerOwn subclass will compare the "name" parameter with the name of the of the native function used in the code that registered this extension. As you can see in this demo, TTestExtensionHandler.Execute compares the "name" parameter with
"mouseover" and "sendresulttobrowser" to execute the code you want for each of those custom functions.

TTestExtensionHandler.Execute is executed in the RENDERER PROCESS and it uses a process message to send some results to he BROWSER PROCESS.
It uses TCefv8ContextRef.Current.Browser.MainFrame to call the SendProcessMessage procedure for the main frame.

The message is a TCefProcessMessageRef instance and you can set the information you want to send using its ArgumentList property.

You can add several items to ArgumentList using different indexes in the SetString, SetInt, SetBool, SetBinary, etc. functions.
There is a size limit in the binary parameters of only a few kilobytes. Compress the binary data, use alternative IPC methods or use a database protected by a mutex if necessary.

For more information about this, read the following pages :
https://bitbucket.org/chromiumembedded/ ... extensions
https://bitbucket.org/chromiumembedded/ ... #lines-924
User avatar
salvadordf
Posts: 4057
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Does anyone have a simple demo on how to return a value to Delphi?

Post by salvadordf »

stwizard wrote: Mon Sep 09, 2019 5:03 pm So using the JSExtension demo, when you are on the google search screen where you would type in a value of say "CEF4Delphi' and before you click the "Google Search" button, you could type this into the console of the DevTools "document.getElementsByClassName("gLFyf gsfi")[0].value" and it shows the value I would like to return to Delphi. How do I get that back to Delphi. If I had something to pick apart it may help me to better understand this.
JSExtension has 99% of the code you need to send a value from that INPUT element to Delphi.

It already has a context menu option called "Visit DOM in JavaScript" that sends the results of "document.body.innerHTML" to Delphi using the "myextension.sendresulttobrowser" procedure in the JavaScript extension.

Edit the TJSExtensionFrm.Chromium1ContextMenuCommand procedure with this code :

Code: Select all

procedure TJSExtensionFrm.Chromium1ContextMenuCommand(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame;
  const params: ICefContextMenuParams; commandId: Integer;
  eventFlags: Cardinal; out Result: Boolean);
begin
  Result := False;

  // Here is the code executed for each custom context menu entry

  case commandId of
    MINIBROWSER_CONTEXTMENU_SETJSEVENT :
      if (browser <> nil) and (browser.MainFrame <> nil) then
        browser.MainFrame.ExecuteJavaScript(
          'document.body.addEventListener("mouseover", function(evt){'+
            'function getpath(n){'+
              'var ret = "<" + n.nodeName + ">";'+
              'if (n.parentNode){return getpath(n.parentNode) + ret} else '+
              'return ret'+
            '};'+
            'myextension.mouseover(getpath(evt.target))}'+   // This is the call from JavaScript to the extension with DELPHI code in uTestExtensionHandler.pas
          ')', 'about:blank', 0);

    MINIBROWSER_CONTEXTMENU_JSVISITDOM :
      if (browser <> nil) and (browser.MainFrame <> nil) then
        browser.MainFrame.ExecuteJavaScript(
          'var testhtml = document.getElementsByClassName("gLFyf gsfi")[0].value;' +
          'myextension.sendresulttobrowser(testhtml, ' + quotedstr(CUSTOMNAME_MESSAGE_NAME) + ');',  
          'about:blank', 0);
  end;
end;


Notice that I just replaced "document.body.innerHTML" with "document.getElementsByClassName("gLFyf gsfi")[0].value".

The results are received in the TJSExtensionFrm.Chromium1ProcessMessageReceived procedure. It checks that the message.name has a CUSTOMNAME_MESSAGE_NAME to store the string argument with the input box value and then sends a windows message to show it in the main application thread.
stwizard wrote: Mon Sep 09, 2019 5:03 pm I'm also looking at the JSRTTIExtension demo and see you have a "MutationObserver" portion of the demo, but I cannot seem to get it to work. I have right clicked and chose "Add mutation observer" and then typed something into the search field but nothing happens.
FYI: you need to edit this line: // This observer is configured to execute the callback when the attributes in the search box at google.com
Thanks for reporting this issue with the mutation observer!
I'll take a look as soon as I have time.
Post Reply