Page 1 of 2

How to start a nested message loop in renderer process?

PostPosted: Sat May 08, 2021 3:22 am
by Streamlet
I need to implement a sync JS function, so I need to "wait" on the main thread of renderer process, until ipc message received from browser process.
I can not use real waiting methods like "std::condition_variable" or Windws WaitForSingleObject or CefWaitableEvent, because it will block the main thread and renderer process can not receive ipc messages.
I think I need to start a nested message loop to make the waiting.

Noticed that CefDoMessageLoopWork is limited to be run on browser process.
If it is called on renderer process, there is a check that ensure CefContext has been initialized. CefContext is initialied by CefInitialize, which will not run for renderer process.
So, is there any way to execute a raw base::RunLoop::RunUntilIdle ?

Re: How to start a nested message loop in renderer process?

PostPosted: Sat May 08, 2021 11:55 am
by magreenblatt

Re: How to start a nested message loop in renderer process?

PostPosted: Sat May 08, 2021 1:21 pm
by Streamlet
I fully know what it "should be".

However, I am currently migrating an almost 10-years-old historic project to CEF.
It is currently using our magically-modified raw chromium project, whose application interfaces are terrible. That's very hard to upgrade to new chromium version.
We like CEF's well-designed interfaces, and its fast upgrading speed.

This project need to support many many second-party and third-party web pages.
Even second-party web pages, that is maintained by other department in our company, are hard to be changed to cowork with our migrating, let alone third-party web pages.
The web developers are far far away from native developers in company organization, so thay are hard to drive.

This old project provides some sync JavaScript functions.
It "should" be changed to async using callback function, or promise, or something else. Or as the WIKI says, we should provide a custom schema and let JS side make an XMLHttpRequest.
But we are not able to drive all second-party and third-party web develpers to change their JavaScript code.
We need to be compatible with our pre-CEF version and make all web pages running OK.

So, I need a sync IPC method.
I think many other CEF users need it too, especially if they are migrating their old projects.

Re: How to start a nested message loop in renderer process?

PostPosted: Sat May 08, 2021 2:01 pm
by magreenblatt
I have sympathy for your situation. However, CEF does not support sync messaging from the renderer process to the browser process. See here for some approaches that you might be able to implement in JavaScript.

Re: How to start a nested message loop in renderer process?

PostPosted: Sun May 09, 2021 2:13 am
by amaitland
If you absolutely have to then use a third party IPC framework that does support sync communication.

Blocking the render process is unavoidable if you need sync communication.

Personally I think you'd be better off putting your efforts into improving your JavaScript.

Re: How to start a nested message loop in renderer process?

PostPosted: Sun May 09, 2021 11:11 am
by Streamlet
I will implement a sync version, using chromium's sync IPC message, something like this:

cef_messages.h:
Code: Select all
IPC_SYNC_MESSAGE_ROUTED1_1(CefHostMsg_SyncMessage,
                           Cef_Request_Params /* in */,
                           Cef_Request_Params /* out */)


CefFrame:
Code: Select all
  ///
  // Similar to SendProcessMessage, synchronous version.
  // This method MUST be called from renderer process.
  ///
  /*--cef()--*/
  virtual CefRefPtr<CefProcessMessage> SendSyncProcessMessage(
      CefProcessId target_process,
      CefRefPtr<CefProcessMessage> message) = 0;


CefClient:
Code: Select all
///
// Callback interface used for continuation of sync process massage.
///
/*--cef(source=library)--*/
class CefSyncProcessMessageCallback : public virtual CefBaseRefCounted {
 public:
  ///
  // Complete processing of sync process message and return result to calling
  // process.
  ///
  /*--cef(capi_name=cont)--*/
  virtual void Continue(CefRefPtr<CefProcessMessage> result) = 0;

  ///
  // Abort processing of sync process message, and calling process will receive
  // null.
  ///
  /*--cef()--*/
  virtual void Cancel() = 0;
};

///
// Implement this interface to provide handler implementations.
///
/*--cef(source=client,no_debugct_check)--*/
class CefClient : public virtual CefBaseRefCounted {
 public:
  //...

  ///
  // Similar to OnProcessMessageReceived, synchronous version. Return true
  // if the message was handled or will be handled by callback, return false
  // otherwise.
  ///
  /*--cef()--*/
  virtual bool OnSyncProcessMessageReceived(
      CefRefPtr<CefBrowser> browser,
      CefRefPtr<CefFrame> frame,
      CefProcessId source_process,
      CefRefPtr<CefProcessMessage> message,
      CefRefPtr<CefSyncProcessMessageCallback> callback) {
    return false;
  }
};



CefFrameImpl:
Code: Select all
CefRefPtr<CefProcessMessage> CefFrameImpl::SendSyncProcessMessage(
    CefProcessId target_process,
    CefRefPtr<CefProcessMessage> message) {
  DCHECK_EQ(PID_BROWSER, target_process);
  if (!frame_)
    return nullptr;

  Cef_Request_Params params;
  CefProcessMessageImpl* impl =
      static_cast<CefProcessMessageImpl*>(message.get());
  if (!impl->CopyTo(params))
    return nullptr;

  DCHECK(!params.name.empty());

  params.user_initiated = true;
  params.request_id = -1;
  params.expect_response = false;

  Cef_Request_Params* reply = new Cef_Request_Params();
  Send(new CefHostMsg_SyncMessage(MSG_ROUTING_NONE, params, reply));

  if (!reply->expect_response)  // not success
    return nullptr;

  return new CefProcessMessageImpl(const_cast<Cef_Request_Params*>(reply),
                                   true, false);
}


CefFrameHostImpl:
Code: Select all
CefRefPtr<CefProcessMessage> CefFrameHostImpl::SendSyncProcessMessage(
    CefProcessId target_process,
    CefRefPtr<CefProcessMessage> message) {
  NOTREACHED() << "SendSyncProcessMessage cannot be called from the browser process";
  return nullptr;
}

namespace {

class SyncProcessMessageCallback : public CefSyncProcessMessageCallback {
 public:
  typedef base::OnceCallback<void(IPC::Message* message)> IPCMessageSender;
  SyncProcessMessageCallback(IPCMessageSender sender,
                             IPC::Message* reply_msg,
                             int request_id,
                             bool user_initiated)
      : sender_(std::move(sender)), reply_msg_(reply_msg) {
    reply_.request_id = request_id;
    reply_.user_initiated = user_initiated;
    reply_.expect_response = false;  // Use expect_response for result
  }
  void Continue(CefRefPtr<CefProcessMessage> result) override {
    if (result) {
      CefProcessMessageImpl* impl =
          static_cast<CefProcessMessageImpl*>(result.get());
      if (impl->CopyTo(reply_)) {
        reply_.expect_response = true;
      }
    }
    Send();
  }
  void Cancel() override { Send(); }

 private:
  void Send() {
    DCHECK(!sender_.is_null()) << "Called callback more than once? or called "
                                  "callback and returned false?";
    CefHostMsg_SyncMessage::WriteReplyParams(reply_msg_, reply_);
    std::move(sender_).Run(reply_msg_);
  }
  IPCMessageSender sender_;
  IPC::Message* reply_msg_;
  Cef_Request_Params reply_;
  IMPLEMENT_REFCOUNTING(SyncProcessMessageCallback);
};


}  // namespace

void CefFrameHostImpl::OnSyncMessage(const Cef_Request_Params& params,
                                     IPC::Message* reply_msg) {
  bool success = false;

  if (params.user_initiated) {
    auto browser = GetBrowserHostBase();
    if (browser && browser->GetClient()) {
      // Give the user a chance to handle the request.
      CefRefPtr<CefProcessMessageImpl> message(new CefProcessMessageImpl(
          const_cast<Cef_Request_Params*>(&params), false, true));
      CefRefPtr<SyncProcessMessageCallback> callback =
          new SyncProcessMessageCallback(
              base::BindOnce(&CefFrameHostImpl::Send, this), reply_msg,
              params.request_id, params.user_initiated);
      success = browser->GetClient()->OnSyncProcessMessageReceived(
          browser.get(), this, PID_RENDERER, message.get(), callback.get());
      message->Detach(nullptr);
      if (!success) {
        callback->Cancel();
      }
    }
  } else {
    // Invalid request.
    NOTREACHED();
  }
}


Re: How to start a nested message loop in renderer process?

PostPosted: Wed May 12, 2021 9:23 am
by magreenblatt
Legacy IPC is being removed from Chromium so I don't recommend relying on that. See https://bitbucket.org/chromiumembedded/cef/issues/3123

Re: How to start a nested message loop in renderer process?

PostPosted: Wed May 12, 2021 11:14 am
by HarmlessDave
If this is Windows and you only need to send messages to the browser process, would it make sense to use Win32 SendMessage() as a hack?

Re: How to start a nested message loop in renderer process?

PostPosted: Wed May 12, 2021 11:45 am
by magreenblatt
HarmlessDave wrote:If this is Windows and you only need to send messages to the browser process, would it make sense to use Win32 SendMessage() as a hack?

You likely won’t have access to SendMessage if the sandbox is enabled.

Re: How to start a nested message loop in renderer process?

PostPosted: Thu May 13, 2021 2:55 am
by Streamlet
magreenblatt wrote:Legacy IPC is being removed from Chromium so I don't recommend relying on that. See https://bitbucket.org/chromiumembedded/cef/issues/3123


Mojo also supports sync message as far as I known, is it?
In which version will CEF remove Legacy IPC?