WebKit中的Chrome 和 ChromeClient
原文地址:http://blog.csdn.net/dlmu2001/article/details/6208241
摘要: 浏览器的GUI接口分成两种,一种是控件的绘制,另一种则是同应用息息相关的窗口交互操作。本文主要介绍的是后一种,在WebKit里面,称之为宿主窗口。
Chrome类为WebKit内核定义了一系列的宿主窗口相关的操作接口,并最终在不同的实现中由ChromeClient类的派生类来实现,比如,在Qt里面的ChromeClientQt类。
本文将介绍Chrome类在WebKit中的作用,以及在移植WebKit的时候,如何来实现 ChromeClient类。
1. Chrome类在WebKit中的作用
浏览器的定义,特意查了百度百科,我综合一下,比较好的解释可能是这样,”浏览器是Web/Wap服务的客户端浏览程序,可向Web/Wap服务器发送各种请求,并对服务器发回的超文本信息和各种多媒体数据格式进行解释、显示和播放,并让用户与此些文件互动“。
从上面这个定义里面,我简单提炼出了浏览器需要的几个功能件:发送请求(http),解释超文本信息和各种多媒体数据(解析),显示和播放这些信息(排版,渲染,以及可能存在的插件),互动(交互)。这几个模块里面,同平台GUI相关的是排版、渲染和互动。而Chrome类就是WebCore内核渲染网页以及互动所需的并定义出来会由移植实现的同平台相关的接口,这个接口不包括控件的渲染。ChromeClient的具体实现(比如ChromeClientQt),则是移植对这些接口的实现。如果以MVC的角度来看,Chrome就是V,当然WebKit并非MVC的架构。
2. 类关系
Chrome是对应于Page的,每个Page都会在构造函数中创建一个Chrome对象,并将对象指针赋值给Page类的 m_chrome成员
Chrome类继承自HostWindow类,HostWindow类定义了宿主窗口必须实现的一系列接口,包括刷窗口(及内容),滚动,窗口相对坐标和屏幕坐标之间的相互转换等接口。
HostWindow是个抽象类,没有构造函数,无法实例化,Chrome通过继承对这个类进行了实现
在类Chrome的实现中,通过了组合的方式,将具体的实现委托给了ChromeClient。
例:
1 void Chrome::invalidateWindow(const IntRect& updateRect, bool immediate) 2 { 3 m_client->invalidateWindow(updateRect, immediate); 4 } 5 6 void Chrome::invalidateContentsAndWindow(const IntRect& updateRect, bool immediate) 7 { 8 m_client->invalidateContentsAndWindow(updateRect, immediate); 9 } 10 11 void Chrome::invalidateContentsForSlowScroll(const IntRect& updateRect, bool immediate) 12 { 13 m_client->invalidateContentsForSlowScroll(updateRect, immediate); 14 }
ChromeClient也是抽象接口类,没有构造函数,必须在porting的时候,进行继承,由不同的移植依托自己的平台进行实现。
以Qt移植为例,由ChromeClientQt类来最终实现(google的Chrome分支主要由 ChromeClientImp类实现)
而在ChromeClientQt的具体实现中,很多又是通过Page类的内部QwebPageClient类数据成员(client)提供的接口来实现的。
同样的,QWebPageClient是抽象接口类,无法实例化,通过继承类PageClientQWidget来实现。
而PageClientQWidget的实现又最终通过qwebview来实现,这个过程有点绕弯弯。。。
PageClientQWidget的实例化在QwebPage::setView接口中完成。
在代码结构上,ChromeClientQt.cpp 和 PageClientQt.cpp都位于WebKit/qt/WebCoreSupport目录下,表明他们是为了实现WebCore需要实现的移植层代码。
例:
1 void QWebPage::setView(QWidget* view) 2 { 3 if (this->view() == view) 4 return; 5 6 d->view = view; 7 setViewportSize(view ? view->size() : QSize(0, 0)); 8 9 // If we have no client, we install a special client delegating 10 // the responsibility to the QWidget. This is the code path 11 // handling a.o. the "legacy" QWebView. 12 // 13 // If such a special delegate already exist, we substitute the view. 14 15 if (d->client) { 16 if (d->client->isQWidgetClient()) 17 static_cast<PageClientQWidget*>(d->client.get())->view = view; 18 return; 19 } 20 21 if (view) 22 d->client = new PageClientQWidget(view, this); 23 }
1 IntPoint ChromeClientQt::screenToWindow(const IntPoint& point) const 2 { 3 QWebPageClient* pageClient = platformPageClient(); 4 if (!pageClient) 5 return point; 6 7 QWidget* ownerWidget = pageClient->ownerWidget(); 8 if (!ownerWidget) 9 return point; 10 11 return ownerWidget->mapFromGlobal(point); 12 } 13 14 PlatformPageClient ChromeClientQt::platformPageClient() const 15 { 16 return m_webPage->d->client.get(); 17 }
3. 主要接口
3.1. ChromeClientQt
1 ChromeClientQt::ChromeClientQt(QWebPage* webPage)
描述:
构造函数,以QWebPage为依赖对象创建。一般在创建Page对象前调用这个构造函数实例化ChromeClientQt,并以之为参数创建Page对象
代码:
1 QWebPagePrivate::QWebPagePrivate(QWebPage *qq) 2 : q(qq) 3 , page(0) 4 { 5 WebCore::InitializeLoggingChannelsIfNecessary(); //初始化环境变量 QT_WEBKIT_LOG 指定的log channel,xitongji 6 ScriptController::initializeThreading(); //初始化线程, 注意:必须在主线程里面调用。可以安全多次调用,可重入//仅仅初始化一次 7 WTF::initializeMainThread(); 8 WebCore::SecurityOrigin::setLocalLoadPolicy(WebCore::SecurityOrigin::AllowLocalLoadsForLocalAndSubstituteData); 9 WebPlatformStrategies::initialize(); 10 11 Page::PageClients pageClients; 12 pageClients.chromeClient = new ChromeClientQt(q); 13 pageClients.contextMenuClient = new ContextMenuClientQt(); 14 pageClients.editorClient = new EditorClientQt(q); 15 pageClients.dragClient = new DragClientQt(q); 16 pageClients.inspectorClient = new InspectorClientQt(q); 17 #if ENABLE(DEVICE_ORIENTATION) 18 pageClients.deviceOrientationClient = new DeviceOrientationClientQt(q); 19 pageClients.deviceMotionClient = new DeviceMotionClientQt(q); 20 #endif 21 #if ENABLE(CLIENT_BASED_GEOLOCATION) 22 if (QWebPagePrivate::drtRun) 23 pageClients.geolocationClient = new GeolocationClientMock(); 24 else 25 pageClients.geolocationClient = new GeolocationClientQt(q); 26 #endif 27 page = new Page(pageClients); 28 page->setGroupName("Default Group"); 29 ...... 30 }
3.2 windowRect
1 virtual FloatRect windowRect();
描述:
获得当前浏览器窗口区域的大小,这个区域不止包括显示区域,还包括状态条,菜单栏,工具栏等等
代码:
1 FloatRect ChromeClientQt::windowRect() 2 { 3 if (!platformPageClient()) 4 return FloatRect(); 5 return platformPageClient()->windowRect(); 6 }
3.3 pageRect
1 virtual FloatRect pageRect();
描述:
获取显示区域的大小,在排版的时候会经常调用
代码:
1 FloatRect ChromeClientQt::pageRect() 2 { 3 if (!m_webPage) 4 return FloatRect(); 5 return FloatRect(QRectF(QPointF(0, 0), m_webPage->viewportSize())); 6 }
3.4 createWindow
1 virtual Page* createWindow(Frame*, const FrameLoadRequest&, const WindowFeatures&, const NavigationAction&);
描述:
创建一个窗口。一般是在新建一个新窗口或者tab页的时候调用。
一个新窗口的创建意味着会有一个qwebview的创建和qwebpage的创建。窗口创建成功以后,还要在qwebframe主帧中发起网页请求。
这个接口在诸多移植中非常重要。
代码:
1 Page* ChromeClientQt::createWindow(Frame*, const FrameLoadRequest& request, const WindowFeatures& features, const NavigationAction&) 2 { 3 QWebPage* newPage = m_webPage->createWindow(features.dialog ? QWebPage::WebModalDialog : QWebPage::WebBrowserWindow); 4 if (!newPage) 5 return 0; 6 7 // A call to QWebPage::mainFrame() implicitly creates the main frame. 8 // Make sure it exists, as WebCore expects it when returning from this call. 9 newPage->mainFrame(); 10 return newPage->d->page; 11 }
3.5 setToolbarsVisible
1 setToolbarsVisible(bool visible); 2 bool toolbarsVisible(); 3 void setStatusbarVisible(bool); 4 statusbarVisible(); 5 setScrollbarsVisible(bool); 6 scrollbarsVisible(); 7 setMenubarVisible(bool); 8 menubarVisible();
描述:
这些接口用来设置这些区域的显示与否
3.6 addMessageToConsole
1 virtual void addMessageToConsole(MessageSource, MessageType, MessageLevel, const String& message, unsigned int lineNumber, const String& sourceID);
描述:
很多浏览器都在提供了javascript控制台工具,方便开发人员进行调试,这个接口 就是要把信息在控制台中打印出来
代码:
1 void ChromeClientQt::addMessageToConsole(MessageSource, MessageType, MessageLevel, const String& message, 2 unsigned int lineNumber, const String& sourceID) 3 { 4 QString x = message; 5 QString y = sourceID; 6 m_webPage->javaScriptConsoleMessage(x, lineNumber, y); 7 }
3.7 runJavaScriptAlert
1 virtual void runJavaScriptAlert(Frame*, const String&); 2 virtual bool runJavaScriptConfirm(Frame*, const String&); 3 virtual bool runJavaScriptPrompt(Frame*, const String& message, const String& defaultValue, String& result);
描述:
用来实现javascript中的alert框,确认框,提示框。完成同用户的交互。
代码:
1 void ChromeClientQt::runJavaScriptAlert(Frame* f, const String& msg) 2 { 3 QString x = msg; 4 QWebFrame* webFrame = qobject_cast<QWebFrame*>(f->loader()->networkingContext()->originatingObject()); 5 m_webPage->javaScriptAlert(webFrame, x); 6 } 7 8 bool ChromeClientQt::runJavaScriptConfirm(Frame* f, const String& msg) 9 { 10 QString x = msg; 11 QWebFrame* webFrame = qobject_cast<QWebFrame*>(f->loader()->networkingContext()->originatingObject()); 12 return m_webPage->javaScriptConfirm(webFrame, x); 13 } 14 15 bool ChromeClientQt::runJavaScriptPrompt(Frame* f, const String& message, const String& defaultValue, String& result) 16 { 17 QString x = result; 18 QWebFrame* webFrame = qobject_cast<QWebFrame*>(f->loader()->networkingContext()->originatingObject()); 19 bool rc = m_webPage->javaScriptPrompt(webFrame, (QString)message, (QString)defaultValue, &x); 20 21 // Fix up a quirk in the QInputDialog class. If no input happened the string should be empty 22 // but it is null. See https://bugs.webkit.org/show_bug.cgi?id=30914. 23 if (rc && x.isNull()) 24 result = String(""); 25 else 26 result = x; 27 28 return rc; 29 }
3.8 setStatusbarText
1 virtual void setStatusbarText(const String&);
描述:
设置状态条显示信心
代码:
1 void ChromeClientQt::setStatusbarText(const String& msg) 2 { 3 QString x = msg; 4 emit m_webPage->statusBarMessage(x); 5 }
3.9 invalidateContentsAndWindow
1 virtual void invalidateContentsAndWindow(const IntRect&, bool);
描述:
非常重要的一个移植接口,用来刷屏(包含内容和窗体)。
一般情况下,平台趋向于一个异步的调用(并不马上调用),也就是说,可能多次的invalidateContentsAndWindow调用的结果才导致一次屏幕的刷新。
第二个bool类型的参数用来表示是否立即进行屏幕刷新,不过很多移植都不对这个参数进行支持
代码:
1 void ChromeClientQt::invalidateContentsAndWindow(const IntRect& windowRect, bool immediate) 2 { 3 // No double buffer, so only update the QWidget if content changed. 4 if (platformPageClient()) { 5 QRect rect(windowRect); 6 rect = rect.intersected(QRect(QPoint(0, 0), m_webPage->viewportSize())); 7 if (!rect.isEmpty()) 8 platformPageClient()->update(rect); 9 } 10 QMetaObject::invokeMethod(m_webPage, "repaintRequested", Qt::QueuedConnection, Q_ARG(QRect, windowRect)); 11 12 // FIXME: There is no "immediate" support for window painting. This should be done always whenever the flag 13 // is set. 14 }
3.10 scroll
1 virtual void scroll(const IntSize& scrollDelta, const IntRect& rectToScroll, const IntRect& clipRect);
描述:
滚动支持。移植一般调用native widget缺省的scroll功能来实现这个接口
代码:
1 void ChromeClientQt::scroll(const IntSize& delta, const IntRect& scrollViewRect, const IntRect&) 2 { 3 if (platformPageClient()) 4 platformPageClient()->scroll(delta.width(), delta.height(), scrollViewRect); 5 emit m_webPage->scrollRequested(delta.width(), delta.height(), scrollViewRect); 6 }
3.11 windowToScreen
1 virtual IntPoint screenToWindow(const IntPoint&) const; 2 virtual IntRect windowToScreen(const IntRect&) const;
描述:
非常重要的移植接口,用来实现基于控件或者小窗口的相对坐标和屏幕坐标之间的转换。在排版的时候,会经常用到这两个转换
代码:
1 IntRect ChromeClientQt::windowToScreen(const IntRect& rect) const 2 { 3 QWebPageClient* pageClient = platformPageClient(); 4 if (!pageClient) 5 return rect; 6 7 QWidget* ownerWidget = pageClient->ownerWidget(); 8 if (!ownerWidget) 9 return rect; 10 11 QRect screenRect(rect); 12 screenRect.translate(ownerWidget->mapToGlobal(QPoint(0, 0))); 13 14 return screenRect; 15 } 16 17 IntPoint ChromeClientQt::screenToWindow(const IntPoint& point) const 18 { 19 QWebPageClient* pageClient = platformPageClient(); 20 if (!pageClient) 21 return point; 22 23 QWidget* ownerWidget = pageClient->ownerWidget(); 24 if (!ownerWidget) 25 return point; 26 27 return ownerWidget->mapFromGlobal(point); 28 }
3.12 requestGeolocationPermissionForFrame
1 virtual void requestGeolocationPermissionForFrame(Frame*, Geolocation*) { } 2 virtual void cancelGeolocationPermissionRequestForFrame(Frame*, Geolocation*) { }
描述:
在Geolocation(基于浏览器的地理定位技术)的时候,浏览器在调用Geolocation API获取你的地理位置之前,会有一个用户确认,这两个接口就是用来实现这个确认以及确认的取消
3.13 createPopupMenu
1 virtual PassRefPtr<PopupMenu> createPopupMenu(PopupMenuClient*) const;
描述:
在webkit中,网页中的下拉框(select+option)并不是作为一个控件来实现的,而是结合画 input,画下来三角 和弹出选项来实现。
这两个接口就是用来弹出选项的。画框和input则在 RenderThemeQt中实现
代码:
1 PassRefPtr<PopupMenu> ChromeClientQt::createPopupMenu(PopupMenuClient* client) const 2 { 3 return adoptRef(new PopupMenuQt(client, this)); 4 }
3.14 show
1 virtual void show();
描述:
用来显示窗体和内容,立即执行刷屏
代码:
1 void ChromeClientQt::show() 2 { 3 if (!m_webPage) 4 return; 5 QWidget* view = m_webPage->view(); 6 if (!view) 7 return; 8 view->window()->show(); 9 }