WebCore::Widget浅探

研究方法:

  • WebKit的xcode工程里以Widget为关键字全局搜索,查看相关函数名与注释
  • 新建一个使用了UIWebView的工程,运行时lldb里image lookup与Widget有关的类

探寻:

Widget.h里有以下注释:

// The Widget class serves as a base class for three kinds of objects: 
// (1) Scrollable areas (ScrollView) 
// (2) Scrollbars (Scrollbar) 
// (3) Plugins (PluginView) 
// 
// A widget may or may not be backed by a platform-specific object (e.g., HWND on Windows, NSView on Mac, QWidget on Qt). 
// 
// Widgets are connected in a hierarchy, with the restriction that plugins and scrollbars are always leaves of the 
// tree.  Only ScrollViews can have children (and therefore the Widget class has no concept of children). 
// 
// The rules right now for which widgets get platform-specific objects are as follows: 
// ScrollView - Mac 
// Scrollbar - Mac, Gtk 
// Plugin - Mac, Windows (windowed only), Qt (windowed only, widget is an HWND on windows), Gtk (windowed only) 

我是这么理解:
Widget类是对不同平台的系统控件的封装,能成为Widget的东西有三种:可滚动区域、滚动条、插件。这三种东西在不同平台(甚至不同的浏览器)可以有不同的实现。更直观地说,这些Widget的外观都可以不同,例如chrome在windows和OS X的右侧滚动条就不同。

在Widget的构造函数就能看出一些端倪:

#if PLATFORM(MAC) 
OBJC_CLASS NSView; 
OBJC_CLASS NSWindow; 
typedef NSView *PlatformWidget; 
#endif 
 
#if PLATFORM(WIN) 
typedef struct HWND__* HWND; 
typedef HWND PlatformWidget; 
#endif 
 
class Widget : public RefCounted<Widget> { 
public: 
    explicit Widget(PlatformWidget = 0); 

在lldb搜索,会有:

WebCore::Widget::Widget(WAKView*) 
WebCore::Widget::setPlatformWidget(WAKView*)

可见在iOS是用WAKView是代替了NSView。下面是WAKView的继承关系图:

WebCore::Widget浅探

WAKView是一个NSObject,并不是UIView。WAKView的成员变量声明:

@interface WAKView : WAKResponder 
{ 
    struct _WKViewContext viewContext; 
    struct WKView *viewRef; 
    NSMutableSet *subviewReferences; 
    BOOL _isHidden; 
    BOOL _drawsOwnDescendants; 
} 

其中的两个结构体为:

struct _WKViewContext { 
    void *notificationCallback; 
    void *notificationUserInfo; 
    void *responderCallback; 
    void *responderUserInfo; 
    void *willRemoveSubviewCallback; 
    void *invalidateGStateCallback; 
}; 
struct WKView { 
    struct _WKObject _field1; 
    struct _WKViewContext *_field2; 
    struct WKWindow *_field3; 
    struct WKView *_field4; 
    struct __CFArray *_field5; 
    struct CGPoint _field6; 
    struct CGRect _field7; 
    unsigned int _field8; 
    float _field9; 
    void *_field10; 
}; 

WAKView有77个函数,大部分是NSView也有的函数,如addSubview,setFrame等。对应NSView,在iOS有UIView,但UIView没有NSView那么多函数与功能。

而WAKView的父类WAKResponder则和NSResponder类似。对应NSResponder,在iOS有UIResponder,但UIResponder没有NSResponder那么多函数与功能;WAKResponder就像是删减了UIResponder没有的函数的NSResponder,例如WAKResponder和NSResponder会有以下几个相同的但UIResponder没有的函数:

- (void)mouseDown:(id)arg1; 
- (void)mouseUp:(id)arg1; 
- (void)moveUpAndModifySelection:(id)arg1; 
- (void)moveUp:(id)arg1; 
- (void)moveRightAndModifySelection:(id)arg1; 
- (void)moveRight:(id)arg1; 
- (void)moveLeftAndModifySelection:(id)arg1; 
- (void)moveLeft:(id)arg1; 
- (void)moveDownAndModifySelection:(id)arg1; 
- (void)moveDown:(id)arg1; 

WKView在WebKit2工程里的声明:

WK_EXPORT 
@interface WKView : NSView <NSTextInputClient> { 
@private 
    WKViewData *_data; 
    unsigned _unused; 
} 

可见WKView在Mac和iOS的表示是不同的,一个是Objective-C类,一个是结构体。

在iOS运行UIWebView,lldb搜索WKView

(lldb) image lookup -r -s WKView 

会得到44个结果,几乎全是C函数。即在iOS上操作WKView都是C的方式。

搜索webkit的wiki http://trac.webkit.org/wiki/WebKit2,得到如下信息:

 Why is it named WebKit2?

The somewhat pedestrian reason is that it's an incompatible API change from the original WebKit, so it will probably be installed as something like /System/Library/WebKit2.framework on Mac.

C API

WebKit2 will provide a stable C-based non-blocking API that is mostly platform agnostic. In order to achieve the goal of a non-blocking API, several techniques are used to make the API usable while still providing a comprehensive set of features to the embedder. These techniques include:

  • Notification style client callbacks (e.g. didFinishLoadForFrame) These inform the embedder that something has happened, but do not give them the chance to do anything about it.
  • Policy style clients callbacks (e.g. decidePolicyForNavigationAction) These allow the embedder to decide on an action at their leisure, notifying the page through a listener object.
  • Policy settings (e.g. WKContextSetCacheModel, WKContextSetPopupPolicy) These allow the embedder to opt into a predefined policy without any callbacks into the UIProcess. These can either be an enumerated set of specific policies, or something more fine-grained, such as a list of strings with wildcards.
  • Injected code (e.g. WebBundle) Code can be loaded into the WebProcess for cases where all the other options fail. This can useful when access to the DOM is required. [Planned, but not currently implemented]

The major API classes are:

WKContextRef

  • Encapsulates all pages related to specific use of WebKit. All pages in this context share the same visited link set, local storage set, and preferences.

WKPageNamespaceRef

  • Encapsulates all pages that can script each other.

WKPageRef

  • Basic unit of browsing. Stays the same as the main frame changes.

WKView[Ref]

  • Native view that hooks into the platform's toolkit. On Windows, this wraps a HWND. On the Mac, it inherits from NSView.

Note that the requirement to be fully non-blocking requires an incompatible API break - many features of most existing WebKit APIs cannot be fulfilled in a non-blocking manner. Since we needed the API break anyway, we also took advantage of the opportunity to clean up and simplify the API.

综合以上信息,应该可以推断出以下结论:

1. Widget封装平台控件,外部可通过Widget获取到平台控件的信息(主要是大小),并由平台控件传递用户事件信息(如mouseDown)到Widget。

2. iOS的UIWebView采用双线程架构,其实现是模仿了Mac Safari的双进程架构,并做了很多的适配,WAKView是为了在iOS上模仿NSView的替代品,从而令部分代码在Mac和iOS上复用。WAK应该是Web App Kit的缩写,因为对应iOS上UIView所属的UIKit,在Mac上是NSView所属的AppKit。这一层是线程间通信的中间层,主要起到状态信息保存和中转消息的作用。


另外,在调试实践中,还有以下的信息:

1. 当Plugin由系统控件来实现,并禁止其自行接收和处理事件时,如禁用视频的控制面板MPMoviePlayerController.controlStyle =MPMovieControlStyleNone,即会把mouse/key事件直接传递到该Plugin所属的HTMLElement

2. drag and drop 里,plugin有特殊行为,如视频可能支持拖动。当然,iOS也不支持。

3. WebCore对Widget能有统一管理,如attach和dettach,widget本身还有树结构

4. EventHandler内的处理逻辑,当发现是点击在widget上,可以另外传递事件,不产生click。 对Widget focus,widget可能会有特别的行为,例如进入模态

    // Many AppKit widgets run their own event loops and consume events while the mouse is down. 
    // When they finish, currentEvent is the mouseUp that they exited on.  We need to update 
    // the EventHandler state with this mouseUp, which we never saw. 
    // If this event isn't a mouseUp, we assume that the mouseUp will be coming later.  There 
    // is a hole here if the widget consumes both the mouseUp and subsequent events. 

长按菜单就是例子,由WebThread发起同步消息, WebSheet会显示成模态,新起一个EventLoop。

5. Widget在Mac上会有特别的逻辑,这些会和iOS不同。如下是例子。

/* 
 A hack for the benefit of AK's PopUpButton, which uses the Carbon menu manager, which thus 
 eats all subsequent events after it is starts its modal tracking loop.  After the interaction 
 is done, this routine is used to fix things up.  When a mouse down started us tracking in 
 the widget, we post a fake mouse up to balance the mouse down we started with. When a  
 key down started us tracking in the widget, we post a fake key up to balance things out. 
 In addition, we post a fake mouseMoved to get the cursor in sync with whatever we happen to  
 be over after the tracking is done. 
 */ 
void EventHandler::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent) 

6. HTMLMediaElement的PLUGIN_PROXY_FOR_VIDEO宏会让media由plugin作为widget打开。这些标签也会建立Widget:HTMLObjectElement, HTMLEmbedElement,HTMLAppletElement。

上一篇:创新推出 | Serverless 场景排查问题利器:函数实例命令行操作


下一篇:Linux——进程编程(一):fork 、exec