chromium浏览器页面longclick弹出菜单功能的实现

来源:互联网 发布:中航信托银川大数据数 编辑:程序博客网 时间:2024/05/22 00:08

最开始做这个功能是在chromium34上面实现的,后来移植到39上面,调用的相关的系统和内核的底层的接口还都好用,从34到39版本变化,chromium内核对于事件的传递这块逻辑代码应该没有太大的变化。

首先说下webkit浏览器是如何实现长按网页弹出菜单的:

从最开始的说起,对于android使用原生webview的浏览器来讲,长按一个链接(当然也包括图片,网站,邮箱,手机号码等),都会弹出一个菜单供用户选择,当然用户点击长按的内容不同,菜单弹出来的内容也不一样。那么系统把判断用户点击的类别大致包括下面几类:WebView.HitTestResult.PHONE_TYPE,WebView.HitTestResult.EMAIL_TYPE,WebView.HitTestResult.GEO_TYPE,WebView.HitTestResult.IMAGE_TYPE,WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE,WebView.HitTestResult.SRC_ANCHOR_TYPE,WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE。

这些在一个android原生浏览器的代码中,都是可以看到的,上面也写出了类名,方便大家查找。

那么这些信息,在应用层是如何得到的呢?在webview依赖的activity中,有一个方法:

@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {

}

这其中第二个参数,返回的webview就能够获取到WebView.HitTestResult的相关信息(当然这块不是所有的时候都会返回webview,我在这里说的我想大家应该都明白,我就不像写代码那样严谨的描述了)。接下来就是处理的相关逻辑了,这里不再赘述,自己看代码就好了。


接下来说下chromiun内核的浏览器,应用层可以copy安卓原生浏览器的代码,非常简单,和底层的交互就略有些复杂。在实现这个功能的过程中,主要做两部分内容的工作:一部分是响应事件;另一部分是是将点击事件的位置信息传到底层,底层处理后将获取到的信息再反给上层。

先在这里插一句:我们是将contentshell封装成自己的webview,提供了和android原生的相似的接口供上层调用,这也符合软件开发的基本原则。

1.事件响应:想要得到onCreateContextMenu回调,需要webview performLongclick,contentView中的performLongclick也要设置为返回true,这样才能保证一层一层的传给activity

2.将点击事件的位置信息传到底层,底层处理后将获取到的信息再反给上层:

webview的onTouchEvent传给Shell进行处理,在shell中调用nativeRequestNewTestDataAt方法,对应的c++文件是shell_android.cc文件,添加RequestNewTestDataAt方法,再调用shell_render_view_host_ext.cc中的RequestNewTestDataAt方法,这个方法有两个参数(Int view_x, int view_y),这两个就是坐标信息。前面的操作都是在主进程中进行,我们要把这两个参数通过browser进程发送给render进程。我们在shell_render_view_message.h中注册IPC通信的代码:

(browser to render)
IPC_MESSAGE_ROUTED2(ShellViewMsg_DoHitTest, int, int)

(render to browser)
IPC_MESSAGE_ROUTED1(ShellViewHostMsg_UpdateHitTestData, content::ShellHitTestData)

这块注册了两个IPC_MESSAGE信息,分别用于收发。

发送消息流程:shell_render_view_host_ext.cc中的RequestNewTestDataAt进行发送;

shell_render_view_ext.cc中进行接收,下面是主要代码:

(省略了大部分详细处理过程,省略的这些在chromium39源码中都能找到)

void ShellRenderViewExt::OnDoHitTest(int view_x, int view_y) {
  if (!render_view() || !render_view()->GetWebView())
    return;

  const blink::WebHitTestResult result =
      render_view()->GetWebView()->hitTestResultAt(
          blink::WebPoint(view_x, view_y));
  ShellHitTestData data;

  if (!result.urlElement().isNull()) {
    data.anchor_text = result.urlElement().innerText();
    data.href = GetHref(result.urlElement());
  }

  PopulateHitTestData(result.absoluteLinkURL(),
                      result.absoluteImageURL(),
                      result.isContentEditable(),
                      &data);
  Send(new ShellViewHostMsg_UpdateHitTestData(routing_id(), data));
}

将处理后的信息,封装成ShellHitTestData再次发送给browser进程,注意,shell_render_view_ext.cc是在子进程中进行的。

render进程再次将处理后封装好的东西发送给browser进程。(ShellHitTestData是在shell_hit_test_data.cc中创建的,有的代码里面叫AwHitTestData,这里不再贴代码了)

下面是在shell_render_view_host_ext.cc中处理的代码(主进程)

void ShellRenderViewHostExt::OnUpdateHitTestData(
    const ShellHitTestData& hit_test_data) {
  DCHECK(CalledOnValidThread());
  last_hit_test_data_ = hit_test_data;
  has_new_hit_test_data_ = true;
}

这里面同时也要提供接口:

const ShellHitTestData& ShellRenderViewHostExt::GetLastHitTestData() const {
  DCHECK(CalledOnValidThread());
  return last_hit_test_data_;
}

我们在shell_android.cc中调用上面的接口:

void Shell::UpdateLastHitTestData(JNIEnv* env, jobject obj) {
  if (!shell_render_view_host_ext_->HasNewHitTestData()) return;

  const ShellHitTestData& data = shell_render_view_host_ext_->GetLastHitTestData();
  shell_render_view_host_ext_->MarkHitTestDataRead();

  // Make sure to null the Java object if data is empty/invalid.
  ScopedJavaLocalRef<jstring> extra_data_for_type;
  if (data.extra_data_for_type.length())
    extra_data_for_type = ConvertUTF8ToJavaString(
        env, data.extra_data_for_type);

  ScopedJavaLocalRef<jstring> href;
  if (data.href.length())
    href = ConvertUTF16ToJavaString(env, data.href);

  ScopedJavaLocalRef<jstring> anchor_text;
  if (data.anchor_text.length())
    anchor_text = ConvertUTF16ToJavaString(env, data.anchor_text);

  ScopedJavaLocalRef<jstring> img_src;
  if (data.img_src.is_valid())
    img_src = ConvertUTF8ToJavaString(env, data.img_src.spec());

  Java_Shell_updateHitTestData(env,
                                    obj,
                                    data.type,
                                    extra_data_for_type.obj(),
                                    href.obj(),
                                    anchor_text.obj(),
                                    img_src.obj());
}

上面代码中最后一个掉用的方法,刚好是回调Shell.java中的方法,相当于转了一圈又回到了应用层,调用java层的updateHitTestData方法中包含了一些参数,其中data.type就是最前面说的哪几种类型,后面就不说了,源码中bean里面的注释都有自己看吧。

接下来就是在onCreateContextMenu中进行处理了,回调也有了,数据也传回来了,接下来全都是在java层处理的代码,打开新窗口,下载等操作了。

0 0
原创粉丝点击