blob: fb49073f4ccf6a3ab2cea09e66c90507a279f87a [file] [log] [blame]
// Copyright 2014 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 "third_party/blink/renderer/core/frame/event_handler_registry.h"
#include "third_party/blink/renderer/core/dom/events/event_listener_options.h"
#include "third_party/blink/renderer/core/events/event_util.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h"
namespace blink {
namespace {
cc::EventListenerProperties GetEventListenerProperties(bool has_blocking,
bool has_passive) {
if (has_blocking && has_passive)
return cc::EventListenerProperties::kBlockingAndPassive;
if (has_blocking)
return cc::EventListenerProperties::kBlocking;
if (has_passive)
return cc::EventListenerProperties::kPassive;
return cc::EventListenerProperties::kNone;
}
LocalFrame* GetLocalFrameForTarget(EventTarget* target) {
LocalFrame* frame = nullptr;
if (Node* node = target->ToNode()) {
frame = node->GetDocument().GetFrame();
} else if (LocalDOMWindow* dom_window = target->ToLocalDOMWindow()) {
frame = dom_window->GetFrame();
} else {
NOTREACHED() << "Unexpected target type for event handler.";
}
return frame;
}
} // namespace
EventHandlerRegistry::EventHandlerRegistry(LocalFrame& frame) : frame_(frame) {}
EventHandlerRegistry::~EventHandlerRegistry() {
for (size_t i = 0; i < kEventHandlerClassCount; ++i) {
EventHandlerClass handler_class = static_cast<EventHandlerClass>(i);
CheckConsistency(handler_class);
}
}
bool EventHandlerRegistry::EventTypeToClass(
const AtomicString& event_type,
const AddEventListenerOptions& options,
EventHandlerClass* result) {
if (event_type == EventTypeNames::scroll) {
*result = kScrollEvent;
} else if (event_type == EventTypeNames::wheel ||
event_type == EventTypeNames::mousewheel) {
*result = options.passive() ? kWheelEventPassive : kWheelEventBlocking;
} else if (event_type == EventTypeNames::touchend ||
event_type == EventTypeNames::touchcancel) {
*result = options.passive() ? kTouchEndOrCancelEventPassive
: kTouchEndOrCancelEventBlocking;
} else if (event_type == EventTypeNames::touchstart ||
event_type == EventTypeNames::touchmove) {
*result = options.passive() ? kTouchStartOrMoveEventPassive
: kTouchStartOrMoveEventBlocking;
} else if (EventUtil::IsPointerEventType(event_type)) {
// The pointer events never block scrolling and the compositor
// only needs to know about the touch listeners.
*result = kPointerEvent;
#if DCHECK_IS_ON()
} else if (event_type == EventTypeNames::load ||
event_type == EventTypeNames::mousemove ||
event_type == EventTypeNames::touchstart) {
*result = kEventsForTesting;
#endif
} else {
return false;
}
return true;
}
const EventTargetSet* EventHandlerRegistry::EventHandlerTargets(
EventHandlerClass handler_class) const {
CheckConsistency(handler_class);
return &targets_[handler_class];
}
bool EventHandlerRegistry::HasEventHandlers(
EventHandlerClass handler_class) const {
CheckConsistency(handler_class);
return targets_[handler_class].size();
}
bool EventHandlerRegistry::UpdateEventHandlerTargets(
ChangeOperation op,
EventHandlerClass handler_class,
EventTarget* target) {
EventTargetSet* targets = &targets_[handler_class];
if (op == kAdd) {
if (!targets->insert(target).is_new_entry) {
// Just incremented refcount, no real change.
return false;
}
} else {
DCHECK(op == kRemove || op == kRemoveAll);
DCHECK(op == kRemoveAll || targets->Contains(target));
if (op == kRemoveAll) {
if (!targets->Contains(target))
return false;
targets->RemoveAll(target);
} else {
if (!targets->erase(target)) {
// Just decremented refcount, no real update.
return false;
}
}
}
return true;
}
bool EventHandlerRegistry::UpdateEventHandlerInternal(
ChangeOperation op,
EventHandlerClass handler_class,
EventTarget* target) {
unsigned old_num_handlers = targets_[handler_class].size();
bool target_set_changed =
UpdateEventHandlerTargets(op, handler_class, target);
unsigned new_num_handlers = targets_[handler_class].size();
bool handlers_changed = old_num_handlers != new_num_handlers;
if (op != kRemoveAll) {
if (handlers_changed)
NotifyHasHandlersChanged(target, handler_class, new_num_handlers > 0);
if (target_set_changed) {
NotifyDidAddOrRemoveEventHandlerTarget(GetLocalFrameForTarget(target),
handler_class);
}
}
return handlers_changed;
}
void EventHandlerRegistry::UpdateEventHandlerOfType(
ChangeOperation op,
const AtomicString& event_type,
const AddEventListenerOptions& options,
EventTarget* target) {
EventHandlerClass handler_class;
if (!EventTypeToClass(event_type, options, &handler_class))
return;
UpdateEventHandlerInternal(op, handler_class, target);
}
void EventHandlerRegistry::DidAddEventHandler(
EventTarget& target,
const AtomicString& event_type,
const AddEventListenerOptions& options) {
UpdateEventHandlerOfType(kAdd, event_type, options, &target);
}
void EventHandlerRegistry::DidRemoveEventHandler(
EventTarget& target,
const AtomicString& event_type,
const AddEventListenerOptions& options) {
UpdateEventHandlerOfType(kRemove, event_type, options, &target);
}
void EventHandlerRegistry::DidAddEventHandler(EventTarget& target,
EventHandlerClass handler_class) {
UpdateEventHandlerInternal(kAdd, handler_class, &target);
}
void EventHandlerRegistry::DidRemoveEventHandler(
EventTarget& target,
EventHandlerClass handler_class) {
UpdateEventHandlerInternal(kRemove, handler_class, &target);
}
void EventHandlerRegistry::DidMoveIntoPage(EventTarget& target) {
if (!target.HasEventListeners())
return;
// This code is not efficient at all.
Vector<AtomicString> event_types = target.EventTypes();
for (size_t i = 0; i < event_types.size(); ++i) {
EventListenerVector* listeners = target.GetEventListeners(event_types[i]);
if (!listeners)
continue;
for (unsigned count = listeners->size(); count > 0; --count) {
EventHandlerClass handler_class;
if (!EventTypeToClass(event_types[i], (*listeners)[count - 1].Options(),
&handler_class))
continue;
DidAddEventHandler(target, handler_class);
}
}
}
void EventHandlerRegistry::DidMoveOutOfPage(EventTarget& target) {
DidRemoveAllEventHandlers(target);
}
void EventHandlerRegistry::DidRemoveAllEventHandlers(EventTarget& target) {
bool handlers_changed[kEventHandlerClassCount];
bool target_set_changed[kEventHandlerClassCount];
for (size_t i = 0; i < kEventHandlerClassCount; ++i) {
EventHandlerClass handler_class = static_cast<EventHandlerClass>(i);
EventTargetSet* targets = &targets_[handler_class];
target_set_changed[i] = targets->Contains(&target);
handlers_changed[i] =
UpdateEventHandlerInternal(kRemoveAll, handler_class, &target);
}
for (size_t i = 0; i < kEventHandlerClassCount; ++i) {
EventHandlerClass handler_class = static_cast<EventHandlerClass>(i);
if (handlers_changed[i]) {
bool has_handlers = targets_[handler_class].Contains(&target);
NotifyHasHandlersChanged(&target, handler_class, has_handlers);
}
if (target_set_changed[i]) {
NotifyDidAddOrRemoveEventHandlerTarget(GetLocalFrameForTarget(&target),
handler_class);
}
}
}
void EventHandlerRegistry::NotifyHasHandlersChanged(
EventTarget* target,
EventHandlerClass handler_class,
bool has_active_handlers) {
LocalFrame* frame = GetLocalFrameForTarget(target);
switch (handler_class) {
case kScrollEvent:
GetPage()->GetChromeClient().SetHasScrollEventHandlers(
frame, has_active_handlers);
break;
case kWheelEventBlocking:
case kWheelEventPassive:
GetPage()->GetChromeClient().SetEventListenerProperties(
frame, cc::EventListenerClass::kMouseWheel,
GetEventListenerProperties(HasEventHandlers(kWheelEventBlocking),
HasEventHandlers(kWheelEventPassive)));
break;
case kTouchStartOrMoveEventBlockingLowLatency:
GetPage()->GetChromeClient().SetNeedsLowLatencyInput(frame,
has_active_handlers);
FALLTHROUGH;
case kTouchAction:
case kTouchStartOrMoveEventBlocking:
case kTouchStartOrMoveEventPassive:
case kPointerEvent:
GetPage()->GetChromeClient().SetEventListenerProperties(
frame, cc::EventListenerClass::kTouchStartOrMove,
GetEventListenerProperties(
HasEventHandlers(kTouchAction) ||
HasEventHandlers(kTouchStartOrMoveEventBlocking) ||
HasEventHandlers(kTouchStartOrMoveEventBlockingLowLatency),
HasEventHandlers(kTouchStartOrMoveEventPassive) ||
HasEventHandlers(kPointerEvent)));
break;
case kTouchEndOrCancelEventBlocking:
case kTouchEndOrCancelEventPassive:
GetPage()->GetChromeClient().SetEventListenerProperties(
frame, cc::EventListenerClass::kTouchEndOrCancel,
GetEventListenerProperties(
HasEventHandlers(kTouchEndOrCancelEventBlocking),
HasEventHandlers(kTouchEndOrCancelEventPassive)));
break;
#if DCHECK_IS_ON()
case kEventsForTesting:
break;
#endif
default:
NOTREACHED();
break;
}
if (RuntimeEnabledFeatures::PaintTouchActionRectsEnabled()) {
if (handler_class == kTouchStartOrMoveEventBlocking ||
handler_class == kTouchStartOrMoveEventBlockingLowLatency) {
if (auto* node = target->ToNode()) {
if (auto* layout_object = node->GetLayoutObject())
layout_object->MarkEffectiveWhitelistedTouchActionChanged();
} else if (auto* dom_window = target->ToLocalDOMWindow()) {
// This event handler is on a window. Ensure the layout view is
// invalidated because the layout view tracks the window's blocking
// touch event rects.
if (auto* layout_view = dom_window->GetFrame()->ContentLayoutObject())
layout_view->MarkEffectiveWhitelistedTouchActionChanged();
}
}
}
}
void EventHandlerRegistry::NotifyDidAddOrRemoveEventHandlerTarget(
LocalFrame* frame,
EventHandlerClass handler_class) {
ScrollingCoordinator* scrolling_coordinator =
GetPage()->GetScrollingCoordinator();
if (scrolling_coordinator &&
(handler_class == kTouchAction ||
handler_class == kTouchStartOrMoveEventBlocking ||
handler_class == kTouchStartOrMoveEventBlockingLowLatency)) {
scrolling_coordinator->TouchEventTargetRectsDidChange(
&frame->LocalFrameRoot());
}
}
void EventHandlerRegistry::Trace(blink::Visitor* visitor) {
visitor->Trace(frame_);
visitor->template RegisterWeakMembers<
EventHandlerRegistry, &EventHandlerRegistry::ClearWeakMembers>(this);
}
void EventHandlerRegistry::ClearWeakMembers(Visitor* visitor) {
Vector<UntracedMember<EventTarget>> dead_targets;
for (size_t i = 0; i < kEventHandlerClassCount; ++i) {
EventHandlerClass handler_class = static_cast<EventHandlerClass>(i);
const EventTargetSet* targets = &targets_[handler_class];
for (const auto& event_target : *targets) {
Node* node = event_target.key->ToNode();
LocalDOMWindow* window = event_target.key->ToLocalDOMWindow();
if (node && !ThreadHeap::IsHeapObjectAlive(node)) {
dead_targets.push_back(node);
} else if (window && !ThreadHeap::IsHeapObjectAlive(window)) {
dead_targets.push_back(window);
}
}
}
for (size_t i = 0; i < dead_targets.size(); ++i)
DidRemoveAllEventHandlers(*dead_targets[i]);
}
void EventHandlerRegistry::DocumentDetached(Document& document) {
// Remove all event targets under the detached document.
for (size_t handler_class_index = 0;
handler_class_index < kEventHandlerClassCount; ++handler_class_index) {
EventHandlerClass handler_class =
static_cast<EventHandlerClass>(handler_class_index);
Vector<UntracedMember<EventTarget>> targets_to_remove;
const EventTargetSet* targets = &targets_[handler_class];
for (const auto& event_target : *targets) {
if (Node* node = event_target.key->ToNode()) {
for (Document* doc = &node->GetDocument(); doc;
doc = doc->LocalOwner() ? &doc->LocalOwner()->GetDocument()
: nullptr) {
if (doc == &document) {
targets_to_remove.push_back(event_target.key);
break;
}
}
} else if (event_target.key->ToLocalDOMWindow()) {
// DOMWindows may outlive their documents, so we shouldn't remove their
// handlers here.
} else {
NOTREACHED();
}
}
for (size_t i = 0; i < targets_to_remove.size(); ++i)
UpdateEventHandlerInternal(kRemoveAll, handler_class,
targets_to_remove[i]);
}
}
void EventHandlerRegistry::CheckConsistency(
EventHandlerClass handler_class) const {
#if DCHECK_IS_ON()
const EventTargetSet* targets = &targets_[handler_class];
for (const auto& event_target : *targets) {
if (Node* node = event_target.key->ToNode()) {
// See the comment for |documentDetached| if either of these assertions
// fails.
DCHECK(node->GetDocument().GetPage());
DCHECK_EQ(frame_, &node->GetDocument().GetFrame()->LocalFrameRoot());
} else if (LocalDOMWindow* window = event_target.key->ToLocalDOMWindow()) {
// If any of these assertions fail, LocalDOMWindow failed to unregister
// its handlers properly.
DCHECK(window->GetFrame());
DCHECK(window->GetFrame()->GetPage());
DCHECK_EQ(frame_, &window->GetFrame()->LocalFrameRoot());
}
}
#endif // DCHECK_IS_ON()
}
Page* EventHandlerRegistry::GetPage() const {
DCHECK(frame_->GetPage());
return frame_->GetPage();
}
} // namespace blink