Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for async functions/Promises in ExecuteScript #416

Open
Kitiara opened this issue Aug 31, 2020 · 22 comments
Open

Support for async functions/Promises in ExecuteScript #416

Kitiara opened this issue Aug 31, 2020 · 22 comments
Labels
feature request feature request tracked We are tracking this work internally.

Comments

@Kitiara
Copy link

Kitiara commented Aug 31, 2020

Is there a way to read data as text from a blob url ? I've tried executing async/await javascript but i couldn't make it work for blobs.

AB#28965236

@champnic
Copy link
Member

Can you share some more details about your scenario/code? Are you doing this entirely on the javascript side? Does you code work in the browser (not in a webview)?

@Kitiara
Copy link
Author

Kitiara commented Aug 31, 2020

Can you share some more details about your scenario/code? Are you doing this entirely on the javascript side? Does you code work in the browser (not in a webview)?

wstring script(L"(() => {"
L"async function fnc() {"
    L"let bl = await fetch(document.getElementById('test').href).then(r => r.blob());"
    L"var reader = new FileReader;"
    L"reader.readAsText(bl);"
    L"let promise = new Promise((res, rej) => {"
        L"setTimeout(() => res(reader.result), 100)"
    L"});"

    L"return await promise;"
L"}"

L"return fnc().then(function(value) {"
    L"return value; });"
L"})();");

webview->ExecuteScript(script.c_str(), Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
	[](HRESULT errorCode, PCWSTR result) -> HRESULT {
		MessageBox(nullptr, result, L"ExecuteScript Result", MB_OK);
	return S_OK;
}).Get());

The result is always '{}'. I have tested the javascript on the console side of browser and it seems to be working just fine but not in webview2 ??

@champnic
Copy link
Member

A quirk of ExecuteScript (and the underlying CDP call) is that objects are often just returned as "{}". Try wrapping your return value in a .toString().

@champnic
Copy link
Member

We're tracking improvements here as part of #105.

@Kitiara
Copy link
Author

Kitiara commented Sep 1, 2020

A quirk of ExecuteScript (and the underlying CDP call) is that objects are often just returned as "{}". Try wrapping your return value in a .toString().

Async functions always return promises, .toString has just proved that. I couldn't get any data no matter what i try.
But i managed to get some data by using ajax. Check this out:

    wstring jquery(L"var jqry = document.createElement('script');"
    L"jqry.src = 'https://code.jquery.com/jquery-3.3.1.min.js';"
    L"document.getElementsByTagName('head')[0].appendChild(jqry);");
    webview->ExecuteScript(jquery.c_str(), Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
        [webview](HRESULT errorCode, PCWSTR result) -> HRESULT {
        return S_OK;
    }).Get());
    Sleep(500);
    wstring script(L"jQuery.noConflict();"
    L"function testAjax() {"
    L"var result='';"
    L"jQuery.ajax({"
        L"url:document.getElementById('test').href,"
            L"async: false,"
            L"success:function(data) {"
                L"result = data; "
            L"}"
            L"});"
        L"return result;"
    L"}"
    L"(() => {"
        L"return testAjax()"
    L"})();");
    webview->ExecuteScript(script.c_str(), Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
        [webview](HRESULT errorCode, LPCWSTR result) -> HRESULT {
            wcout << wstring(result).c_str() << endl;

        return S_OK;
    }).Get());

This way i'm getting some data but not all of it. On the other hand, i have tested this ajax script on the console side of the browser and it worked there. Any ideas ?

@champnic
Copy link
Member

champnic commented Sep 2, 2020

Ah, I understand now. We don't currently support async function results in ExecuteScript. It looks like there's a path forwards for implementing support there, so I'll open a feature request on our side to track that.

As a workaround, you should be able to use window.chrome.webview.postMessage(data) and add_WebMessageReceived to get out the data from inside your "then" function with the result. Let me know if that work.

@champnic champnic added the feature request feature request label Sep 2, 2020
@Kitiara
Copy link
Author

Kitiara commented Sep 2, 2020

The ajax method is working. I noticed that certain unicodes are causing wcout to fail. I managed to print whole data by using
wprintf(L"%ls\n", result);
But i've found something strange. If you remove or replace null characters from javascript side then result of ExecuteScript become completely NULL.

@champnic
Copy link
Member

champnic commented Sep 2, 2020

I believe the ajax is working because you are making it a synchronous call - I don't think we'd recommend this, as it will block the web code while executing. postMessage when the async calls are complete is preferred.

Can you share the code that is doing the character replacement? When you say NULL do you mean the return string is "null" or that the return string is a nullptr/NULL/0x0?

@Kitiara
Copy link
Author

Kitiara commented Sep 2, 2020

I believe the ajax is working because you are making it a synchronous call - I don't think we'd recommend this, as it will block the web code while executing. postMessage when the async calls are complete is preferred.

I don't need async for the project i'm working on, so it's ok for me.

Can you share the code that is doing the character replacement? When you say NULL do you mean the return string is "null" or that the return string is a nullptr/NULL/0x0?

I mean the return string is "null". Replacing or removing any '\u0000' is causing this phenomenon.

return result.replace(/[\u0000]/g, 'X');

@champnic
Copy link
Member

champnic commented Sep 2, 2020

A "null" string can be returned when the javascript object is undefined, there's an exception in the script, or if there's an error in trying to serialize the result using JSON. I'm not sure why removing the null character would cause those. Can you confirm that code works in the console/browser?

@Kitiara
Copy link
Author

Kitiara commented Sep 2, 2020

A "null" string can be returned when the javascript object is undefined, there's an exception in the script, or if there's an error in trying to serialize the result using JSON. I'm not sure why removing the null character would cause those. Can you confirm that code works in the console/browser?

Yes, it is working without any problem.

@champnic
Copy link
Member

champnic commented Sep 2, 2020

Are you able to share what the result string is before and after the replacement? If you just use a static string (not from the server) is there some minimal string that reproduces the problem so we can take a look on our end?

@Kitiara
Copy link
Author

Kitiara commented Sep 2, 2020

It's not a static string. The result i'm getting is constantly changing but it's pattern is always the same and i'm always getting null after the replacement of the null character. So you should be able to reproduce the problem. Here it is:
Original text: before.txt
Expected outcome: after.txt

@champnic
Copy link
Member

champnic commented Sep 2, 2020

Working on getting a repro. Are you able to share the url you are using in you ajax request? Does the problem still happen without the .replace(...) call?

@Kitiara
Copy link
Author

Kitiara commented Sep 2, 2020

The url isn't static either. The problem is happening with the replace call, otherwise it is fine.

@champnic champnic changed the title Reading data from BLOB URL? Support for async functions/Promises in ExecuteScript Mar 2, 2021
@killerfurbel
Copy link

killerfurbel commented Jun 7, 2021

I think this is very important. Since WebView2 is rolled out with the new Office 365 it will be more interesting as a replacement for CEF/CefSharp. Maybe you can have a look at their interface. Solving the problem similar would probably help when migrating the code from CEF to WebView2!

https://github.com/cefsharp/CefSharp/wiki/General-Usage#2-how-do-you-call-a-javascript-method-that-returns-a-result

@champnic champnic removed the question label Dec 2, 2021
@TonyLugg
Copy link

I've been struggling all day with trying to await ExecuteScriptAsync on an async javascript function. As soon as it hits the await inside the JS function, it returns to the .NET code. Is there no way to wait for the async JS code to finish before moving on?

@champnic
Copy link
Member

Currently no - the ExecuteScript function doesn't wait on async javascript functions. That's what this feature request is tracking.

You can use host objects or postMessage/WebMessageReceived to message a result back to the host app if needed.

@TonyLugg
Copy link

OK, hopefully it gets added soon. Using a callback/post-back defeats the purpose of "await".

@R0Wi
Copy link

R0Wi commented Mar 17, 2022

I wonder if anyone could provide a complete working example for this problem using WebView2 postMessage communication instead of awaiting the result of an asynchronous JavaScript function on the host side?

We are working with the WinForms/C# integration and we plan to embed a serverside Blazor application. Like described in the docs we shall use invokeMethodAsync on a DotNetObjectReference object inside the JavaScript world, to execute Blazor Serverside functions. One of these functions we want to support is called bool HasUnsavedData(). It can be called from the host to see if the Blazor application has unsaved data and react accordingly (for example ask the user to save his data before closing the complete application).

For sake of simplicity let's use the JavaScript setTimeout function to simulate the async call to invokeMethodAsync done by the host system to ask the web application if it has unsaved data. Since the main thread of the .NET application is not allowed to be blocked i tried something (hacky) like

private async bool ExecuteHasUnsavedData()
{
    var received = false;
    var result = false;

    void OnMessageReceived(object _, CoreWebView2WebMessageReceivedEventArgs args)
    {
        var msg = args.TryGetWebMessageAsString();
        if (msg.StartsWith("RETURN|"))
        {
            result = bool.Parse(msg.Split('|')[1]);
            received = true;
        }
    }
    
    _webView2.CoreWebView2.WebMessageReceived += OnMessageReceived;

    // Assume this is our async JS call which was rewritten so that it uses postMessage to populate it's result
    var jsCode = "setTimeout(() => window.chrome.webview.postMessage('RETURN|true'), 2000)";
    await _webView2.ExecuteScriptAsync(jsCode);
   
     while(!received)
        Application.DoEvents();

    _webView2.CoreWebView2.WebMessageReceived -= OnMessageReceived;
    return result;
}

but that didn't work.

Would be really nice if anyone has a suggestion otherwise i'm afraid we're not able to use WebView2 currently 😢

@amaitland
Copy link

amaitland commented Mar 21, 2022

Technically this is possible already via the DevTools Protocol.

For ease of use I've just released WebView2.DevTools.Dom to nuget.org

It's a DevTools Protocol based framework for JavaScript execution, DOM access/manipulation. (Direct connection to browser via CoreWebView2 instance, doesn't open a remote debugging port). More details in the Readme

 await webView.EnsureCoreWebView2Async();
 
 // Create one instance per CoreWebView2
 // Reuse devToolsContext if possible, dispose (via DisposeAsync) before creating new instance
 // Make sure to call DisposeAsync when finished or await using as per this example
// Add using WebView2.DevTools.Dom; to access the CreateDevToolsContextAsync extension method
 await using var devToolsContext = await webView2Browser.CoreWebView2.CreateDevToolsContextAsync();

 var meaningOfLifeAsInt = await devToolsContext.EvaluateFunctionAsync<int>("() => Promise.resolve(42)");

@R0Wi
Copy link

R0Wi commented Mar 31, 2022

If anyone is just interested in invoking async Javascript via DevTools interface, i just created a little helper class which might point you to the right direction.

If you need extended functionallity i can really recommend the library WebView2.DevTools.Dom, thanks @amaitland for creating this! And also thanks for the great hint 🚀

@champnic champnic added the tracked We are tracking this work internally. label May 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request feature request tracked We are tracking this work internally.
Projects
None yet
Development

No branches or pull requests

6 participants