Jump to content
Rafael Dipold

Exec JS scripts in TEdgeBrowser

Recommended Posts

Hi, lay questions are accepted?

 

I need to run some scripts in a sequence on a document, where the result of the previous script is used to the next script.

 

I created a simplified example for better understanding:

type
  TForm1 = class(TForm)
    EdgeBrowser: TEdgeBrowser;
    procedure EdgeBrowserWebMessageReceived(Sender: TCustomEdgeBrowser; Args: TWebMessageReceivedEventArgs);
  private
    FMyResult: String;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LMyScript: String;
begin
  LMyScript := 'window.chrome.webview.postMessage("MyResult");';
  EdgeBrowser.ExecuteScript(LMyScript);

  //How wait for EdgeBrowserWebMessageReceived() to get FMyResult?
  ShowMessage(FMyResult);
end;

procedure TForm1.EdgeBrowserWebMessageReceived(Sender: TCustomEdgeBrowser; Args: TWebMessageReceivedEventArgs);
begin
  FMyResult := '"MyResult"'; //Get from Args (omitted for simplification)
end;

The comment in the code shows my need.

 

I tried several workarounds like run ExecuteScript() on a TTask.Future(), but EdgeBrowserWebMessageReceived() only executes when the thread finalize.


Does anyone have any suggestions? It doesn't have to be an elegant solution, because this is for internal use.

 

Thanks in advance

Share this post


Link to post

ExecuteScript runs asynchronously I think - it returns (potentially) before the script has finished executing.  There's a web page View Source example here, which looks similar to your setup, but the event has different parameters:

 

https://blogs.embarcadero.com/execute-scripts-and-view-source-with-tedgebrowser/

 

uses
  System.NetEncoding;

procedure TEdgeViewForm.EdgeBrowser1ExecuteScript(Sender: TCustomEdgeBrowser;
  AResult: HRESULT; const AResultObjectAsJson: string);
begin
  if AResultObjectAsJson <> 'null' then
    memoHTML.Text := TNetEncoding.URL.Decode(AResultObjectAsJson).DeQuotedString('"');
end;

The above code looks slightly odd at a glance, in that the event is named ExecuteScript too - perhaps it's a typo?

 

A really messy way to wait, at least just as a quick test, might be:

procedure TForm1.Button1Click(Sender: TObject);
var
  LMyScript: String;
begin
  FMyResult := ''; // Clear it
  LMyScript := 'window.chrome.webview.postMessage("MyResult");';
  EdgeBrowser.ExecuteScript(LMyScript);

  //How wait for EdgeBrowserWebMessageReceived() to get FMyResult?
  while FMyResult = '' do Application.ProcessMessages; // Not good - but might work

  ShowMessage(FMyResult);
end;

Of course if the script fails to return a result, your app is left hanging - and Application.ProcessMessages would allow other events to trigger that you might not be anticipating - like the user clicking buttons in the UI.  Assuming synchronous behaviour is desired, you could perhaps avoid Application.ProcessMessages altogether by moving ShowMessage(FMyResult); into the WebMessageReceived handler, and disable the UI while your function executes?

 

Incidentally I've posted a feature request for an improved FMX TWebBrowser.EvaluateJavascript function that returns a result.  I don't think FMX TWebBrowser currently surfaces an OnScriptExecutionCompleted event: https://quality.embarcadero.com/browse/RSP-35537

 

Since getting a result back from script execution is a very common use case, I think it would be great if VCL TEdgeBrowser and FMX TWebBrowser could be enhanced to include this sort of functionality built in (synchronous and asynchronous).

Edited by Incus J

Share this post


Link to post

Exactly. I did some workarounds to get around this problem.

It works, but it's not an ideal code...

 

type
  TForm1 = class(TForm)
    EdgeBrowser: TEdgeBrowser;
    procedure EdgeBrowserWebMessageReceived(Sender: TCustomEdgeBrowser; Args: TWebMessageReceivedEventArgs);
  private
    FQueue: TQueue<String>;

    FMyResult1: String;
    FMyResult2: String;

    procedure JSExec;
    procedure ShowResult;
  end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FQueue.Enqueue('JSExec1');
  FQueue.Enqueue('JSExec2');

  JSExec;
end;

procedure TForm1.EdgeBrowserWebMessageReceived(Sender: TCustomEdgeBrowser; Args: TWebMessageReceivedEventArgs);
begin
  if (JSON.messageType = 'R1') then //Get from Args (omitted for simplification)
    FMyResult1 := JSON.value; 

  if (JSON.messageType = 'R2') then
    FMyResult2 := JSON.value; 

  JSExec;
end;

procedure TForm1.JSExec;
var
  LExec: String;
  LMyScript: String;
begin
  if (FQueue.Count = 0) then
  begin
    ShowResult;
    Exit;
  end;

  LExec := FQueue.Dequeue();

  if (LExec = 'JSExec1') then
  begin
    LMyScript := 'window.chrome.webview.postMessage({messageType: "R1", value: "MyResult1"});';
    EdgeBrowser.ExecuteScript(LMyScript);
    Exit;
  end;

  if (LExec = 'JSExec2') then
  begin
    LMyScript := 'window.chrome.webview.postMessage({messageType: "R2", value: "MyResult2"});';
    EdgeBrowser.ExecuteScript(LMyScript);
    Exit;
  end;
end;

procedure TForm1.ShowResult;
begin
  ShowMessage(FResult1);
  ShowMessage(FResult2);
end;

 

Edited by Rafael Dipold

Share this post


Link to post

I think that's actually quite a good solution!  (Especially given what TEdgeBrowser provides for you to work with :)

 

If I've understood it correctly it does mean that JSExec2 could complete before JSExec1 completes - if JSExec1 turns out to be the lengthier process.  But it kind of gives synchronous behaviour in the end by waiting for all results to come in.

 

Would just need to be careful to ensure script2 doesn't depend directly on the result of script1 (?) (since they might not complete in the order they were queued).

Edited by Incus J

Share this post


Link to post

Thanks! In this example above I'm exactly queuing the JS calls in series because Script2 (in my application) depends on values returned from Script1.

 

As you said, if you run the JS calls in parallel, the results in EdgeBrowserWebMessageReceived() won't necessarily come in the order they were called.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×