blob: 56729585cd8ed089fd6b9eb9c45361a185280fd8 [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 "cc/base/features.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_event_listener_options.h"
#include "third_party/blink/renderer/core/dom/events/add_event_listener_options_resolved.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 (int 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 == event_type_names::kScroll) {
*result = kScrollEvent;
} else if (event_type == event_type_names::kWheel ||
event_type == event_type_names::kMousewheel) {
*result = options->passive() ? kWheelEventPassive : kWheelEventBlocking;
} else if (event_type == event_type_names::kTouchend ||
event_type == event_type_names::kTouchcancel) {
*result = options->passive() ? kTouchEndOrCancelEventPassive
: kTouchEndOrCancelEventBlocking;
} else if (event_type == event_type_names::kTouchstart ||
event_type == event_type_names::kTouchmove) {
*result = options->passive() ? kTouchStartOrMoveEventPassive
: kTouchStartOrMoveEventBlocking;
} else if (event_type == event_type_names::kPointerrawupdate) {
// This will be used to avoid waking up the main thread to
// process pointerrawupdate events and hit-test them when
// there is no listener on the page.
*result = kPointerRawUpdateEvent;
} else if (event_util::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 == event_type_names::kLoad ||
event_type == event_type_names::kMousemove ||
event_type == event_type_names::kTouchstart) {
*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();
}
void EventHandlerRegistry::UpdateEventHandlerTargets(
ChangeOperation op,
EventHandlerClass handler_class,
EventTarget* target) {
EventTargetSet* targets = &targets_[handler_class];
switch (op) {
case kAdd:
targets->insert(target);
return;
case kRemove:
DCHECK(targets->Contains(target));
targets->erase(target);
return;
case kRemoveAll:
targets->RemoveAll(target);
return;
}
NOTREACHED();
}
bool EventHandlerRegistry::UpdateEventHandlerInternal(
ChangeOperation op,
EventHandlerClass handler_class,
EventTarget* target) {
unsigned old_num_handlers = targets_[handler_class].size();
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 && handlers_changed)
NotifyHandlersChanged(target, handler_class, new_num_handlers > 0);
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 (wtf_size_t i = 0; i < event_types.size(); ++i) {
EventListenerVector* listeners = target.GetEventListeners(event_types[i]);
if (!listeners)
continue;
for (wtf_size_t 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 (int 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 (int i = 0; i < kEventHandlerClassCount; ++i) {
EventHandlerClass handler_class = static_cast<EventHandlerClass>(i);
if (handlers_changed[i]) {
bool has_handlers = targets_[handler_class].Contains(&target);
NotifyHandlersChanged(&target, handler_class, has_handlers);
}
}
}
void EventHandlerRegistry::NotifyHandlersChanged(
EventTarget* target,
EventHandlerClass handler_class,
bool has_active_handlers) {
LocalFrame* frame = GetLocalFrameForTarget(target);
// TODO(keishi): Added for crbug.com/1090687. Change to CHECK once bug is
// fixed.
if (!GetPage())
return;
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 kPointerRawUpdateEvent:
GetPage()->GetChromeClient().SetEventListenerProperties(
frame, cc::EventListenerClass::kPointerRawUpdate,
GetEventListenerProperties(false,
HasEventHandlers(kPointerRawUpdateEvent)));
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 (handler_class == kTouchStartOrMoveEventBlocking ||
handler_class == kTouchStartOrMoveEventBlockingLowLatency) {
if (auto* node = target->ToNode()) {
if (auto* layout_object = node->GetLayoutObject()) {
layout_object->MarkEffectiveAllowedTouchActionChanged();
auto* continuation = layout_object->VirtualContinuation();
while (continuation) {
continuation->MarkEffectiveAllowedTouchActionChanged();
continuation = continuation->VirtualContinuation();
}
}
} 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->MarkEffectiveAllowedTouchActionChanged();
}
} else if (handler_class == kWheelEventBlocking) {
if (base::FeatureList::IsEnabled(::features::kWheelEventRegions)) {
if (auto* node = target->ToNode()) {
if (auto* layout_object = node->GetLayoutObject()) {
layout_object->MarkBlockingWheelEventHandlerChanged();
auto* continuation = layout_object->VirtualContinuation();
while (continuation) {
continuation->MarkBlockingWheelEventHandlerChanged();
continuation = continuation->VirtualContinuation();
}
}
} 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
// wheel event handler rects.
if (auto* layout_view = dom_window->GetFrame()->ContentLayoutObject())
layout_view->MarkBlockingWheelEventHandlerChanged();
}
}
}
}
void EventHandlerRegistry::Trace(Visitor* visitor) const {
visitor->Trace(frame_);
visitor->template RegisterWeakCallbackMethod<
EventHandlerRegistry, &EventHandlerRegistry::ProcessCustomWeakness>(this);
}
void EventHandlerRegistry::ProcessCustomWeakness(const LivenessBroker& info) {
// We use Vector<UntracedMember<>> here to avoid BlinkGC allocation in a
// custom weak callback.
Vector<UntracedMember<EventTarget>> dead_targets;
for (int 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 && !info.IsHeapObjectAlive(node)) {
dead_targets.push_back(node);
} else if (window && !info.IsHeapObjectAlive(window)) {
dead_targets.push_back(window);
}
}
}
for (wtf_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 (int handler_class_index = 0;
handler_class_index < kEventHandlerClassCount; ++handler_class_index) {
EventHandlerClass handler_class =
static_cast<EventHandlerClass>(handler_class_index);
HeapVector<Member<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 (wtf_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