Chromium的Plugin进程启动过程分析

发表于 5年以前  | 总阅读数:3114 次

前面我们分析了Chromium的Render进程和GPU进程的启动过程,它们都是由Browser进程启动的。在Chromium中,还有一类进程是由Browser进程启动的,它们就是Plugin进程。顾名思义,Plugin进程是用来运行浏览器插件的。浏览器插件的作用是扩展网页功能,它们由第三方开发,安全性和稳定性都无法得到保证,因此运行在独立的进程中。本文接下来就详细分析Plugin进程的启动过程。

在Chromium中,还有一种组件比较容易和浏览器插件混淆,它们称为浏览器扩展(Extension)。浏览器扩展工作在浏览器层面上,用来扩展浏览器的功能,例如可以在浏览器的工具栏上增加一个按钮,并且通过浏览器提供的API实现某种功能,例如翻墙。浏览器插件工作在网页层面上,用来扩展网页的功能,例如在网页上显示一个PDF文档或者播放Flash视频。

本文要分析的是浏览器插件。在Chromium中,浏览器插件又分为两类,一类称为NPAPI插件,另一类称为PPAPI插件。NPAPI的全称是Netscape Plugin API,就是运行在Netscape Navigator浏览器上的一种插件,也就是由Netscape定义一套API接口,插件开发商通过调用这些API接口来扩展网页的功能。Netscape Navigator浏览器虽然早已离我们远去,但是NPAPI插件还顽强地活着,并且成为了一种几乎所有浏览器都支持的跨平台插件标准。因此,Chromium也对NPAPI插件进行了支持,目的是为了可以运行数以万计已经存在的NPAPI插件。

但是由于NPAPI插件定义的API接口太古老了,也太难用了,崩溃问题和安全问题也很多,无法适应现代浏览器的发展,因此Chromium搞了另外一套API接口,称为PPAPI接口,全称是Pepper Plugin API。基于PPAPI实现的插件就称为PPAPI插件。Chromium从2014年1月,逐渐放弃对NPAPI插件的支持,因此本文接下来要分析的是PPAPI插件。

PPAPI插件使用了一种称为Native Client(NaCl)的技术来解决安全性问题。在Native Client技术中,我们可以使用熟悉的Native语言C/C++开发插件核心模块,以满足效率要求。这些C/C++模块通过IPC可以与网页中的JS进行通信,同时它们还可以通过PPAPI使用浏览器提供的功能,例如可以通过PPB_OpenGLES2接口使用浏览器提供的OpenGL渲染能力。为了保证安全性,Native Client在工具链层面上限制C/C++模块能够调用的本地OS提供的API接口。也就是说,PPAPI插件的C/C++模块要通过Native Client提供的编译工具进行编译,而这些编译工具会检查PPAPI插件的C/C++模块调用的API,并且禁止非法API调用。这样实际上就给PPAPI插件加上了两层安全保护。第一层安全保护体现在运行时,将PPAPI插件运行在一个受限进程中,即运行在一个沙箱中。第二层安全保护体现在编译时,禁止Native代码调用非法API。

PPAPI插件有三种运行模式。第一种模式是运行在一个受限的独立进程中,即运行在沙箱中。第二种模式运行在一个非受限的独立进程中。第三种模式直接运行在Render进程中。一个PPAPI插件运行在哪一种模式取决于浏览器内部实现和浏览器命令行参数。浏览器内部实现会对某些内部PPAPI的运行模式进行Hard Code,例如PDF插件。这一点可以参考ChromeContentClient类的成员函数AddPepperPlugins(定义在文件external/chromium_org/chrome/common/chrome_content_client.cc中)。此外,如果浏览器命令行参数指定了switches::kPpapiInProcess选项,则其余安装的PPAPI插件将运行在第三种模式中,否则的话,就运行在第一种模式中。这一点可以参考函数ComputePluginsFromCommandLine(定义在文件external/chromium_org/content/common/pepper_plugin_list.cc中)。

本文主要关注的是PPAPI插件进程的启动过程。通过这个启动过程,我们可以了解PPAPI插件进程与Browser进程和Render进程的关系,如下所示:

图1 PPAPI插件进程与Browser进程、Render进程的关系

Render进程在解析网页时,如果遇到一个embed标签,那么就会创建一个HTMLPlugInElement对象来描述该标签,并且调用该HTMLPlugInElement对象的成员函数loadPlugin加载对应的PPAPI插件。但是Render进程没有启动PPAPI插件进程的权限,因此它就会通过之前与Browser进程建立的IPC通道向Browser进程发出一个启动PPAPI插件进程的请求。

Browser进程接收到启动PPAPI插件进程的请求之后,就会启动一个PPAPI插件进程,并且创建一个PpapiPluginProcessHost对象描述该PPAPI插件进程。PPAPI插件进程启动起来之后,会在内部创建一个ChildProcess对象,用来与Browser进程中的PpapiPluginProcessHost对象建立一个IPC通道。有了这个IPC通道之后,Browser进程请求PPAPI插件进程创建另外一个UINX Socket。该UNIX Socket的Server端文件描述符保留在PPAPI进程中,Client端文件描述符则返回给Render进程。基于这个UNIX Socket的Server端和Client端文件描述符,PPAPI插件进程和Render进程将分别创建一个HostDispatcher对象和一个PluginDispatcher对象,构成一个Plugin通道。以后PPAPI插件进程和Render进程就通过该Plugin通道进行通信。

接下来,我们就从HTMLPlugInElement类的成员函数loadPlugin开始,分析PPAPI插件进程的启动过程,如下所示:

bool HTMLPlugInElement::loadPlugin(const KURL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback, bool requireRenderer)
    {
        LocalFrame* frame = document().frame();

        ......

        RefPtr<Widget> widget = m_persistedPluginWidget;
        if (!widget) {
            ......
            widget = frame->loader().client()->createPlugin(this, url, paramNames, paramValues, mimeType, loadManually, policy);
        }

        ......

        return true;
    }

这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLPlugInElement.cpp中。

HTMLPlugInElement类的成员变量m_persistedPluginWidget的值等于NULL的时候,说明当前正在处理的HTMLPlugInElement对象描述的插件实例还没有创建,或者需要重新创建。在这种情况下,HTMLPlugInElement类的成员函数loadPlugin就会首先获得当前网页的Document对象,然后通过该Document对象获得一个LocalFrame对象,这个LocalFrame对象描述的是当前网页的页框。每一个LocalFrame对象都有一个关联的FrameLoader对象,这个FrameLoader对象用来加载当前网页的页框。每一个FrameLoader对象又关联有一个FrameLoaderClientImpl对象,通过这个FrameLoaderClientImpl对象,WebKit可以用来请求执行平台相关的操作,例如,HTMLPlugInElement类的成员函数loadPlugin就通过调用它的成员函数createPlugin创建一个插件实例。

FrameLoaderClientImpl类的成员函数createPlugin的实现如下所示:

PassRefPtr<Widget> FrameLoaderClientImpl::createPlugin(
        HTMLPlugInElement* element,
        const KURL& url,
        const Vector<String>& paramNames,
        const Vector<String>& paramValues,
        const String& mimeType,
        bool loadManually,
        DetachedPluginPolicy policy)
    {
        ......

        WebPlugin* webPlugin = m_webFrame->client()->createPlugin(m_webFrame, params);
        ......

        // The container takes ownership of the WebPlugin.
        RefPtr<WebPluginContainerImpl> container =
            WebPluginContainerImpl::create(element, webPlugin);

        if (!webPlugin->initialize(container.get()))
            return nullptr;

        ......

        return container;
    }

这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/FrameLoaderClientImpl.cpp中。

FrameLoaderClientImpl类的成员变量m_webFrame指向的是一个WebLocalFrameImpl对象,该WebLocalFrameImpl对象用来描述当前处理的网页内容是在当前进程进行渲染的,并且通过该WebLocalFrameImpl对象的成员函数client可以获得一个WebFrameClient对象,该WebFrameClient对象是用WebKit用来与上层使用者,即Chromium进行交互的,例如WebKit就通过调用它的成员函数createPlugin创建一个插件实例。创建出来的插件实例在WebKit中用一个WebPlugin对象描述,这个WebPlugin对象最后封装在一个WebPluginContainerImpl对象中返回给调用者。

创建出来的WebPlugin对象在返回给调用者之前,会先调用其成员函数initailize进行初始化。对于NPAPI插件来说,初始化工作包括请求Browser进程启动一个插件进程,但是对于PPAPI插件来说,插件进程是在上述的插件实例创建过程中请求Browser进程启动的,接下来我们就会到这一点。由于NPAPI插件进程和PPAPI插件进程的启动过程类似,因此我们只关注PPAPI插件进程的启动过程。

Chromium将上述WebFrameClient对象指定为一个RenderFrameImpl对象,这个RenderFrameImpl对象是Chromium用来描述当前正在处理的一个网页的,它的成员函数createPlugin的实现如下所示:

blink::WebPlugin* RenderFrameImpl::createPlugin(
        blink::WebLocalFrame* frame,
        const blink::WebPluginParams& params) {
      DCHECK_EQ(frame_, frame);
      blink::WebPlugin* plugin = NULL;
      if (GetContentClient()->renderer()->OverrideCreatePlugin(
              this, frame, params, &plugin)) {
        return plugin;
      }

      if (base::UTF16ToASCII(params.mimeType) == kBrowserPluginMimeType) {
        return render_view_->GetBrowserPluginManager()->CreateBrowserPlugin(
            render_view_.get(), frame, false);
      }

    #if defined(ENABLE_PLUGINS)
      WebPluginInfo info;
      std::string mime_type;
      bool found = false;
      Send(new FrameHostMsg_GetPluginInfo(
          routing_id_, params.url, frame->top()->document().url(),
          params.mimeType.utf8(), &found, &info, &mime_type));
      if (!found)
        return NULL;

      if (info.type == content::WebPluginInfo::PLUGIN_TYPE_BROWSER_PLUGIN) {
        return render_view_->GetBrowserPluginManager()->CreateBrowserPlugin(
            render_view_.get(), frame, true);
      }


      WebPluginParams params_to_use = params;
      params_to_use.mimeType = WebString::fromUTF8(mime_type);
      return CreatePlugin(frame, info, params_to_use);
    #else
      return NULL;
    #endif  // defined(ENABLE_PLUGINS)
    }

这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。

Chromium是多层架构的,从上到下大概分为浏览器层、Content层和WebKit层。其中,WebKit层用来解析网页,Content层位于WebKit层之上,并且提供多进程架构。这样我们就可以基于Content层,实现一个浏览器。例如,Chrome就是基于Chromium的Content层提供的API实现的。这意味着我们也可以基于Chromium的Content层实现一个与Chrome类似的浏览器。

Chromium为了能够让浏览层定制一些Content层的行为,允许浏览器层设置一个Content Client到Content层来。Chromium在执行某些操作时,就会首先通过函数GetContentClient获得上述Content Client,然后再通过它询问浏览层是否需要接管该操作。例如,在我们这个场景中,RenderFrameImpl类的成员函数createPlugin会先询问浏览器层是否需要接管创建插件实例的操作。如果浏览器层需要接管,那么它就负责根据指定的参数创建一个相应的插件实现,这时候RenderFrameImpl类的成员函数createPlugin就什么也不用做。

浏览器层设置到Content层的Content Client包含有两个部分,一部分称为Browser端,另一部分称为Renderer端。其中,Browser端运行在Chromium的Browser进程中,负责接管Chromium的Browser进程的某些操作,而Renderer端运行在Chromium的Render进程中,负责接管Chromium的Render进程的某些操作。从这里我们可以看到,RenderFrameImpl类的成员函数createPlugin是通过调用浏览器层设置到Content层的Content Client的Renderer端的成员函数OverrideCreatePlugin来询问浏览层是否需要接管创建插件实例的操作的。

假设浏览层不接管创建插件实例的操作,那么RenderFrameImpl类的成员函数createPlugin接下来判断embed标签的type属性值是否等于kBrowserPluginMimeType,即"application/browser-plugin"。如果等于的话,那么就意味着要创建一个Browser Plugin,这是通过调用一个Browser Plugin Manager的成员函数CreateBrowserPlugin创建的。Browser Plugin的作用类似于HTML中的iframe,是用来在当前网页中内嵌另外一个网页的,更详细的信息可以参考这里:CEF3 Enable support for the new browser plugin。注意,Browser Plugin是由Chromium提供实现支持的,而不是某一个Plugin。

假设embed标签的type属性值不等于kBrowserPluginMimeType,那么RenderFrameImpl类的成员函数createPlugin接下来向Browser进程发送一个类型为FrameHostMsg_GetPluginInfo的IPC消息,用来获取要创建的插件实例的更详细的信息,实际上就是通过embed标签的type属性值在已安装的插件列表找到一个对应的插件。

如果没有找到与embed标签的type属性值对应的插件,那么RenderFrameImpl类的成员函数createPlugin就什么也不做就返回了。另一方面,如果找到的插件的类型为content::WebPluginInfo::PLUGIN_TYPE_BROWSER_PLUGIN,那么同样意味要创建一个Browser Plugin,因此这时候也会调用Browser Plugin Manager的成员函数CreateBrowserPlugin创建一个Browser Plugin。

假设找到了一个对应的插件,并且该插件的类型不是content::WebPluginInfo::PLUGIN_TYPE_BROWSER_PLUGIN,那么RenderFrameImpl类的成员函数createPlugin接下来就会调用另外一个成员函数CreatePlugin创建一个插件实现,如下所示:

blink::WebPlugin* RenderFrameImpl::CreatePlugin(
        blink::WebFrame* frame,
        const WebPluginInfo& info,
        const blink::WebPluginParams& params) {
      DCHECK_EQ(frame_, frame);
    #if defined(ENABLE_PLUGINS)
      bool pepper_plugin_was_registered = false;
      scoped_refptr<PluginModule> pepper_module(PluginModule::Create(
          this, info, &pepper_plugin_was_registered));
      if (pepper_plugin_was_registered) {
        if (pepper_module.get()) {
          return new PepperWebPluginImpl(pepper_module.get(), params, this);
        }
      }
      ......
      // TODO(jam): change to take RenderFrame.
      return new WebPluginImpl(frame, params, info.path, render_view_, this);
    #endif
    #else
      return NULL;
    #endif
    }

这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。

RenderFrameImpl类的成员函数CreatePlugin首先是调用PluginModule类的成员函数Create查询要创建的插件是否是一个PPAPI插件。如果是的话,那么PluginModule类的成员函数Create会将输出参数pepper_plugin_was_registered的值设置为true,并且会返回一个PluginModule对象,用来描述PPAPI插件所在的模块,最后这个PluginModule对象将会被封装在一个PepperWebPluginImpl对象,并且返回给调用者。这意味着PPAPI插件是通过PepperWebPluginImpl类来描述的。

另一方面,如果PluginModule类的成员函数Create将输出参数pepper_plugin_was_registered的值设置为false,那么就意味着要创建的是一个NPAPI插件。NPAPI插件通过WebPluginImpl类来描述,因此在这种情况下,RenderFrameImpl类的成员函数CreatePlugin就创建一个WebPluginImpl对象返回给调用者。

接下来我们继续分析PluginModule类的成员函数Create的实现,如下所示:

scoped_refptr<PluginModule> PluginModule::Create(
        RenderFrameImpl* render_frame,
        const WebPluginInfo& webplugin_info,
        bool* pepper_plugin_was_registered) {
      *pepper_plugin_was_registered = true;

      // See if a module has already been loaded for this plugin.
      base::FilePath path(webplugin_info.path);
      scoped_refptr<PluginModule> module =
          PepperPluginRegistry::GetInstance()->GetLiveModule(path);
      if (module.get()) {
        if (!module->renderer_ppapi_host()) {
          // If the module exists and no embedder state was associated with it,
          // then the module was one of the ones preloaded and is an in-process
          // plugin. We need to associate our host state with it.
          CreateHostForInProcessModule(render_frame, module.get(), webplugin_info);
        }
        return module;
      }

      // In-process plugins will have always been created up-front to avoid the
      // sandbox restrictions. So getting here implies it doesn't exist or should
      // be out of process.
      const PepperPluginInfo* info =
          PepperPluginRegistry::GetInstance()->GetInfoForPlugin(webplugin_info);
      if (!info) {
        *pepper_plugin_was_registered = false;
        return scoped_refptr<PluginModule>();
      } else if (!info->is_out_of_process) {
        // In-process plugin not preloaded, it probably couldn't be initialized.
        return scoped_refptr<PluginModule>();
      }

      // Out of process: have the browser start the plugin process for us.
      IPC::ChannelHandle channel_handle;
      base::ProcessId peer_pid;
      int plugin_child_id = 0;
      render_frame->Send(new ViewHostMsg_OpenChannelToPepperPlugin(
          path, &channel_handle, &peer_pid, &plugin_child_id));
      if (channel_handle.name.empty()) {
        // Couldn't be initialized.
        return scoped_refptr<PluginModule>();
      }

      ppapi::PpapiPermissions permissions(info->permissions);

      // AddLiveModule must be called before any early returns since the
      // module's destructor will remove itself.
      module = new PluginModule(info->name, path, permissions);
      PepperPluginRegistry::GetInstance()->AddLiveModule(path, module.get());

      if (!module->CreateOutOfProcessModule(render_frame,
                                            path,
                                            permissions,
                                            channel_handle,
                                            peer_pid,
                                            plugin_child_id,
                                            false))  // is_external = false
        return scoped_refptr<PluginModule>();

      return module;
    }

这个函数定义在文件external/chromium_org/content/renderer/pepper/plugin_module.cc中。

在Render进程中,所有的插件模块都是由一个PepperPluginRegistry单例管理的,这个PepperPluginRegistry单例可以通过调用PepperPluginRegistry类的静态成员函数GetInstance获得。

PluginModule类的成员函数Create每加载一个插件模块,就会创建一个PluginModule对象,并且以该插件模块的文件路径为键值,注册在上述PepperPluginRegistry单例中进行管理,这是通过调用PepperPluginRegistry类的成员函数AddLiveModule实现的。

参数webplugin_info指向的一个WebPluginInfo对象的成员变量path描述的即为当前要创建的插件所在模块的文件路径,PluginModule类的成员函数Create首先调用上述PepperPluginRegistry单例的成员函数GetLiveModule检查对应的PluginModule对象是否已经创建。如果已经创建,并且该PluginModule对象描述的插件模块已经预加载过了,那么PluginModule类的成员函数Create直接将它返回给调用者即可。从这里可以看到,同一个模块的所有插件实例具有相同的PluginModule对象,这是因为同一种插件的所有实现都是在同一个插件进程中运行的。

每一个已经预加载过的插件模块对应的PluginModel对象都关联有一个RendererPpapiHostImpl对象,这个RendererPpapiHostImpl对象可以通过调用PluginModel类的在成员函数renderer_ppapi_host获得。有时候一个插件模块已经预加载,但是还没有关联一个RendererPpapiHostImpl对象。这种情况只会出现在Render进程中运行的插件中,这时候PluginModule类的成员函数Create就会调用一个全局函数CreateHostForInProcessModule为该进程内运行的插件模块创建一个RendererPpapiHostImpl对象。该RendererPpapiHostImpl对象以后将用来与插件实例进行通信。

接下来,PluginModule类的成员函数Create要处理的是参数webplugin_info描述的插件模块还没有预加载的情况,如下所示:

1. 调用Render进程中的PepperPluginRegistry单例的成员函数GetInfoForPlugin获得要加载的插件模块的信息。这个插件模块信息使用一个PepperPluginInfo对象来描述。如果获取不到对应的插件模块信息,也就是获取不到一个PepperPluginInfo对象对象,那么就说明不能够为参数webplugin_info指定的信息创建一个PPAPI插件。这时候就会将输出参数pepper_plugin_was_registered的值设置为false,以便调用者可以尝试为参数webplugin_info指定的信息创建一个NPAPI插件。

2. 如果获取到了对应的插件模块信息,但是该信息表示这是一个运行Render进程内部的插件模块,那么就直接返回一个默认构造的PluginModule对象,表示对应的插件模块还没有预加载。

再接下来,PluginModule类的成员函数Create要处理的是参数webplugin_info描述的插件模块需要运行在独立的插件进程的情况,如下所示:

1. 调用参数render_frame描述的一个RenderFrameImpl对象的成员函数Send向Browser进程发送一个类型为ViewHostMsg_OpenChannelToPepperPlugin的IPC消息,请求Browser进程返回一个到插件进程的Plugin通道。这时候如果插件进程还没有启动,那么需要先启动该插件进程。Plugin通道与前面Chromium的Render进程启动过程分析Chromium的GPU进程启动过程分析两篇文章描述的IPC通道和GPU通道一样,都是通过UNIX Socket实现的。类型为ViewHostMsg_OpenChannelToPepperPlugin的IPC消息是一个同步IPC消息,Browser进程对其进行处理之后,返回来一个ChannelHandle对象,该ChannelHandle对象描述了一个UNIX Socket的Client端文件描述符。

2. 完成上一步之后,插件模块就完成预加载了,这时候就需要创建一个PluginModule对象,并且交给Render进程中的PepperPluginRegistry单例进行管理,这是通过调用它的成员函数AddLiveModule实现的。

3. 调用上一步创建的PluginModule对象的成员函数CreateOutOfProcessModule为其关联一个RendererPpapiHostImpl对象,并且将前面获得的UNIX Socket的Client端文件描述符封装在一个Client端Plugin通道中。

4. 将上述PluginModule对象返回给调用者。

从前面的分析就可以知道,对于在Render进程内运行的PPAPI插件模块,它所关联的RendererPpapiHostImpl对象是通过调用全局函数CreateHostForInProcessModule设置的,而对于在独立插件进程运行的PPAPI模块,它所关联的RendererPpapiHostImpl对象是通过调用PluginModule类的成员函数CreateOutOfProcessModule设置的,接下来我们就分别分析它们的实现,以便可以了解在Render进程内运行和在独立插件进程运行的插件的区别。

全局函数CreateHostForInProcessModule的实现如下所示:

void CreateHostForInProcessModule(RenderFrameImpl* render_frame,
                                      PluginModule* module,
                                      const WebPluginInfo& webplugin_info) {
      ......

      ppapi::PpapiPermissions perms(PepperPluginRegistry::GetInstance()
                                        ->GetInfoForPlugin(webplugin_info)
                                        ->permissions);
      RendererPpapiHostImpl* host_impl =
          RendererPpapiHostImpl::CreateOnModuleForInProcess(module, perms);
      ......
    }

这个函数定义在文件external/chromium_org/content/renderer/pepper/plugin_module.cc中。

全局函数CreateHostForInProcessModule首先是获得参数module指向的一个PluginModel对象所描述的插件模块对应的权限信息,接着再调用RendererPpapiHostImpl类的静态成员函数CreateOnModuleForInProcess为其关联一个RendererPpapiHostImpl对象,如下所示:

RendererPpapiHostImpl* RendererPpapiHostImpl::CreateOnModuleForInProcess(
        PluginModule* module,
        const ppapi::PpapiPermissions& permissions) {
      DCHECK(!module->renderer_ppapi_host());
      RendererPpapiHostImpl* result =
          new RendererPpapiHostImpl(module, permissions);

      // Takes ownership of pointer.
      module->SetRendererPpapiHost(scoped_ptr<RendererPpapiHostImpl>(result));

      return result;
    }

这个函数定义在文件external/chromium_org/content/renderer/pepper/renderer_ppapi_host_impl.cc中。

RendererPpapiHostImpl类的静态成员函数CreateOnModuleForInProcess的实现很简单,它只是创建了一个RendererPpapiHostImpl对象,然后调用PluginModel类的成员函数SetRendererPpapiHost将其关联给参数module描述的PluginModel对象。

接下来我们再分析给运行在独立插件进程中的PPAPI插件模块关联RendererPpapiHostImpl对象的过程,即PluginModule类的成员函数CreateOutOfProcessModule的实现,如下所示:

RendererPpapiHostImpl* PluginModule::CreateOutOfProcessModule(
        RenderFrameImpl* render_frame,
        const base::FilePath& path,
        ppapi::PpapiPermissions permissions,
        const IPC::ChannelHandle& channel_handle,
        base::ProcessId peer_pid,
        int plugin_child_id,
        bool is_external) {
      scoped_refptr<PepperHungPluginFilter> hung_filter(new PepperHungPluginFilter(
          path, render_frame->GetRoutingID(), plugin_child_id));
      scoped_ptr<HostDispatcherWrapper> dispatcher(new HostDispatcherWrapper(
          this, peer_pid, plugin_child_id, permissions, is_external));
      if (!dispatcher->Init(
              channel_handle,
              &GetInterface,
              ppapi::Preferences(render_frame->render_view()->webkit_preferences()),
              hung_filter.get()))
        return NULL;

      RendererPpapiHostImpl* host_impl =
          RendererPpapiHostImpl::CreateOnModuleForOutOfProcess(
              this, dispatcher->dispatcher(), permissions);
      ......

      return host_impl;
    }

这个函数定义在文件external/chromium_org/content/renderer/pepper/plugin_module.cc中。

PluginModule类的成员函数CreateOutOfProcessModule首先是创建了一个HostDispatcherWrapper对象,接着调用该HostDispatcherWrapper对象的成员函数Init对其进行初始化,如下所示:

bool HostDispatcherWrapper::Init(const IPC::ChannelHandle& channel_handle,
                                     PP_GetInterface_Func local_get_interface,
                                     const ppapi::Preferences& preferences,
                                     PepperHungPluginFilter* filter) {
      ......

      dispatcher_.reset(new ppapi::proxy::HostDispatcher(
          module_->pp_module(), local_get_interface, filter, permissions_));

      if (!dispatcher_->InitHostWithChannel(dispatcher_delegate_.get(),
                                            peer_pid_,
                                            channel_handle,
                                            true,  // Client.
                                            preferences)) {
        ......
      }

      ......

      return true;
    }

这个函数定义在文件external/chromium_org/content/renderer/pepper/host_dispatcher_wrapper.cc中。

HostDispatcherWrapper类的成员函数Init首先是创建了一个图1所示的HostDispatcher对象,接着再调用该HostDispatcher对象的成员函数InitHostWithChannel将保存在参数channel_handle中的UNIX Socket的Client端文件描述符封装在一个Client端Plugin通道中,如下所示:

bool HostDispatcher::InitHostWithChannel(
        Delegate* delegate,
        base::ProcessId peer_pid,
        const IPC::ChannelHandle& channel_handle,
        bool is_client,
        const ppapi::Preferences& preferences) {
      if (!Dispatcher::InitWithChannel(delegate, peer_pid, channel_handle,
                                       is_client))
        return false;

      ......

      return true;
    }

这个函数定义在文件external/chromium_org/ppapi/proxy/host_dispatcher.cc中。

HostDispatcher类是从Dispatcher类继承下来的。HostDispatcher类的成员函数InitHostWithChannel主要做的事情就是调用父类Dispatcher的成员函数InitWithChannel将保存在参数channel_handle中的UNIX Socket的Client端文件描述符封装在一个Client端Plugin通道中。

Dispatcher类又是从ProxyChannel类继承下来的,如下所示:

class PPAPI_PROXY_EXPORT Dispatcher : public ProxyChannel {
      ......
    };

这个类定义在文件external/chromium_org/ppapi/proxy/dispatcher.h中。

Dispatcher类继承了父类ProxyChannel的成员函数,因此在HostDispatcher类的成员函数InitHostWithChannel中,实际上调用的是ProxyChannel类的成员函数InitWithChannel将保存在参数channel_handle中的UNIX Socket的Client端文件描述符封装在一个Client端Plugin通道中。

ProxyChannel类的成员函数InitWithChannel的实现如下所示:

bool ProxyChannel::InitWithChannel(Delegate* delegate,
                                       base::ProcessId peer_pid,
                                       const IPC::ChannelHandle& channel_handle,
                                       bool is_client) {
      delegate_ = delegate;
      peer_pid_ = peer_pid;
      IPC::Channel::Mode mode = is_client
          ? IPC::Channel::MODE_CLIENT
          : IPC::Channel::MODE_SERVER;
      channel_ = IPC::SyncChannel::Create(channel_handle, mode, this,
                                          delegate->GetIPCMessageLoop(), true,
                                          delegate->GetShutdownEvent());
      return true;
    }

这个函数定义在文件external/chromium_org/ppapi/proxy/proxy_channel.cc中。

从这里可以看到,ProxyChannel类的成员函数InitWithChannel通过调用我们在前面Chromium的Render进程启动过程分析一文分析的IPC::SyncChannel类的静态成员函数Create创建一个IPC通道作为Plugin通道。从前面的调用过程又可以知道,参数is_client的值等于true,因此这里创建的Plugin通道是一个Client端Plugin通道。这个Client端Plugin通道以后就负责在Render进程中向插件进程发送IPC消息或者从插件进程中接收IPC消息。

这一步执行完成之后,返回到前面分析的PluginModule类的成员函数CreateOutOfProcessModule中,它创建了一个Client端Plugin通道之后,接下来就调用RendererPpapiHostImpl类的静态成员函数CreateOnModuleForOutOfProcess为在独立插件进程中运行的插件模块关联一个RendererPpapiHostImpl对象,如下所示:

RendererPpapiHostImpl* RendererPpapiHostImpl::CreateOnModuleForOutOfProcess(
        PluginModule* module,
        ppapi::proxy::HostDispatcher* dispatcher,
        const ppapi::PpapiPermissions& permissions) {
      DCHECK(!module->renderer_ppapi_host());
      RendererPpapiHostImpl* result =
          new RendererPpapiHostImpl(module, dispatcher, permissions);

      // Takes ownership of pointer.
      module->SetRendererPpapiHost(scoped_ptr<RendererPpapiHostImpl>(result));

      return result;
    }

这个函数定义在文件external/chromium_org/content/renderer/pepper/renderer_ppapi_host_impl.cc中。

从这里就可以看到,在Render进程内运行的插件模块与在独立插件进程中运行的插件模块所关联的RendererPpapiHostImpl对象的最大区别在于,前者没有包含一个HostDispatcher对象,而后者包含有一个HostDispatcher对象,这是因为Render进程需要通过后者与插件进程执行IPC。

接下来,我们继续分析Browser进程处理类型为ViewHostMsg_OpenChannelToPepperPlugin的IPC消息的过程。

在前面Chromium的Render进程启动过程分析一文中提到,Browser进程在启动Render进程之前,即在RenderProcessHostImpl类的成员函数Init中,会调用RenderProcessHostImpl类的另外一个成员函数CreateMessageFilters注册一系列的Filter,用来过滤从其它进程发送过来的IPC消息,如下所示:

void RenderProcessHostImpl::CreateMessageFilters() {
      ......

      scoped_refptr<RenderMessageFilter> render_message_filter(
          new RenderMessageFilter(
              GetID(),
    #if defined(ENABLE_PLUGINS)
              PluginServiceImpl::GetInstance(),
    #else
              NULL,
    #endif
              GetBrowserContext(),
              GetBrowserContext()->GetRequestContextForRenderProcess(GetID()),
              widget_helper_.get(),
              audio_manager,
              media_internals,
              storage_partition_impl_->GetDOMStorageContext()));
      AddFilter(render_message_filter.get());

      ......
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。

其中的一个Filter为RenderMessageFilter,它用来处理从Render进程发送过来的、与插件操作相关的IPC消息,如下所示:

bool RenderMessageFilter::OnMessageReceived(const IPC::Message& message) {
      bool handled = true;
      IPC_BEGIN_MESSAGE_MAP(RenderMessageFilter, message)
        ......
        IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_OpenChannelToPepperPlugin,
                                        OnOpenChannelToPepperPlugin)
        ......
        IPC_MESSAGE_UNHANDLED(handled = false)
      IPC_END_MESSAGE_MAP()

      return handled;
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_message_filter.cc中。

从这里可以看到,从Render进程发送过来的类型为ViewHostMsg_OpenChannelToPepperPlugin的IPC消息由RenderMessageFilter类的成员函数OnOpenChannelToPepperPlugin处理,如下所示:

void RenderMessageFilter::OnOpenChannelToPepperPlugin(
        const base::FilePath& path,
        IPC::Message* reply_msg) {
      plugin_service_->OpenChannelToPpapiPlugin(
          render_process_id_,
          path,
          profile_data_directory_,
          new OpenChannelToPpapiPluginCallback(this, resource_context_, reply_msg));
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_message_filter.cc中。

RenderMessageFilter类的成员变量plugin_service_指向的是一个PluginServiceImpl对象,RenderMessageFilter类的成员函数OnOpenChannelToPepperPlugin调用它的成员函数OpenChannelToPpapiPlugin创建一个到由参数path指定的插件模块进程的Plugin通道。

传递给PluginServiceImpl类的成员函数OpenChannelToPpapiPlugin的最后一个参数是一个OpenChannelToPpapiPluginCallback对象,当要求的Plugin通道创建完成之后,该OpenChannelToPpapiPluginCallback对象的成员函数OnPpapiChannelOpened就会被调用,用来将创建出来的Plugin通道信息返回给Render进程。

PluginServiceImpl类的成员函数OpenChannelToPpapiPlugin的实现如下所示:

void PluginServiceImpl::OpenChannelToPpapiPlugin(
        int render_process_id,
        const base::FilePath& plugin_path,
        const base::FilePath& profile_data_directory,
        PpapiPluginProcessHost::PluginClient* client) {
      PpapiPluginProcessHost* plugin_host = FindOrStartPpapiPluginProcess(
          render_process_id, plugin_path, profile_data_directory);
      if (plugin_host) {
        plugin_host->OpenChannelToPlugin(client);
      } 

      ......
    }

这个函数定义在文件external/chromium_org/content/browser/plugin_service_impl.cc中。

PluginServiceImpl类的成员函数OpenChannelToPpapiPlugin首先调用另外一个成员函数FindOrStartPpapiPluginProcess检查由参数plugin_path指定的插件进程是否已经启动起来了。如果已经启动起来,那么就会得到一个PpapiPluginProcessHost对象。否则的话,就需要创建一个新的PpapiPluginProcessHost对象,并且启动由参数plugin_path指定的插件进程。

完成上述操作后,PluginServiceImpl类的成员函数OpenChannelToPpapiPlugin就可以确保所需要的插件进程已经启动,并且得到一个对应的PpapiPluginProcessHost对象,最后调用该PpapiPluginProcessHost对象的成员函数OpenChannelToPlugin就可以请求其描述的插件进程创建一个Plugin通道。

接下来,我们首先分析插件进程的启动过程,即PluginServiceImpl类的成员函数FindOrStartPpapiPluginProcess的实现,接着再分析请求插件进程创建Plugin通道的过程,即PpapiPluginProcessHost类的成员函数OpenChannelToPlugin。

PluginServiceImpl类的成员函数FindOrStartPpapiPluginProcess的实现如下所示:

PpapiPluginProcessHost* PluginServiceImpl::FindOrStartPpapiPluginProcess(
        int render_process_id,
        const base::FilePath& plugin_path,
        const base::FilePath& profile_data_directory) {
      ......

      PpapiPluginProcessHost* plugin_host =
          FindPpapiPluginProcess(plugin_path, profile_data_directory);
      if (plugin_host)
        return plugin_host;

      PepperPluginInfo* info = GetRegisteredPpapiPluginInfo(plugin_path);
      ......

      // This plugin isn't loaded by any plugin process, so create a new process.
      plugin_host = PpapiPluginProcessHost::CreatePluginHost(
          *info, profile_data_directory);
      ......

      return plugin_host;
    }

这个函数定义在文件external/chromium_org/content/browser/plugin_service_impl.cc中。

PluginServiceImpl类的成员函数FindOrStartPpapiPluginProcess首先是调用另外一个函数FindPpapiPluginProcess检查参数plugin_path描述的插件进程是否已经启动。如果已经启动,那么就会得到一个PpapiPluginProcessHost对象。这时候直接将获得的PpapiPluginProcessHost对象返回给调用者即可。

假设参数plugin_path描述的插件进程还没有启动,这时候PluginServiceImpl类的成员函数FindOrStartPpapiPluginProcess首先调用另外一个成员函数GetRegisteredPpapiPluginInfo获由参数plugin_path描述的插件更多信息,这些信息通过一个PepperPluginInfo对象描述,接着以该PepperPluginInfo对象为参数,调用PpapiPluginProcessHost类的静态成员函数CreatePluginHost创建一个PpapiPluginProcessHost对象,并且启动一个插件进程。创建出来的PpapiPluginProcessHost对象最后会返回给调用者,以便调用者可以通过它来请求已经启动起来的插件进程创建一个Plugin通道。

PpapiPluginProcessHost类的静态成员函数CreatePluginHost的实现如下所示:

PpapiPluginProcessHost* PpapiPluginProcessHost::CreatePluginHost(
        const PepperPluginInfo& info,
        const base::FilePath& profile_data_directory) {
      PpapiPluginProcessHost* plugin_host = new PpapiPluginProcessHost(
          info, profile_data_directory);
      ......
      if (plugin_host->Init(info))
        return plugin_host;

      ......
      return NULL;
    }

这个函数定义在文件external/chromium_org/content/browser/ppapi_plugin_process_host.cc中。

PpapiPluginProcessHost类的静态成员函数CreatePluginHost首先是创建了一个PpapiPluginProcessHost对象,接着调用该PpapiPluginProcessHost对象的成员函数Init启动一个插件进程。

PpapiPluginProcessHost对象的创建过程如下所示:

PpapiPluginProcessHost::PpapiPluginProcessHost(
        const PepperPluginInfo& info,
        const base::FilePath& profile_data_directory)
        : profile_data_directory_(profile_data_directory),
          is_broker_(false) {
      ......

      process_.reset(new BrowserChildProcessHostImpl(
          PROCESS_TYPE_PPAPI_PLUGIN, this));

      ......
    }

这个函数定义在文件external/chromium_org/content/browser/ppapi_plugin_process_host.cc中。

除了Render进程可以请求Browser进程启动插件进程之外,插件进程也可以请求Browser进程启动一个特权插件进程。后一种情况是插件进程通过调用Pepper API接口PPB_BrokerTrusted完成的,目的是为了可以执行一些特权操作。特权插件进程加载的模块文件与请求启动它的插件进程加载的模块文件是一样的,不过两者调用了模块文件中的不同函数作为入口点。当特权插件进程启动起来之后,请求启动它的插件进程会获得一个能够与它进行通信的通道,这样后者就可以请求前者执行特权操作。不过有一点需要注意的是,插件在调用Pepper API接口PPB_BrokerTrusted请求Browser进程创建特权插件进程的时候,浏览器会在Info Bar向用户提示,只有用户同意之后,该接口才可以成功调用。

Browser进程同样是通过一个PpapiPluginProcessHost对象来启动特权插件进程的,不过该PpapiPluginProcessHost对象是通过以下构造函数创建的,如下所示:

PpapiPluginProcessHost::PpapiPluginProcessHost()
        : is_broker_(true) {
      process_.reset(new BrowserChildProcessHostImpl(
          PROCESS_TYPE_PPAPI_BROKER, this));

      ......
    }

这个函数定义在文件external/chromium_org/content/browser/ppapi_plugin_process_host.cc中。

从这里可以看到,用来描述普通插件进程和特权插件进程的PpapiPluginProcessHost对象的主要区别在于它们的成员变量isbroker,前者的值等于false,而后者的值等于true。这个成员变量的值最后会作为启动参数,传递给插件进程,以便插件进程在启动的时候可以知道自己是不是一个特权进程。

此外,PpapiPluginProcessHost类还有另外一个成员变量process_,它指向的是一个BrowserChildProcessHostImpl对象,这个BrowserChildProcessHostImpl对象是用来启动插件进程的。

回到PpapiPluginProcessHost类的静态成员函数CreatePluginHost中,当它创建了一个PpapiPluginProcessHost对象之后,接下来就会调用它的成员函数Init启动一个插件进程,如下所示:

bool PpapiPluginProcessHost::Init(const PepperPluginInfo& info) {
      plugin_path_ = info.path;
      ......

      std::string channel_id = process_->GetHost()->CreateChannel();
      ......

      CommandLine* cmd_line = new CommandLine(exe_path);
      cmd_line->AppendSwitchASCII(switches::kProcessType,
                                  is_broker_ ? switches::kPpapiBrokerProcess
                                             : switches::kPpapiPluginProcess);
      cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
      ......

      process_->Launch(
          new PpapiPluginSandboxedProcessLauncherDelegate(is_broker_,
                                                          info,
                                                          process_->GetHost()),
          cmd_line);
      return true;
    }

这个函数定义在文件external/chromium_org/content/browser/ppapi_plugin_process_host.cc中。

PpapiPluginProcessHost类的成员函数Init首先将要在启动的插件进程中加载的插件模块文件路径保存在成员变量plugin_path_中,等到插件进程启动起来之后,Browser进程再将该插件模块文件路径作为参数发送给它,以便后者可以进行加载。

PpapiPluginProcessHost类的成员函数Init接下来调用成员变量process_指向的一个BrowserChildProcessHostImpl对象的成员函数GetHost获得一个ChildProcessHostImpl对象,并且调用该ChildProcessHostImpl对象的成员函数CreateChannel创建一个IPC通道。这个创建过程可以参考前面Chromium的GPU进程启动过程分析一文。创建出来的IPC通道是用来在Browser进程与插件进程之间执行通信的,它的底层实现是一个UNIX Socket通道,ChildProcessHostImpl类的成员函数CreateChannel最后会返回该UNIX Socket的名称给PpapiPluginProcessHost类的成员函数Init,以便后者接下来可以将其作为命令参数中的一个switches::kProcessChannelID选项值传递给即将要启动的插件进程。

同时传递给要启动的插件进程的命令行参数还包括另外一个switches::kProcessType选项。从前面的分析就可以知道,当启动的插件进程是一个特权插件进程时,该选项的值等于switches::kPpapiBrokerProcess,否则的话,就等于switches::kPpapiPluginProcess。

PpapiPluginProcessHost类的成员函数Init最后调用成员变量process_指向的一个BrowserChildProcessHostImpl对象的成员函数Launch启动了一个插件进程。从前面Chromium的GPU进程启动过程分析一文可以知道,BrowserChildProcessHostImpl对象的成员函数Launch是通过ChildProcessLauncher类来启动一个Service进程,这个过程又可以参考前面Chromium的Render进程启动过程分析一文。

从前面Chromium的Render进程启动过程分析一文还可以知道,当前进程,也就是Browser进程,会将前面在ChildProcessHostImpl类的成员函数CreateChannel中创建的一个UNIX Socket的Client端文件描述符通过Binder IPC传递给上述Service进程,以便后者可以创建一个Client端IPC通道与Browser进程进行通信。最后,上述Service进程会通过JNI调用Native层的函数RunNamedProcessTypeMain,后者的实现如下所示:

int RunNamedProcessTypeMain(    
        const std::string& process_type,    
        const MainFunctionParams& main_function_params,    
        ContentMainDelegate* delegate) {    
      static const MainFunction kMainFunctions[] = {    
    #if !defined(CHROME_MULTIPLE_DLL_CHILD)    
        { "",                            BrowserMain },    
    #endif    
    #if !defined(CHROME_MULTIPLE_DLL_BROWSER)    
    #if defined(ENABLE_PLUGINS)    
    #if !defined(OS_LINUX)    
        { switches::kPluginProcess,      PluginMain },    
    #endif    
        { switches::kWorkerProcess,      WorkerMain },    
        { switches::kPpapiPluginProcess, PpapiPluginMain },    
        { switches::kPpapiBrokerProcess, PpapiBrokerMain },    
    #endif  // ENABLE_PLUGINS    
        { switches::kUtilityProcess,     UtilityMain },    
        { switches::kRendererProcess,    RendererMain },    
        { switches::kGpuProcess,         GpuMain },    
    #endif  // !CHROME_MULTIPLE_DLL_BROWSER    
      };    

      ......    

      for (size_t i = 0; i < arraysize(kMainFunctions); ++i) {    
        if (process_type == kMainFunctions[i].name) {    
          if (delegate) {    
            int exit_code = delegate->RunProcess(process_type,    
                main_function_params);    
    #if defined(OS_ANDROID)    
            // In Android's browser process, the negative exit code doesn't mean the    
            // default behavior should be used as the UI message loop is managed by    
            // the Java and the browser process's default behavior is always    
            // overridden.    
            if (process_type.empty())    
              return exit_code;    
    #endif    
            if (exit_code >= 0)    
              return exit_code;    
          }    
          return kMainFunctions[i].function(main_function_params);    
        }    
      }    

      ......    
    }    

这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

在前面Chromium的Render进程启动过程分析一文中,我们已经分析过函数RunNamedProcessTypeMain的实现了,不过这时候它的参数process_type的值等于switches::kPpapiPluginProcess,也就是来自于前面提到的命令行参数中的switches::kProcessType选项,因此接下来函数RunNamedProcessTypeMain将调用函数PpapiPluginMain作为GPU进程的入口函数。

函数PpapiPluginMain的实现如下所示:

int PpapiPluginMain(const MainFunctionParams& parameters) {
      const CommandLine& command_line = parameters.command_line;
      ......

      base::MessageLoop main_message_loop;
      ......

      ChildProcess ppapi_process;
      ppapi_process.set_main_thread(
          new PpapiThread(parameters.command_line, false));  // Not a broker.

      ......

      main_message_loop.Run();
      return 0;
    }

这个函数定义在文件external/chromium_org/content/ppapi_plugin/ppapi_plugin_main.cc中。

函数PpapiPluginMain首先在当前线程创建一个默认消息循环,最后它会通过该消息循环进入到运行状态。

函数PpapiPluginMain接下来又通过ChildProcess类的默认构造函数创建了一个ChildProcess对象。从前面Chromium的Render进程启动过程分析一文可以知道,ChildProcess类的默认构造函数会创建一个IO线程,在插件进程中,该IO线程是用来与其它进程(例如Render进程和Browser进程)进行IPC的。

函数PpapiPluginMain再接下来还会创建一个PpapiThread对象来描述当前线程,该PpapiThread对象的创建过程如下所示:

PpapiThread::PpapiThread(const CommandLine& command_line, bool is_broker)
        : is_broker_(is_broker),
          ...... {
      ......
    }

这个函数定义在文件external/chromium_org/content/ppapi_plugin/ppapi_thread.cc中。

从前面的调用过程可以知道,参数is_broker的值等于false,表示当前创建的PpapiThread对象是用来描述普通插件进程的主线程的。我们前面提到的特权插件进程,也是通过一个PpapiThread对象对象来描述它的主线程,不过在创建PpapiThread对象时,传递进来的参数is_broker的值等于true。PpapiThread类的构造函数将该参数保存在成员变量is_broker_中,后面在加载插件模块文件时需要使用到。

PpapiThread类是从ChildThread类继承下来的,如下所示:

class PpapiThread : public ChildThread,
                        public ppapi::proxy::PluginDispatcher::PluginDelegate,
                        public ppapi::proxy::PluginProxyDelegate {
      ......
    };

这个类定义在文件external/chromium_org/content/ppapi_plugin/ppapi_thread.h中。

因此,在前面分析的PpapiThread类的构造函数中,会调用父类ChildThread的默认构造函数创建一个与Browser进程进行通信的IPC通道,如下所示:

ChildThread::ChildThread()
        : router_(this),
          channel_connected_factory_(this),
          in_browser_process_(false) {
      channel_name_ = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          switches::kProcessChannelID);
      Init();
    }

这个函数定义在文件external/chromium_org/content/child/child_thread.cc中。

ChildThread类的默认构造函数首先通过命令行参数的switches::kProcessChannelID选项获得由Browser进程创建的UNIX Socket的名称,并且保存在成员变量channel_name_中,接下来再调用另外一个成员函数Init创建一个Client端IPC通道,这个Client端IPC通道的对端是前面在Browser进程中创建的Server端IPC通道,这样以后插件进程和Browser进程就可以进行IPC了。关于ChildThread类的成员函数Init的实现,可以参考前面Chromium的Render进程启动过程分析一文。

这一步执行完成之后,插件进程的启动过程就完成了,回到前面分析的PluginServiceImpl类的成员函数OpenChannelToPpapiPlugin中,它接下来PpapiPluginProcessHost类的成员函数OpenChannelToPlugin请求刚刚启动起来的插件进程创建一个Plugin通道,它的实现如下所示:

void PpapiPluginProcessHost::OpenChannelToPlugin(Client* client) {
      if (process_->GetHost()->IsChannelOpening()) {
        // The channel is already in the process of being opened.  Put
        // this "open channel" request into a queue of requests that will
        // be run once the channel is open.
        pending_requests_.push_back(client);
        return;
      }

      // We already have an open channel, send a request right away to plugin.
      RequestPluginChannel(client);
    }

这个函数定义在文件external/chromium_org/content/browser/ppapi_plugin_process_host.cc中。

前面提到,PpapiPluginProcessHost类的成员变量process_指向的是一个BrowserChildProcessHostImpl对象,调用该BrowserChildProcessHostImpl对象的成员函数GetHost获得的是一个ChildProcessHostImpl对象。在插件进程与Browser进程之间的IPC通道还没有建立连接之前,调用该ChildProcessHostImpl对象的成员函数IsChannelOpening得到的返回值等于true。这时候PpapiPluginProcessHost类的成员函数OpenChannelToPlugin将参数client描述的一个OpenChannelToPpapiPluginCallback对象保存在成员变量pending_requests_描述的一个std::vector中。

另一方面,如果在调用PpapiPluginProcessHost类的成员函数OpenChannelToPlugin时,插件进程与Browser进程之间的IPC通道已经建立连接,那么PpapiPluginProcessHost类的成员函数OpenChannelToPlugin就会马上调用另外一个成员函数RequestPluginChannel请求插件进程创建一个Plugin通道。

假设此时插件进程与Browser进程之间的IPC通道还没有建立连接。从前面Chromium的Render进程启动过程分析一文可以知道,等到插件进程与Browser进程之间的IPC通道建立连接的时候,PpapiPluginProcessHost类的成员函数OnChannelConnected将会被调用,它的实现如下所示:

// Called when the browser <--> plugin channel has been established.
    void PpapiPluginProcessHost::OnChannelConnected(int32 peer_pid) {
      // This will actually load the plugin. Errors will actually not be reported
      // back at this point. Instead, the plugin will fail to establish the
      // connections when we request them on behalf of the renderer(s).
      Send(new PpapiMsg_LoadPlugin(plugin_path_, permissions_));

      // Process all pending channel requests from the renderers.
      for (size_t i = 0; i < pending_requests_.size(); i++)
        RequestPluginChannel(pending_requests_[i]);
      pending_requests_.clear();
    }

这个函数定义在文件external/chromium_org/content/browser/ppapi_plugin_process_host.cc中。

PpapiPluginProcessHost类的成员函数OnChannelConnected首先是向刚刚启动起来的插件进程发送一个类型为PpapiMsg_LoadPlugin的IPC消息,用来请求插件进程加载由成员变量plugin_path_指定的插件模块文件。

PpapiPluginProcessHost类的成员函数OnChannelConnected接下来再遍历保存在成员变量pending_requests_描述的一个std::vector中的OpenChannelToPpapiPluginCallback对象。对于每一个OpenChannelToPpapiPluginCallback对象,都会调用另外一个成员函数RequestPluginChannel请求插件进程为其创建一个Plugin通道。

接下来,我们首先分析插件进程处理类型为PpapiMsg_LoadPlugin的IPC消息的过程,接下来再分析Browser进程请求插件进程创建Plugin通道的过程,即PpapiPluginProcessHost类的成员函数RequestPluginChannel的实现。

类型为PpapiMsg_LoadPlugin的IPC消息是一个控制类IPC消息,由在插件进程中创建的PpapiThread对象的成员函数OnControlMessageReceived处理。关于IPC消息的接收和分发过程,可以参考前面Chromium的IPC消息发送、接收和分发机制分析一文。

PpapiThread类的成员函数OnControlMessageReceived的实现如下所示:

bool PpapiThread::OnControlMessageReceived(const IPC::Message& msg) {
      bool handled = true;
      IPC_BEGIN_MESSAGE_MAP(PpapiThread, msg)
        IPC_MESSAGE_HANDLER(PpapiMsg_LoadPlugin, OnLoadPlugin)
        ......
        IPC_MESSAGE_UNHANDLED(handled = false)
      IPC_END_MESSAGE_MAP()
      return handled;
    }

这个函数定义在文件external/chromium_org/content/ppapi_plugin/ppapi_thread.cc中。

从这里可以看到,类型为PpapiMsg_LoadPlugin的IPC消息由PpapiThread类的成员函数OnLoadPlugin处理,它的实现如下所示:

void PpapiThread::OnLoadPlugin(const base::FilePath& path,
                                   const ppapi::PpapiPermissions& permissions) {
      ......

      base::ScopedNativeLibrary library;
      if (plugin_entry_points_.initialize_module == NULL) {
        // Load the plugin from the specified library.
        base::NativeLibraryLoadError error;
        library.Reset(base::LoadNativeLibrary(path, &error));
        ......

        // Get the GetInterface function (required).
        plugin_entry_points_.get_interface =
            reinterpret_cast<PP_GetInterface_Func>(
                library.GetFunctionPointer("PPP_GetInterface"));
        ......

        // The ShutdownModule/ShutdownBroker function is optional.
        plugin_entry_points_.shutdown_module =
            is_broker_ ?
            reinterpret_cast<PP_ShutdownModule_Func>(
                library.GetFunctionPointer("PPP_ShutdownBroker")) :
            reinterpret_cast<PP_ShutdownModule_Func>(
                library.GetFunctionPointer("PPP_ShutdownModule"));

        if (!is_broker_) {
          // Get the InitializeModule function (required for non-broker code).
          plugin_entry_points_.initialize_module =
              reinterpret_cast<PP_InitializeModule_Func>(
                  library.GetFunctionPointer("PPP_InitializeModule"));
          ......
        }
      }

      ......

      if (is_broker_) {
        // Get the InitializeBroker function (required).
        InitializeBrokerFunc init_broker =
            reinterpret_cast<InitializeBrokerFunc>(
                library.GetFunctionPointer("PPP_InitializeBroker"));
        ......

        int32_t init_error = init_broker(&connect_instance_func_);
        ......
      } else {
        ......

        int32_t init_error = plugin_entry_points_.initialize_module(
            local_pp_module_,
            &ppapi::proxy::PluginDispatcher::GetBrowserInterface);
        ......
      }

      ......
    }

这个函数定义在文件external/chromium_org/content/ppapi_plugin/ppapi_thread.cc中。

PpapiThread类的成员变量plugin_entry_points_指向的是一个PepperPluginInfo::EntryPoints对象,该PepperPluginInfo::EntryPoints对象通过三个成员变量保存了插件模块需要导出的三个函数:

  1. get_interface,保存由插件导出的PPP_GetInterface函数,在获取插件实现的接口时调用。

  2. shutdown_module,保存由插件导出的PPP_ShutdownModule函数,在关闭插件模块时调用。

  3. initialize_module,保存由插件导出的PPP_InitializeModule函数,在初始化插件模块时调用。

对于需要启动特权插件进程的模块,还要求导出另外两个函数PPP_ShutdownBroker和PPP_InitializeBroker,它们分别保存在上述PepperPluginInfo::EntryPoints对象的成员变量shutdown_module和initialize_module中。

有了上述背景知识后,我们就容易理解PpapiThread类的成员函数OnLoadPlugin的实现了。其中,加载模块文件是通过调用base::LoadNativeLibrary函数实现的,实际上就是调用dlopen函数来加载,而从模块文件获得相应的导出函数是通过base::ScopedNativeLibrary类的成员函数GetFunctionPointer实现的,实际上就是调用dlsym函数来获取。

最后,对于特权插件进程,最后需要调用它的导出函数PPP_InitializeBroker执行模块初始化工作,而对于普通插件进程,则需要调用它的导出函数PPP_InitializeModule执行模块初始化工作。关于插件需要实现的导出函数,以及插件调用Pepper API的过程,我们以后再专门分析。

这一步执行完成之后,插件模块在插件进程的加载过程就完成了,回到前面分析的PpapiPluginProcessHost类的成员函数OnChannelConnected中,接下来它就会调用另外一个成员函数RequestPluginChannel请求插件创建一个Plugin通道,如下所示:

void PpapiPluginProcessHost::RequestPluginChannel(Client* client) {
      base::ProcessHandle process_handle;
      int renderer_child_id;
      client->GetPpapiChannelInfo(&process_handle, &renderer_child_id);

      base::ProcessId process_id = (process_handle == base::kNullProcessHandle) ?
          0 : base::GetProcId(process_handle);

      // We can't send any sync messages from the browser because it might lead to
      // a hang. See the similar code in PluginProcessHost for more description.
      PpapiMsg_CreateChannel* msg = new PpapiMsg_CreateChannel(
          process_id, renderer_child_id, client->OffTheRecord());
      msg->set_unblock(true);
      if (Send(msg)) {
        sent_requests_.push(client);
      } else {
        client->OnPpapiChannelOpened(IPC::ChannelHandle(), base::kNullProcessId, 0);
      }
    }

这个函数定义在文件external/chromium_org/content/browser/ppapi_plugin_process_host.cc中。

PpapiPluginProcessHost类的成员函数RequestPluginChannel首先是获得Browser进程的PID以及请求创建Plugin通道的Render进程的PID,接着将两个PID封装在一个类型为PpapiMsg_CreateChannel的IPC消息中,并且发送给插件进程处理。一旦发送成功,参数client描述的一个OpenChannelToPpapiPluginCallback对象就会保存在成员变量sent_requests_描述的一个std::queue中。

接下来我们就继续分析插件进程处理类型为PpapiMsg_CreateChannel的IPC消息的过程,与前面分析的类型为PpapiMsg_LoadPlugin的IPC一样,该IPC消息也是由PpapiThread类的成员函数OnControlMessageReceived接收的,如下所示:

bool PpapiThread::OnControlMessageReceived(const IPC::Message& msg) {
      bool handled = true;
      IPC_BEGIN_MESSAGE_MAP(PpapiThread, msg)
        ......
        IPC_MESSAGE_HANDLER(PpapiMsg_CreateChannel, OnCreateChannel)
        ......
      IPC_END_MESSAGE_MAP()
      return handled;
    }

这个函数定义在文件external/chromium_org/content/ppapi_plugin/ppapi_thread.cc中。

从这里可以看到,类型为PpapiMsg_CreateChannel的IPC消息是由PpapiThread类的成员函数OnCreateChannel进行处理的,如下所示:

void PpapiThread::OnCreateChannel(base::ProcessId renderer_pid,
                                      int renderer_child_id,
                                      bool incognito) {
      IPC::ChannelHandle channel_handle;

      if (!plugin_entry_points_.get_interface ||  // Plugin couldn't be loaded.
          !SetupRendererChannel(renderer_pid, renderer_child_id, incognito,
                                &channel_handle)) {
        Send(new PpapiHostMsg_ChannelCreated(IPC::ChannelHandle()));
        return;
      }

      Send(new PpapiHostMsg_ChannelCreated(channel_handle));
    }

这个函数定义在文件external/chromium_org/content/ppapi_plugin/ppapi_thread.cc中。

只有插件模块已经成功加载的前提下,PpapiThread类的成员函数OnCreateChannel才会响应Browser进程的请求创建一个Plugin通道。这个Plugin通道的创建过程是通过调用PpapiThread类的成员函数SetupRendererChannel实现的。

Plugin通道与IPC通道一样,底层也是通过UNIX Socket实现的,因此,当PpapiThread类的成员函数OnCreateChannel调用另外一个成员函数SetupRendererChannel成功创建一个Plugin通道之后,就会将该Plugin通道底层所使用的UNIX Socket的Client端文件描述符通过一个类型为PpapiHostMsg_ChannelCreated的IPC消息返回给Browser进程。

接下来我们首先分析插件进程创建Plugin通道的过程,即PpapiThread类的成员函数SetupRendererChannel的实现,接下来再分析Browser进程处理类型为PpapiHostMsg_ChannelCreated的IPC消息的过程。

PpapiThread类的成员函数SetupRendererChannel的实现如下所示:

bool PpapiThread::SetupRendererChannel(base::ProcessId renderer_pid,
                                           int renderer_child_id,
                                           bool incognito,
                                           IPC::ChannelHandle* handle) {
      DCHECK(is_broker_ == (connect_instance_func_ != NULL));
      IPC::ChannelHandle plugin_handle;
      plugin_handle.name = IPC::Channel::GenerateVerifiedChannelID(
          base::StringPrintf(
              "%d.r%d", base::GetCurrentProcId(), renderer_child_id));

      ppapi::proxy::ProxyChannel* dispatcher = NULL;
      bool init_result = false;
      if (is_broker_) {
        BrokerProcessDispatcher* broker_dispatcher =
            new BrokerProcessDispatcher(plugin_entry_points_.get_interface,
                                        connect_instance_func_);
        init_result = broker_dispatcher->InitBrokerWithChannel(this,
                                                               renderer_pid,
                                                               plugin_handle,
                                                               false);
        dispatcher = broker_dispatcher;
      } else {
        PluginProcessDispatcher* plugin_dispatcher =
            new PluginProcessDispatcher(plugin_entry_points_.get_interface,
                                        permissions_,
                                        incognito);
        init_result = plugin_dispatcher->InitPluginWithChannel(this,
                                                               renderer_pid,
                                                               plugin_handle,
                                                               false);
        dispatcher = plugin_dispatcher;
      }

      ......

      handle->name = plugin_handle.name;
    #if defined(OS_POSIX)
      // On POSIX, transfer ownership of the renderer-side (client) FD.
      // This ensures this process will be notified when it is closed even if a
      // connection is not established.
      handle->socket = base::FileDescriptor(dispatcher->TakeRendererFD(), true);
      ......
    #endif

      // From here, the dispatcher will manage its own lifetime according to the
      // lifetime of the attached channel.
      return true;
    }

这个函数定义在文件external/chromium_org/content/ppapi_plugin/ppapi_thread.cc中。

PpapiThread类的成员函数SetupRendererChannel首先是调用IPC::Channel类的静态成员函数GenerateVerifiedChannelID生成一个UNIX Socket名称。IPC::Channel类的静态成员函数GenerateVerifiedChannelID的实现可以参考前面Chromium的Render进程启动过程分析一文。

接下来,如果当前进程是一个特权插件进程,即当前处理的PpapiThread对象的成员变量broker_的值等于true,那么PpapiThread类的成员函数SetupRendererChannel就会创建一个BrokerProcessDispatcher对象,并且调用该BrokerProcessDispatcher对象的成员函数InitBrokerWithChannel根据前面生成的UNIX Socket名称创建一个UNIX Socket,并且将该UNIX Socket封装在一个Server端Plugin通道中,用来与Render进程的Client端Plugin通道执行IPC。

另一方面,如果当前进程是一个普通插件进程,即当前处理的PpapiThread对象的成员变量broker_的值等于false,那么PpapiThread类的成员函数SetupRendererChannel就会创建一个PluginProcessDispatcher对象,并且调用该PluginProcessDispatcher对象的成员函数InitPluginWithChannel根据前面生成的UNIX Socket名称创建一个UNIX Socket,并且将该UNIX Socket封装在一个Server端Plugin通道中,用来与Render进程的Client端Plugin通道执行IPC。

最后,前面创建的UNIX Socket的名称以及Client端文件描述符将会保存在参数handle描述的一个ChannelHandle对象中,以便可以返回给Browser进程处理。

我们以普通插件进程为例,分析Server端Plugin通道的创建过程,即分析PluginProcessDispatcher类的成员函数InitPluginWithChannel的实现。

PluginProcessDispatcher类是从PluginDispatcher类继承下来的,如下所示:

class PluginProcessDispatcher : public ppapi::proxy::PluginDispatcher {
      ......
    };

这个类定义在文件external/chromium_org/content/ppapi_plugin/plugin_process_dispatcher.h中。

因此,在前面分析的PpapiThread类的成员函数SetupRendererChannel中,创建的PluginProcessDispatcher对象就是图1所示的PluginDispatcher对象。由于PluginProcessDispatcher类的成员函数InitPluginWithChannel是从父类PluginDispatcher继承下来的,因此我们接下来分析PluginDispatcher类的成员函数InitPluginWithChannel的实现,如下所示:

bool PluginDispatcher::InitPluginWithChannel(
        PluginDelegate* delegate,
        base::ProcessId peer_pid,
        const IPC::ChannelHandle& channel_handle,
        bool is_client) {
      if (!Dispatcher::InitWithChannel(delegate, peer_pid, channel_handle,
                                       is_client))
        return false;

      ......

      return true;
    }

这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_dispatcher.cc中。

PluginDispatcher类是从Dispatcher类继承下来的,PluginDispatcher类的成员函数InitPluginWithChannel调用了父类Dispatcher的成员函数InitWithChannel来创建一个Plugin通道。前面我们已经分析过Dispatcher类的成员函数InitWithChannel的实现了,因此这里不再复述。不过,有一点需要注意的是,这里调用Dispatcher类的成员函数InitWithChannel的时候,最后一个参数is_client的值指定为false,因此这里创建的是一个Server端Plugin通道。

这一步执行完成之后,回到前面分析的PpapiThread类的成员函数OnCreateChannel中,接下来它将前面创建的Server端Plugin通道使用的UNIX Socket的名称以及Client端文件描述符通过一个类型为PpapiHostMsg_ChannelCreated的IPC消息返回给Browser进程,Browser进程通过PpapiPluginProcessHost类的成员函数OnMessageReceived接收该IPC消息,如下所示:

bool PpapiPluginProcessHost::OnMessageReceived(const IPC::Message& msg) {
      bool handled = true;
      IPC_BEGIN_MESSAGE_MAP(PpapiPluginProcessHost, msg)
        IPC_MESSAGE_HANDLER(PpapiHostMsg_ChannelCreated,
                            OnRendererPluginChannelCreated)
        IPC_MESSAGE_UNHANDLED(handled = false)
      IPC_END_MESSAGE_MAP()
      DCHECK(handled);
      return handled;
    }

这个函数定义在文件external/chromium_org/content/browser/ppapi_plugin_process_host.cc中。

从这里可以看到,PpapiPluginProcessHost类的成员函数OnRendererPluginChannelCreated负责处理类型为PpapiHostMsg_ChannelCreated的IPC消息,如下所示:

void PpapiPluginProcessHost::OnRendererPluginChannelCreated(
        const IPC::ChannelHandle& channel_handle) {
      if (sent_requests_.empty())
        return;

      // All requests should be processed FIFO, so the next item in the
      // sent_requests_ queue should be the one that the plugin just created.
      Client* client = sent_requests_.front();
      sent_requests_.pop();

      const ChildProcessData& data = process_->GetData();
      client->OnPpapiChannelOpened(channel_handle, base::GetProcId(data.handle),
                                   data.id);
    }

这个函数定义在文件external/chromium_org/content/browser/ppapi_plugin_process_host.cc中。

前面在分析PpapiPluginProcessHost类的成员函数RequestPluginChannel时提到,Browser进程在向插件进程发送创建Plugin通道的IPC消息时,会将一个OpenChannelToPpapiPluginCallback对象保存在PpapiPluginProcessHost类的成员变量sent_requests_描述的一个std::queue中。

由于当PpapiPluginProcessHost类的成员函数OnRendererPluginChannelCreated被调用时,Browser进程请求插件进程创建的Plugin通道已经创建完成,并且该Plugin通道底层使用的UNIX Socket的名称以及Client端文件描述符也已经传递过来,因此PpapiPluginProcessHost类的成员函数OnRendererPluginChannelCreated就可以将前面保存的OpenChannelToPpapiPluginCallback对象取出来,并且调用它的成员函数OnPpapiChannelOpened,以及它可以将获得的UNIX Socket的名称以及Client端文件描述符返回给Render进程处理。

OpenChannelToPpapiPluginCallback类的成员函数OnPpapiChannelOpened的实现如下所示:

class OpenChannelToPpapiPluginCallback
        : public RenderMessageCompletionCallback,
          public PpapiPluginProcessHost::PluginClient {
     public:
      ......

      virtual void OnPpapiChannelOpened(const IPC::ChannelHandle& channel_handle,
                                        base::ProcessId plugin_pid,
                                        int plugin_child_id) OVERRIDE {
        ViewHostMsg_OpenChannelToPepperPlugin::WriteReplyParams(
            reply_msg(), channel_handle, plugin_pid, plugin_child_id);
        SendReplyAndDeleteThis();
      }

      ......
    }

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_message_filter.cc中。

OpenChannelToPpapiPluginCallback类的成员函数OnPpapiChannelOpened所做的事情就是回复前面从Render进程发送过来的类型为ViewHostMsg_OpenChannelToPepperPlugin的IPC消息。回复消息封装了在插件进程中创建的Server端Plugin通道使用的UNIX Socket的名称以及Client端文件描述符,因此,Render进程就可以根据它们来创建一个Client端Plugin通道,并且将这个Client端Plugin通道封装在一个HostDispatcher对象中。这个过程我们在前面已经分析过了,因此这里不再复述。

至此,我们就分析完成Chromium的插件进程的启动过程了,它是由Render进程请求Browser进程启动的,并且在启动完成之后,Browser进程会请求插件进程创建一个Plugin通道与Render进程进行边接,以后Render进程和插件进程就可以通过该Plugin通道进行IPC。

与此同时,Chromium的多进程架构我们也分析完成了,我们主要是通过Chromium的三类核心进程Render进程、GPU进程和Plugin进程的启动过程来理解Chromium的多进程架构。理解Chromium的多进程架构对于我们阅读Chromium的源码非常重要,因它可以让我们理清Chromium源码的骨骼。重新学习Chromium的多进程架构,可以从前面Chromium多进程架构简要介绍和学习计划一文开始。

接下来,基于Chromium的多进程架构,我们将继续分析Chromium的GPU渲染机制,这是Chromium渲染网页涉及到的核心模块,因此我们将重点学习,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 目录