How to start a nested message loop in renderer process?

Having problems with building or using CEF's C/C++ APIs? This forum is here to help. Please do not post bug reports or feature requests here.

How to start a nested message loop in renderer process?

Postby Streamlet » Sat May 08, 2021 3:22 am

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 ?
Streamlet
Mentor
 
Posts: 61
Joined: Tue Aug 27, 2019 6:47 am

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

Postby magreenblatt » Sat May 08, 2021 11:55 am

magreenblatt
Site Admin
 
Posts: 10921
Joined: Fri May 29, 2009 6:57 pm

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

Postby Streamlet » Sat May 08, 2021 1:21 pm

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.
Streamlet
Mentor
 
Posts: 61
Joined: Tue Aug 27, 2019 6:47 am

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

Postby magreenblatt » Sat May 08, 2021 2:01 pm

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.
magreenblatt
Site Admin
 
Posts: 10921
Joined: Fri May 29, 2009 6:57 pm

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

Postby amaitland » Sun May 09, 2021 2:13 am

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.
Maintainer of the CefSharp project.
amaitland
Master
 
Posts: 958
Joined: Wed Jan 14, 2015 2:35 am

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

Postby Streamlet » Sun May 09, 2021 11:11 am

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();
  }
}

Streamlet
Mentor
 
Posts: 61
Joined: Tue Aug 27, 2019 6:47 am

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

Postby magreenblatt » Wed May 12, 2021 9:23 am

Legacy IPC is being removed from Chromium so I don't recommend relying on that. See https://bitbucket.org/chromiumembedded/cef/issues/3123
magreenblatt
Site Admin
 
Posts: 10921
Joined: Fri May 29, 2009 6:57 pm

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

Postby HarmlessDave » Wed May 12, 2021 11:14 am

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?
HarmlessDave
Expert
 
Posts: 315
Joined: Fri Jul 11, 2014 2:02 pm

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

Postby magreenblatt » Wed May 12, 2021 11:45 am

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.
magreenblatt
Site Admin
 
Posts: 10921
Joined: Fri May 29, 2009 6:57 pm

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

Postby Streamlet » Thu May 13, 2021 2:55 am

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?
Streamlet
Mentor
 
Posts: 61
Joined: Tue Aug 27, 2019 6:47 am

Next

Return to Support Forum

Who is online

Users browsing this forum: Google [Bot] and 13 guests