zoukankan      html  css  js  c++  java
  • 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
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 相关阅读:
    vscode debugger 调试服务
    巴克斯诺尔范式 && 乔姆斯基谱系,词法 && 语法
    推荐好用的建站系统以及各网站系统优缺点介绍
    解决emlog默认导航不能修改的问题以及修改后台登录地址的方法
    易企CMS主要模板文件介绍
    易企CMS模板调用标签列表
    易企CMS仿站标签说明
    使用Custom scrollbar(彩色滚动条)插件实现WordPress滚动条变色的方法
    2018给网页滚动条变色的新方法
    javascript实现双击网页自动滚动,单击滚动停止
  • 原文地址:https://www.cnblogs.com/bigben0123/p/13175909.html
Copyright © 2011-2022 走看看