Bug 1589742 - Collect telemetry on the types of user clicks r=smaug
authorSean Feng <sefeng@mozilla.com>
Wed, 13 Nov 2019 18:12:02 +0000
changeset 502062 78cd6dd843caca297127b2c190295501142cb3bd
parent 502061 6fc0c2b4b9096e7ff61265dddb682adb21ee5eed
child 502063 4eb85afc10db43b9c64e0a19adefd78afd616418
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1589742
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1589742 - Collect telemetry on the types of user clicks r=smaug Differential Revision: https://phabricator.services.mozilla.com/D51508
dom/base/Element.cpp
dom/events/EventDispatcher.cpp
dom/events/EventListenerManager.cpp
dom/events/EventListenerManager.h
dom/events/EventStateManager.cpp
toolkit/components/telemetry/Histograms.json
widget/BasicEvents.h
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2908,16 +2908,25 @@ nsresult Element::PostHandleEventForLink
         // occurred even if the cause is an untrusted click event.
         InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
         actEvent.mDetail = 1;
 
         rv = EventDispatcher::Dispatch(this, aVisitor.mPresContext, &actEvent,
                                        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;
     }
     case eLegacyDOMActivate: {
       if (aVisitor.mEvent->mOriginalTarget == this) {
         nsAutoString target;
         GetLinkTarget(target);
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -341,16 +341,22 @@ class EventTargetChainItem {
       if (!MayHaveListenerManager() && !aCd.MayHaveNewListenerManager()) {
         return;
       }
       mManager = mTarget->GetExistingListenerManager();
     }
     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());
       NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr,
                    "CurrentTarget should be null!");
     }
   }
 
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -107,17 +107,19 @@ EventListenerManagerBase::EventListenerM
       mMayHaveSystemGroupListeners(false),
       mMayHaveTouchEventListener(false),
       mMayHaveMouseEnterLeaveEventListener(false),
       mMayHavePointerEnterLeaveEventListener(false),
       mMayHaveKeyEventListener(false),
       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!");
 }
 
 EventListenerManager::EventListenerManager(EventTarget* aTarget)
     : EventListenerManagerBase(), mTarget(aTarget) {
   NS_ASSERTION(aTarget, "unexpected null pointer");
 
@@ -401,16 +403,23 @@ void EventListenerManager::AddEventListe
   if (mTarget) {
     mTarget->EventListenerAdded(aTypeAtom);
   }
 
   if (mIsMainThreadELM && mTarget) {
     EventListenerService::NotifyAboutMainThreadListenerChange(mTarget,
                                                               aTypeAtom);
   }
+
+  if (!mHasNonPrivilegedClickListeners || mUnknownNonPrivilegedClickListeners) {
+    if (IsNonChromeClickListener(listener)) {
+      mHasNonPrivilegedClickListeners = true;
+      mUnknownNonPrivilegedClickListeners = false;
+    }
+  }
 }
 
 void EventListenerManager::ProcessApzAwareEventListenerAdd() {
   // Mark the node as having apz aware listeners
   nsCOMPtr<nsINode> node = do_QueryInterface(mTarget);
   if (node) {
     node->SetMayBeApzAware();
   }
@@ -582,27 +591,47 @@ void EventListenerManager::RemoveEventLi
 
   RefPtr<EventListenerManager> kungFuDeathGrip(this);
 
   for (uint32_t i = 0; i < count; ++i) {
     listener = &mListeners.ElementAt(i);
     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) {
           DisableDevice(aEventMessage);
         }
         return;
       }
     }
   }
 }
 
+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
 
 {
   MOZ_ASSERT(aEventMessage == aEvent->mMessage ||
                  aEventMessage == GetLegacyEventMessage(aEvent->mMessage),
              "aEvent and aEventMessage should agree, modulo legacyness");
@@ -834,24 +863,34 @@ void EventListenerManager::RemoveEventHa
   if (mClearingListeners) {
     return;
   }
 
   EventMessage eventMessage = GetEventMessage(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)) {
       DisableDevice(eventMessage);
     }
   }
 }
 
+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());
   MOZ_ASSERT(aListener->mHandlerIsString,
              "Why are we compiling a non-string JS listener?");
   JSEventHandler* jsEventHandler = aListener->GetJSEventHandler();
   MOZ_ASSERT(!jsEventHandler->GetTypedEventHandler().HasEventHandler(),
              "What is there to compile?");
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -152,17 +152,19 @@ class EventListenerManagerBase {
   uint16_t mMayHaveTouchEventListener : 1;
   uint16_t mMayHaveMouseEnterLeaveEventListener : 1;
   uint16_t mMayHavePointerEnterLeaveEventListener : 1;
   uint16_t mMayHaveKeyEventListener : 1;
   uint16_t mMayHaveInputOrCompositionEventListener : 1;
   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;
 };
 
 /*
  * Event listener manager
  */
 
 class EventListenerManager final : public EventListenerManagerBase {
   ~EventListenerManager();
@@ -421,16 +423,18 @@ class EventListenerManager final : publi
   }
   bool MayHavePointerEnterLeaveEventListener() {
     return mMayHavePointerEnterLeaveEventListener;
   }
   bool MayHaveSelectionChangeEventListener() {
     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.
    */
   bool MayHaveKeyEventListener() { return mMayHaveKeyEventListener; }
 
   /**
    * Returns true if there may be an advanced input event listener (input,
@@ -453,16 +457,18 @@ class EventListenerManager final : publi
 
   bool HasNonSystemGroupListenersForUntrustedKeyEvents();
   bool HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents();
 
   bool HasApzAwareListeners();
   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.
    */
   void RemoveAllListeners();
 
  protected:
   MOZ_CAN_RUN_SCRIPT
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/ScrollTypes.h"
 #include "mozilla/TextComposition.h"
 #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"
 #include "mozilla/dom/FrameLoaderBinding.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/dom/BrowserChild.h"
 #include "mozilla/dom/BrowserParent.h"
 #include "mozilla/dom/UIEvent.h"
@@ -4994,16 +4995,21 @@ nsresult EventStateManager::InitAndDispa
   }
 
   // Use local event status for each click event dispatching since it'll be
   // cleared by EventStateManager::PreHandleEvent().  Therefore, dispatching
   // an event means that previous event status will be ignored.
   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
   // actions handled here.
   aMouseUpEvent->mFlags.mMultipleActionsPrevented |=
       event.mFlags.mMultipleActionsPrevented;
   // If current status is nsEventStatus_eConsumeNoDefault, we don't need to
   // overwrite it.
@@ -5120,16 +5126,24 @@ nsresult EventStateManager::DispatchClic
     rv = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseDoubleClick,
                                    aPresShell, aClickTarget, currentTarget,
                                    notDispatchToContents, aOverrideClickTarget);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
+  // 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;
 }
 
 nsresult EventStateManager::HandleMiddleClickPaste(
     PresShell* aPresShell, WidgetMouseEvent* aMouseEvent,
     nsEventStatus* aStatus, TextEditor* aTextEditor) {
   MOZ_ASSERT(aPresShell);
   MOZ_ASSERT(aMouseEvent);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -14556,16 +14556,31 @@
       "Unload_Req_Peer",
       "Unload_Req_Peer_MSE",
       "Unload_Req_MSE",
       "SPD_Unload_Req_Peer",
       "Other"
     ],
     "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"],
     "alert_emails": ["sefeng@mozilla.com"],
     "bug_numbers": [1531917, 1551935],
     "expires_in_version": "never",
     "kind": "boolean",
     "releaseChannelCollection": "opt-out",
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -170,16 +170,20 @@ struct BaseEventFlags {
   // for when the parent process need the know first how the event was used
   // by content before handling it itself.
   bool mWantReplyFromContentProcess : 1;
   // If mPostedToRemoteProcess is true, the event has been posted to the
   // remote process (but it's not handled yet if it's not a duplicated event
   // 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);
   }
 
   /**
    * Helper methods for methods of DOM Event.
    */
@@ -360,17 +364,17 @@ struct BaseEventFlags {
   // instance's bit becomes true.  In other words, this works like:
   // eventFlags |= aOther;
   inline void Union(const BaseEventFlags& aOther) {
     RawFlags rawFlags = GetRawFlags() | aOther.GetRawFlags();
     SetRawFlags(rawFlags);
   }
 
  private:
-  typedef uint32_t RawFlags;
+  typedef uint64_t RawFlags;
 
   inline void SetRawFlags(RawFlags aRawFlags) {
     static_assert(sizeof(BaseEventFlags) <= sizeof(RawFlags),
                   "mozilla::EventFlags must not be bigger than the RawFlags");
     memcpy(this, &aRawFlags, sizeof(BaseEventFlags));
   }
   inline RawFlags GetRawFlags() const {
     RawFlags result = 0;