Qt WebChannel Impliment based chromium

Qt WebChannel Impliment based chromium

在開始介紹之前, 我們先看一段常用的一個應用的一段代碼:

  QWebEnginePage *page = new QWebEnginePage(this);
  ui->webEngineView->setPage(page);
  QWebChannel *channel = new QWebChannel(this);
  channel->registerObject(QStringLiteral("content"), &MsgHandler); //MsgHandler是交互類對象
  page->setWebChannel(channel);
  ui->webEngineView->setUrl(QUrl("you_html_page_uri.html")); //加載一個頁面
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
window.onload = function() {          
    new QWebChannel(qt.webChannelTransport, function(channel) {
      var content = channel.objects.content;    //把對象賦值到JS中
      content.updateText("a new msg string");     //updateText 是一個c++中的slot 函數;
    }
//連接QT中發出的信號,裏面的message是參數;只需要在QT中調用此函數也就是sendText就可以調用JS函數
  content.sendText.connect(function(message) {
     alert("Received message: " + message);              
  });
}

有編程經驗的人應該很容易看出來這其中的意思, C++中初始化了一個webengine(qt的web引擎組件已經基於chromium了)將ui組件和WebEngineView和WebEngine綁定, 之後創建了一個QWebChannel對象, 並通過WebChannel對象註冊了一個名字爲content的對象, 並綁定一個MsgHandler的Qobject的對象用來處理和頁面的交互;待會的分析就是從WebEnginePage的setWebChannel(QwebChannel*)函數來開始分析, 因爲這裏是一切的開始;

而在js端, 當頁面load完成時, 就可以通過獲取這個註冊的js對象;有了這個對象,配合Qt 的MetaObject 引入的signal 和 slot機制, 來完成頁面的消息傳遞和函數調用;

當頁面初始化的時候,qwebchannel.js會將所有的c++端交互對象的信號和槽在js中生成對應的對象和函數,當c++觸發某個信號的時候, 會通過WebChannel內部的一個對象觸發一個Chromium的IPC消息, 這個消息以json字符串的形式打包並傳送到頁面的渲染進程;在render進程收到這個消息的時候,通過調用一個WebChannelIPCTransport::dispatchWebChannelMessage函數將這個消息給到V8的運行環境中, 藉助於qwebchannel.js中的全局對象qt 拿到一個js對象中的webChannelTransport 對象;通過這個對象的onmessage函數,將這個打包好的ipc數據按照一定的格式解析出來,調用到對應的消息處理函數上;

另一方面, 通過在一個創建一個頁面的時候, 通過向當前頁面註冊webChannelTransport 這個C++中的包裝對象的時候,同樣通過google自己實現的一套包裝模版函數將c++中WebChannelTransport的成員函數NativeQtSendMessage註冊爲頁面中的send函數;這樣, 頁面也同樣可以發送消息給到主進程當中;之後通過qwebchannel類來與應用程序或者對應的QML接口的封裝類;

官方另外一個例子是藉助於qtwebchannel建立一個簡單的websocket server的例子;see here

在Qt包裝Webcontents的時候,通過一個Adaptor類WebContentsAdapter來包裝整個webcontents必要的接口, 裏面提供了下面兩個接口:

QWebChannel *webChannel() const;
void setWebChannel(QWebChannel *, uint worldId);

通過這兩個接口, 可以向頁面註冊一個QWebChannel的對象, Adaptor對象通過調用其WebContentsAdapterPrivate對象創建並初始化其webChannelTransport對象;

void WebContentsAdapter::setWebChannel(QWebChannel *channel, uint worldId) {
    Q_D(WebContentsAdapter);
    if (d->webChannel == channel && d->webChannelWorld == worldId)
        return;
    if (!d->webChannelTransport.get())
        d->webChannelTransport.reset(new WebChannelIPCTransportHost(d->webContents.get(), worldId));
    ...
    d->webChannel = channel;
    d->webChannelWorld = worldId;
    ...
      channel->connectTo(d->webChannelTransport.get());
}

當調用setWebChannel的時候, 如果沒有建立相應的IPC通信類, 就創建一個WebChannelIPCTransportHost 對象; 之後將channel和world id保存在webcontntsadapterprivate對象內部, 做爲附加在這個頁面上的一個channel對象;之後所有的消息、以及映射的信號與槽函數的處理, 都是通過IPC消息轉換到channel對象進行處理;在函數最後, 調用了channel的connectTo函數, 將這個IPCtransport 和 channel進行綁定;這個稍後分析, 先來看看WebChannelIPCTransportHost 構造函數,其實我們可以想想, 基於chromium這種多進程架構, 如果要與網頁環境所在的Render Process建立聯繫,那麼我們必然要通過content API 去setup這個IPC通信環境;

WebChannelIPCTransportHost::WebChannelIPCTransportHost(content::WebContents *contents, uint worldId, QObject *parent)
    : QWebChannelAbstractTransport(parent)
    , content::WebContentsObserver(contents)
    , m_worldId(worldId)
{
    Send(new WebChannelIPCTransport_Install(routing_id(), m_worldId));
}

通過上面的類圖可以知道, WebChannelIPCTransportHost類是繼承了QWebChannelAbstractTransportcontent::WebContentsObserver對象; 實現前者的sendMessage函數來向頁面發送信號與消息, 後者利用content的接口對象WebContentsObserver來實現chromium多進程之間的ipc消息的發送和接收;在上面的構造函數中, 裏面簡單的就只有一句話“發送一個Install 的消息給render進程,用來向Render進程的V8執行環境註冊安裝一些私有函數和全局對象”

IPC_MESSAGE_HANDLER(WebChannelIPCTransport_Install, installWebChannel)

void WebChannelIPCTransport::installWebChannel(uint worldId)
{
    blink::WebView *webView = render_view()->GetWebView();
    if (!webView)
        return;
    WebChannelTransport::Install(webView->mainFrame(), worldId);
    m_installed = true;
    m_installedWorldId = worldId;
}

這裏說明一下,WebChannelIPCTransport對象的創建是在render進程中的renderview對象初始化的時候通過content_render_client.h 裏的接口RenderViewCreated 創建的;它繼承了RenderViewObserver對象;看到這裏應該不難看出, chromium在多進程設計上的一個邏輯對稱性; 如果對chromium代碼熟悉的同學應該深有感觸; 上面的code調用了一個靜態函數WebChannelTransport::Install 來像V8的context註冊一些全局的對象與函數對象;

void WebChannelTransport::Install(blink::WebFrame *frame, uint worldId)
{
    v8::Isolate *isolate = v8::Isolate::GetCurrent();
    v8::HandleScope handleScope(isolate);
    v8::Handle<v8::Context> context;
    if (worldId == 0)
        context = frame->mainWorldScriptContext();
    else
        context = frame->toWebLocalFrame()->isolatedWorldScriptContext(worldId, 0);
    v8::Context::Scope contextScope(context);

    gin::Handle<WebChannelTransport> transport = gin::CreateHandle(isolate, new WebChannelTransport);
    v8::Handle<v8::Object> global = context->Global();
    v8::Handle<v8::Object> qt = global->Get(gin::StringToV8(isolate, "qt"))->ToObject();
    if (qt.IsEmpty()) {
        qt = v8::Object::New(isolate);
        global->Set(gin::StringToV8(isolate, "qt"), qt);
    }
    qt->Set(gin::StringToV8(isolate, "webChannelTransport"), transport.ToV8());
}

最後附上一張整個調用的時序圖:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章