blob: 6c34fb36bf7aec7fc2e5698c282f033a43ffa76f [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
* Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
* (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include <memory>
#include "base/format_macros.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/renderer/bindings/core/v8/add_event_listener_options_or_boolean.h"
#include "third_party/blink/renderer/bindings/core/v8/event_listener_options_or_boolean.h"
#include "third_party/blink/renderer/bindings/core/v8/script_event_listener.h"
#include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_abstract_event_handler.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_event_listener_impl.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h"
#include "third_party/blink/renderer/core/dom/events/event_target_impl.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/events/event_util.h"
#include "third_party/blink/renderer/core/events/pointer_event.h"
#include "third_party/blink/renderer/core/frame/frame_console.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/performance_monitor.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/use_counter.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_dom_activity_logger.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/threading.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
namespace {
enum PassiveForcedListenerResultType {
kPreventDefaultNotCalled,
kDocumentLevelTouchPreventDefaultCalled,
kPassiveForcedListenerResultTypeMax
};
Event::PassiveMode EventPassiveMode(
const RegisteredEventListener& event_listener) {
if (!event_listener.Passive()) {
if (event_listener.PassiveSpecified())
return Event::PassiveMode::kNotPassive;
return Event::PassiveMode::kNotPassiveDefault;
}
if (event_listener.PassiveForcedForDocumentTarget())
return Event::PassiveMode::kPassiveForcedDocumentLevel;
if (event_listener.PassiveSpecified())
return Event::PassiveMode::kPassive;
return Event::PassiveMode::kPassiveDefault;
}
Settings* WindowSettings(LocalDOMWindow* executing_window) {
if (executing_window) {
if (LocalFrame* frame = executing_window->GetFrame()) {
return frame->GetSettings();
}
}
return nullptr;
}
bool IsTouchScrollBlockingEvent(const AtomicString& event_type) {
return event_type == EventTypeNames::touchstart ||
event_type == EventTypeNames::touchmove;
}
bool IsWheelScrollBlockingEvent(const AtomicString& event_type) {
return event_type == EventTypeNames::mousewheel ||
event_type == EventTypeNames::wheel;
}
bool IsScrollBlockingEvent(const AtomicString& event_type) {
return IsTouchScrollBlockingEvent(event_type) ||
IsWheelScrollBlockingEvent(event_type);
}
bool IsInstrumentedForAsyncStack(const AtomicString& event_type) {
return event_type == EventTypeNames::load ||
event_type == EventTypeNames::error;
}
base::TimeDelta BlockedEventsWarningThreshold(ExecutionContext* context,
const Event& event) {
if (!event.cancelable())
return base::TimeDelta();
if (!IsScrollBlockingEvent(event.type()))
return base::TimeDelta();
return PerformanceMonitor::Threshold(context,
PerformanceMonitor::kBlockedEvent);
}
void ReportBlockedEvent(ExecutionContext* context,
const Event& event,
RegisteredEventListener* registered_listener,
base::TimeDelta delayed) {
if (registered_listener->Callback()->IsNativeBased())
return;
String message_text = String::Format(
"Handling of '%s' input event was delayed for %" PRId64
" ms due to main thread being busy. "
"Consider marking event handler as 'passive' to make the page more "
"responsive.",
event.type().GetString().Utf8().data(), delayed.InMilliseconds());
PerformanceMonitor::ReportGenericViolation(
context, PerformanceMonitor::kBlockedEvent, message_text, delayed,
GetFunctionLocation(context, registered_listener->Callback()));
registered_listener->SetBlockedEventWarningEmitted();
}
// UseCounts the event if it has the specified type. Returns true iff the event
// type matches.
bool CheckTypeThenUseCount(const Event& event,
const AtomicString& event_type_to_count,
const WebFeature feature,
const Document* document) {
if (event.type() != event_type_to_count)
return false;
UseCounter::Count(*document, feature);
return true;
}
} // namespace
EventTargetData::EventTargetData() = default;
EventTargetData::~EventTargetData() = default;
void EventTargetData::Trace(blink::Visitor* visitor) {
visitor->Trace(event_listener_map);
}
EventTarget::EventTarget() = default;
EventTarget::~EventTarget() = default;
Node* EventTarget::ToNode() {
return nullptr;
}
const DOMWindow* EventTarget::ToDOMWindow() const {
return nullptr;
}
const LocalDOMWindow* EventTarget::ToLocalDOMWindow() const {
return nullptr;
}
LocalDOMWindow* EventTarget::ToLocalDOMWindow() {
return nullptr;
}
MessagePort* EventTarget::ToMessagePort() {
return nullptr;
}
ServiceWorker* EventTarget::ToServiceWorker() {
return nullptr;
}
// An instance of EventTargetImpl is returned because EventTarget
// is an abstract class, and making it non-abstract is unfavorable
// because it will increase the size of EventTarget and all of its
// subclasses with code that are mostly unnecessary for them,
// resulting in a performance decrease.
// We also don't use ImplementedAs=EventTargetImpl in EventTarget.idl
// because it will result in some complications with classes that are
// currently derived from EventTarget.
// Spec: https://dom.spec.whatwg.org/#dom-eventtarget-eventtarget
EventTarget* EventTarget::Create(ScriptState* script_state) {
return EventTargetImpl::Create(script_state);
}
inline LocalDOMWindow* EventTarget::ExecutingWindow() {
if (ExecutionContext* context = GetExecutionContext())
return context->ExecutingWindow();
return nullptr;
}
bool EventTarget::IsTopLevelNode() {
if (ToLocalDOMWindow())
return true;
Node* node = ToNode();
if (!node)
return false;
if (node->IsDocumentNode() || node->GetDocument().documentElement() == node ||
node->GetDocument().body() == node) {
return true;
}
return false;
}
void EventTarget::SetDefaultAddEventListenerOptions(
const AtomicString& event_type,
EventListener* event_listener,
AddEventListenerOptionsResolved& options) {
options.SetPassiveSpecified(options.hasPassive());
if (!IsScrollBlockingEvent(event_type)) {
if (!options.hasPassive())
options.setPassive(false);
return;
}
LocalDOMWindow* executing_window = ExecutingWindow();
if (executing_window) {
if (options.hasPassive()) {
UseCounter::Count(executing_window->document(),
options.passive()
? WebFeature::kAddEventListenerPassiveTrue
: WebFeature::kAddEventListenerPassiveFalse);
}
}
if (RuntimeEnabledFeatures::PassiveDocumentEventListenersEnabled() &&
IsTouchScrollBlockingEvent(event_type)) {
if (!options.hasPassive() && IsTopLevelNode()) {
options.setPassive(true);
options.SetPassiveForcedForDocumentTarget(true);
return;
}
}
if (IsWheelScrollBlockingEvent(event_type) && IsTopLevelNode()) {
if (options.hasPassive()) {
if (executing_window) {
UseCounter::Count(
executing_window->document(),
options.passive()
? WebFeature::kAddDocumentLevelPassiveTrueWheelEventListener
: WebFeature::kAddDocumentLevelPassiveFalseWheelEventListener);
}
} else { // !options.hasPassive()
if (executing_window) {
UseCounter::Count(
executing_window->document(),
WebFeature::kAddDocumentLevelPassiveDefaultWheelEventListener);
}
if (RuntimeEnabledFeatures::PassiveDocumentWheelEventListenersEnabled()) {
options.setPassive(true);
options.SetPassiveForcedForDocumentTarget(true);
return;
}
}
}
// For mousewheel event listeners that have the target as the window and
// a bound function name of "ssc_wheel" treat and no passive value default
// passive to true. See crbug.com/501568.
if (RuntimeEnabledFeatures::SmoothScrollJSInterventionEnabled() &&
event_type == EventTypeNames::mousewheel && ToLocalDOMWindow() &&
event_listener && !options.hasPassive()) {
v8::Local<v8::Object> callback_object;
if (V8AbstractEventHandler* v8_listener =
V8AbstractEventHandler::Cast(event_listener))
callback_object = v8_listener->GetExistingListenerObject();
if (V8EventListenerImpl* v8_listener =
V8EventListenerImpl::Cast(event_listener))
callback_object = v8_listener->GetListenerObject();
if (!callback_object.IsEmpty() && callback_object->IsFunction() &&
strcmp(
"ssc_wheel",
*v8::String::Utf8Value(
v8::Isolate::GetCurrent(),
v8::Local<v8::Function>::Cast(callback_object)->GetName())) ==
0) {
options.setPassive(true);
if (executing_window) {
UseCounter::Count(executing_window->document(),
WebFeature::kSmoothScrollJSInterventionActivated);
executing_window->GetFrame()->Console().AddMessage(
ConsoleMessage::Create(
kInterventionMessageSource, kWarningMessageLevel,
"Registering mousewheel event as passive due to "
"smoothscroll.js usage. The smoothscroll.js library is "
"buggy, no longer necessary and degrades performance. See "
"https://www.chromestatus.com/feature/5749447073988608"));
}
return;
}
}
if (Settings* settings = WindowSettings(ExecutingWindow())) {
switch (settings->GetPassiveListenerDefault()) {
case PassiveListenerDefault::kFalse:
if (!options.hasPassive())
options.setPassive(false);
break;
case PassiveListenerDefault::kTrue:
if (!options.hasPassive())
options.setPassive(true);
break;
case PassiveListenerDefault::kForceAllTrue:
options.setPassive(true);
break;
}
} else {
if (!options.hasPassive())
options.setPassive(false);
}
if (!options.passive() && !options.PassiveSpecified()) {
String message_text = String::Format(
"Added non-passive event listener to a scroll-blocking '%s' event. "
"Consider marking event handler as 'passive' to make the page more "
"responsive. See "
"https://www.chromestatus.com/feature/5745543795965952",
event_type.GetString().Utf8().data());
PerformanceMonitor::ReportGenericViolation(
GetExecutionContext(), PerformanceMonitor::kDiscouragedAPIUse,
message_text, base::TimeDelta(), nullptr);
}
}
bool EventTarget::addEventListener(const AtomicString& event_type,
EventListener* listener,
bool use_capture) {
AddEventListenerOptionsResolved options;
options.setCapture(use_capture);
SetDefaultAddEventListenerOptions(event_type, listener, options);
return AddEventListenerInternal(event_type, listener, options);
}
bool EventTarget::addEventListener(
const AtomicString& event_type,
EventListener* listener,
const AddEventListenerOptionsOrBoolean& options_union) {
if (options_union.IsBoolean())
return addEventListener(event_type, listener, options_union.GetAsBoolean());
if (options_union.IsAddEventListenerOptions()) {
AddEventListenerOptionsResolved options =
options_union.GetAsAddEventListenerOptions();
return addEventListener(event_type, listener, options);
}
return addEventListener(event_type, listener);
}
bool EventTarget::addEventListener(const AtomicString& event_type,
EventListener* listener,
AddEventListenerOptionsResolved& options) {
SetDefaultAddEventListenerOptions(event_type, listener, options);
return AddEventListenerInternal(event_type, listener, options);
}
bool EventTarget::AddEventListenerInternal(
const AtomicString& event_type,
EventListener* listener,
const AddEventListenerOptionsResolved& options) {
if (!listener)
return false;
V8DOMActivityLogger* activity_logger =
V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld();
if (activity_logger) {
Vector<String> argv;
argv.push_back(ToNode() ? ToNode()->nodeName() : InterfaceName());
argv.push_back(event_type);
activity_logger->LogEvent("blinkAddEventListener", argv.size(),
argv.data());
}
RegisteredEventListener registered_listener;
bool added = EnsureEventTargetData().event_listener_map.Add(
event_type, listener, options, &registered_listener);
if (added) {
AddedEventListener(event_type, registered_listener);
if (listener->IsJSBased() && IsInstrumentedForAsyncStack(event_type)) {
probe::AsyncTaskScheduled(GetExecutionContext(), event_type, listener);
}
}
return added;
}
void EventTarget::AddedEventListener(
const AtomicString& event_type,
RegisteredEventListener& registered_listener) {
if (const LocalDOMWindow* executing_window = ExecutingWindow()) {
if (const Document* document = executing_window->document()) {
if (event_type == EventTypeNames::auxclick)
UseCounter::Count(*document, WebFeature::kAuxclickAddListenerCount);
else if (event_type == EventTypeNames::appinstalled)
UseCounter::Count(*document, WebFeature::kAppInstalledEventAddListener);
else if (EventUtil::IsPointerEventType(event_type))
UseCounter::Count(*document, WebFeature::kPointerEventAddListenerCount);
else if (event_type == EventTypeNames::slotchange)
UseCounter::Count(*document, WebFeature::kSlotChangeEventAddListener);
}
}
if (EventUtil::IsDOMMutationEventType(event_type)) {
if (ExecutionContext* context = GetExecutionContext()) {
String message_text = String::Format(
"Added synchronous DOM mutation listener to a '%s' event. "
"Consider using MutationObserver to make the page more responsive.",
event_type.GetString().Utf8().data());
PerformanceMonitor::ReportGenericViolation(
context, PerformanceMonitor::kDiscouragedAPIUse, message_text,
base::TimeDelta(), nullptr);
}
}
}
bool EventTarget::removeEventListener(const AtomicString& event_type,
const EventListener* listener,
bool use_capture) {
EventListenerOptions options;
options.setCapture(use_capture);
return RemoveEventListenerInternal(event_type, listener, options);
}
bool EventTarget::removeEventListener(
const AtomicString& event_type,
const EventListener* listener,
const EventListenerOptionsOrBoolean& options_union) {
if (options_union.IsBoolean()) {
return removeEventListener(event_type, listener,
options_union.GetAsBoolean());
}
if (options_union.IsEventListenerOptions()) {
EventListenerOptions options = options_union.GetAsEventListenerOptions();
return removeEventListener(event_type, listener, options);
}
return removeEventListener(event_type, listener);
}
bool EventTarget::removeEventListener(const AtomicString& event_type,
const EventListener* listener,
EventListenerOptions& options) {
return RemoveEventListenerInternal(event_type, listener, options);
}
bool EventTarget::RemoveEventListenerInternal(
const AtomicString& event_type,
const EventListener* listener,
const EventListenerOptions& options) {
if (!listener)
return false;
EventTargetData* d = GetEventTargetData();
if (!d)
return false;
wtf_size_t index_of_removed_listener;
RegisteredEventListener registered_listener;
if (!d->event_listener_map.Remove(event_type, listener, options,
&index_of_removed_listener,
&registered_listener))
return false;
// Notify firing events planning to invoke the listener at 'index' that
// they have one less listener to invoke.
if (d->firing_event_iterators) {
for (const auto& firing_iterator : *d->firing_event_iterators) {
if (event_type != firing_iterator.event_type)
continue;
if (index_of_removed_listener >= firing_iterator.end)
continue;
--firing_iterator.end;
// Note that when firing an event listener,
// firingIterator.iterator indicates the next event listener
// that would fire, not the currently firing event
// listener. See EventTarget::fireEventListeners.
if (index_of_removed_listener < firing_iterator.iterator)
--firing_iterator.iterator;
}
}
RemovedEventListener(event_type, registered_listener);
return true;
}
void EventTarget::RemovedEventListener(
const AtomicString& event_type,
const RegisteredEventListener& registered_listener) {}
RegisteredEventListener* EventTarget::GetAttributeRegisteredEventListener(
const AtomicString& event_type) {
EventListenerVector* listener_vector = GetEventListeners(event_type);
if (!listener_vector)
return nullptr;
for (auto& event_listener : *listener_vector) {
EventListener* listener = event_listener.Callback();
if (listener->IsAttribute() &&
listener->BelongsToTheCurrentWorld(GetExecutionContext()))
return &event_listener;
}
return nullptr;
}
bool EventTarget::SetAttributeEventListener(const AtomicString& event_type,
EventListener* listener) {
RegisteredEventListener* registered_listener =
GetAttributeRegisteredEventListener(event_type);
if (!listener) {
if (registered_listener)
removeEventListener(event_type, registered_listener->Callback(), false);
return false;
}
if (registered_listener) {
if (listener->IsJSBased() && IsInstrumentedForAsyncStack(event_type)) {
probe::AsyncTaskScheduled(GetExecutionContext(), event_type, listener);
}
registered_listener->SetCallback(listener);
return true;
}
return addEventListener(event_type, listener, false);
}
EventListener* EventTarget::GetAttributeEventListener(
const AtomicString& event_type) {
RegisteredEventListener* registered_listener =
GetAttributeRegisteredEventListener(event_type);
if (registered_listener)
return registered_listener->Callback();
return nullptr;
}
bool EventTarget::dispatchEventForBindings(Event* event,
ExceptionState& exception_state) {
if (!event->WasInitialized()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"The event provided is uninitialized.");
return false;
}
if (event->IsBeingDispatched()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"The event is already being dispatched.");
return false;
}
if (!GetExecutionContext())
return false;
event->SetTrusted(false);
// Return whether the event was cancelled or not to JS not that it
// might have actually been default handled; so check only against
// CanceledByEventHandler.
return DispatchEventInternal(*event) !=
DispatchEventResult::kCanceledByEventHandler;
}
DispatchEventResult EventTarget::DispatchEvent(Event& event) {
event.SetTrusted(true);
return DispatchEventInternal(event);
}
DispatchEventResult EventTarget::DispatchEventInternal(Event& event) {
event.SetTarget(this);
event.SetCurrentTarget(this);
event.SetEventPhase(Event::kAtTarget);
DispatchEventResult dispatch_result = FireEventListeners(event);
event.SetEventPhase(0);
return dispatch_result;
}
static const AtomicString& LegacyType(const Event& event) {
if (event.type() == EventTypeNames::transitionend)
return EventTypeNames::webkitTransitionEnd;
if (event.type() == EventTypeNames::animationstart)
return EventTypeNames::webkitAnimationStart;
if (event.type() == EventTypeNames::animationend)
return EventTypeNames::webkitAnimationEnd;
if (event.type() == EventTypeNames::animationiteration)
return EventTypeNames::webkitAnimationIteration;
if (event.type() == EventTypeNames::wheel)
return EventTypeNames::mousewheel;
return g_empty_atom;
}
void EventTarget::CountLegacyEvents(
const AtomicString& legacy_type_name,
EventListenerVector* listeners_vector,
EventListenerVector* legacy_listeners_vector) {
WebFeature unprefixed_feature;
WebFeature prefixed_feature;
WebFeature prefixed_and_unprefixed_feature;
if (legacy_type_name == EventTypeNames::webkitTransitionEnd) {
prefixed_feature = WebFeature::kPrefixedTransitionEndEvent;
unprefixed_feature = WebFeature::kUnprefixedTransitionEndEvent;
prefixed_and_unprefixed_feature =
WebFeature::kPrefixedAndUnprefixedTransitionEndEvent;
} else if (legacy_type_name == EventTypeNames::webkitAnimationEnd) {
prefixed_feature = WebFeature::kPrefixedAnimationEndEvent;
unprefixed_feature = WebFeature::kUnprefixedAnimationEndEvent;
prefixed_and_unprefixed_feature =
WebFeature::kPrefixedAndUnprefixedAnimationEndEvent;
} else if (legacy_type_name == EventTypeNames::webkitAnimationStart) {
prefixed_feature = WebFeature::kPrefixedAnimationStartEvent;
unprefixed_feature = WebFeature::kUnprefixedAnimationStartEvent;
prefixed_and_unprefixed_feature =
WebFeature::kPrefixedAndUnprefixedAnimationStartEvent;
} else if (legacy_type_name == EventTypeNames::webkitAnimationIteration) {
prefixed_feature = WebFeature::kPrefixedAnimationIterationEvent;
unprefixed_feature = WebFeature::kUnprefixedAnimationIterationEvent;
prefixed_and_unprefixed_feature =
WebFeature::kPrefixedAndUnprefixedAnimationIterationEvent;
} else if (legacy_type_name == EventTypeNames::mousewheel) {
prefixed_feature = WebFeature::kMouseWheelEvent;
unprefixed_feature = WebFeature::kWheelEvent;
prefixed_and_unprefixed_feature = WebFeature::kMouseWheelAndWheelEvent;
} else {
return;
}
if (const LocalDOMWindow* executing_window = ExecutingWindow()) {
if (const Document* document = executing_window->document()) {
if (legacy_listeners_vector) {
if (listeners_vector)
UseCounter::Count(*document, prefixed_and_unprefixed_feature);
else
UseCounter::Count(*document, prefixed_feature);
} else if (listeners_vector) {
UseCounter::Count(*document, unprefixed_feature);
}
}
}
}
DispatchEventResult EventTarget::FireEventListeners(Event& event) {
#if DCHECK_IS_ON()
DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden());
#endif
DCHECK(event.WasInitialized());
EventTargetData* d = GetEventTargetData();
if (!d)
return DispatchEventResult::kNotCanceled;
EventListenerVector* legacy_listeners_vector = nullptr;
AtomicString legacy_type_name = LegacyType(event);
if (!legacy_type_name.IsEmpty())
legacy_listeners_vector = d->event_listener_map.Find(legacy_type_name);
EventListenerVector* listeners_vector =
d->event_listener_map.Find(event.type());
bool fired_event_listeners = false;
if (listeners_vector) {
fired_event_listeners = FireEventListeners(event, d, *listeners_vector);
} else if (event.isTrusted() && legacy_listeners_vector) {
AtomicString unprefixed_type_name = event.type();
event.SetType(legacy_type_name);
fired_event_listeners =
FireEventListeners(event, d, *legacy_listeners_vector);
event.SetType(unprefixed_type_name);
}
// Only invoke the callback if event listeners were fired for this phase.
if (fired_event_listeners) {
event.DoneDispatchingEventAtCurrentTarget();
event.SetExecutedListenerOrDefaultAction();
// Only count uma metrics if we really fired an event listener.
Editor::CountEvent(GetExecutionContext(), event);
CountLegacyEvents(legacy_type_name, listeners_vector,
legacy_listeners_vector);
}
return GetDispatchEventResult(event);
}
bool EventTarget::FireEventListeners(Event& event,
EventTargetData* d,
EventListenerVector& entry) {
// Fire all listeners registered for this event. Don't fire listeners removed
// during event dispatch. Also, don't fire event listeners added during event
// dispatch. Conveniently, all new event listeners will be added after or at
// index |size|, so iterating up to (but not including) |size| naturally
// excludes new event listeners.
if (const LocalDOMWindow* executing_window = ExecutingWindow()) {
if (const Document* document = executing_window->document()) {
if (CheckTypeThenUseCount(event, EventTypeNames::beforeunload,
WebFeature::kDocumentBeforeUnloadFired,
document)) {
if (executing_window != executing_window->top())
UseCounter::Count(*document, WebFeature::kSubFrameBeforeUnloadFired);
} else if (CheckTypeThenUseCount(event, EventTypeNames::unload,
WebFeature::kDocumentUnloadFired,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::pagehide,
WebFeature::kDocumentPageHideFired,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::pageshow,
WebFeature::kDocumentPageShowFired,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::DOMFocusIn,
WebFeature::kDOMFocusInOutEvent,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::DOMFocusOut,
WebFeature::kDOMFocusInOutEvent,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::focusin,
WebFeature::kFocusInOutEvent,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::focusout,
WebFeature::kFocusInOutEvent,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::textInput,
WebFeature::kTextInputFired, document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::touchstart,
WebFeature::kTouchStartFired,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::mousedown,
WebFeature::kMouseDownFired, document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::pointerdown,
WebFeature::kPointerDownFired,
document)) {
if (event.IsPointerEvent() &&
static_cast<PointerEvent&>(event).pointerType() == "touch") {
UseCounter::Count(*document, WebFeature::kPointerDownFiredForTouch);
}
} else if (CheckTypeThenUseCount(event, EventTypeNames::pointerenter,
WebFeature::kPointerEnterLeaveFired,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::pointerleave,
WebFeature::kPointerEnterLeaveFired,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::pointerover,
WebFeature::kPointerOverOutFired,
document)) {
} else if (CheckTypeThenUseCount(event, EventTypeNames::pointerout,
WebFeature::kPointerOverOutFired,
document)) {
} else if (event.eventPhase() == Event::kCapturingPhase ||
event.eventPhase() == Event::kBubblingPhase) {
if (CheckTypeThenUseCount(
event, EventTypeNames::DOMNodeRemoved,
WebFeature::kDOMNodeRemovedEventListenedAtNonTarget,
document)) {
} else if (CheckTypeThenUseCount(
event, EventTypeNames::DOMNodeRemovedFromDocument,
WebFeature::
kDOMNodeRemovedFromDocumentEventListenedAtNonTarget,
document)) {
}
}
}
}
ExecutionContext* context = GetExecutionContext();
if (!context)
return false;
wtf_size_t i = 0;
wtf_size_t size = entry.size();
if (!d->firing_event_iterators)
d->firing_event_iterators = std::make_unique<FiringEventIteratorVector>();
d->firing_event_iterators->push_back(
FiringEventIterator(event.type(), i, size));
base::TimeDelta blocked_event_threshold =
BlockedEventsWarningThreshold(context, event);
base::TimeTicks now;
bool should_report_blocked_event = false;
if (!blocked_event_threshold.is_zero()) {
now = CurrentTimeTicks();
should_report_blocked_event =
now - event.PlatformTimeStamp() > blocked_event_threshold;
}
bool fired_listener = false;
while (i < size) {
RegisteredEventListener registered_listener = entry[i];
// Move the iterator past this event listener. This must match
// the handling of the FiringEventIterator::iterator in
// EventTarget::removeEventListener.
++i;
if (!registered_listener.ShouldFire(event))
continue;
EventListener* listener = registered_listener.Callback();
// The listener will be retained by Member<EventListener> in the
// registeredListener, i and size are updated with the firing event iterator
// in case the listener is removed from the listener vector below.
if (registered_listener.Once())
removeEventListener(event.type(), listener,
registered_listener.Capture());
// If stopImmediatePropagation has been called, we just break out
// immediately, without handling any more events on this target.
if (event.ImmediatePropagationStopped())
break;
event.SetHandlingPassive(EventPassiveMode(registered_listener));
bool passive_forced = registered_listener.PassiveForcedForDocumentTarget();
probe::UserCallback probe(context, nullptr, event.type(), false, this);
probe::AsyncTask async_task(context, listener, "event",
IsInstrumentedForAsyncStack(event.type()));
// To match Mozilla, the AT_TARGET phase fires both capturing and bubbling
// event listeners, even though that violates some versions of the DOM spec.
listener->handleEvent(context, &event);
fired_listener = true;
// If we're about to report this event listener as blocking, make sure it
// wasn't removed while handling the event.
if (should_report_blocked_event && i > 0 &&
entry[i - 1].Callback() == listener && !entry[i - 1].Passive() &&
!entry[i - 1].BlockedEventWarningEmitted() &&
!event.defaultPrevented()) {
ReportBlockedEvent(context, event, &entry[i - 1],
now - event.PlatformTimeStamp());
}
if (passive_forced) {
DEFINE_STATIC_LOCAL(EnumerationHistogram, passive_forced_histogram,
("Event.PassiveForcedEventDispatchCancelled",
kPassiveForcedListenerResultTypeMax));
PassiveForcedListenerResultType breakage_type = kPreventDefaultNotCalled;
if (event.PreventDefaultCalledDuringPassive())
breakage_type = kDocumentLevelTouchPreventDefaultCalled;
passive_forced_histogram.Count(breakage_type);
}
event.SetHandlingPassive(Event::PassiveMode::kNotPassive);
CHECK_LE(i, size);
}
d->firing_event_iterators->pop_back();
return fired_listener;
}
DispatchEventResult EventTarget::GetDispatchEventResult(const Event& event) {
if (event.defaultPrevented())
return DispatchEventResult::kCanceledByEventHandler;
if (event.DefaultHandled())
return DispatchEventResult::kCanceledByDefaultEventHandler;
return DispatchEventResult::kNotCanceled;
}
EventListenerVector* EventTarget::GetEventListeners(
const AtomicString& event_type) {
EventTargetData* data = GetEventTargetData();
if (!data)
return nullptr;
return data->event_listener_map.Find(event_type);
}
Vector<AtomicString> EventTarget::EventTypes() {
EventTargetData* d = GetEventTargetData();
return d ? d->event_listener_map.EventTypes() : Vector<AtomicString>();
}
void EventTarget::RemoveAllEventListeners() {
EventTargetData* d = GetEventTargetData();
if (!d)
return;
d->event_listener_map.Clear();
// Notify firing events planning to invoke the listener at 'index' that
// they have one less listener to invoke.
if (d->firing_event_iterators) {
for (const auto& iterator : *d->firing_event_iterators) {
iterator.iterator = 0;
iterator.end = 0;
}
}
}
void EventTarget::EnqueueEvent(Event& event, TaskType task_type) {
ExecutionContext* context = GetExecutionContext();
if (!context)
return;
probe::AsyncTaskScheduled(context, event.type(), &event);
context->GetTaskRunner(task_type)->PostTask(
FROM_HERE,
WTF::Bind(&EventTarget::DispatchEnqueuedEvent, WrapPersistent(this),
WrapPersistent(&event), WrapPersistent(context)));
}
void EventTarget::DispatchEnqueuedEvent(Event* event,
ExecutionContext* context) {
if (!GetExecutionContext()) {
probe::AsyncTaskCanceled(context, event);
return;
}
probe::AsyncTask async_task(context, event);
DispatchEvent(*event);
}
STATIC_ASSERT_ENUM(WebSettings::PassiveEventListenerDefault::kFalse,
PassiveListenerDefault::kFalse);
STATIC_ASSERT_ENUM(WebSettings::PassiveEventListenerDefault::kTrue,
PassiveListenerDefault::kTrue);
STATIC_ASSERT_ENUM(WebSettings::PassiveEventListenerDefault::kForceAllTrue,
PassiveListenerDefault::kForceAllTrue);
} // namespace blink