MFC使用CEF并实现js与C++交互功能,解决Render进程中OnContextCreated绑定与OnWebKitInitialized的js扩展无法回调问题

原文地址:http://blog.csdn.net/lixiang987654321/article/details/52197726研究一个东西就是一个不懈的过程,前几篇文章中都一直在研究CEF浏览器内核在MFC中的使用(当然我的习惯是将duilib应用到MFC中,既能用MF...

原文地址:http://blog.csdn.net/lixiang987654321/article/details/52197726

研究一个东西就是一个不懈的过程,前几篇文章中都一直在研究CEF浏览器内核在MFC中的使用(当然我的习惯是将duilib应用到MFC中,既能用MFC快捷创建对话框的功能、多窗口功能<这个很重要,因为duilib所有控件是显示到一个hwnd中的,假如你在其中的控件中显示视频呢?会把所有控件都渲染了,除非你定制你的渲染库,只渲染窗口的某一部分>,又可以解决MFC自绘困难、效果不佳的功能),包括了简单的CEF编译、CEF在MFC的嵌入、CEF在MFC实现多选项卡功能,这章我将带为大家做的贡献就是如何实现C++与JS的交互(关于JS以及网页相关的技术我并不默认,本来我一开始从事Javaweb相关工作有两年有余,之后才转行到C++)功能,我看过相关的技术文章,有一下几个问题:

(1)都有介绍JS与C++的交互,但是仅仅局限于英文的翻译(我看过对应的英文原文,发现很多中文的仅仅是从英文翻译过来),没有加入自己的功能或想法。

(2)哪些自以为很牛逼的人都有一个很大缺点就是从不站在“用户”的角度去看待问题,认为自己会的其他人都会,殊不知自己跟着编码下来就是死活不能按照原来的逻辑执行,我敢打包票不是我编码问题,最后的确也不是我的问题,是哪些哥们没有点名要点(只有内容的有什么用,一个要点没说明白,给你所有内容都没法跑起来)

(3)还有的就是我经常发现那些“技术牛人”,一个一个都很装逼,没有一个愿意将源码奉献出来。我们需要的不就是一个完完整整的Demon吗?不用废话,直接上源码,一目了然!可是事情往往不是你想得那样,就是一些人要么不上源码只写技术文章,要么上了源码,但最恶心的就是下载了、给分了,最后下载下来的缺少东西!

        以上是我在最近研究CEF浏览器这块亲身遇到的,分用了不少,但是没几个有质量的,最后还是查阅将近5天时间才解决了我的问题,这里我先抛出我的问题:Render进程中的OnContextCreated与OnWebKitInitialized回调在调式模式下根本不执行!

       在讲我遇到的问题前,我得带大家了解一下CEF大致的结构,其实我就是没有完全理解CEF多进程模式才导致了这个问题,所以我很认真的讲:真的很有必要!

       这张图上的描述是我对CEF的大致理解,给位如果有其他不对之处,还请纠正,这幅图还不能说明的一个问题就是CEF的多进程模式,CEF有bRowser主进程,Render进程、GPU进程,但是都是在我们运行起来主进程之后创建的子进程,这些进程我想告诉你们的是,不是我们手动去创建的,而是它自己库后台创建的,所以不要问我如果创建Render等其他子进程。截图如下所所示,默认创建3个进程(3个进程使用进程通信进行交互,或许是为了防止一个进程死掉不会影响其他进程的运行):

      问题来了,默认起来了3个进程,其中就有RenderProcess(这里我是放在一个app里面实现的,这里不推荐这么使用,我仅仅是做demon,简单这么做),和主进程browser。你可能最想问题的问题:

(1)js脚本的加载与C++交互由Render进程处理(这里很重要),那么Render进程什么时候起来的,我如何知道?

(2)如果我需要调试Render进程怎么办?

       很多人遇到以上两个问题,包括我在内,特别是在使用C++与JS交互的时候我们必须要的解决就是以上两个问题,第一个问题Render进程如何起来的?我们知道我们用vs跑起来,默认起来了3个进程?我们跑的是主进程,所有app中我们实现了BrowserProcessHandler,那么主进程应有能知道子进程的创建才是,所以我们找到BrowserProcessHandler就可以发现里面的几个接口:

[cpp] view plain copy 

  1. class CefBrowserProcessHandler : public virtual CefBase {  
  2.  public:  
  3.   ///  
  4.   // Called on the browser process UI thread immediately after the CEF context  
  5.   // has been initialized.  
  6.   ///  
  7.   /*--cef()--*/  
  8.   virtual void OnContextInitialized() {}  
  9.   
  10.   ///  
  11.   // Called before a child process is launched. Will be called on the browser  
  12.   // process UI thread when launching a render process and on the browser  
  13.   // process IO thread when launching a GPU or plugin process. Provides an  
  14.   // opportunity to modify the child process command line. Do not keep a  
  15.   // reference to |command_line| outside of this method.  
  16.   ///  
  17.   /*--cef()--*/  
  18. <span style="background-color: rgb(255, 0, 0);">  virtual void OnBeforeChildProcessLaunch(  
  19.       CefRefPtr<CefCommandLine> command_line) {}</span>  
  20.   
  21.   ///  
  22.   // Called on the browser process IO thread after the main thread has been  
  23.   // created for a new render process. Provides an opportunity to specify extra  
  24.   // information that will be passed to  
  25.   // CefRenderProcessHandler::OnRenderThreadCreated() in the render process. Do  
  26.   // not keep a reference to |extra_info| outside of this method.  
  27.   ///  
  28.   /*--cef()--*/  
  29.  <span style="background-color: rgb(255, 0, 0);"> virtual void OnRenderProcessThreadCreated(  
  30.       CefRefPtr<CefListValue> extra_info) {}</span>  
  31.   
  32.   ///  
  33.   // Return the handler for printing on Linux. If a print handler is not  
  34.   // provided then printing will not be supported on the Linux platform.  
  35.   ///  
  36.   /*--cef()--*/  
  37.   virtual CefRefPtr<CefPrintHandler> GetPrintHandler() {  
  38.     return NULL;  
  39.   }  
  40.   
  41.   ///  
  42.   // Called from any thread when work has been scheduled for the browser process  
  43.   // main (UI) thread. This callback is used in combination with CefSettings.  
  44.   // external_message_pump and CefDoMessageLoopWork() in cases where the CEF  
  45.   // message loop must be integrated into an existing application message loop  
  46.   // (see additional comments and warnings on CefDoMessageLoopWork). This  
  47.   // callback should schedule a CefDoMessageLoopWork() call to happen on the  
  48.   // main (UI) thread. |delay_ms| is the requested delay in milliseconds. If  
  49.   // |delay_ms| is <= 0 then the call should happen reasonably soon. If  
  50.   // |delay_ms| is > 0 then the call should be scheduled to happen after the  
  51.   // specified delay and any currently pending scheduled call should be  
  52.   // cancelled.  
  53.   ///  
  54.   /*--cef()--*/  
  55.   virtual void OnScheduleMessagePumpWork(int64 delay_ms) {}  
  56. };  

       红色部分就是Render进程的创建和启动,我们在调试的时候app实现接口方法,断点即可知道什么时候启动的,那么Render进程中的app实现了RenderProcessHandler,主进程启动的Render子进程,所以Render进程启动后对应的处理器也就生效了,处理器中的方法OnContextCreated与OnWebKitInitialized自然就能被调用(前提是接口方法被app实现,这里app可以与browser进程的app不是一个)。

       第二个问题,我们如何调试Render进程,vs启动的调试就是主进程,但是子进程Render由主进程创建,我们没法再主进程中调试,所以,我们在主进程中RenderProcessHandler实现的app中OnContextCreated与OnWebKitInitialized在调试状态下断点永远无效就是这个原因!那么我们如何知道OnContextCreated与OnWebKitInitialized接口方法被调用了呢?也就是如何调试Render进程?在没完全理解CEF多进程情况下,我查阅了许多资料都无果,没有人提及这个要点(百度上所有中文文章我都一一浏览过),不经意发现了一篇英文文章,某某某也遇到了类似问题:

       看了这篇文章,我的问题终于得到了解答,谢谢这哥外国哥们给我指引了正确方向,为什么OnContextCreated没有被调用?这文章说了原因并给出了步骤:

       默认样本CEF应用是多进程模式的,可以通过将代码附加到Render进程或在Debugger模式下降浏览器设置为单进程模式即可,所以我之前的初始化代码是这样的:

       

[cpp] view plain copy 

  1. BOOL CMyBrowserApp::InitInstance()  
  2. {  
  3.     INITCOMMONCONTROLSEX InitCtrls;  
  4.     InitCtrls.dwSize = sizeof(InitCtrls);  
  5.     InitCtrls.dwICC = ICC_WIN95_CLASSES;  
  6.     InitCommonControlsEx(&InitCtrls);  
  7.   
  8.     CWinAppEx::InitInstance();  
  9.     AfxEnableControlContainer();  
  10.   
  11.     // Duilib Init  
  12.     DuiLib::CPaintManagerUI::SetInstance(AfxGetInstanceHandle());  
  13.   
  14.     // CEF Init  
  15.     CefEnableHighDPISupport();  
  16.     CefSettings settings;  
  17.     settings.no_sandbox = true;  
  18.     settings.multi_threaded_message_loop = true;  
  19.   
  20.     CefRefPtr<CCefBrowserApp> objApp(new CCefBrowserApp());  
  21.     CefMainArgs mainArgs(AfxGetInstanceHandle());  
  22.     CefInitialize(mainArgs, settings, objApp.get() /*NULL*/, NULL);  
  23.   
  24.     TCHAR szFilePath[MAX_PATH];  
  25.     GetModuleFileName(NULL, szFilePath, MAX_PATH);  
  26.     _tcsrchr(szFilePath, _T('\\'))[1] = _T('\0');  
  27.     _tcscat(szFilePath, _T("index.html"));  
  28.   
  29.     m_pBrowserWnd = new CBrowserWnd(szFilePath);  
  30.     m_pBrowserWnd->Create(NULL, NULL, UI_WNDSTYLE_DIALOG, 0, 0, 0, 0, 0, NULL);  
  31.     m_pMainWnd = CWnd::FromHandle(m_pBrowserWnd->GetHWND());  
  32.       
  33.     CRect rtWindow;  
  34.     GetWindowRect(GetDesktopWindow(), &rtWindow);  
  35.     ::MoveWindow(m_pBrowserWnd->GetHWND(), rtWindow.left, rtWindow.top, rtWindow.Width(), rtWindow.Height(), TRUE);  
  36.     m_pBrowserWnd->ShowModal();  
  37.   
  38.     return FALSE;  
  39. }

       之后将cef设置更改为单进程模式:[cpp] view plain copy 

  1. BOOL CMyBrowserApp::InitInstance()  
  2. {  
  3.     INITCOMMONCONTROLSEX InitCtrls;  
  4.     InitCtrls.dwSize = sizeof(InitCtrls);  
  5.     InitCtrls.dwICC = ICC_WIN95_CLASSES;  
  6.     InitCommonControlsEx(&InitCtrls);  
  7.   
  8.     CWinAppEx::InitInstance();  
  9.     AfxEnableControlContainer();  
  10.   
  11.     // Duilib Init  
  12.     DuiLib::CPaintManagerUI::SetInstance(AfxGetInstanceHandle());  
  13.   
  14.     // CEF Init  
  15.     CefEnableHighDPISupport();  
  16.     CefSettings settings;  
  17.     settings.no_sandbox = true;  
  18.     settings.multi_threaded_message_loop = true;  
  19. </span><span style="background-color: rgb(255, 102, 102);">#ifdef _DEBUG  
  20.     settings.single_process = true;  
  21. #endif</span><span style="background-color: rgb(255, 255, 255);">  
  22.   
  23.     CefRefPtr<CCefBrowserApp> objApp(new CCefBrowserApp());  
  24.     CefMainArgs mainArgs(AfxGetInstanceHandle());  
  25.     CefInitialize(mainArgs, settings, objApp.get() /*NULL*/, NULL);  
  26.   
  27.     TCHAR szFilePath[MAX_PATH];  
  28.     GetModuleFileName(NULL, szFilePath, MAX_PATH);  
  29.     _tcsrchr(szFilePath, _T('\\'))[1] = _T('\0');  
  30.     _tcscat(szFilePath, _T("index.html"));  
  31.   
  32.     m_pBrowserWnd = new CBrowserWnd(szFilePath);  
  33.     m_pBrowserWnd->Create(NULL, NULL, UI_WNDSTYLE_DIALOG, 0, 0, 0, 0, 0, NULL);  
  34.     m_pMainWnd = CWnd::FromHandle(m_pBrowserWnd->GetHWND());  
  35.       
  36.     CRect rtWindow;  
  37.     GetWindowRect(GetDesktopWindow(), &rtWindow);  
  38.     ::MoveWindow(m_pBrowserWnd->GetHWND(), rtWindow.left, rtWindow.top, rtWindow.Width(), rtWindow.Height(), TRUE);  
  39.     m_pBrowserWnd->ShowModal();  
  40.   
  41.     return FALSE;  
  42. }  

果不其然,浏览器进程由3个变为1个进程:


这时候,就是说Render和Process都在一个进程中实现,没有使用多进程模式,所以,我们实现RenderProcess接口后对应的接口OnContextCreated与OnWebKitInitialized调用就生效了!断点试试!

       另外关于OnContextCreated与OnWebKitInitialized这两个接口,我先说说作用,具体作用看CEF接口中英文注释,非常明了;其中OnContextCreated是js上下文创建后被调用的,每一个Frame都有一个Context都会创建,包括每一次Frame加载都会调用OnContextCreated,在这里我们可以对应的Frame的Context,将js函数绑定banding到context对象中(注意既然是每一个context都可以绑定,说明被绑定的变量或函数智能在改farame中生效,而不是全局的,也即是不能跨frame调用);OnWebKitInitialized这个接口是Webkit初始化后调用的(都知道chrome底层就是webkit实现,它这不是是封装丰富了功能而已),作用是给你注册js扩展的机会,这里注册的js扩展是全局的、跨frame的!下面我们来看看js与c++如何实现互相调用:

(1)C++中调用js脚本

        这个很简单,每一个Frame中都有一个Context对象,Context使用cefv8JavaScriptEngine解析调用js,其他在Frame里面就有一个接口:

 

[cpp] view plain copy 

  1.   ///  
  2.   // Execute a string of JavaScript code in this frame. The |script_url|  
  3.   // parameter is the URL where the script in question can be found, if any.  
  4.   // The renderer may request this URL to show the developer the source of the  
  5.   // error.  The |start_line| parameter is the base line number to use for error  
  6.   // reporting.  
  7.   ///  
  8.   /*--cef(optional_param=script_url)--*/  
  9.   virtual void ExecuteJavaScript(const CefString& code,  
  10.                                  const CefString& script_url,  
  11.                                  int start_line) =0;

       这个接口就是执行javascript脚本,在我的demon中提供的index.html中提供了一个js函数:

 

[javascript] view plain copy 

  1. <script type="text/javascript" >  
  2.     function showAlert()  
  3.     {  
  4.         alert("this is test string from js");  
  5.     }  
  6. </script>  

       我们在c++里面就可以执行对应脚本:browser->getMainFrame->ExecuteJavaScript(_T("showAlert();"))就可以指定该函数了,执行后会弹出提示this is test string from js.

(2)js脚本调用c++代码

        这里有两种,一种是单个frame的js脚本,假如我们加载了Index.html网页,我们需要调用c++的代码,按照第一种方式,我们将js函数bind到window中:

[cpp] view plain copy 

  1. void CCefBrowserApp::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,CefRefPtr<CefV8Context> context) OVERRIDE  
  2. {  
  3.     // The var type can accept all object or variable  
  4.     CefRefPtr<CefV8Value> window = context->GetGlobal();  
  5.   
  6.     // bind value into window[or you can bind value into window sub node]  
  7.     CefRefPtr<CefV8Value> strValue = CefV8Value::CreateString(_T("say yes"));  
  8.     window->SetValue(_T("say_yes"), strValue, V8_PROPERTY_ATTRIBUTE_NONE);  
  9.   
  10.     // bind function   
  11.     CefRefPtr<CV8JsHandler> pJsHandler(new CV8JsHandler());  
  12.     CefRefPtr<CefV8Value> myFunc = CefV8Value::CreateFunction(_T("addFunction"), pJsHandler);  
  13.     window->SetValue(_T("addFunction"), myFunc, V8_PROPERTY_ATTRIBUTE_NONE);  
  14.   
  15.     CefRefPtr<CV8JsHandler> pJsHandler2(new CV8JsHandler());  
  16.     CefRefPtr<CefV8Value> myFunc2 = CefV8Value::CreateFunction(_T("hehe"), pJsHandler2);  
  17.     window->SetValue(_T("hehe"), myFunc2, V8_PROPERTY_ATTRIBUTE_NONE);  
  18. }  

         通过context-GetGlobal获取到window对象(js中所有对象或值都用var声明,这里的cefv8value一个道理),然后做了三件事:

(1)将一个say_yes值bind到window下(可以在window下建立多个对象,将值bind到其他值下就像dom一样)脚本中且值为"say yes"

(2)在window下增加了一个函数addFunction并bind到window下(这里函数参数不需要声明,调用的时候传递参数个数与后续给的cefv8handler

[cpp] view plain copy 

  1. bool CV8JsHandler::Execute(const CefString& func_name,  
  2.                              CefRefPtr<CefV8Value> object,  
  3.                              const CefV8ValueList& arguments,  
  4.                              CefRefPtr<CefV8Value>& retval,  
  5.                              CefString& exception)  
  6. {  
  7.     if (func_name == _T("addFunction"))  
  8.     {  
  9.         int32 nSum = 0;  
  10.         for (size_t i = 0; i < arguments.size(); ++i)  
  11.         {  
  12.             if(!arguments[i]->IsInt())  
  13.                 return false;  
  14.             nSum += arguments[i]->GetIntValue();  
  15.         }  
  16.         retval = CefV8Value::CreateInt(nSum);  
  17.         return true;  
  18.     }  
  19.     else if (func_name == _T("hehe"))  
  20.     {  
  21.         retval = CefV8Value::CreateString(_T("hehe hehe!"));  
  22.         return true;  
  23.     }  
  24.       
  25.     else  
  26.     {  
  27.         return false;  
  28.     }  

处理器需要的参数个数和类型一致就可以),并且该函数给了对应的v8js处理器

(3)在window下增加了一个函数addFunction并bind到window下且给出函数对应的v8脚本处理器。

      首先看我的html代码:

[html] view plain copy 

  1. <!DOCTYPE HTML>  
  2. <html>  
  3.     <head>  
  4.         <meta charset="utf-8" />  
  5.         <script type="text/javascript" >  
  6.             function showValue()  
  7.             {  
  8.                 alert(window.say_yes);// c++提供的值(bind到浏览器window下)  
  9.             }  
  10.             function showAlert()  
  11.             {  
  12.                 alert("this is test string from js");  
  13.             }  
  14.             function sayHellow()  
  15.             {  
  16.                 alert(g_value);  
  17.             }  
  18.             function add()  
  19.             {  
  20.                 // add 函数名不能与window对象挂接addFunction相同  
  21.                 var result = window.addFunction(10, 20);// C++提供的接口,bind到window对象上  
  22.                 alert("10 + 20 = " + result);  
  23.             }  
  24.             function jsExt()  
  25.             {  
  26.                 alert(test.myfunc());  
  27.             }  
  28.         </script>  
  29.     </head>  
  30.     <body style="width:100%;height:100%;background-color:green;">  
  31.         <p>这是c++与JS交互测试脚本</p>  
  32.         <div >  
  33.             <button onclick="showValue();">显示CEF中与窗口bind的test值</button>  
  34.             <button onclick="sayHellow();">说Hellow</button>  
  35.             <button onclick="add();">两个数相加</button>  
  36.             <button onclick="jsExt();">JS扩展</button>  
  37.         </div>  
  38.     </body>  
  39. </html>

      第一,window对象下注册了一个js全局值,我们很好理解,我们在js脚本中调用window.say_yes即可获取到对应值“say yes”。

      第二,我们在js脚本中调用注册到window对象下的addFunction并传递任何参数(这里任何必须是你jshandler支持的任何),这里我调用的是:window.addFunction(10, 20),对应的cefv8jshandler中我的处理逻辑:     

[cpp] view plain copy 

  1. bool CV8JsHandler::Execute(const CefString& func_name,  
  2.                              CefRefPtr<CefV8Value> object,  
  3.                              const CefV8ValueList& arguments,  
  4.                              CefRefPtr<CefV8Value>& retval,  
  5.                              CefString& exception)  
  6. {  
  7.     if (func_name == _T("addFunction"))  
  8.     {  
  9.         int32 nSum = 0;  
  10.         for (size_t i = 0; i < arguments.size(); ++i)  
  11.         {  
  12.             if(!arguments[i]->IsInt())  
  13.                 return false;  
  14.             nSum += arguments[i]->GetIntValue();  
  15.         }  
  16.         retval = CefV8Value::CreateInt(nSum);  
  17.         return true;  
  18.     }  
  19.     else if (func_name == _T("hehe"))  
  20.     {  
  21.         retval = CefV8Value::CreateString(_T("hehe hehe!"));  
  22.         return true;  
  23.     }  
  24.       
  25.     else  
  26.     {  
  27.         return false;  
  28.     } 

       如果函数名为addFunction,我从参数列表CefV8ValueList中逐个去除所有值,如果值类型不是int就认为错误,否则做一个加法,最有将int转为CefV8Value值返回!测试结果:

          以上方式注册变量和代码仅能用于某个frame中,在js扩展中我们实现js注册如下:

[cpp] view plain copy 

  1. void CCefBrowserApp::OnWebKitInitialized()  
  2. {  
  3.     std::string extensionCode =  
  4.         "var g_value=\"global value here\";"  
  5.         "var test;"  
  6.         "if (!test)"  
  7.         "  test = {};"  
  8.         "(function() {"  
  9.         "  test.myfunc = function() {"  
  10.         "    native function hehe(int,int);"  
  11.         "    return hehe(10, 50);"  
  12.         "  };"  
  13.         "})();";  
  14.   
  15.     // 声明本地函数 native function hehe();" 如果有参数列表需要写具体的类型,而不能写var类型!与本地声明一直  
  16.     // 调用本地函数    return hehe();"  
  17.   
  18.     // Create an instance of my CefV8Handler object.  
  19.     CefRefPtr<CefV8Handler> handler = new CV8JsHandler();  
  20.   
  21.     // Register the extension.  
  22.     CefRegisterExtension("v8/mycode", extensionCode, handler);    
  23. }  

       我们注册了一个g_value值和一个test对象,该对象有个方法test.myfunc,对象方法调用了向js注册的本地方法hehe,本地方法有两个参数,参数类型为int,声明该本地方法后,直接调用本地方法heh传递10和50(本地方法我们在frame中注册的),运行效果如下:显示g_value值和调用test.myfunc对象的myfunc方法。

       上面的例子将的js与c++调用实例,更深层次的要了解这块东西,请看英文连接:

        https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md

        这里面有需要注意的地方:

(1)c++执行js脚本

 ExecuteJavaScript

     The simplest way to execute JS from a client application is using the CefFrame::ExecuteJavaScript() function. This function is available in both the browser process and the renderer process and can safely be used from outside of a JS context.

CefRefPtr<CefBrowser> browser = ...;
CefRefPtr<CefFrame> frame = browser->GetMainFrame();
frame->ExecuteJavaScript("alert('ExecuteJavaScript works!');",
    frame->GetURL(), 0);

        这段话告诉我们frame中的接口ExecuteJavaScript方法在browser进程和render都可以调用,而且在一个context外面都是安全的。(这里的inside和outside就是一个context设计,进入一个context用enter退出用exit)

    The ExecuteJavaScript() function can be used to interact with functions and variables in the frame's JS context. In order to return values from JS to the client application consider using Window Binding or Extensions

    还有就是,虽然ExecuteJavaScript方法可以和context中的函数或变量产生交互,但是没有返回值,需要需要返回值,必须使用window bind方式或者js扩展方式

(2) 关于窗口bind方式注意

Window Binding

     Window binding allows the client application to attach values to a frame's window object. Window bindings are implemented using the CefRenderProcessHandler::OnContextCreated() method.

void MyRenderProcessHandler::OnContextCreated(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefV8Context> context) {
  // Retrieve the context's window object.
  CefRefPtr<CefV8Value> object = context->GetGlobal();

  // Create a new V8 string value. See the "Basic JS Types" section below.
  CefRefPtr<CefV8Value> str = CefV8Value::CreateString("My Value!");

  // Add the string to the window object as "window.myval". See the "JS Objects" section below.
  object->SetValue("myval", str, V8_PROPERTY_ATTRIBUTE_NONE);
}

JavaScript in the frame can then interact with the window bindings.

<script language="JavaScript">
alert(window.myval); // Shows an alert box with "My Value!"
</script>

    Window bindings are reloaded each time a frame is reloaded giving the client application an opportunity to change the bindings if necessary. For example, different frames may be given access to different features in the client application by modifying the values that are bound to the window object for that frame.

          窗口绑定允许客户应用程序附加值到框架的window对象中,绑定过程通过CefRenderprocessHandler处理器的OnContextCreated方法实现(也就是bind过程在此处实现即可);窗口绑定在一个frame框架被客户应用程序重新载入的时候都被重新载入调用,这样的好处就是,不同的窗口可以通过修改bind到window对象中的值从而具有不同的特性。

(3)js扩展

Extensions

    Extensions are like window bindings except they are loaded into the context for every frame and cannot be modified once loaded. The DOM does not exist when an extension is loaded and attempts to access the DOM during extension loading will result in a crash. Extensions are registered using the CefRegisterExtension() function which should be called from the CefRenderProcessHandler::OnWebKitInitialized() method.

void MyRenderProcessHandler::OnWebKitInitialized() {
  // Define the extension contents.
  std::string extensionCode =
    "var test;"
    "if (!test)"
    "  test = {};"
    "(function() {"
    "  test.myval = 'My Value!';"
    "})();";

  // Register the extension.
  CefRegisterExtension("v8/test", extensionCode, NULL);
}

    The string represented by extensionCode can be any valid JS code. JS in the frame can then interact with the extension code.

<script language="JavaScript">
alert(test.myval); // Shows an alert box with "My Value!"
</script>

      js扩展就像window bind,只不过js扩展被载入到每一个frame的context中,而且一旦被载入后就不能再修改;另外就是当js扩展载入的时候 DOM还并不存在,所以在js扩展被载入过程中不要访问DOM,否则会导致崩溃!js扩展是通过在CefRenderProcessHandler::OnWebKitInitialized()进程中使用CefRegisterExtension函数进行注册。

(4)Frame的Context上下文工作

 

Working with Contexts

     Each frame in a browser window has its own V8 context. The context defines the scope for all variables, objects and functions defined in that frame. V8 will be inside a context if the current code location has a CefV8Handler, CefV8Accessor or OnContextCreated()/OnContextReleased() callback higher in the call stack.

    The OnContextCreated() and OnContextReleased() methods define the complete life span for the V8 context associated with a frame. You should be careful to follow the below rules when using these methods:

  1. Do not hold onto or use a V8 context reference past the call to OnContextReleased() for that context.

  2. The lifespan of all V8 objects is unspecified (up to the GC). Be careful when maintaining references directly from V8 objects to your own internal implementation objects. In many cases it may be better to use a proxy object that your application associates with the V8 context and which can be "disconnected" (allowing your internal implementation object to be freed) when OnContextReleased() is called for the context.

     If V8 is not currently inside a context, or if you need to retrieve and store a reference to a context, you can use one of two available CefV8Context static methods. GetCurrentContext() returns the context for the frame that is currently executing JS. GetEnteredContext() returns the context for the frame where JS execution began. For example, if a function in frame1 calls a function in frame2 then the current context will be frame2 and the entered context will be frame1.

    Arrays, objects and functions may only be created, modified and, in the case of functions, executed, if V8 is inside a context. If V8 is not inside a context then the application needs to enter a context by calling Enter() and exit the context by calling Exit(). The Enter() and Exit() methods should only be used:

  1. When creating a V8 object, function or array outside of an existing context. For example, when creating a JS object in response to a native menu callback.

  2. When creating a V8 object, function or array in a context other than the current context. For example, if a call originating from frame1 needs to modify the context of frame2.

      这段话非常重要!浏览器中的每一个Frame都定义了一个v8 Context,context定义了所有变量、对象、数组或函数等的作用域。如果你的当前本地代码拥有cefv8handler、CefV8Accessor 或者调用堆栈顶层的OnContextCreated()/OnContextReleased()回调,那么v8就在你的上下文中;OnContextCreated() and OnContextReleased()定义了一个与frame相关联的v8上下文的完整的声明周期,在使用这些方法的时候你必须要注意一下几点:

a. 不要持有或使用一个v8上下文传递给OnContextReleased()调用

b. 所有的v8上下文对象的声明周期是不确定的,所以,当你直接从v8对象中保存一个引用到你的内部实现中的时

   候,千万要小心。 最好使用与你应用程序相关联的一个代理对象的v8上下文。

    如果v8不在一个当前的上下文当中或者说你想提取或存储一个上下文引用。你可以使用CefV8Context的静态方法,GetCurrentContext()返回一个当前正在实行js脚本的frame的上下文,GetEnteredContext()返回js执行开始的frame的context。例如一个frame1中的函数调用frame2中的函数,那么当前的context就是frame2,开始的上下文就是frame1。如果v8在一个上下文当中,数组、对象、函数才可能就会被创建、修改或执行;否则应用程序需要调用Enter进入v8或调用exit退出v8;如下情况进入和退出上下文必须要使用:

  i. 当在context外部创建数组、对象或函数的时候,例如在响应本地菜单回调函数中创建一个js对象的时候。

  ii.不是在当前上下文中去创建修改数组、对象或函数的时候,例如一个来自frame1中的调用需要修改frame2中的

     上下文的时候。

      到这里,关于c++调用js代码以及js调用c++代码如何实现已经讲解完了,时间有限可能讲的不太清楚,我会奉献我的代码1(忙了几天只要3分,能解决你的问题的就是好东西,即使我要10分你也得下,是这个道理吧?所以,请谅解!):http://download.csdn.net/detail/lixiang987654321/9602430

       效果如下:

      

       很多网友说我在csdn上提供的代码没什么注释,我想说的是:不是没有什么注释,是一般我不会注释,我觉得简单干净,对我来说注释就是对于新手而言的(这也可能确实重要)。如果你们发现我的Demon里面确实没有注释,实在不好意思,这是本人习惯,不好改,如果有疑问请邮件lixiang6153@126.com,本人尽可能回复您的问题!在这里我还想说一句,没有注释可能在另一方面讲会让你对这些代码更深刻(你会为了理解他不断思考查阅)!

 

本文标题为:MFC使用CEF并实现js与C++交互功能,解决Render进程中OnContextCreated绑定与OnWebKitInitialized的js扩展无法回调问题

基础教程推荐