Asynchronous JavaScript and C++ Communication in CEF with Callbacks

The CEF3 framwork operates using a multi-process architecture where the Browser and Renderer live in separate address spaces. While the Browser process typically aligns with the application's main window thread, the Renderer runs independently. Both processes maintain their own instances of browser and frame objects.

Traditionally, interacting with C++ from JavaScript involves subclassing CefV8Handler and implementing the execution method:

bool MyV8Handler::Execute(const CefString& funcName,
                          CefRefPtr<CefV8Value> targetObject,
                          const CefV8ValueList& args,
                          CefRefPtr<CefV8Value>& retVal,
                          CefString& errorMsg)

This synchronous approach works well for immediate tasks where the result is readily available. However, complications arise when the Renderer needs data residing in the Browser process. While you can dispatch a message to the Browser using frame->SendProcessMessage(PID_BROWSER, message), the inter-process communication (IPC) is asynchronous. This means the Execute method cannot simply wait for a return value without blocking the render thread.

To solve this, we can leverage JavaScript's native support for asynchronous operations via callbacks. The strategy involves the following workflow:

  1. JS Invocation: The JavaScript code calls a bound function (e.g., queryBackend) and passes a callback function as an argument. This binding usually occurs within the Render process's OnContextCreated method.
  2. Handler Logic: Inside the Execute method, the C++ handler extracts the callback argument (a CefV8Value) and stores it in a static map or cache associated with a unique request ID.
  3. IPC Dispatch: The handler sends a process message to the Browser process requesting the necessary operation, including the unique ID.
  4. Browser Processing: The Browser process handles the message in OnProcessMessageReceived, performs the required logic, and sends a response message back to the Renderer containing the result and the original ID.
  5. Callback Executtion: The Renderer receives the response, looks up the ID in the cache, retrieves the stored JavaScript callback, and invokes it with the returned data.

Here is a conceptual example of how the V8 handler might manage the asynchronous request:

// Snippet of the V8 Handler logic
std::map<int, CefRefPtr<CefV8Value>> g_callbackCache;
int g_nextId = 0;

bool MyV8Handler::Execute(...) {
    if (funcName == "queryBackend") {
        // 1. Extract the JS callback
        CefRefPtr<CefV8Value> jsCallback = args[0];
        
        // 2. Generate ID and cache the callback
        int reqId = g_nextId++;
        g_callbackCache[reqId] = jsCallback;

        // 3. Create message and send to Browser
        CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("fetch_data");
        msg->GetArgumentList()->SetInt(0, reqId);
        frame->SendProcessMessage(PID_BROWSER, msg);
        return true;
    }
    return false;
}

When the response arrives in the Renderer's OnProcessMessageReceived:

bool MyRenderApp::OnProcessMessageReceived(...) {
    if (message->GetName() == "fetch_data_response") {
        int reqId = message->GetArgumentList()->GetInt(0);
        CefString result = message->GetArgumentList()->GetString(1);

        // Retrieve and execute the callback
        auto it = g_callbackCache.find(reqId);
        if (it != g_callbackCache.end()) {
            CefV8ValueList callbackArgs;
            callbackArgs.push_back(CefV8Value::CreateString(result));
            it->second->ExecuteFunction(nullptr, callbackArgs);
            g_callbackCache.erase(it);
        }
        return true;
    }
    return false;
}

Key Considerations:

  • Context Creation: If your applicasion uses multiple iframes, OnContextCreated fires for each one. To avoid registering the function multiple times, consider initializing bindings in OnWebKitInitialized if appropriate for your scope.
  • Cross-Origin Frames: Be aware that iframes from different domains might face restrictions regarding message reception.
  • Handler Lifetime: It is recommended to initialize your CefV8Handler once during the Render process setup to prevent the function cache from becoming inaccessible or empty across different frame contexts.

Tags: CEF Chromium Embedded Framework C++ javascript IPC

Posted on Thu, 14 May 2026 00:06:32 +0000 by jahwobbler