天天看點

CEF3實作js與C++互動功能, Render程序中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts

        研究一個東西就是一個不懈的過程,前幾篇文章中都一直在研究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多程序模式才導緻了這個問題,是以我很認真的講:真的很有必要!

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts

       這張圖上的描述是我對CEF的大緻了解,給位如果有其他不對之處,還請糾正,這幅圖還不能說明的一個問題就是CEF的多程序模式,CEF有bRowser主程序,Render程序、GPU程序,但是都是在我們運作起來主程序之後建立的子程序,這些程序我想告訴你們的是,不是我們手動去建立的,而是它自己庫背景建立的,是以不要問我如果建立Render等其他子程序。截圖如下所所示,預設建立3個程序(3個程序使用程序通信進行互動,或許是為了防止一個程序死掉不會影響其他程序的運作):

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts

      問題來了,預設起來了3個程序,其中就有RenderProcess(這裡我是放在一個app裡面實作的,這裡不推薦這麼使用,我僅僅是做demon,簡單這麼做),和主程序browser。你可能最想問題的問題:

(1)js腳本的加載與C++互動由Render程序處理(這裡很重要),那麼Render程序什麼時候起來的,我如何知道?

(2)如果我需要調試Render程序怎麼辦?

       很多人遇到以上兩個問題,包括我在内,特别是在使用C++與JS互動的時候我們必須要的解決就是以上兩個問題,第一個問題Render程序如何起來的?我們知道我們用vs跑起來,預設起來了3個程序?我們跑的是主程序,所有app中我們實作了BrowserProcessHandler,那麼主程序應有能知道子程序的建立才是,是以我們找到BrowserProcessHandler就可以發現裡面的幾個接口:

[cpp] view plain copy 

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
  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.   virtual void OnContextInitialized() {}  
  8.   ///  
  9.   // Called before a child process is launched. Will be called on the browser  
  10.   // process UI thread when launching a render process and on the browser  
  11.   // process IO thread when launching a GPU or plugin process. Provides an  
  12.   // opportunity to modify the child process command line. Do not keep a  
  13.   // reference to |command_line| outside of this method.  
  14.   ///  
  15. <span style="background-color: rgb(255, 0, 0);">  virtual void OnBeforeChildProcessLaunch(  
  16.       CefRefPtr<CefCommandLine> command_line) {}</span>  
  17.   ///  
  18.   // Called on the browser process IO thread after the main thread has been  
  19.   // created for a new render process. Provides an opportunity to specify extra  
  20.   // information that will be passed to  
  21.   // CefRenderProcessHandler::OnRenderThreadCreated() in the render process. Do  
  22.   // not keep a reference to |extra_info| outside of this method.  
  23.   ///  
  24.  <span style="background-color: rgb(255, 0, 0);"> virtual void OnRenderProcessThreadCreated(  
  25.       CefRefPtr<CefListValue> extra_info) {}</span>  
  26.   ///  
  27.   // Return the handler for printing on Linux. If a print handler is not  
  28.   // provided then printing will not be supported on the Linux platform.  
  29.   ///  
  30.   virtual CefRefPtr<CefPrintHandler> GetPrintHandler() {  
  31.     return NULL;  
  32.   }  
  33.   ///  
  34.   // Called from any thread when work has been scheduled for the browser process  
  35.   // main (UI) thread. This callback is used in combination with CefSettings.  
  36.   // external_message_pump and CefDoMessageLoopWork() in cases where the CEF  
  37.   // message loop must be integrated into an existing application message loop  
  38.   // (see additional comments and warnings on CefDoMessageLoopWork). This  
  39.   // callback should schedule a CefDoMessageLoopWork() call to happen on the  
  40.   // main (UI) thread. |delay_ms| is the requested delay in milliseconds. If  
  41.   // |delay_ms| is <= 0 then the call should happen reasonably soon. If  
  42.   // |delay_ms| is > 0 then the call should be scheduled to happen after the  
  43.   // specified delay and any currently pending scheduled call should be  
  44.   // cancelled.  
  45.   ///  
  46.   virtual void OnScheduleMessagePumpWork(int64 delay_ms) {}  
  47. };  

       紅色部分就是Render程序的建立和啟動,我們在調試的時候app實作接口方法,斷點即可知道什麼時候啟動的,那麼Render程序中的app實作了RenderProcessHandler,主程序啟動的Render子程序,是以Render程序啟動後對應的處理器也就生效了,處理器中的方法OnContextCreated與OnWebKitInitialized自然就能被調用(前提是接口方法被app實作,這裡app可以與browser程序的app不是一個)。

       第二個問題,我們如何調試Render程序,vs啟動的調試就是主程序,但是子程序Render由主程序建立,我們沒法再主程序中調試,是以,我們在主程序中RenderProcessHandler實作的app中OnContextCreated與OnWebKitInitialized在調試狀态下斷點永遠無效就是這個原因!那麼我們如何知道OnContextCreated與OnWebKitInitialized接口方法被調用了呢?也就是如何調試Render程序?在沒完全了解CEF多程序情況下,我查閱了許多資料都無果,沒有人提及這個要點(百度上所有中文文章我都一一浏覽過),不經意發現了一篇英文文章,某某某也遇到了類似問題:

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts

       看了這篇文章,我的問題終于得到了解答,謝謝這哥外國哥們給我指引了正确方向,為什麼OnContextCreated沒有被調用?這文章說了原因并給出了步驟:

       預設樣本CEF應用是多程序模式的,可以通過将代碼附加到Render程序或在Debugger模式下降浏覽器設定為單程序模式即可,是以我之前的初始化代碼是這樣的:

[cpp] view plain copy 

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
  1. BOOL CMyBrowserApp::InitInstance()  
  2. {  
  3.     INITCOMMONCONTROLSEX InitCtrls;  
  4.     InitCtrls.dwSize = sizeof(InitCtrls);  
  5.     InitCtrls.dwICC = ICC_WIN95_CLASSES;  
  6.     InitCommonControlsEx(&InitCtrls);  
  7.     CWinAppEx::InitInstance();  
  8.     AfxEnableControlContainer();  
  9.     // Duilib Init  
  10.     DuiLib::CPaintManagerUI::SetInstance(AfxGetInstanceHandle());  
  11.     // CEF Init  
  12.     CefEnableHighDPISupport();  
  13.     CefSettings settings;  
  14.     settings.no_sandbox = true;  
  15.     settings.multi_threaded_message_loop = true;  
  16.     CefRefPtr<CCefBrowserApp> objApp(new CCefBrowserApp());  
  17.     CefMainArgs mainArgs(AfxGetInstanceHandle());  
  18.     CefInitialize(mainArgs, settings, objApp.get() , NULL);  
  19.     TCHAR szFilePath[MAX_PATH];  
  20.     GetModuleFileName(NULL, szFilePath, MAX_PATH);  
  21.     _tcsrchr(szFilePath, _T('\\'))[1] = _T('\0');  
  22.     _tcscat(szFilePath, _T("index.html"));  
  23.     m_pBrowserWnd = new CBrowserWnd(szFilePath);  
  24.     m_pBrowserWnd->Create(NULL, NULL, UI_WNDSTYLE_DIALOG, 0, 0, 0, 0, 0, NULL);  
  25.     m_pMainWnd = CWnd::FromHandle(m_pBrowserWnd->GetHWND());  
  26.     CRect rtWindow;  
  27.     GetWindowRect(GetDesktopWindow(), &rtWindow);  
  28.     ::MoveWindow(m_pBrowserWnd->GetHWND(), rtWindow.left, rtWindow.top, rtWindow.Width(), rtWindow.Height(), TRUE);  
  29.     m_pBrowserWnd->ShowModal();  
  30.     return FALSE;  
  31. }

       之後将cef設定更改為單程序模式:

[cpp] view plain copy 

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
  1. BOOL CMyBrowserApp::InitInstance()  
  2. {  
  3.     INITCOMMONCONTROLSEX InitCtrls;  
  4.     InitCtrls.dwSize = sizeof(InitCtrls);  
  5.     InitCtrls.dwICC = ICC_WIN95_CLASSES;  
  6.     InitCommonControlsEx(&InitCtrls);  
  7.     CWinAppEx::InitInstance();  
  8.     AfxEnableControlContainer();  
  9.     // Duilib Init  
  10.     DuiLib::CPaintManagerUI::SetInstance(AfxGetInstanceHandle());  
  11.     // CEF Init  
  12.     CefEnableHighDPISupport();  
  13.     CefSettings settings;  
  14.     settings.no_sandbox = true;  
  15.     settings.multi_threaded_message_loop = true;  
  16. </span><span style="background-color: rgb(255, 102, 102);">#ifdef _DEBUG  
  17.     settings.single_process = true;  
  18. #endif</span><span style="background-color: rgb(255, 255, 255);">  
  19.     CefRefPtr<CCefBrowserApp> objApp(new CCefBrowserApp());  
  20.     CefMainArgs mainArgs(AfxGetInstanceHandle());  
  21.     CefInitialize(mainArgs, settings, objApp.get() , NULL);  
  22.     TCHAR szFilePath[MAX_PATH];  
  23.     GetModuleFileName(NULL, szFilePath, MAX_PATH);  
  24.     _tcsrchr(szFilePath, _T('\\'))[1] = _T('\0');  
  25.     _tcscat(szFilePath, _T("index.html"));  
  26.     m_pBrowserWnd = new CBrowserWnd(szFilePath);  
  27.     m_pBrowserWnd->Create(NULL, NULL, UI_WNDSTYLE_DIALOG, 0, 0, 0, 0, 0, NULL);  
  28.     m_pMainWnd = CWnd::FromHandle(m_pBrowserWnd->GetHWND());  
  29.     CRect rtWindow;  
  30.     GetWindowRect(GetDesktopWindow(), &rtWindow);  
  31.     ::MoveWindow(m_pBrowserWnd->GetHWND(), rtWindow.left, rtWindow.top, rtWindow.Width(), rtWindow.Height(), TRUE);  
  32.     m_pBrowserWnd->ShowModal();  
  33.     return FALSE;  
  34. }  

果不其然,浏覽器程序由3個變為1個程序:

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts

這時候,就是說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 

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
  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.   virtual void ExecuteJavaScript(const CefString& code,  
  9.                                  const CefString& script_url,  
  10.                                  int start_line) =0;

       這個接口就是執行javascript腳本,在我的demon中提供的index.html中提供了一個js函數:

[javascript] view plain copy 

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
  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 

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
  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.     // bind value into window[or you can bind value into window sub node]  
  6.     CefRefPtr<CefV8Value> strValue = CefV8Value::CreateString(_T("say yes"));  
  7.     window->SetValue(_T("say_yes"), strValue, V8_PROPERTY_ATTRIBUTE_NONE);  
  8.     // bind function   
  9.     CefRefPtr<CV8JsHandler> pJsHandler(new CV8JsHandler());  
  10.     CefRefPtr<CefV8Value> myFunc = CefV8Value::CreateFunction(_T("addFunction"), pJsHandler);  
  11.     window->SetValue(_T("addFunction"), myFunc, V8_PROPERTY_ATTRIBUTE_NONE);  
  12.     CefRefPtr<CV8JsHandler> pJsHandler2(new CV8JsHandler());  
  13.     CefRefPtr<CefV8Value> myFunc2 = CefV8Value::CreateFunction(_T("hehe"), pJsHandler2);  
  14.     window->SetValue(_T("hehe"), myFunc2, V8_PROPERTY_ATTRIBUTE_NONE);  
  15. }  

         通過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 

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
  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.     else  
  25.     {  
  26.         return false;  
  27.     }  

處理器需要的參數個數和類型一緻就可以),并且該函數給了對應的v8js處理器

(3)在window下增加了一個函數addFunction并bind到window下且給出函數對應的v8腳本處理器。

      首先看我的html代碼:

[html] view plain copy 

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
  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”。

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts

      第二,我們在js腳本中調用注冊到window對象下的addFunction并傳遞任何參數(這裡任何必須是你jshandler支援的任何),這裡我調用的是:window.addFunction(10, 20),對應的cefv8jshandler中我的處理邏輯:     

[cpp] view plain copy 

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
  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.     else  
  25.     {  
  26.         return false;  
  27.     } 

       如果函數名為addFunction,我從參數清單CefV8ValueList中逐個去除所有值,如果值類型不是int就認為錯誤,否則做一個加法,最有将int轉為CefV8Value值傳回!測試結果:

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts

          以上方式注冊變量和代碼僅能用于某個frame中,在js擴充中我們實作js注冊如下:

[cpp] view plain copy 

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
  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.     // 聲明本地函數 native function hehe();" 如果有參數清單需要寫具體的類型,而不能寫var類型!與本地聲明一直  
  15.     // 調用本地函數    return hehe();"  
  16.     // Create an instance of my CefV8Handler object.  
  17.     CefRefPtr<CefV8Handler> handler = new CV8JsHandler();  
  18.     // Register the extension.  
  19.     CefRegisterExtension("v8/mycode", extensionCode, handler);    
  20. }  

       我們注冊了一個g_value值和一個test對象,該對象有個方法test.myfunc,對象方法調用了向js注冊的本地方法hehe,本地方法有兩個參數,參數類型為int,聲明該本地方法後,直接調用本地方法heh傳遞10和50(本地方法我們在frame中注冊的),運作效果如下:顯示g_value值和調用test.myfunc對象的myfunc方法。

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts

       上面的例子将的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

       效果如下:

CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts
CEF3實作js與C++互動功能, Render程式中OnContextCreated綁定與OnWebKitInitialized的js擴充無法回調 ExecuteJavaScriptWindow BindingExtensionsWorking with Contexts

       很多網友說我在csdn上提供的代碼沒什麼注釋,我想說的是:不是沒有什麼注釋,是一般我不會注釋,我覺得簡單幹淨,對我來說注釋就是對于新手而言的(這也可能确實重要)。如果你們發現我的Demon裡面确實沒有注釋,實在不好意思,這是本人習慣,不好改,如果有疑問請郵件[email protected],本人盡可能回複您的問題!在這裡我還想說一句,沒有注釋可能在另一方面講會讓你對這些代碼更深刻(你會為了了解他不斷思考查閱)!