blob: c1cd91a7b3b8ea8ed08326a216adce6b06764ac8 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/input/BoundaryEventDispatcher.h"
#include "core/dom/Node.h"
#include "core/dom/shadow/FlatTreeTraversal.h"
namespace blink {
namespace {
bool isInDocument(EventTarget* target) {
return target && target->toNode() && target->toNode()->isConnected();
}
void buildAncestorChain(EventTarget* target,
HeapVector<Member<Node>, 20>* ancestors) {
if (!isInDocument(target))
return;
Node* targetNode = target->toNode();
DCHECK(targetNode);
targetNode->updateDistribution();
// Index 0 element in the ancestors arrays will be the corresponding
// target. So the root of their document will be their last element.
for (Node* node = targetNode; node; node = FlatTreeTraversal::parent(*node))
ancestors->append(node);
}
void buildAncestorChainsAndFindCommonAncestors(
EventTarget* exitedTarget,
EventTarget* enteredTarget,
HeapVector<Member<Node>, 20>* exitedAncestorsOut,
HeapVector<Member<Node>, 20>* enteredAncestorsOut,
size_t* exitedAncestorsCommonParentIndexOut,
size_t* enteredAncestorsCommonParentIndexOut) {
DCHECK(exitedAncestorsOut);
DCHECK(enteredAncestorsOut);
DCHECK(exitedAncestorsCommonParentIndexOut);
DCHECK(enteredAncestorsCommonParentIndexOut);
buildAncestorChain(exitedTarget, exitedAncestorsOut);
buildAncestorChain(enteredTarget, enteredAncestorsOut);
*exitedAncestorsCommonParentIndexOut = exitedAncestorsOut->size();
*enteredAncestorsCommonParentIndexOut = enteredAncestorsOut->size();
while (*exitedAncestorsCommonParentIndexOut > 0 &&
*enteredAncestorsCommonParentIndexOut > 0) {
if ((*exitedAncestorsOut)[(*exitedAncestorsCommonParentIndexOut) - 1] !=
(*enteredAncestorsOut)[(*enteredAncestorsCommonParentIndexOut) - 1])
break;
(*exitedAncestorsCommonParentIndexOut)--;
(*enteredAncestorsCommonParentIndexOut)--;
}
}
} // namespace
void BoundaryEventDispatcher::sendBoundaryEvents(EventTarget* exitedTarget,
EventTarget* enteredTarget) {
if (exitedTarget == enteredTarget)
return;
// Dispatch out event
if (isInDocument(exitedTarget))
dispatchOut(exitedTarget, enteredTarget);
// Create lists of all exited/entered ancestors, locate the common ancestor
// Based on httparchive, in more than 97% cases the depth of DOM is less
// than 20.
HeapVector<Member<Node>, 20> exitedAncestors;
HeapVector<Member<Node>, 20> enteredAncestors;
size_t exitedAncestorsCommonParentIndex = 0;
size_t enteredAncestorsCommonParentIndex = 0;
// A note on mouseenter and mouseleave: These are non-bubbling events, and
// they are dispatched if there is a capturing event handler on an ancestor or
// a normal event handler on the element itself. This special handling is
// necessary to avoid O(n^2) capturing event handler checks.
//
// Note, however, that this optimization can possibly cause some
// unanswered/missing/redundant mouseenter or mouseleave events in certain
// contrived eventhandling scenarios, e.g., when:
// - the mouseleave handler for a node sets the only
// capturing-mouseleave-listener in its ancestor, or
// - DOM mods in any mouseenter/mouseleave handler changes the common ancestor
// of exited & entered nodes, etc.
// We think the spec specifies a "frozen" state to avoid such corner cases
// (check the discussion on "candidate event listeners" at
// http://www.w3.org/TR/uievents), but our code below preserves one such
// behavior from past only to match Firefox and IE behavior.
//
// TODO(mustaq): Confirm spec conformance, double-check with other browsers.
buildAncestorChainsAndFindCommonAncestors(
exitedTarget, enteredTarget, &exitedAncestors, &enteredAncestors,
&exitedAncestorsCommonParentIndex, &enteredAncestorsCommonParentIndex);
bool exitedNodeHasCapturingAncestor = false;
const AtomicString& leaveEvent = getLeaveEvent();
for (size_t j = 0; j < exitedAncestors.size(); j++) {
if (exitedAncestors[j]->hasCapturingEventListeners(leaveEvent)) {
exitedNodeHasCapturingAncestor = true;
break;
}
}
// Dispatch leave events, in child-to-parent order.
for (size_t j = 0; j < exitedAncestorsCommonParentIndex; j++)
dispatchLeave(exitedAncestors[j], enteredTarget,
!exitedNodeHasCapturingAncestor);
// Dispatch over event
if (isInDocument(enteredTarget))
dispatchOver(enteredTarget, exitedTarget);
// Defer locating capturing enter listener until /after/ dispatching the leave
// events because the leave handlers might set a capturing enter handler.
bool enteredNodeHasCapturingAncestor = false;
const AtomicString& enterEvent = getEnterEvent();
for (size_t i = 0; i < enteredAncestors.size(); i++) {
if (enteredAncestors[i]->hasCapturingEventListeners(enterEvent)) {
enteredNodeHasCapturingAncestor = true;
break;
}
}
// Dispatch enter events, in parent-to-child order.
for (size_t i = enteredAncestorsCommonParentIndex; i > 0; i--)
dispatchEnter(enteredAncestors[i - 1], exitedTarget,
!enteredNodeHasCapturingAncestor);
}
} // namespace blink