PPAPI插件与浏览器的交互过程

上一篇理解了一下PPAPI的设计,并从代码角度理解了一下相关主题,这篇文章关注下面几点:

  • 插件实例对象的创建与使用流程
  • 实例大小的确认
  • 渲染(绘图)
  • 处理输入事件

foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。

插件实例对象的创建与使用流程

画了一个简单的调用流程图:

PPAPI插件与浏览器的交互过程_第1张图片

结合stub.c或graphics_2d_example.c的代码,应该比较容易理解。

前两步我们在上一篇文章“理解PPAPI的设计”里已经介绍了。接着往下说。

当PPP_GetInterface()被调用后,浏览器拿到了PPP_Instance接口,会调用PPP_Instance::DidCreate()来创建插件实例对象。一般我们在这个函数里会分配一些数据结构来保存新创建的实例。graphics_2d_example.c是这么实现DidCreate函数的:

PP_Bool Instance_DidCreate(PP_Instance instance,
                           uint32_t argc,
                           const char* argn[],
                           const char* argv[]) {
  struct InstanceInfo* info =
      (struct InstanceInfo*)malloc(sizeof(struct InstanceInfo));
  info->pp_instance = instance;
  info->last_size.width = 0;
  info->last_size.height = 0;
  info->graphics = 0;

  /* Insert into linked list of live instances. */
  info->next = all_instances;
  all_instances = info;
  return PP_TRUE;
}

上面的代码,仅仅是malloc了struct InstanceInfo,做了初始化,保存插件实例对象的句柄,将新实例加入一个全局的实例对象链表。

一般的插件实例对象会对应网页所在视图(view)上的一个区域,这个区域会有坐标、大小,插件绘制的内容就显示在这个区域内,用户也通过这个区域和插件交互(比如鼠标、按键、触摸等)。这个区域的大小,是在插件实例对象与view关联起来时确定的。

当插件实例对象与view关联时,PPP_Instance::DidChangeView方法会被调用。下面是graphics_2d_example中的实现:

void Instance_DidChangeView(PP_Instance pp_instance,
                            PP_Resource view) {
  struct PP_Rect position;
  struct InstanceInfo* info = FindInstance(pp_instance);
  if (!info)
    return;

  if (g_view_interface->GetRect(view, &position) == PP_FALSE)
    return;

  if (info->last_size.width != position.size.width ||
      info->last_size.height != position.size.height) {
    /* Got a resize, repaint the plugin. */
    Repaint(info, &position.size);
    info->last_size.width = position.size.width;
    info->last_size.height = position.size.height;
  }
}

上面的代码,先根据插件实例对象的句柄从全局链表找到插件实例对象,然后通过之前在PPP_InitializeModule()方法中获取到的PPP_View接口(g_view_interface)来获取由view所代表的视图(区域)的rect,再接着重绘,保存大小;

再接下来,就进入到了交互和渲染循环,直到Web页面被关闭时,PPP_Instance::DidDestroy被调用,再后来,PPP_ShutdownModule被调用。

渲染(绘图)

这里只说明PPAPI插件这一侧的渲染逻辑。实际上插件是在一块代表图像的内存上渲染,渲染完毕后通知浏览器,浏览器在合适的时候更新。

前面我们展示了graphics_2d_example中的DidChangeView函数实现。它在view尺寸变化时会调用Repaint方法来更新自己。Repaint方法里包含了基本的绘图逻辑,我们来分析一下。代码如下:

void Repaint(struct InstanceInfo* instance, const struct PP_Size* size) {
  PP_Resource image;
  struct PP_ImageDataDesc image_desc;
  uint32_t* image_data;
  int num_words, i;

  /* Ensure the graphics 2d is ready. */
  if (!instance->graphics) {
    instance->graphics = MakeAndBindGraphics2D(instance->pp_instance, size);
    if (!instance->graphics)
      return;
  }

  /* Create image data to paint into. */
  image = g_image_data_interface->Create(
      instance->pp_instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, PP_TRUE);
  if (!image)
    return;
  g_image_data_interface->Describe(image, &image_desc);

  /* Fill the image with blue. */
  image_data = (uint32_t*)g_image_data_interface->Map(image);
  if (!image_data) {
    g_core_interface->ReleaseResource(image);
    return;
  }
  num_words = image_desc.stride * size->height / 4;
  for (i = 0; i < num_words; i++)
    image_data[i] = 0xFF0000FF;

  /* Paint image to graphics 2d. */
  g_graphics_2d_interface->ReplaceContents(instance->graphics, image);
  g_graphics_2d_interface->Flush(instance->graphics,
      PP_MakeCompletionCallback(&FlushCompletionCallback, NULL));

  g_core_interface->ReleaseResource(image);
}

首先它会确保当前插件实例对象绑定了2D图形上下文。这是在MakeAndBindGraphics2D()函数内完成,代码如下:

PP_Resource MakeAndBindGraphics2D(PP_Instance instance,
                                  const struct PP_Size* size) {
  PP_Resource graphics;

  graphics = g_graphics_2d_interface->Create(instance, size, PP_FALSE);
  if (!graphics)
    return 0;

  if (!g_instance_interface->BindGraphics(instance, graphics)) {
    g_core_interface->ReleaseResource(graphics);
    return 0;
  }
  return graphics;
}

可以看到它使用了g_graphics_2d_interface接口指针(类型是PPP_Graphics2D),这个接口在PPP_InitializeModule()中使用get_browser_interface获取,名字是PPP_GRAPHICS_2D_INTERFACE。

PPP_Graphics2D操作图形上下文(2D Graphics Context)的接口,它的Create方法可以创建一个2D Graphics Context。2D图形上下文与插件实例对象绑定后,插件实例对象就可以操作图形上下文,进行渲染。

图形上下文与插件实例对象的绑定是通过PPP_Instance接口的BindGraphics方法完成的(参见ppb_instance.h)。PPP_Instance接口在PPP_InitializeModule()中使用get_browser_interface获取,名字是PPP_INSTANCE_INTERFACE。

PPP_Graphics2D能操作2D图形上下文,通知浏览器更新。但是更新的图像数据,是放在别处的(一个由句柄标识的内存区域),不在PPP_Graphics2D内。保存在内存中的图形数据,可以通过PPP_ImageData接口来操作。这个接口,同样也是在PPP_InitializeModule()中使用get_browser_interface获取,名字是PPP_IMAGEDATA_INTERFACE。接口的具体定义在ppb_image_data.h文件内。

PPP_ImageData的Create方法可以根据指定格式和大小分配一块代表图形数据的内存资源,Map方法可以把图形数据资源映射到插件模块所在的进程内,返回内存起始地址,我们可以操作内存进行绘图操作。前面的Repaint方法就是这么干的。

Repaint方法绘图完成后,调用PPP_Graphics2D的ReplaceContents方法来更新上下文,调用Flush来刷新(不调用不生效哦)。

刷新是异步的,你可以指定一个回调接口(PP_CompletionCallback,见pp_completion_callback.h),当刷新完成后,PP_CompletionCallback的func成员会被调用。有一个方便的内联函数PP_MakeCompletionCallback可以帮你创建一个PP_CompletionCallback实例,它接受一个回调函数和一个void*作为user_data。

graphics_2d_example中的回调函数FlushCompletionCallback嘛事儿没干。我们在实际开发时,可以在回调完成后继续复用我们的分配的图形资源,这样可以提高效率。

好啦,这就是绘图的基本流程了。

处理输入事件

输入事件在ppb_input_event.h中定义。看文件名开头,就明白输入事件接口是由浏览器这端提供的。

输入事件的处理也分浏览器和插件两侧。

浏览器侧实现了PPP_InputEvent接口,定义接口名字的宏是PPP_INPUT_EVENT_INTERFACE。插件可以在PPP_InitializeModule函数中通过get_browser_interface函数指针按名字获取到。

PPP_InputEvent是入口,通过这个接口,插件实例对象可以通过RequestInputEvents和RequestFilteringInputEvents两个函数来声明自己感兴趣的输入事件(鼠标、键盘、触摸、滚轮、输入法等,见ppb_input_event.h)。

浏览器发现有插件实例对象关心某一类输入事件,就会根据这个插件模块导出的PPP_GetInterface函数来获取名为PPP_INPUT_EVENT_INTERFACE的接口。PPP_GetInterface应该返回PPP_InputEvent接口。

插件侧负责实现PPP_InputEvent接口(见ppp_input_event.h),这个接口(结构体)定义了一个HandleInputEvent方法。浏览器通过调用HandleInputEvent方法把事件传递给插件实例对象。

PPP_InputEvent的HandleInputEvent函数原型如下:

PP_Bool (*HandleInputEvent)(PP_Instance instance, PP_Resource input_event);

第一个参数是插件实例对象句柄,第二个参数是输入事件句柄(32位整数)。使用PPP_InputEvent接口和输入事件句柄,可以判断事件类型、获取事件的其它信息。

但是某一类别的事件,有时需要专门的接口来获取特定的信息,比如鼠标事件对应的浏览器侧接口是PPP_MouseInputEvent,可以以PPP_MOUSE_INPUT_EVENT_INTERFACE为参数调用get_browser_interface函数指针获取。

PPP_MouseInputEvent定义了一些方法,比如GetButton可以获取是左键还是右键点击,GetPosition可以获取鼠标点击的坐标,这些方法允许我们获取完整的事件信息,根据事件信息做逻辑处理。

好啦,处理输入事件的流程就这样子了。

这次到这里,下篇我们结合绘图和交互,来完成一个简单的示例。

相关文章参考:

  • CEF Windows开发环境搭建
  • CEF加载PPAPI插件
  • VS2013编译最简单的PPAPI插件
  • 理解PPAPI的设计

你可能感兴趣的:(浏览器,chromium,CEF,PPAPI)