写点什么

WebKit 三件套 (3):WebKit 之 Port 篇

作者:zhoulujun
  • 2023-04-09
    广东
  • 本文字数:5050 字

    阅读完需:约 17 分钟

了解其有关 Port 方面的设计,从而了解究竟如何能移植 WebKit 到自己的应用中。

WebKitPort 方面的内容是可以很广的,例如可将不同的图形库、网络库与 WebCore 集成,提供不同的 Port 接口供外部程序使用等,例如同样在 windows 平台上可以运行的 Google Chrome 和 Safari 就是针对 WebKit 的不同移植。

我们想了解有关 Port 方面的主要内容在于提供不同的 Port 接口供外部程序使用以及如何与外部程序交互,因为 WebKit 中的其它两部分 WebCore、Javascript 实现,从逻辑上讲是不直接提供接口给外部程序使用的。同时为了完成浏览器的核心功能,WebKit 也需要从外部程序中通过 Port 接口的方式获取一些支持。从这个角度讲 WebKit 作为一个相对独立的整体,它与外部程序之间的交互也就有一组相对固定的接口来定义及维护它们之间的关系,它们之间的关系与插件跟浏览器引擎之间的关系完全类似,接口相当一组协议,有的是由 WebKit 来实现,而供外部程序调用;有的的正好相反。通过前面的了解我们知道 WebKit 的主要功能集中在分析 Html、渲染布局 Web 内容以及 Javascript 实现方面等,而这些 Web 内容显示在哪个窗口及消息处理的启动循环等都需要由外部程序来提供


初步分析已有 WebKit Port 移植实现

与 WebCore 交互接口的实现

在 WebKit 源代码目录结构中 WebKit 目录下分别包含 gtk、mac、qt、win、wx 目录,其分别对应不同的 Port 移植方式。

在每一个目录下面都包括 WebCoreSupport 目录。

在不同的 WebCoreSupport 目录下分别包含有对类接口:WebCore::ChromeClient、WebCore::ContextMenuClient、WebCore::DragClient、WebCore::EditorClient、WebCore::FrameLoaderClient、WebCore::InspectorClient 等的实现,它们代表外部程序提供给 WebKit 内部使用的接口实现,其中 WebCore::ChromeClient、WebCore::FrameLoaderClient 非常重要。

初步了解其接口定义能基本了解其对应的含义,这些接口往往需要由 Port 移植部分来提供实现,往往由 WebKit 内部根据一定的条件来调用。下面初步来了解几个主要接口:

WebCore::ChromeClient 接口:

//往往在运行 window.open 脚本时调用,以便由外部程序决定如何打开一个新页面如新建一个窗口、新建一个 Tab 页签等;

virtual WebCore::Page* createWindow(WebCore::Frame*, const WebCore::FrameLoadRequest&, const WebCore::WindowFeatures&);

//通知外部程序显示页面;

virtual void show();

virtual bool canRunModal();

//通知外部程序以 Modal 的方式显示页面;

virtual void runModal();

//通知外部程序显示 JS 警告提示窗口;

virtual void runJavaScriptAlert(WebCore::Frame*, const WebCore::String&);

//通知外部程序显示 JS 警告确认窗口;

virtual bool runJavaScriptConfirm(WebCore::Frame*, const WebCore::String&);

WebCore::FrameLoaderClient 接口:

//检查是否拥有主页面窗口;

virtual bool hasWebView() const;

//检查是否拥有页面窗口;

virtual bool hasFrameView() const;

//通知外部程序有关 http 请求开始、结束、获取数据等,如通常浏览器状态栏显示的信息;

virtual void dispatchDidReceiveResponse(WebCore::DocumentLoader*, unsigned long identifier, const WebCore::ResourceResponse&);

virtual void dispatchDidReceiveContentLength(WebCore::DocumentLoader*, unsigned long identifier, int lengthReceived);

virtual void dispatchDidFinishLoading(WebCore::DocumentLoader*, unsigned long identifier);

virtual void dispatchDidFailLoading(WebCore::DocumentLoader*, unsigned long identifier, const WebCore::ResourceError&);

//通知外部程序 WebKit 内部主要事件处理,以便外部程序及时响应或创建维护数据等

virtual void dispatchDidHandleOnloadEvents();

virtual void dispatchDidReceiveServerRedirectForProvisionalLoad();

virtual void dispatchDidCancelClientRedirect();

virtual void dispatchWillPerformClientRedirect(const WebCore::KURL&, double interval, double fireDate);

virtual void dispatchDidChangeLocationWithinPage();

virtual void dispatchWillClose();

virtual void dispatchDidReceiveIcon();

virtual void dispatchDidStartProvisionalLoad();

virtual void dispatchDidReceiveTitle(const WebCore::String&);

virtual void dispatchDidCommitLoad();

virtual void dispatchDidFinishDocumentLoad();

virtual void dispatchDidFinishLoad();

virtual void dispatchDidFirstLayout();

//告诉外部程序需要提供切换到一个新页面状态。此时外部程序往往会新建 FrameView,并将 FrameView 与 Frame 关联,设置原生窗口句柄及其消息处理机制等等;

virtual void transitionToCommittedForNewPage();

//告诉外部程序创建一个新的 Frame,如遇到 html 中 iframe 标签时,需要外部程序创建一个新的 Frame 及原生窗口句柄等;

virtual PassRefPtr createFrame(const WebCore::KURL& url, const WebCore::String& name, WebCore::HTMLFrameOwnerElement* ownerElement,

const WebCore::String& referrer, bool allowsScrolling, int marginWidth, int marginHeight);

//告诉外部程序需要创建一个 Plugin 实例,从而创建其原生窗口等等;

virtual WebCore::Widget* createPlugin(const WebCore::IntSize&, WebCore::Element*, const WebCore::KURL&, const Vector&, const Vector&, const WebCore::String&, bool loadManually);

对 WebCore 中的 page/loader 等方面的类提供对应 Port 的实现支持

如 EventHandlerWin.cpp、FrameLoaderWin.cpp、DocumentLoaderWin.cpp、DocumentLoaderWin.cpp、WidgetWin.cpp、KeyEventWin.cpp 等

实现 WebView 及 WebFrame 等以便外部程序嵌入 WebKit

不同的 Port 移植对 WebView 及 WebFrame 的定义及实现有所不同,但其与 WebCore 中的 Page、Frame 之间的关系大致与浅谈 WebKit 之 WebCore 篇图一描述相一致。

具体关于 WebView、WebFrame 的定义与实现,特别是初始化时的动作可根据不同的 Port 移植而有所不同,同时初始化时会将上面提到的 WebCore Port 接口实现告诉 WebKit 内部。主要示例代码如下:

static void webkit_web_view_init(WebKitWebView* webView){    WebKitWebViewPrivate* priv = WEBKIT_WEB_VIEW_GET_PRIVATE(webView);    webView->priv = priv;    priv->corePage = new Page(new WebKit::ChromeClient(webView), new WebKit::ContextMenuClient(webView), new WebKit::EditorClient(webView), new WebKit::DragClient, new WebKit::InspectorClient);    priv->mainFrame = WEBKIT_WEB_FRAME(webkit_web_frame_new(webView));    priv->lastPopupXPosition = priv->lastPopupYPosition = -1;    priv->editable = false;    ................................    priv->webSettings = webkit_web_settings_new();    webkit_web_view_update_settings(webView);    ..................................}WebKitWebFrame* webkit_web_frame_new(WebKitWebView* webView){    g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), NULL);    WebKitWebFrame* frame = WEBKIT_WEB_FRAME(g_object_new(WEBKIT_TYPE_WEB_FRAME, NULL));    WebKitWebFramePrivate* priv = frame->priv;    WebKitWebViewPrivate* viewPriv = WEBKIT_WEB_VIEW_GET_PRIVATE(webView);    priv->webView = webView;    priv->client = new WebKit::FrameLoaderClient(frame);    priv->coreFrame = Frame::create(viewPriv->corePage, 0, priv->client).get();    priv->coreFrame->init();    return frame;}
复制代码


Chrome 中对 Port 移植方面的实现

其基本上与其他 Port 移植类似,其主要代码在 webkit\glue 目录中,可重点关注带 client_impl.cc 后缀的文件、webview_impl.cc、webwidget_impl.cc 等;但是其究竟如何创建原生 windows 窗口、如何创建 Render 进程、Render 进程与创建的原生 windows 窗口的关系如何等需要更进一步深入研究 Chrome,如果能从上面提到的 Port 部分入手也许很快就可得到答案,这一点以后有机会单独研究。

Android 中对 Port 移植方面的实现

其实现有点特殊,由于 Andriod 将 WebKit 以一个 Java 类接口的方式提供给 Java 环境使用(不像上面提到的 Chrome、Safari 等都是将 WebKit 以 一个 C++动态或静态库的方式供 C/C++外部程序调用),这样 WebKit 内部与外部即 JavaVM 的交互(如上面提到的 ChromeClient、 FrameLoaderClient 接口实现)需要一个 Bridge 类来协调处理,同时 WebView、WebFrame 接口绑定给 JavaVM 的 jni 接口实现也需要通过这个 Bridge 来支持协调处理。具体可详细参考 android 源码代码中 WebCore\platform\android 目录下的源文件。

通过进一步了解 WebCore Port 接口及其实现,可以加深这样一个认识:

如果从 MVC 的角度来看整个基于 WebKit 的浏览器(当然不尽合理),WebKit 的 Port 部分相当于 V 部分,它提供显示页面内容及其辅助信息(如提示状态)的场所(即原生窗口)以及控制该显示场所的状态变化及消息响应(如改变大小、鼠标移动等);而 M 部分往往由 WebCore 来实现,至于 WebCore 如何组织 DOM 则往往由 htmlparser 部分根据 DOM 定义来组织,如何在提供的显示场所显示 Web 内容则往往由 WebCore 中的 layout 部分来实现,其中充分利用了 Css 定义来布局显示该显示的内容;一旦涉及控制或动态处理往往由 Port 部分发起而由 Javascript 脚本来实现处理,其任务由 JavascriptCore 或 V8 来完成。

一般说来新打开一个页面,Port 部分需要提供一个主显示场所(即原生窗口),如果页面中含有 iframe 标签,则需要在主显示场所内创建一个子显示场所,以显示 iframe 标签对应 src 的内容;如果页面中含有 embed/object 等插件标签同样往往也需要在主显示场所内创建一个子显示场所(除非 windowless),以交由插件实现在提供的显示场所中显示内容。

特别需要说明的是我们通常看到的页面表单元素 input text field、textArea、button、radiobutton 等往往不像 window 图形库中的按钮、菜单、输入框等会对应一个原生窗口,页面中的表单元素在一个显示场所(即原生窗口)中完全是利用 Css 等通过 layout 方式来达到我们所看到的类似原生按钮、输入框、列表框、滚动条等效果,其中特别是能准确定位元素大小、设置 focus、光标显示、响应事件等,这充分的说明了浏览器引擎内部布局部分的威力所在。

从另外一个角度来看一个页面一般说来(除非遇到 iframe 或插件需要另外提供一块子画布)相当于一块画布,浏览器引擎能在其精确的位置绘制不同颜色的文字、图片、图标等,同时根据当前的鼠标及一个模拟的输入提示光标位置,接收键盘输入操作。页面中的绝大多数元素与原生的窗口元素几乎没有关联,完全通过组合、布局、准确定位来处理一切。。。

如何利用 WebKit?

了解 WebKit Port 部分,对我们如何利用 WebKit 有非常现实的意义,目前已经将 WebKit 移植到多种平台如 windows、qt、gtk、mac、wx、java、framebuffer 等,甚至移植到 python、ruby 及 3D 等环境中去。通过借鉴或利用这些已有的 WebKit Port 实现,完全可以将 WebKit 发扬广大。

前一阶段正好得到一个网友抓取网页的需求,试想目前移植利用 WebKit 基本都用来显示页面,往往涉及图形显示方面,但随着 ajax 及动态页面的广泛使用,未来动态生成的页面越来越多,传统的搜索引擎仅仅抓取静态的页面内容显然是不够的,现代化的搜索引擎应该能抓取动态的页面内容,这样它从某种意义讲相当于一个能获取对应的动态页面但不真正显示出其内容的浏览器,这样一个搜索引擎不仅能分析 DOM 树,同时能运行 Javascript 脚本(如运行 ajax),以真正完整获取页面内容,其实这样一个搜索引擎如果利用 WebKit 来实现的话,应该是个不错的选择,在我们了解 WebKit Port 部分之后,我们是否可以来模拟一个不真正具备图形显示方面的 Port,进而充分利用 WebKit 中的 WebCore 及 Javascript 实现方面的功能呢?一点想法,今后有机会可以试试,或许 Google、Yahoo 的搜索引擎已经有了相关的实现,不知是否使用的就是 WebKit?应该不会,有谁清楚的话,烦请通知一声。

但愿我们也能利用利用 WebKit 整出一个象模象样的东东如机顶盒浏览器、手机浏览器等等。。


参考资源

原文:http://ourpgh.blogspot.com/2008/10/webkitport.html


转载本站文章《WebKit三件套(3):WebKit之Port篇》,请注明出处:https://www.zhoulujun.cn/html/webfront/browser/webkit/2021_0422_8632.html

用户头像

zhoulujun

关注

还未添加个人签名 2021-06-25 加入

15年草根站长,尽在:zhoulujun.cn

评论

发布
暂无评论
WebKit三件套(3):WebKit之Port篇_zhoulujun_InfoQ写作社区