【问题标题】:How to invoke CefV8Context::Eval() method in browser process?如何在浏览器进程中调用 CefV8Context::Eval() 方法?
【发布时间】:2014-05-05 07:13:24
【问题描述】:

我想调用 CefV8Context::Eval 函数并在浏览器进程的 UI 线程中获取返回值。但是 CEF3 C++ API Docs 声明 V8 句柄只能从创建它们的线程访问。创建 V8 句柄的有效线程包括渲染进程主线程 (TID_RENDERER) 和 WebWorker 线程。这是否意味着我应该使用进程间通信(CefProcessMessage)来调用该方法并获取返回值?如果是这样,如何在同步模式下做到这一点?

【问题讨论】:

  • 我不确定为什么这个问题被否决了。它显示了对问题的清晰理解和一些研究 - 我已经编辑了问题以尝试并使这一点更加明显。因此,我赞成并很高兴听到反对者的想法?

标签: c++ chromium chromium-embedded


【解决方案1】:

简短回答:CefFrame::ExecuteJavaScript 用于简单的请求。对于更复杂的,您必须放弃一级同步或使用自定义消息循环。


我了解您想要做的是执行一些 Javascript 代码作为您本机应用程序 UI 线程的一部分。有两种可能:

  1. 它是通用的 JS 代码,不会真正访问 JS 中的任何变量或函数,因此没有 上下文。这意味着 Cef 可以启动一个新的 V8 上下文并执行您的代码 - 请参阅 CefFrame::ExecuteJavaScript()。引用CEF's JS Integration link上的例子:

    CefRefPtr 浏览器 = ...; CefRefPtr frame = browser->GetMainFrame(); frame->ExecuteJavaScript("alert('ExecuteJavaScript 有效!');", 框架->GetURL(), 0);

  2. 这是 JS 代码带有上下文。在这种情况下,请继续阅读。

- CEF 的设计使得只有 RenderProcess 可以访问 V8 引擎,您必须使用 CefProcessMessage 前往浏览器并在那里进行评估。你听起来好像你已经知道该怎么做。我会为其他不知道并且以后可能会偶然发现的人链接我的答案:Background process on the native function at Chromium Embedded Framework

从 Browser 到 Render 进程的 CEFProcessMessage 是请求必须同步的地方。

因此,在您将逻辑发送到渲染进程之后,您需要实际执行 javascript 代码。幸运的是,这很容易——同样的 JS 集成链接继续说:

Native 代码可以使用 ExecuteFunction() 来执行 JS 函数 和 ExecuteFunctionWithContext() 方法

最好的部分 - 执行似乎是同步的(我说似乎是,因为我找不到具体的文档)。示例中的用法说明了这一点:

if (callback_func_->ExecuteFunctionWithContext(callback_context_, NULL, args, retval, exception, false)) {
  if (exception.get()) {
    // Execution threw an exception.
  } else {
    // Execution succeeded.
  }
}

您会注意到,第二行假定第一行已完成执行,并且所述执行的结果对其可用。因此,CefV8Value::ExecuteFunction() 调用本质上是同步的。

所以问题归结为 - 如何将 CefProcessMessage 从 Browser 同步发布到 Renderer 进程?。不幸的是,类本身并没有设置为这样做。更重要的是,IPC Wiki Page 明确禁止它:

从渲染器的角度来看,某些消息应该是同步的。 这主要发生在应该有一个 WebKit 调用给我们时 返回一些东西,但是我们必须在浏览器中做。示例 这种类型的消息是拼写检查和获取 cookie JavaScript。不允许同步浏览器到渲染器 IPC 防止在可能不稳定的渲染器上阻塞用户界面。

这有什么大不了的吗?好吧,我真的不知道,因为我没有遇到过这种需求 - 对我来说,没关系,因为浏览器的消息循环会一直旋转等待某事做,并且在您的渲染器发送带有JS的结果。浏览器需要做其他事情的唯一方法是发生某些交互时,因为渲染器阻塞,所以不能。

如果您真的肯定需要同步,我建议您使用自定义 MessageLoop,它在每次迭代时调用 CefDoMessageLoopWork()。这样,您可以设置一个标志来暂停循环工作,直到从渲染器收到您的消息。请注意,CefDoMessageLoopWork()CefRunMessageLoop()互斥的,不能相互协作 - 你要么自己管理循环,要么让 CEF 为你做。

这很长,涵盖了您可能想做的大部分事情 - 希望对您有所帮助!

【讨论】:

  • 非常感谢您的回答。我可以通过将 js 代码发送到渲染进程并执行它然后将结果发送回浏览器进程来异步执行此操作。在 CefClient::OnProcessMessageReceived() 回调中,我可以获得结果,但是在下一个(或下一个下一个,没关系)消息循环迭代中调用此回调,这不适合我的程序。我想要的是发送js代码并获取结果可以包装在一个c++函数中。
  • @Kery 请在CefDoMessageLoopWork() 上查看我的回答的最后一部分。这对你有帮助吗?顺便说一句,我确实同意文档,如果您需要这种同步性,您要么足够深以保证自定义消息循环,要么需要重新查看代码结构......
  • 谢谢!运行另一个消息循环有效!首先,我定义了一个标志变量,在将消息发送到渲染进程之前重置此标志,并在收到包含 js 执行返回值的消息时设置标志。在 c++ 方法中(向渲染进程发送消息)使用 while 循环检查该标志,调用 CefDoMessageLoopWork() 直到设置该标志。
  • 但这又引入了另一个小问题,似乎 CefDoMessageLoopWork 不能与 CefRunMessageLoop 一起使用,所以我必须在主循环中使用 CefDoMessageLoopWork 而不是 CefRunMessageLoop。无论如何,它有效!再次感谢!
  • 对不起!我昨天在家里无法登录我的帐户。我的最终解决方案是将 CefRunMessageLoop 调用保留在主循环中,因为我不想关心调用 CefDoMessageLoopWork 的频率。此外,在将消息发送到渲染进程之后,运行更深层次的消息循环(仍然使用 CefRunMessageLoop)。收到返回的消息后,调用 CefQuitMessageLoop 以便退出更深层次的消息循环。
猜你喜欢
  • 2014-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多