diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 31cea05f374e..514e4e952365 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -2913,6 +2913,15 @@ nsresult Element::PostHandleEventForLinks(EventChainPostVisitor& aVisitor) { nullptr, &status); if (NS_SUCCEEDED(rv)) { aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault; + if (!actEvent.DefaultPreventedByContent() && + mouseEvent->IsTrusted() && + mouseEvent->mInputSource != + MouseEvent_Binding::MOZ_SOURCE_KEYBOARD && + mouseEvent->mInputSource != + MouseEvent_Binding::MOZ_SOURCE_UNKNOWN) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_TYPES_OF_USER_CLICKS::Link); + } } } break; diff --git a/dom/events/EventDispatcher.cpp b/dom/events/EventDispatcher.cpp index 306cb426e327..4dece574d3b5 100644 --- a/dom/events/EventDispatcher.cpp +++ b/dom/events/EventDispatcher.cpp @@ -346,6 +346,12 @@ class EventTargetChainItem { if (mManager) { NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr, "CurrentTarget should be null!"); + + if (aVisitor.mEvent->mMessage == eMouseClick) { + aVisitor.mEvent->mFlags.mHadNonPrivilegedClickListeners = + aVisitor.mEvent->mFlags.mHadNonPrivilegedClickListeners || + mManager->HasNonPrivilegedClickListeners(); + } mManager->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent, &aVisitor.mDOMEvent, CurrentTarget(), &aVisitor.mEventStatus, IsItemInShadowTree()); diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index b6e396366e3f..db6b013378a5 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -112,7 +112,9 @@ EventListenerManagerBase::EventListenerManagerBase() mMayHaveInputOrCompositionEventListener(false), mMayHaveSelectionChangeEventListener(false), mClearingListeners(false), - mIsMainThreadELM(NS_IsMainThread()) { + mIsMainThreadELM(NS_IsMainThread()), + mHasNonPrivilegedClickListeners(false), + mUnknownNonPrivilegedClickListeners(false) { static_assert(sizeof(EventListenerManagerBase) == sizeof(uint32_t), "Keep the size of EventListenerManagerBase size compact!"); } @@ -406,6 +408,13 @@ void EventListenerManager::AddEventListenerInternal( EventListenerService::NotifyAboutMainThreadListenerChange(mTarget, aTypeAtom); } + + if (!mHasNonPrivilegedClickListeners || mUnknownNonPrivilegedClickListeners) { + if (IsNonChromeClickListener(listener)) { + mHasNonPrivilegedClickListeners = true; + mUnknownNonPrivilegedClickListeners = false; + } + } } void EventListenerManager::ProcessApzAwareEventListenerAdd() { @@ -587,6 +596,9 @@ void EventListenerManager::RemoveEventListenerInternal( if (EVENT_TYPE_EQUALS(listener, aEventMessage, aUserType, aAllEvents)) { if (listener->mListener == aListenerHolder && listener->mFlags.EqualsForRemoval(aFlags)) { + if (IsNonChromeClickListener(listener)) { + mUnknownNonPrivilegedClickListeners = true; + } mListeners.RemoveElementAt(i); NotifyEventListenerRemoved(aUserType); if (!aAllEvents && deviceType) { @@ -598,6 +610,23 @@ void EventListenerManager::RemoveEventListenerInternal( } } +bool EventListenerManager::HasNonPrivilegedClickListeners() { + if (mUnknownNonPrivilegedClickListeners) { + Listener* listener; + + mUnknownNonPrivilegedClickListeners = false; + for (uint32_t i = 0; i < mListeners.Length(); ++i) { + listener = &mListeners.ElementAt(i); + if (IsNonChromeClickListener(listener)) { + mHasNonPrivilegedClickListeners = true; + return mHasNonPrivilegedClickListeners; + } + } + mHasNonPrivilegedClickListeners = false; + } + return mHasNonPrivilegedClickListeners; +} + bool EventListenerManager::ListenerCanHandle(const Listener* aListener, const WidgetEvent* aEvent, EventMessage aEventMessage) const @@ -839,6 +868,9 @@ void EventListenerManager::RemoveEventHandler(nsAtom* aName) { Listener* listener = FindEventHandler(eventMessage, aName); if (listener) { + if (IsNonChromeClickListener(listener)) { + mUnknownNonPrivilegedClickListeners = true; + } mListeners.RemoveElementAt(uint32_t(listener - &mListeners.ElementAt(0))); NotifyEventListenerRemoved(aName); if (IsDeviceType(eventMessage)) { @@ -847,6 +879,13 @@ void EventListenerManager::RemoveEventHandler(nsAtom* aName) { } } +bool EventListenerManager::IsNonChromeClickListener(Listener* aListener) { + return !aListener->mFlags.mInSystemGroup && !aListener->mIsChrome && + aListener->mEventMessage == eMouseClick && + (aListener->GetJSEventHandler() || + aListener->mListener.HasWebIDLCallback()); +} + nsresult EventListenerManager::CompileEventHandlerInternal( Listener* aListener, const nsAString* aBody, Element* aElement) { MOZ_ASSERT(aListener->GetJSEventHandler()); diff --git a/dom/events/EventListenerManager.h b/dom/events/EventListenerManager.h index 5e1230b507f6..e3807e6e5119 100644 --- a/dom/events/EventListenerManager.h +++ b/dom/events/EventListenerManager.h @@ -157,7 +157,9 @@ class EventListenerManagerBase { uint16_t mMayHaveSelectionChangeEventListener : 1; uint16_t mClearingListeners : 1; uint16_t mIsMainThreadELM : 1; - // uint16_t mUnused : 4; + uint16_t mHasNonPrivilegedClickListeners : 1; + uint16_t mUnknownNonPrivilegedClickListeners : 1; + // uint16_t mUnused : 2; }; /* @@ -426,6 +428,8 @@ class EventListenerManager final : public EventListenerManagerBase { return mMayHaveSelectionChangeEventListener; } + bool HasNonPrivilegedClickListeners(); + /** * Returns true if there may be a key event listener (keydown, keypress, * or keyup) registered, or false if there definitely isn't. @@ -458,6 +462,8 @@ class EventListenerManager final : public EventListenerManagerBase { bool IsApzAwareListener(Listener* aListener); bool IsApzAwareEvent(nsAtom* aEvent); + // Return true if aListener is a non-chrome-privileged click event listner + bool IsNonChromeClickListener(Listener* aListener); /** * Remove all event listeners from the event target this EventListenerManager * is for. diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index 20e10509d621..56acefa728e5 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -19,6 +19,7 @@ #include "mozilla/TextEditor.h" #include "mozilla/TextEvents.h" #include "mozilla/TouchEvents.h" +#include "mozilla/Telemetry.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/DragEvent.h" #include "mozilla/dom/Event.h" @@ -4999,6 +5000,11 @@ nsresult EventStateManager::InitAndDispatchClickEvent( nsEventStatus status = nsEventStatus_eIgnore; nsresult rv = aPresShell->HandleEventWithTarget( &event, targetFrame, MOZ_KnownLive(target), &status); + + if (event.mFlags.mHadNonPrivilegedClickListeners && !aNoContentDispatch) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_TYPES_OF_USER_CLICKS::Has_JS_Listener); + } // Copy mMultipleActionsPrevented flag from a click event to the mouseup // event only when it's set to true. It may be set to true if an editor has // already handled it. This is important to avoid two or more default @@ -5125,6 +5131,14 @@ nsresult EventStateManager::DispatchClickEvents( } } + // notDispatchToContents is used here because we don't want to + // count auxclicks. + if (XRE_IsParentProcess() && !IsRemoteTarget(aClickTarget) && + !notDispatchToContents) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_TYPES_OF_USER_CLICKS::Browser_Chrome); + } + return rv; } diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 627a578d8b06..b87c1284be6d 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -14561,6 +14561,21 @@ ], "description": "The common combinations of BFCacheStatus when we determine whether the page can be BFCached or not; If it uses BFCache, we record BFCache_Success; If it's not and it falls under common failure reasons combinations, we record the corresponding combination; Otherwise, we record Other to indicate this is not a common failure" }, + "TYPES_OF_USER_CLICKS": { + "record_in_processes": ["main", "content"], + "products": ["firefox"], + "alert_emails": ["sefeng@mozilla.com", "perfteam@mozilla.com"], + "bug_numbers": [1589742], + "expires_in_version": "79", + "kind": "categorical", + "releaseChannelCollection": "opt-out", + "labels": [ + "Browser_Chrome", + "Link", + "Has_JS_Listener" + ], + "description": "When a user click(Mousedown + Mouseup event by using the left mouse button) happens, record if it's a click on browser chrome (parent process), a anchor element or an element which has non-privileged click event listener associated" + }, "BFCACHE_PAGE_RESTORED": { "record_in_processes": ["main", "content"], "products": ["firefox", "fennec", "geckoview"], diff --git a/widget/BasicEvents.h b/widget/BasicEvents.h index 026aaf60833e..37c7a99bbb22 100644 --- a/widget/BasicEvents.h +++ b/widget/BasicEvents.h @@ -175,6 +175,10 @@ struct BaseEventFlags { // instance). bool mPostedToRemoteProcess : 1; + // At lease one of the event in the event path had non privileged click + // listener. + bool mHadNonPrivilegedClickListeners : 1; + // If the event is being handled in target phase, returns true. inline bool InTargetPhase() const { return (mInBubblingPhase && mInCapturePhase); @@ -365,7 +369,7 @@ struct BaseEventFlags { } private: - typedef uint32_t RawFlags; + typedef uint64_t RawFlags; inline void SetRawFlags(RawFlags aRawFlags) { static_assert(sizeof(BaseEventFlags) <= sizeof(RawFlags),