现在的位置: 首页 > 综合 > 正文

WebKit DOM Event 实现

2018年05月01日 ⁄ 综合 ⁄ 共 5982字 ⁄ 字号 评论关闭

DOM Event 规范

DOM Event 主要定义了三类接口:

  • EventTarget, 所有DOM 节点 都实现EventTarget接口

    1
    2
    3
    4
    5
    
    class EventTarget  {
    void addEventListener(in DOMString type,in EventListener listener, in boolean useCapture);
    void removeEventListener(in DOMString type, in EventListener listener, in boolean useCapture);
    boolean dispatchEvent(in Event event)
    };
  • EventListener, 用户实现该接口,并且在一个EventTarget上注册,使用addEventListener 完成注册;
    void  handleEvent(in Event evt);
  • Event 给EventHandle提供上下文信息
    readonly attribute DOMString        type;
    readonly attribute EventTarget      target;
    readonly attribute EventTarget      currentTarget;
    readonly attribute unsigned short   eventPhase;
    readonly attribute boolean          bubbles;
    readonly attribute boolean          cancelable;
    readonly attribute DOMTimeStamp     timeStamp;
     
    void               stopPropagation();
    void               preventDefault();
    [OldStyleObjC] void initEvent(in DOMString eventTypeArg,
    in boolean canBubbleArg,
    in boolean cancelableArg);
     
    // DOM Level 3 Additions.
    readonly attribute boolean defaultPrevented;
    void stopImmediatePropagation();
     
    // IE Extensions
    readonly attribute EventTarget      srcElement;
    attribute boolean          returnValue;
    attribute boolean          cancelBubble;
     
    #if defined(LANGUAGE_JAVASCRIPT) && LANGUAGE_JAVASCRIPT
    readonly attribute [Custom] Clipboard        clipboardData;
    #endif

事件流

  • 基本事件流

    每个事件都对应一个事件目标(EventTarget)(也是一个node 节点),EventTarget 有event 的target 属性指定。 每个事件目标注册有若干事件监听者(EventListerner), 这些监听者在事件到达后激活,激活的顺序在DOM规范中没有定义。如果没有定义事件capture或者bubbling,则当事件目标上的所有事件监听者响应完毕后,则这个事件处理完毕。

  • 事件捕获

    事件捕获发生在如下情况下: 一个事件监听者注册在某个事件的目标节点的祖先节点上,当该事件发生后,在其到达目标节点之前,祖先节点的事件监听者首先捕获并处理,然后将事件逐级下传,直到目标节点。

  • 事件冒泡

    事件冒泡初始阶段同基本事件流相同,然而在事件的目标节点所有事件监听者响应完毕后,是将将会沿着节点向其祖先方向上传,直到document点,上传过程中将会逐级激发在遇到节点上注册的的所有事件监听者(捕获事件除外)。

  • 事件取消

    一些事件可以规定为可取消的,这些事件一般都会应有一个缺省的动作。当此类事件发生时,首先传递给目标节点上的事件监听者,事件监听者可以选择是否取消该事件的缺省动作。

下面两图为事件流图:

WebKit DOM Event 类图

WebKit 实现逻辑

实现逻辑(以鼠标事件为例):

  • 鼠标事件发生
  • 根据鼠标事件发生的位置, 找到对应的EventTarget 节点
  • 在EventTarget的 dispatchGenericEvent函数中,获取到所有的父节点,保存到list中;
  • 进入事件捕获阶段
  • 触发当前EventTarget的当前事件的EventListen
  • 进入事件冒泡阶段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
bool EventTargetNode::dispatchGenericEvent(PassRefPtr<Event> e, ExceptionCode&, bool tempEvent)
 
{
    RefPtr<Event> evt(e);
    ASSERT(!eventDispatchForbidden());
    ASSERT(evt->target());
    ASSERT(!evt->type().isNull()); // JavaScript code could create an event with an empty name
    // work out what nodes to send event to
    DeprecatedPtrList<Node> nodeChain;
 
    if (inDocument()) {
        for (Node* n = this; n; n = n->eventParentNode()) {
            n->ref();
            nodeChain.prepend(n);
        }
    } else {
        // if node is not in the document just send event to itself 
        ref();
        nodeChain.prepend(this);
    }
 
    DeprecatedPtrListIterator<Node> it(nodeChain);
     // Before we begin dispatching events, give the target node a chance to do some work prior
    // to the DOM event handlers getting a crack.
    void* data = preDispatchEventHandler(evt.get());
    // trigger any capturing event handlers on our way down
    evt->setEventPhase(Event::CAPTURING_PHASE);
    it.toFirst();
    // Handle window events for capture phase, except load events, this quirk is needed
    // because Mozilla used to never propagate load events to the window object
    if (evt->type() != loadEvent && it.current()->isDocumentNode() && !evt->propagationStopped())
        static_cast<Document*>(it.current())->handleWindowEvent(evt.get(), true);
    for (; it.current() && it.current() != this && !evt->propagationStopped(); ++it) {
        evt->setCurrentTarget(EventTargetNodeCast(it.current()));
       EventTargetNodeCast(it.current())->handleLocalEvents(evt.get(), true);
    }   
 
    // dispatch to the actual target node
    it.toLast();
    if (!evt->propagationStopped()) {
        evt->setEventPhase(Event::AT_TARGET);
        evt->setCurrentTarget(EventTargetNodeCast(it.current()));
        // We do want capturing event listeners to be invoked here, even though
        // that violates the specification since Mozilla does it.
        EventTargetNodeCast(it.current())->handleLocalEvents(evt.get(), true);
        EventTargetNodeCast(it.current())->handleLocalEvents(evt.get(), false);
    }
    --it;
   // ok, now bubble up again (only non-capturing event handlers will be called)
    // ### recalculate the node chain here? (e.g. if target node moved in document by previous event handlers)
    // no. the DOM specs says:
    // The chain of EventTargets from the event target to the top of the tree
    // is determined before the initial dispatch of the event.
    // If modifications occur to the tree during event processing,
    // event flow will proceed based on the initial state of the tree.
    //
    // since the initial dispatch is before the capturing phase,
    // there's no need to recalculate the node chain.
    // (tobias)
    if (evt->bubbles()) {
        evt->setEventPhase(Event::BUBBLING_PHASE);
        for (; it.current() && !evt->propagationStopped() && !evt->cancelBubble(); --it) {
            evt->setCurrentTarget(EventTargetNodeCast(it.current()));
            EventTargetNodeCast(it.current())->handleLocalEvents(evt.get(), false);
        }
        // Handle window events for bubbling phase, except load events, this quirk is needed
        // because Mozilla used to never propagate load events at all
        it.toFirst();
        if (evt->type() != loadEvent && it.current()->isDocumentNode() && !evt->propagationStopped() && !evt->cancelBubble()) {
            evt->setCurrentTarget(EventTargetNodeCast(it.current()));
            static_cast<Document*>(it.current())->handleWindowEvent(evt.get(), false);
        }
    }
    evt->setCurrentTarget(0);
    evt->setEventPhase(0); // I guess this is correct, the spec does not seem to say
                           // anything about the default event handler phase.
    // Now call the post dispatch.
    postDispatchEventHandler(evt.get(), data);
  // now we call all default event handlers (this is not part of DOM - it is internal to khtml)
    it.toLast();
    if (evt->bubbles())
        for (; it.current() && !evt->defaultPrevented() && !evt->defaultHandled(); --it)
            EventTargetNodeCast(it.current())->defaultEventHandler(evt.get());
    else if (!evt->defaultPrevented() && !evt->defaultHandled())
        EventTargetNodeCast(it.current())->defaultEventHandler(evt.get());
    // deref all nodes in chain
    it.toFirst();
    for (; it.current(); ++it)
        it.current()->deref(); // this may delete us
    Document::updateDocumentsRendering();
    // If tempEvent is true, this means that the DOM implementation
    // will not be storing a reference to the event, i.e.  there is no
    // way to retrieve it from javascript if a script does not already
    // have a reference to it in a variable.  So there is no need for
    // the interpreter to keep the event in it's cache
    Frame *frame = document()->frame();
    if (tempEvent && frame && frame->scriptProxy())
        frame->scriptProxy()->finishedWithEvent(evt.get());
    return !evt->defaultPrevented(); // ### what if defaultPrevented was called before dispatchEvent?
}

抱歉!评论已关闭.