Rafael Dipold 0 Posted September 15, 2021 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
Incus J 10 Posted September 24, 2021 (edited) 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 September 24, 2021 by Incus J Share this post Link to post
Rafael Dipold 0 Posted September 24, 2021 (edited) 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 September 25, 2021 by Rafael Dipold Share this post Link to post
Incus J 10 Posted September 24, 2021 (edited) 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 September 24, 2021 by Incus J Share this post Link to post
Rafael Dipold 0 Posted September 25, 2021 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