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

ExecuteScript with Win32/C++: Need synchronous return value #1905

Closed
MarkusSchreiner opened this issue Nov 3, 2021 · 6 comments
Closed

ExecuteScript with Win32/C++: Need synchronous return value #1905

MarkusSchreiner opened this issue Nov 3, 2021 · 6 comments

Comments

@MarkusSchreiner
Copy link

I also need a proper way to retrieve the return value of a HTML embedded Java Scripts.
(see issues #838 and #840 )

Thus the C++ API of WebView2 does not support the function ExecuteScriptAsync(), this is a very tricky thing!

I determined with the process monitor, that the callback of ICoreWebView2ExecuteScriptCompletedHandler pumps a message with the ID 1025 after execution,

The only way (as I figured out) to retrieve the return value is by 'faking' a local message loop, wait until the message with ID 1025 was received, and then polling a member variable of my ICoreWebView2ExecuteScriptCompletedHandler implementation class.

The code looks a bit like this:

bool executeJavaScrip_Edge()
{
    std::wstring wstrJSCode = L"someJavaScriptFunction();";
    std::wstring* pwstrReturnValue;

    bool bDone = false;
    // with class CExecuteScriptCompletedCallback : public ICoreWebView2ExecuteScriptCompletedHandler
    CExecuteScriptCompletedCallback* pEsch = new CExecuteScriptCompletedCallback(&bDone, pwstrReturnString);
    HRESULT hr = m_pEdge->ExecuteScript(wstrJSCode, pEsch);
    pEsch->Release();
    if (FAILED(hr))
        return false;

    // The return string will be set by CExecuteScriptCompletedCallback::Invoke()
    MSG sMsg;
    do
    {
        VERIFY(GetMessage(&sMsg, nullptr, 1025, 1025));
        TranslateMessage(&sMsg);
        DispatchMessage(&sMsg);
    } while (!bDone);

    // pwstrReturnValue now holds the answer from the JS call
    return true;
}

But this does not look like the best solution, and also it is kind of a little laggy. Sometimes the message loop needs ~5 seconds.

Is there not a 'proper' way to do this?
What do the developers of WebView2 think about this issue?
Is an official implementation for ExecuteScriptAsync() to be pubished in the near future?

@champnic
Copy link
Member

champnic commented Dec 8, 2021

Hey @MarkusSchreiner - Sorry for the delay here. We aren't planning to support a synchronous version of this function. The C++ ExecuteScript is the same as the C# ExecuteScriptAsync (the C# version is just a wrapper for the C++ version). The return value will be supplied to the ICoreWebView2ExecuteScriptCompletedHandler callback. Thanks!

@champnic champnic closed this as completed Dec 8, 2021
@raroraca
Copy link

Do we have a way to proceed with Sync return from ExecuteScript available from webview2? Anything considered? Or how can we achieve await like functionality with C++?

@raroraca
Copy link

@MarkusSchreiner When we tried the above sample code, we do not see any message pump in the while loop in the function and invoke was never called.
Did you called this function from the main thread or any other thread? Can you please share some more details on this?
Is this the final solution you decided on(as you mentioned that solution is laggy)? Or any other way was found?

@MarkusSchreiner
Copy link
Author

@raroraca I implemented the callback handler the 'old school' way, perhaps sharing the code of this class helps you:
I call the function from within the Main-Thread.
And yes, this is the final solution for me. The laggy behaviour does not occur in release-builds and is efficient enough (for my needs).

class CExecuteScriptCompletedCallback : public ICoreWebView2ExecuteScriptCompletedHandler
{
public:
    CExecuteScriptCompletedCallback(bool* pbDone, std::wstring* pwstrResult);

    // Inherited via ICoreWebView2ExecuteScriptCompletedHandler
    virtual HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject) override;
    virtual ULONG __stdcall AddRef(void) override;
    virtual ULONG __stdcall Release(void) override;
    virtual HRESULT __stdcall Invoke(HRESULT hr, LPCWSTR resultObjectAsJson) override;

protected:
    long m_lRef = 1;

    /// <summary>
    /// Fetches the result from any JavaScript-execution within Edge.
    /// This string is retrieved encoded by the JSON Backslash encoding.
    /// </summary>
    std::wstring* m_pwstrResult;

    /// <summary>
    /// Helper bool, to have an abort-criteria for our 'extra' Message Queue.
    /// </summary>
    bool* m_pbDone;
};
CExecuteScriptCompletedCallback::CExecuteScriptCompletedCallback(bool* pbDone, CHILStringW* pwstrResult)
{
    m_pbDone = pbDone;
    m_pwstrResult = pwstrResult;
}

HRESULT __stdcall CExecuteScriptCompletedCallback::QueryInterface(REFIID riid, void** ppvObject)
{
    if (riid == __uuidof(ICoreWebView2ExecuteScriptCompletedHandler) || riid == IID_IUnknown)
    {
        *ppvObject = this;
        AddRef();
        return S_OK;
    }
    *ppvObject = nullptr;
    return E_NOINTERFACE;
}

ULONG __stdcall CExecuteScriptCompletedCallback::AddRef(void)
{
    return _InterlockedIncrement(&m_lRef);
}

ULONG __stdcall CExecuteScriptCompletedCallback::Release(void)
{
    long iRet = _InterlockedDecrement(&m_lRef);

    if (!iRet)
        delete this;
    return iRet;
}

HRESULT __stdcall CExecuteScriptCompletedCallback::Invoke(HRESULT hr, LPCWSTR resultObjectAsJson)
{
    if (SUCCEEDED(hr) && m_pwstrResult)
    {
         // convert LPCWSTR resultObjectAsJson to a std::wstring and store
         // it in  *m_pwstrResult.
         // Be careful: resultObjectAsJson is JSON-encoded !
    }
    *m_pbDone = true;
    return S_OK;
}

@lxb320124
Copy link

very good!thanks

@zhazhazhou
Copy link

zhazhazhou commented Apr 12, 2024

Can you provide a complete demo? QueryInterface doesn't seem to work when I try to run it.
Here is the js method I called

function asleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function test() {
const startTime = Date.now();
console.log(begin: ${startTime});

console.log(`await 1s: ${new Date().toISOString()}`);
await asleep(1000);
console.log(`await end: ${new Date().toISOString()}`);

console.log(`await 1s: ${new Date().toISOString()}`);
await asleep(1000);
console.log(`await end: ${new Date().toISOString()}`);

const endTime = Date.now();
const totalTime = endTime - startTime;
console.log(`totalTime: ${totalTime}ms`);

// 返回总耗时
return {"totalTime": totalTime};

}

I expect results {"totalTime":2000} , not {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants