electron chromium 内部事件发送到外围 通知到js

参考网页加载完成的事件,实现chromium内部对外的js发送事件。

FrameHostMsg_DidFinishLoad

带反馈的可参考:FrameMsg_BeforeUnload ,反馈事件:FrameHostMsg_BeforeUnload_ACK,FrameHostMsg_RunBeforeUnloadConfirm

基于electron 7:

从js addEventListener事件截获,发往iframe。

首先新添加消息。在D:develectron7srccontentcommonframe_messages.h 定义消息。

IPC_MESSAGE_ROUTED1(FrameMsg_Notify_addEventListener, bool /* is_reload */)

1,事件截获:d:develectron7src hird_partylink enderercoredomeventsevent_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:develectron7src hird_partylink enderercoreframelocal_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:develectron7src hird_partylink enderercoreexportedlocal_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:develectron7src hird_partylink enderercoreexportedlocal_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:develectron7src hird_partylinkpublicwebweb_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:develectron7src hird_partylink enderercoreframeweb_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:develectron7src hird_partylink enderercoreframeweb_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:develectron7srccontent enderer ender_frame_impl.h

d:develectron7srccontent enderer ender_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:develectron7srccontentrowserweb_contentsweb_contents_impl.h 添加事件处理函数:

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

D:develectron7srccontentrowserweb_contentsweb_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:develectron7srccontentpublicrowserweb_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:develectron7srcelectronshellrowserapiatom_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:develectron7srcelectronshellrowserapiatom_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({
     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]+" "; } 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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文地址:https://www.cnblogs.com/bigben0123/p/13175909.html