electron chromium 內部事件發送到外圍 通知到js


參考網頁加載完成的事件,實現chromium內部對外的js發送事件。

FrameHostMsg_DidFinishLoad

帶反饋的可參考:FrameMsg_BeforeUnload ,反饋事件:FrameHostMsg_BeforeUnload_ACK,FrameHostMsg_RunBeforeUnloadConfirm

基於electron 7:

從js addEventListener事件截獲,發往iframe。

首先新添加消息。在D:\dev\electron7\src\content\common\frame_messages.h 定義消息。

IPC_MESSAGE_ROUTED1(FrameMsg_Notify_addEventListener, bool /* is_reload */)

 

1,事件截獲:d:\dev\electron7\src\third_party\blink\renderer\core\dom\events\event_target.cc

bool EventTarget::AddEventListenerInternal(
    const AtomicString& event_type,
    EventListener* listener,
    const AddEventListenerOptionsResolved* options) {
  if (!listener)
    return false;

  if (event_type == event_type_names::kTouchcancel ||
      event_type == event_type_names::kTouchend ||
      event_type == event_type_names::kTouchmove ||
      event_type == event_type_names::kTouchstart) {
    if (const LocalDOMWindow* executing_window = ExecutingWindow()) {
      if (const Document* document = executing_window->document()) {
        document->CountUse(options->passive()
                               ? WebFeature::kPassiveTouchEventListener
                               : WebFeature::kNonPassiveTouchEventListener);
      }
    }
  }

  V8DOMActivityLogger* activity_logger =
      V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld();
  if (activity_logger) {
    Vector<String> argv;
    argv.push_back(ToNode() ? ToNode()->nodeName() : InterfaceName());
    argv.push_back(event_type);
    activity_logger->LogEvent("blinkAddEventListener", argv.size(),
                              argv.data());
  }

#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH  // zhibin:patch_to_content
                                                      // start call
  {
    LocalDOMWindow* executing_window = ExecutingWindow();
    LocalFrame* frame = executing_window->GetFrame();
    Node* node = ToNode();

//  String node_name = node ? node->nodeName() : InterfaceName();    

    if (node && node->IsElementNode()) {
      Element* ele = static_cast<Element*>(node);
      AtomicString old_id = ele->IdForStyleResolution();
      frame->Client()->DispatchDidNotifyEventAdded(
          old_id.GetString().Utf8(), event_type.GetString().Utf8());
    }
    /*  don't send built-in addEventListener  
    else{
      frame->Client()->DispatchDidNotifyEventAdded(
        node_name.Utf8(),
        event_type.GetString().Utf8());
    }
    */  
  }
#endif
  RegisteredEventListener registered_listener;
  bool added = EnsureEventTargetData().event_listener_map.Add(
      event_type, listener, options, &registered_listener);
  if (added) {
    AddedEventListener(event_type, registered_listener);
    if (IsA<JSBasedEventListener>(listener) &&
        IsInstrumentedForAsyncStack(event_type)) {
      probe::AsyncTaskScheduled(GetExecutionContext(), event_type,
                                listener->async_task_id());
    }
  }
  return added;

 

 

2,發往外圍client:

d:\dev\electron7\src\third_party\blink\renderer\core\frame\local_frame_client.h

#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH  // zhibin:patch_to_content
                                                      // ipc
  virtual void DispatchDidNotifyEventAdded(const std::string& node_name,
                                           const std::string& event_type) {}
  #endif

d:\dev\electron7\src\third_party\blink\renderer\core\exported\local_frame_client_impl.h

#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH  // zhibin:patch_to_content
                                                      // ipc 
  void DispatchDidNotifyEventAdded(const std::string& node_name,
                                   const std::string& event_type) override;
 #endif

d:\dev\electron7\src\third_party\blink\renderer\core\exported\local_frame_client_impl.cc

#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH  // zhibin:patch_to_content
void LocalFrameClientImpl::DispatchDidNotifyEventAdded(
    const std::string& node_name,
    const std::string& event_type) {
  web_frame_->DidNotifyEvent(node_name,event_type);
}
#endif

3,傳到web層接口:

d:\dev\electron7\src\third_party\blink\public\web\web_local_frame_client.h

#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH  // zhibin:patch_to_content
                                                      // ipc voidparam
  virtual void DidNotifyEventAdded(const std::string& node_name,
                                   const std::string& event_type) {} 

  #endif

實現:d:\dev\electron7\src\third_party\blink\renderer\core\frame\web_local_frame_impl.h

#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH  // zhibin:patch_to_content
  void DidNotifyEvent(const std::string& node_name,
                      const std::string& event_type);
  #endif

d:\dev\electron7\src\third_party\blink\renderer\core\frame\web_local_frame_impl.cc

#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH  // zhibin:patch_to_content
void WebLocalFrameImpl::DidNotifyEvent(const std::string& node_name,
                                       const std::string& event_type) {
  if (!Client())
    return;

//  if (WebPluginContainerImpl* plugin = GetFrame()->GetWebPluginContainer()) //zhi:todo
//    plugin->DidFinishLoading();

  Client()->DidNotifyEventAdded(node_name,event_type);//voidparam not client's interface
}
#endif

4,最后到達frame的實現層,將ipc 消息發送出去:

d:\dev\electron7\src\content\renderer\render_frame_impl.h

d:\dev\electron7\src\content\renderer\render_frame_impl.cc

#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH  // zhibin:patch_to_content
                                                      // voidParam
  void DidNotifyEventAdded(const std::string& node_name,
                           const std::string& event_type) override;
  #endif
#ifndef CONFIG_NO_NOTIFY_ADD_EVENT_LISTENER_DISPATCH  // zhibin:patch_to_content send notification
void RenderFrameImpl::DidNotifyEventAdded(const std::string& node_name,
                                          const std::string& event_type) {
  TRACE_EVENT1("navigation,benchmark,rail", "RenderFrameImpl::DidAddEventListenerCalled",
               "id", routing_id_);
  if (!frame_->Parent()) {
    TRACE_EVENT_INSTANT0("WebCore,benchmark,rail", "DidAddEventListenerCalled",
                         TRACE_EVENT_SCOPE_PROCESS);
  }
  /*
  for (auto& observer : observers_)
    observer.DidFinishLoad();//:todo
*/
 // WebDocumentLoader* document_loader = frame_->GetDocumentLoader();
  Send(new FrameHostMsg_DidAddEventListenerCalled(routing_id_, node_name,
                                                  event_type));
}

 


5,content外層接收事件

D:\dev\electron7\src\content\browser\web_contents\web_contents_impl.h 添加事件處理函數:

  #if 1//zhibin:patch ipc
  void OnAddEventListenerCalled(RenderFrameHost* render_frame_host,
                       const GURL& url) override; #endif

 

D:\dev\electron7\src\content\browser\web_contents\web_contents_impl.cc

#if 1//zhibin:patch ipc
    IPC_MESSAGE_HANDLER(FrameMsg_Notify_addEventListener, OnAddEventListenerCalled)
#endif   


#if 1//zhibin:patch ipc
void WebContentsImpl::OnAddEventListenerCalled(RenderFrameHostImpl* source,
                                               const GURL& url) { if (!source->GetParent()) { size_t frame_count = source->frame_tree_node()->GetFrameTreeSize(); UMA_HISTOGRAM_COUNTS_1000("Navigation.MainFrame.FrameCount", frame_count); } for (auto& observer : observers_) observer.DidAddEventListenerCalled(source, validated_url); } #endif

 

 6,調用ovserver通知出去

D:\dev\electron7\src\content\public\browser\web_contents_observer.h 回調接口,需要被繼承實現。

  virtual void DidFinishLoad(RenderFrameHost* render_frame_host,
                             const GURL& validated_url) {}

#if 1
virtual void DidAddEventListenerCalled(RenderFrameHost* render_frame_host,
const GURL& validated_url) {}

#endif

 

 7,外圍electron實現:

D:\dev\electron7\src\electron\shell\browser\api\atom_api_web_contents.cc

#if 1//zhibin:patch ipc

void WebContents::DidAddEventListenerCalled(
    content::RenderFrameHost* render_frame_host, const GURL& validated_url) { bool is_main_frame = !render_frame_host->GetParent(); int frame_process_id = render_frame_host->GetProcess()->GetID(); int frame_routing_id = render_frame_host->GetRoutingID(); Emit("did-frame-finish-load", is_main_frame, frame_process_id, frame_routing_id); Emit("event-demo"); } #endif

 D:\dev\electron7\src\electron\shell\browser\api\atom_api_web_contents.h

  #if 1//zhibin:patch ipc
  void DidAddEventListenerCalled(content::RenderFrameHost* render_frame_host,
                     const GURL& validated_url) override; #endif

  

 nodejs測試示例:

package.json

{
  "name": "eventdemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "event"
  ],
  "author": "zb",
  "license": "ISC"
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
    <!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
    
  </head>
  <body>
    <h1><a href=index.html> myself1</a></h1>
     <h1><a href=http://www.baidu.com>baidu</a></h1>
    We are using node <script>document.write(process.versions.node)</script>,
    Chrome <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
  </body>
  <button id="btn">submit button</button>
    <script>
       alert("I am a alert.");
   
        document.getElementById("btn").addEventListener("click",function(){
            console.log("button clicked.")
        },false);
       
    </script>
</html>

index.js

const { dialog, app, BrowserWindow } = require('electron')

function createWindow () {   
  // 創建瀏覽器窗口
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })

  // 並且為你的應用加載index.html
  win.loadFile('index.html')

  // 打開開發者工具
  //win.webContents.openDevTools();
  win.webContents.on('will-navigate', (e, url,) => {
  console.log("===will-navigate===");
   });
   
   win.webContents.on('did-finish-load', function() {  
      console.log("===============load page successfully");  
      // win.webContents.executeJavaScript('alert(" load finish alert ");');
   });
    
  /*        
   win.webContents.on('event-demo', function() {  
      console.log("==== event-demo=========================");  
   });
   */   
   
  //addEventListener事件通知 
  win.webContents.on('event-demo',  function(event,element_id, event_name){
      console.log("========================= tistar-addEventListener-event begin =========================");    
    console.log(event);
       console.log(element_id);
       console.log(event_name);
       var str='var bbb=document.getElementById("'+'btn'+'");alert(bbb.innerHTML)';
       console.log(str);   
       win.webContents.executeJavaScript(str);
       console.log("========================= tistar-addEventListener-event end =========================");    
  }); 
  
  //屏蔽彈出框事件 
  win.webContents.on('tistar-dialog-event', function(event,dialog_type, message) { 
    console.log("========= tistar-dialog-event begin =========");
      //console.log(event);
      console.log(dialog_type);
      console.log(message);
      console.log("========= tistar-dialog-event end =========="); 
  });

}

// Electron會在初始化完成並且准備好創建瀏覽器窗口時調用這個方法
// 部分 API 在 ready 事件觸發后才能使用。
app.whenReady().then(createWindow)

//當所有窗口都被關閉后退出
app.on('window-all-closed', () => {
  // 在 macOS 上,除非用戶用 Cmd + Q 確定地退出,
  // 否則絕大部分應用及其菜單欄會保持激活。
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // 在macOS上,當單擊dock圖標並且沒有其他窗口打開時,
  // 通常在應用程序中重新創建一個窗口。
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})

// 您可以把應用程序其他的流程寫在在此文件中
// 代碼 也可以拆分成幾個文件,然后用 require 導入。

 


 方式二:

直接通過發送event,回調到注冊過addEventListener的方法中。這種方法實現非常簡單。

index.html更改為:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
    <!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
    
  </head>
  <body>
    <h1><a href=index.html> myself</a></h1>
    <h1><a href=http://www.baidu.com>baidu</a></h1>
    
    <button id="btn">submit button</button>
Enter your name: <input type="text" id="fname" onchange="upperCase(this.id)">
<script> var ev = new CustomEvent('cust_event_add_event_listener', { bubbles: 'false', cancelable: 'false', detail: 'Tell me your story!' }); addEventListener('cust_event_add_event_listener', function (event) { var tmpobj=event.target;//.document.getElementById("btn") var str; for (var item in tmpobj){ str +=item+":"+tmpobj[item]+"\n"; } console.log(str); console.log(event.target.id); }, false); document.getElementById("btn").addEventListener("click",function(l1,l2,l3){ //alert(l1); console.log("button clicked."); dispatchEvent(ev); },false);

function upperCase(x)
{
var y=document.getElementById(x).value
document.getElementById(x).value=y.toUpperCase()
}

</script>
      </body>
</html>

當有值改動是通知:

void Node::DispatchScopedEvent(Event& event) {
  #ifndef CUST_NO_EVENT_ADD_EVENT_LISTENER_NOTIFY  // zhibin:call js
  if (event.type() == "change") {
    LocalDOMWindow* executing_window = GetDocument().ExecutingWindow();
    Event* ce = Event::CreateBubble(event_interface_names::kCustomEvent);
    ce->SetType("cust_event_notify_change");
    ce->SetTarget(this);
    ce->SetTrusted(true);
    executing_window->DispatchEvent(*ce, this);
  }
#endif 
  event.SetTrusted(true);
  EventDispatcher::DispatchScopedEvent(*this, event);
}

 

在需要發出事件的地方調用:

    LocalDOMWindow* executing_window = ExecutingWindow();
    Node* node = ToNode();

//  String node_name = node ? node->nodeName() : InterfaceName();    

    if (node && node->IsElementNode()) {
      auto* ele = DynamicTo<Element>(node);

      /* call to js */
      CustomEvent* ce = CustomEvent::Create();
      ce->SetType("cust_event_add_event_listener");
//      ce->SetTarget(ele);
//      ce->SetCurrentTarget(this);
      ce->SetEventPhase(Event::kAtTarget);
//      ce->SetTrusted(true);
      executing_window->DispatchEvent(*ce,ele);
    }

 

 


 new一個event參考代碼:

MouseEventInit* initializer = MouseEventInit::Create();
    initializer->setBubbles(!is_mouse_enter_or_leave);
    initializer->setCancelable(!is_mouse_enter_or_leave);
    MouseEvent::SetCoordinatesFromWebPointerProperties(
        mouse_event.FlattenTransform(), target_node->GetDocument().domWindow(),
        initializer);
    UpdateMouseMovementXY(mouse_event, last_position,
                          target_node->GetDocument().domWindow(), initializer);
    initializer->setButton(static_cast<int16_t>(mouse_event.button));
    initializer->setButtons(MouseEvent::WebInputEventModifiersToButtons(
        mouse_event.GetModifiers()));
    initializer->setView(target_node->GetDocument().domWindow());
    initializer->setComposed(true);
    initializer->setDetail(click_count);
    initializer->setRegion(canvas_region_id);
    initializer->setRelatedTarget(related_target);
    UIEventWithKeyState::SetFromWebInputEventModifiers(
        initializer,
        static_cast<WebInputEvent::Modifiers>(mouse_event.GetModifiers()));
    initializer->setSourceCapabilities(
        target_node->GetDocument().domWindow()
            ? target_node->GetDocument()
                  .domWindow()
                  ->GetInputDeviceCapabilities()
                  ->FiresTouchEvents(mouse_event.FromTouch())
            : nullptr);

    MouseEvent* event = MouseEvent::Create(
        mouse_event_type, initializer, mouse_event.TimeStamp(),
        mouse_event.FromTouch() ? MouseEvent::kFromTouch
                                : MouseEvent::kRealOrIndistinguishable,
        mouse_event.menu_source_type);

 


 

參考:https://www.jianshu.com/p/2a2424bdc057

https://www.cnblogs.com/danxi/p/7766147.html

chromium源碼閱讀--進程間通信(IPC)

 

     第一篇就有提到Chromium是目前默認是采用多進程架構,當然,chromium有singe-process的版本。

     多進程與多線程的區別,確實有很多可以講的,我的另一篇博客也講了一些 (Linux 進程,線程),這里是從瀏覽器的角度來說,如果是多線程,如果一個線程崩潰,影響了整個瀏覽器的使用,因為在現在的網頁標准更新了很多個版本,會有不同標准的頁面在網絡上,極大可能出現解析,渲染,插件等問題,那么對於用戶來說,體驗就會差很多了,瀏覽一個頁面出問題,就要重啟瀏覽器。而多進程則可以避免此問題,render進程崩潰只會影響當前的tab。

    嗯,上面說了那么多,就是為了說,多進程之間就需要進程通信來協作,而chromium的進程間通信是非常繁雜的,如何處理這個是我們需要了解的關鍵。

   那么本質的問題就是:

         1、發那些消息(Message Type)

         2、消息通道是怎么建立的 (Message Channel)

         3、發送者和接收者(Sender,Listener)

OK,咱一個個來。

 

一、 Message Type 

     主要分為2類:“routed” 和 “control”。

     1、routed消息

         主要是用來給某個RenderViewHost對象發送消息的。不過,任何類都可以通過GetNextRoutingID 和 AddRoute 注冊,就能接收routed消息。

     2、control消息

          control消息有創建pipe的類處理,當然這些類也可以接收routed消息。比如,請求資源或修改剪貼板不是特定於視圖的,所以是控制消息。

     3、消息的聲明

1 IPC_MESSAGE_ROUTED2(FrameHostMsg_MyMessage, GURL, int)

      這個宏用來聲明routed消息,這里聲明了一個從render進程發送到browser進程的消息,並有一個GURL參數,一個int參數

1 IPC_MESSAGE_CONTROL0(FrameMsg_MyMessage)

     這個宏用來聲明control消息,這里聲明了一個從browser進程發送到render進程的消息,沒有參數。

     這里還有幾個默認的約定:

          (1)這些宏后面的數字表明有幾個參數,最多5個參數,即: IPC_MESSAGE_ROUTED0~IPC_MESSAGE_ROUTED5 或者 IPC_MESSAGE_CONTROL0~IPC_MESSAGE_CONTROL5

          (2)消息名稱表明消息的接受者,FrameHostMsg,帶Host后綴的類,表示在browser進程接收處理的消息,FrameMsg,則表示在render進程處理的消息,如果是Plugin進程,也會帶有Plugin字樣。

 

二、Message Channel

    chromium的使用mojo IPC,並且在官網提供了性能對比 (Times in microseconds)

 

Windows Z840

Linux Z620

MacBook Pro 15" 2016

IPC

36.9

69.5

52.5

Mojo cross-process

28.2

48

34.9

 

這里是官網關於mojo的一些介紹,https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md#System-Overview

從unittest看channel的創建:

 

1 void IPCChannelMojoTestBase::CreateChannel(IPC::Listener* listener) {
2   channel_ =
3       IPC::ChannelMojo::Create(TakeHandle(), IPC::Channel::MODE_SERVER,
4                                listener, base::ThreadTaskRunnerHandle::Get());
5 }

 在IPC::ChannelMojo::Create里看到需要 IPC::ChannelMojo的構造,

復制代碼
 1 ChannelMojo::ChannelMojo(
 2     mojo::ScopedMessagePipeHandle handle,
 3     Mode mode,
 4     Listener* listener,
 5     const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner)
 6     : task_runner_(ipc_task_runner),
 7       pipe_(handle.get()),
 8       listener_(listener),
 9       weak_factory_(this) {
10   weak_ptr_ = weak_factory_.GetWeakPtr();
11   bootstrap_ = MojoBootstrap::Create(std::move(handle), mode, ipc_task_runner);
12 }
復制代碼

 在MojoBootstrapImpl里完成sender和listener的綁定:

復制代碼
 1 class MojoBootstrapImpl : public MojoBootstrap {
 2  public:
 3   MojoBootstrapImpl(
 4       mojo::ScopedMessagePipeHandle handle,
 5       const scoped_refptr<ChannelAssociatedGroupController> controller)
 6       : controller_(controller),
 7         associated_group_(controller),
 8         handle_(std::move(handle)) {}
 9 
10   ~MojoBootstrapImpl() override {
11     controller_->ShutDown();
12   }
13 
14  private:
15   void Connect(mojom::ChannelAssociatedPtr* sender,
16                mojom::ChannelAssociatedRequest* receiver) override {
17     controller_->Bind(std::move(handle_));
18     controller_->CreateChannelEndpoints(sender, receiver);
19   }
20 
21  。。。
22 }
復制代碼

 上面的mojo  Channel的創建過程,linux提供的IPC比如:pipe,unix socket,share memory都不是線程安全的,mojo封裝了底層IPC細節並提供了線程安全保障,並且看上面的性能對比,mojo性能更好,這也是chromium逐漸轉用mojo的主要因素吧。

 OK,上面介紹了mojo,接下來我們會發現,在進程里都是使用IPC::ChannelProxy這個類來代理完成Channel的各種工作。

 這里我們只需看一個例子就能理解了,比如在browser進程的RenderProcessHost類里聲明了GetChannel接口:

1 IPC::ChannelProxy* GetChannel() = 0;

根據chromium的套路,你大致就能想到,有一個RenderProcessHostImpl類會來實現這個接口,嗯,果不其然:

復制代碼
 1 class CONTENT_EXPORT RenderProcessHostImpl
 2     : public RenderProcessHost,
 3       public ChildProcessLauncher::Client,
 4       public ui::GpuSwitchingObserver,
 5       public mojom::RouteProvider,
 6       public mojom::AssociatedInterfaceProvider,
 7       public mojom::RendererHost {
 8       ...
 9       IPC::ChannelProxy* GetChannel() override;
10       ...
11 }
復制代碼

我們可以看到這里會提供一個IPC::ChannelProxy的指針,那么順着這個,ChannelProxy的創建和初始化就不遠了。

復制代碼
bool RenderProcessHostImpl::Init() {
   ...
   if (!channel_)
    InitializeChannelProxy();
  
   ...
   CreateMessageFilters();
  RegisterMojoInterfaces();
  ...

}
復制代碼

可以看到,上面初始化了Channel並給當前實例創建了MessageFilter和在mojo里注冊了消息發送的mojo interface。

mojo會負責將channel兩端連通,之后的消息發送就可使用IPC::ChannelProxy來完成了。

 

三、發送者和接收者

    1、發送者

    chromium里定義了IPC::Sender的接口:

復制代碼
 1 class Message;
 2 
 3 class IPC_EXPORT Sender {
 4  public:
 5   // Sends the given IPC message.  The implementor takes ownership of the
 6   // given Message regardless of whether or not this method succeeds.  This
 7   // is done to make this method easier to use.  Returns true on success and
 8   // false otherwise.
 9   virtual bool Send(Message* msg) = 0;
10 
11  protected:
12   virtual ~Sender() {}
13 };
復制代碼

    上面的使用例子,我們可以看到 IPC::ChannelProxy 是消息的發送者,看類的聲明:

1 class IPC_EXPORT ChannelProxy : public Sender {
2 
3 }

2、接收者

    同樣chromium也定義Listener。

復制代碼
class Message;

// Implemented by consumers of a Channel to receive messages.
class IPC_EXPORT Listener {
 public:
  // Called when a message is received.  Returns true iff the message was
  // handled.
  virtual bool OnMessageReceived(const Message& message) = 0;

  ...
};
復制代碼

我們在前面提到的router,是消息接收者,也是消息發送者:

1 class IPC_EXPORT MessageRouter : public Listener, public Sender { 
2     ...
3 }

還有子線程實例也是Listener:

復制代碼
1 class CONTENT_EXPORT ChildThreadImpl
2     : public IPC::Listener,
3       virtual public ChildThread,
4       private base::FieldTrialList::Observer,
5       public mojom::RouteProvider,
6       public mojom::AssociatedInterfaceProvider,
7       public mojom::ChildControl {
8     ...
9  }
復制代碼

好了,更多例子我也不舉了,chromium IPC還有更多的內容,在代碼待我們學習,這里暫時總結到這里,后續再補充。

 

   

7.Web IDL綁定

當JavaScript訪問node.firstChild時,將調用node.h中的Node :: firstChild()。它是如何工作的?我們來看看node.firstChild是如何工作的。

首先,您需要根據規范定義IDL文件:

// node.idl interface Node : EventTarget { [...] readonly attribute Node? firstChild; }; 

Web IDL的語法在Web IDL規范中定義。 [...]稱為IDL擴展屬性。一些IDL擴展屬性在Web IDL規范中定義,而其他屬性是特定於Blink的IDL擴展屬性。除了特定於Blink的IDL擴展屬性外,IDL文件應以特定的方式編寫(即只需從規范中復制和粘貼)。

其次,您需要為Node定義C ++類並為firstChild實現C ++ getter:

class EventTarget : public ScriptWrappable { // All classes exposed to JavaScript must inherit from ScriptWrappable. ...; }; class Node : public EventTarget { DEFINE_WRAPPERTYPEINFO(); // All classes that have IDL files must have this macro. Node* firstChild() const { return first_child_; } }; 

在一般情況下,就是這樣。構建node.idl時,IDL編譯器會自動為Node接口和Node.firstChild生成Blink-V8綁定。自動生成的綁定在// src / out / {Debug,Release} / gen / third_party / blink / renderer / bindings / core / v8 / v8_node.h中生成。當JavaScript調用node.firstChild時,V8在v8_node.h中調用V8Node :: firstChildAttributeGetterCallback(),然后它調用您在上面定義的Node :: firstChild()。

8.V8和Blink

8.1 Isolate, Context, World

當您編寫涉及V8 API的代碼時,了解Isolate,Context和World的概念非常重要。它們分別由代碼庫中的v8 :: Isolate,v8 :: Context和DOMWrapperWorld表示。

Isolate對應於物理線程。 Isolate : physical thread in Blink = 1 : 1。主線程有自己的隔離。工作線程有自己的隔離。

Context對應於全局對象(在Frame的情況下,它是Frame的窗口對象)。由於每個幀都有自己的窗口對象,因此渲染器進程中有多個上下文。當您調用V8 API時,您必須確保您處於正確的上下文中。否則,v8 :: Isolate :: GetCurrentContext()將返回錯誤的上下文,在最壞的情況下,它將最終泄漏對象並導致安全問題。

World是支持Chrome擴展程序內容腳本的概念。世界與Web標准中的任何內容都不對應。內容腳本希望與網頁共享DOM,但出於安全原因,必須將內容腳本的JavaScript對象與網頁的JavaScript堆隔離。 (另外一個內容腳本的JavaScript堆必須與另一個內容腳本的JavaScript堆隔離。)為了實現隔離,主線程為網頁創建一個主要世界,為每個內容腳本創建一個隔離的世界。主要世界和孤立的世界可以訪問相同的C ++ DOM對象,但它們的JavaScript對象是隔離的。通過為一個C ++ DOM對象創建多個V8包裝器來實現這種隔離。即每個世界一個V8包裝器。

 
v8_blink_arch.jpg

Context,World和Frame之間有什么關系?

 

想象一下,主線上有N個世界(一個主要世界+(N - 1)個孤立的世界)。然后一個Frame應該有N個窗口對象,每個窗口對象用於一個世界。上下文是對應於窗口對象的概念。這意味着當我們有M幀和N個世界時,我們有M * N上下文(但是上下文是懶洋洋地創建的)。

對於worker,只有一個世界和一個全球對象。因此,只有一個上下文。

同樣,當您使用V8 API時,您應該非常小心使用正確的上下文。否則,您最終會在孤立的世界之間泄漏JavaScript對象並導致安全災難(例如,A.com的擴展可以操縱來自B.com的擴展)。

8.2 V8 API

在//v8/include/v8.h中定義了很多V8 API。由於V8 API是低級的並且難以正確使用,因此platform / bindings /提供了一堆包裝V8 API的輔助類。您應該考慮盡可能多地使用幫助程序類。如果您的代碼必須大量使用V8 API,那么這些文件應該放在bindings / {core,modules}中。

V8使用句柄指向V8對象。最常見的句柄是v8 :: Local <>,用於指向機器堆棧中的V8對象。在機器堆棧上分配v8 :: HandleScope后,必須使用v8 :: Local <>。不應在機器堆棧外使用v8 :: Local <>:

void function() { v8::HandleScope scope; v8::Local<v8::Object> object = ...; // This is correct. } class SomeObject : public GarbageCollected<SomeObject> { v8::Local<v8::Object> object_; // This is wrong. }; 
8.3 V8 wrappers

每個C ++ DOM對象(例如,Node)具有其對應的V8包裝器。准確地說,每個C ++ DOM對象每個世界都有相應的V8包裝器。

V8包裝器對其對應的C ++ DOM對象具有強引用。但是,C ++ DOM對象只有對V8包裝器的弱引用。因此,如果您希望將V8包裝器保持活動一段時間,則必須明確地執行此操作。否則,V8包裝器將過早收集,V8包裝器上的JS屬性將丟失。


div = document.getElementbyId("div"); child = div.firstChild; child.foo = "bar"; child = null; gc(); // If we don't do anything, the V8 wrapper of |firstChild| is collected by the GC. assert(div.firstChild.foo === "bar"); //...and this will fail. 

如果我們不做任何事情,那么孩子會被GC收集,因此child.foo會丟失。為了使div.firstChild的V8包裝器保持活動狀態,我們必須添加一種機制,“只要div所屬的DOM樹可以從V8到達,就可以使div.firstChild的V8包裝器保持活動狀態”。



作者:JeffMony
鏈接:https://www.jianshu.com/p/2a2424bdc057
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM